From 78a6e548ad8e20de5110cff23a13c9c2961ed761 Mon Sep 17 00:00:00 2001
From: Andre Sterba <andre.sterba@stud.h-da.de>
Date: Mon, 4 Jul 2022 12:30:02 +0000
Subject: [PATCH] Enhance northbound interface to get user and roles by id

See merge request danet/gosdn!344
---
 api/go/gosdn/rbac/role.pb.go                | 13 +++++++++--
 api/go/gosdn/rbac/user.pb.go                | 13 +++++++++--
 api/openapiv2/gosdn_northbound.swagger.json | 26 +++++++++++++++------
 api/proto/buf.lock                          | 10 ++------
 api/proto/gosdn/rbac/role.proto             |  1 +
 api/proto/gosdn/rbac/user.proto             |  1 +
 cli/cmd/userGet.go                          |  8 ++++++-
 controller/api/role.go                      |  4 +++-
 controller/api/role_test.go                 |  5 +++-
 controller/api/user.go                      |  5 +++-
 controller/api/user_test.go                 |  5 +++-
 controller/northbound/server/role.go        |  8 ++++++-
 controller/northbound/server/role_test.go   |  2 ++
 controller/northbound/server/user.go        |  8 ++++++-
 controller/northbound/server/user_test.go   |  2 ++
 15 files changed, 85 insertions(+), 26 deletions(-)

diff --git a/api/go/gosdn/rbac/role.pb.go b/api/go/gosdn/rbac/role.pb.go
index e3dfae038..c4212deaa 100644
--- a/api/go/gosdn/rbac/role.pb.go
+++ b/api/go/gosdn/rbac/role.pb.go
@@ -213,6 +213,7 @@ type GetRoleRequest struct {
 
 	Timestamp int64  `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
 	RoleName  string `protobuf:"bytes,2,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"`
+	Id        string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"`
 }
 
 func (x *GetRoleRequest) Reset() {
@@ -261,6 +262,13 @@ func (x *GetRoleRequest) GetRoleName() string {
 	return ""
 }
 
+func (x *GetRoleRequest) GetId() string {
+	if x != nil {
+		return x.Id
+	}
+	return ""
+}
+
 type GetRoleResponse struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -808,12 +816,13 @@ var file_gosdn_rbac_role_proto_rawDesc = []byte{
 	0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2a, 0x0a, 0x06, 0x73,
 	0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x67, 0x6f,
 	0x73, 0x64, 0x6e, 0x2e, 0x72, 0x62, 0x61, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
-	0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4b, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x52, 0x6f,
+	0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x5b, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x52, 0x6f,
 	0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d,
 	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69,
 	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f,
 	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65,
-	0x4e, 0x61, 0x6d, 0x65, 0x22, 0x81, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65,
+	0x4e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x02, 0x69, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65,
 	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65,
 	0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d,
 	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
diff --git a/api/go/gosdn/rbac/user.pb.go b/api/go/gosdn/rbac/user.pb.go
index f999c7dde..ef2f31b24 100644
--- a/api/go/gosdn/rbac/user.pb.go
+++ b/api/go/gosdn/rbac/user.pb.go
@@ -221,6 +221,7 @@ type GetUserRequest struct {
 
 	Timestamp int64  `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
 	Name      string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+	Id        string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"`
 }
 
 func (x *GetUserRequest) Reset() {
@@ -269,6 +270,13 @@ func (x *GetUserRequest) GetName() string {
 	return ""
 }
 
+func (x *GetUserRequest) GetId() string {
+	if x != nil {
+		return x.Id
+	}
+	return ""
+}
+
 type GetUserResponse struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -703,11 +711,12 @@ var file_gosdn_rbac_user_proto_rawDesc = []byte{
 	0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
 	0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x67, 0x6f, 0x73, 0x64, 0x6e, 0x2e, 0x72, 0x62,
 	0x61, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
-	0x73, 0x22, 0x42, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75,
+	0x73, 0x22, 0x52, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75,
 	0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
 	0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
 	0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x81, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65,
+	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65,
 	0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d,
 	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69,
 	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
diff --git a/api/openapiv2/gosdn_northbound.swagger.json b/api/openapiv2/gosdn_northbound.swagger.json
index 9db819cb0..3fd28b3f1 100644
--- a/api/openapiv2/gosdn_northbound.swagger.json
+++ b/api/openapiv2/gosdn_northbound.swagger.json
@@ -17,6 +17,9 @@
     {
       "name": "PndService"
     },
+    {
+      "name": "Collector"
+    },
     {
       "name": "gNMI"
     },
@@ -30,19 +33,16 @@
       "name": "CoreService"
     },
     {
-      "name": "Collector"
+      "name": "CsbiService"
     },
     {
       "name": "AuthService"
     },
     {
-      "name": "UserService"
-    },
-    {
-      "name": "CsbiService"
+      "name": "RoleService"
     },
     {
-      "name": "RoleService"
+      "name": "UserService"
     }
   ],
   "consumes": [
@@ -991,6 +991,12 @@
             "in": "query",
             "required": false,
             "type": "string"
+          },
+          {
+            "name": "id",
+            "in": "query",
+            "required": false,
+            "type": "string"
           }
         ],
         "tags": [
@@ -1169,6 +1175,12 @@
             "in": "query",
             "required": false,
             "type": "string"
+          },
+          {
+            "name": "id",
+            "in": "query",
+            "required": false,
+            "type": "string"
           }
         ],
         "tags": [
@@ -2889,7 +2901,7 @@
         }
       },
       "additionalProperties": {},
-      "description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n    Foo foo = ...;\n    Any any;\n    any.PackFrom(foo);\n    ...\n    if (any.UnpackTo(\u0026foo)) {\n      ...\n    }\n\nExample 2: Pack and unpack a message in Java.\n\n    Foo foo = ...;\n    Any any = Any.pack(foo);\n    ...\n    if (any.is(Foo.class)) {\n      foo = any.unpack(Foo.class);\n    }\n\n Example 3: Pack and unpack a message in Python.\n\n    foo = Foo(...)\n    any = Any()\n    any.Pack(foo)\n    ...\n    if any.Is(Foo.DESCRIPTOR):\n      any.Unpack(foo)\n      ...\n\n Example 4: Pack and unpack a message in Go\n\n     foo := \u0026pb.Foo{...}\n     any, err := anypb.New(foo)\n     if err != nil {\n       ...\n     }\n     ...\n     foo := \u0026pb.Foo{}\n     if err := any.UnmarshalTo(foo); err != nil {\n       ...\n     }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n    package google.profile;\n    message Person {\n      string first_name = 1;\n      string last_name = 2;\n    }\n\n    {\n      \"@type\": \"type.googleapis.com/google.profile.Person\",\n      \"firstName\": \u003cstring\u003e,\n      \"lastName\": \u003cstring\u003e\n    }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n    {\n      \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n      \"value\": \"1.212s\"\n    }"
+      "description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n    Foo foo = ...;\n    Any any;\n    any.PackFrom(foo);\n    ...\n    if (any.UnpackTo(\u0026foo)) {\n      ...\n    }\n\nExample 2: Pack and unpack a message in Java.\n\n    Foo foo = ...;\n    Any any = Any.pack(foo);\n    ...\n    if (any.is(Foo.class)) {\n      foo = any.unpack(Foo.class);\n    }\n\nExample 3: Pack and unpack a message in Python.\n\n    foo = Foo(...)\n    any = Any()\n    any.Pack(foo)\n    ...\n    if any.Is(Foo.DESCRIPTOR):\n      any.Unpack(foo)\n      ...\n\nExample 4: Pack and unpack a message in Go\n\n     foo := \u0026pb.Foo{...}\n     any, err := anypb.New(foo)\n     if err != nil {\n       ...\n     }\n     ...\n     foo := \u0026pb.Foo{}\n     if err := any.UnmarshalTo(foo); err != nil {\n       ...\n     }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n    package google.profile;\n    message Person {\n      string first_name = 1;\n      string last_name = 2;\n    }\n\n    {\n      \"@type\": \"type.googleapis.com/google.profile.Person\",\n      \"firstName\": \u003cstring\u003e,\n      \"lastName\": \u003cstring\u003e\n    }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n    {\n      \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n      \"value\": \"1.212s\"\n    }"
     },
     "rbacCreateRolesRequest": {
       "type": "object",
diff --git a/api/proto/buf.lock b/api/proto/buf.lock
index 7cb8f4758..91a4b401f 100644
--- a/api/proto/buf.lock
+++ b/api/proto/buf.lock
@@ -4,14 +4,8 @@ deps:
   - remote: buf.build
     owner: googleapis
     repository: googleapis
-    branch: main
-    commit: ebc3077fbae84b63a71f742dd72f7101
-    digest: b1-gGcU24imC8L2I93v0LRssRMDGX0Fw9yrqgptNqVsTAU=
-    create_time: 2022-05-12T15:09:22.684487Z
+    commit: fdc236b6d1644b29a6161156ce08d8a2
   - remote: buf.build
     owner: grpc-ecosystem
     repository: grpc-gateway
-    branch: main
-    commit: febd9e8be39b4f4b878b9c64bcc203fd
-    digest: b1-K5jSHBrJ24jZXRgQgkmphZuP-cAL2b90ssKrgvDK2hU=
-    create_time: 2022-04-17T01:28:07.574665Z
+    commit: 00116f302b12478b85deb33b734e026c
diff --git a/api/proto/gosdn/rbac/role.proto b/api/proto/gosdn/rbac/role.proto
index 044fbef8a..29edc1cd4 100644
--- a/api/proto/gosdn/rbac/role.proto
+++ b/api/proto/gosdn/rbac/role.proto
@@ -84,6 +84,7 @@ message CreateRolesResponse {
 message GetRoleRequest {
     int64 timestamp = 1;
     string role_name = 2;
+    string id = 3;
 }
 
 message GetRoleResponse {
diff --git a/api/proto/gosdn/rbac/user.proto b/api/proto/gosdn/rbac/user.proto
index 3ad7f88de..8de04465c 100644
--- a/api/proto/gosdn/rbac/user.proto
+++ b/api/proto/gosdn/rbac/user.proto
@@ -78,6 +78,7 @@ message CreateUsersResponse {
 message GetUserRequest {
     int64 timestamp = 1;
     string name = 2;
+    string id = 3;
 }
 
 message GetUserResponse {
diff --git a/cli/cmd/userGet.go b/cli/cmd/userGet.go
index b5f1a0bd9..1a487a3a7 100644
--- a/cli/cmd/userGet.go
+++ b/cli/cmd/userGet.go
@@ -33,6 +33,7 @@ package cmd
 
 import (
 	"code.fbi.h-da.de/danet/gosdn/controller/api"
+	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
@@ -45,7 +46,12 @@ var userGetCmd = &cobra.Command{
 	Long:  `Requests one user using the provided name to search for it in the stored users.`,
 
 	RunE: func(cmd *cobra.Command, args []string) error {
-		resp, err := api.GetUser(createContextWithAuthorization(), viper.GetString("controllerAPIEndpoint"), nbUserName)
+		resp, err := api.GetUser(
+			createContextWithAuthorization(),
+			viper.GetString("controllerAPIEndpoint"),
+			nbUserName,
+			uuid.Nil,
+		)
 		if err != nil {
 			return err
 		}
diff --git a/controller/api/role.go b/controller/api/role.go
index b9d6bcc5c..cef731972 100644
--- a/controller/api/role.go
+++ b/controller/api/role.go
@@ -6,6 +6,7 @@ import (
 
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
 	nbi "code.fbi.h-da.de/danet/gosdn/controller/northbound/client"
+	"github.com/google/uuid"
 )
 
 // CreateRoles creates roles with provided data
@@ -24,7 +25,7 @@ func CreateRoles(ctx context.Context, addr string, roles []*apb.Role) (*apb.Crea
 }
 
 // GetRole returns one requested role found by name
-func GetRole(ctx context.Context, addr, name string) (*apb.GetRoleResponse, error) {
+func GetRole(ctx context.Context, addr, name string, uuid uuid.UUID) (*apb.GetRoleResponse, error) {
 	roleClient, err := nbi.RoleClient(addr, dialOptions...)
 	if err != nil {
 		return nil, err
@@ -33,6 +34,7 @@ func GetRole(ctx context.Context, addr, name string) (*apb.GetRoleResponse, erro
 	r := &apb.GetRoleRequest{
 		Timestamp: time.Now().UnixNano(),
 		RoleName:  name,
+		Id:        uuid.String(),
 	}
 
 	return roleClient.GetRole(ctx, r)
diff --git a/controller/api/role_test.go b/controller/api/role_test.go
index c98b0d277..785ae3128 100644
--- a/controller/api/role_test.go
+++ b/controller/api/role_test.go
@@ -61,6 +61,7 @@ func TestGetRole(t *testing.T) {
 		ctx  context.Context
 		addr string
 		name string
+		id   uuid.UUID
 	}
 	tests := []struct {
 		name    string
@@ -74,6 +75,7 @@ func TestGetRole(t *testing.T) {
 				ctx:  context.TODO(),
 				addr: testAPIEndpoint,
 				name: "adminTestRole",
+				id:   uuid.Nil,
 			},
 			want: &apb.GetRoleResponse{
 				Status: apb.Status_STATUS_OK,
@@ -90,6 +92,7 @@ func TestGetRole(t *testing.T) {
 				ctx:  context.TODO(),
 				addr: testAPIEndpoint,
 				name: "not role",
+				id:   uuid.Nil,
 			},
 			want:    nil,
 			wantErr: true,
@@ -97,7 +100,7 @@ func TestGetRole(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := GetRole(tt.args.ctx, tt.args.addr, tt.args.name)
+			got, err := GetRole(tt.args.ctx, tt.args.addr, tt.args.name, tt.args.id)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("GetRole() error = %v, wantErr %v", err, tt.wantErr)
 				return
diff --git a/controller/api/user.go b/controller/api/user.go
index 31d682860..f5f49ea4d 100644
--- a/controller/api/user.go
+++ b/controller/api/user.go
@@ -6,6 +6,8 @@ import (
 
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
 	nbi "code.fbi.h-da.de/danet/gosdn/controller/northbound/client"
+
+	"github.com/google/uuid"
 )
 
 // CreateUsers creates users with provided data
@@ -24,7 +26,7 @@ func CreateUsers(ctx context.Context, addr string, users []*apb.User) (*apb.Crea
 }
 
 //GetUser returns one requested user found by name
-func GetUser(ctx context.Context, addr, name string) (*apb.GetUserResponse, error) {
+func GetUser(ctx context.Context, addr, name string, uuid uuid.UUID) (*apb.GetUserResponse, error) {
 	userClient, err := nbi.UserClient(addr, dialOptions...)
 	if err != nil {
 		return nil, err
@@ -33,6 +35,7 @@ func GetUser(ctx context.Context, addr, name string) (*apb.GetUserResponse, erro
 	r := &apb.GetUserRequest{
 		Timestamp: time.Now().UnixNano(),
 		Name:      name,
+		Id:        uuid.String(),
 	}
 
 	return userClient.GetUser(ctx, r)
diff --git a/controller/api/user_test.go b/controller/api/user_test.go
index ab8cf858e..afa2f6dc6 100644
--- a/controller/api/user_test.go
+++ b/controller/api/user_test.go
@@ -65,6 +65,7 @@ func TestGetUser(t *testing.T) {
 		ctx  context.Context
 		addr string
 		name string
+		id   uuid.UUID
 	}
 	tests := []struct {
 		name    string
@@ -78,6 +79,7 @@ func TestGetUser(t *testing.T) {
 				ctx:  context.TODO(),
 				addr: testAPIEndpoint,
 				name: "testAdmin",
+				id:   uuid.Nil,
 			},
 			want: &apb.GetUserResponse{
 				Status: apb.Status_STATUS_OK,
@@ -94,6 +96,7 @@ func TestGetUser(t *testing.T) {
 				ctx:  context.TODO(),
 				addr: testAPIEndpoint,
 				name: "foos",
+				id:   uuid.Nil,
 			},
 			want:    nil,
 			wantErr: true,
@@ -102,7 +105,7 @@ func TestGetUser(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := GetUser(tt.args.ctx, tt.args.addr, tt.args.name)
+			got, err := GetUser(tt.args.ctx, tt.args.addr, tt.args.name, tt.args.id)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("GetUser() error = %v, wantErr %v", err, tt.wantErr)
 				return
diff --git a/controller/northbound/server/role.go b/controller/northbound/server/role.go
index 4cb05b6e1..4141c6250 100644
--- a/controller/northbound/server/role.go
+++ b/controller/northbound/server/role.go
@@ -2,6 +2,7 @@ package server
 
 import (
 	"context"
+	"fmt"
 	"time"
 
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
@@ -59,7 +60,12 @@ func (r Role) GetRole(ctx context.Context, request *apb.GetRoleRequest) (*apb.Ge
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	roleData, err := r.roleService.Get(store.Query{Name: request.RoleName})
+	roleID, err := uuid.Parse(request.Id)
+	if err != nil {
+		return nil, fmt.Errorf("could not parse role uuid")
+	}
+
+	roleData, err := r.roleService.Get(store.Query{Name: request.RoleName, ID: roleID})
 	if err != nil {
 		return nil, err
 	}
diff --git a/controller/northbound/server/role_test.go b/controller/northbound/server/role_test.go
index 1f75f2bc4..75582e3c2 100644
--- a/controller/northbound/server/role_test.go
+++ b/controller/northbound/server/role_test.go
@@ -90,6 +90,7 @@ func TestRole_GetRole(t *testing.T) {
 				ctx: context.TODO(),
 				request: &apb.GetRoleRequest{
 					RoleName: "adminTestRole",
+					Id:       uuid.Nil.String(),
 				},
 			},
 			want: &apb.GetRoleResponse{
@@ -107,6 +108,7 @@ func TestRole_GetRole(t *testing.T) {
 				ctx: context.TODO(),
 				request: &apb.GetRoleRequest{
 					RoleName: "not role",
+					Id:       uuid.Nil.String(),
 				},
 			},
 			want:    nil,
diff --git a/controller/northbound/server/user.go b/controller/northbound/server/user.go
index 238858637..2e0bfed22 100644
--- a/controller/northbound/server/user.go
+++ b/controller/northbound/server/user.go
@@ -3,6 +3,7 @@ package server
 import (
 	"context"
 	"encoding/base64"
+	"fmt"
 	"time"
 
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
@@ -82,7 +83,12 @@ func (u User) GetUser(ctx context.Context, request *apb.GetUserRequest) (*apb.Ge
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	userData, err := u.userService.Get(store.Query{Name: request.Name})
+	userID, err := uuid.Parse(request.Id)
+	if err != nil {
+		return nil, fmt.Errorf("could not parse user uuid")
+	}
+
+	userData, err := u.userService.Get(store.Query{Name: request.Name, ID: userID})
 	if err != nil {
 		return nil, err
 	}
diff --git a/controller/northbound/server/user_test.go b/controller/northbound/server/user_test.go
index c84533635..eb8f27fb3 100644
--- a/controller/northbound/server/user_test.go
+++ b/controller/northbound/server/user_test.go
@@ -93,6 +93,7 @@ func TestUser_GetUser(t *testing.T) {
 				ctx: context.TODO(),
 				request: &apb.GetUserRequest{
 					Name: "testAdmin",
+					Id:   uuid.Nil.String(),
 				},
 			},
 			want: &apb.GetUserResponse{Status: apb.Status_STATUS_OK,
@@ -106,6 +107,7 @@ func TestUser_GetUser(t *testing.T) {
 				ctx: context.TODO(),
 				request: &apb.GetUserRequest{
 					Name: "nope",
+					Id:   uuid.Nil.String(),
 				},
 			},
 			want:    nil,
-- 
GitLab