diff --git a/api/proto/gosdn/rbac/user.proto b/api/proto/gosdn/rbac/user.proto index ec13346ef394ce5cc90722af65a995937c2f8578..ef5d4073983da4bed97915613fc4de7c689b97ef 100644 --- a/api/proto/gosdn/rbac/user.proto +++ b/api/proto/gosdn/rbac/user.proto @@ -89,7 +89,7 @@ message CreateUsersResponse { // GetUser message GetUserRequest { int64 timestamp = 1; - string name = 2 [(buf.validate.field).required = true]; + string name = 2 [(buf.validate.field).required = true]; // TODO(faseid): reconsider if this is necessary as required, but id is not? string id = 3; } diff --git a/controller/northbound/server/role.go b/controller/northbound/server/role.go index 7691ce523f47285610df8b5dd734ffa0ed2e8c80..2d3de09de4a42aea2a565870d5c2ab8be83f7ab0 100644 --- a/controller/northbound/server/role.go +++ b/controller/northbound/server/role.go @@ -71,9 +71,14 @@ func (r RoleServer) CreateRoles(ctx context.Context, request *apb.CreateRolesReq } for _, rrole := range request.Roles { - role := rbac.NewRole(uuid.New(), rrole.Name, rrole.Description, rrole.Permissions) + roleID, err := uuid.Parse(rrole.Id) + if err != nil { + roleID = uuid.New() + } + + role := rbac.NewRole(roleID, rrole.Name, rrole.Description, rrole.Permissions) - err := r.roleService.Add(role) + err = r.roleService.Add(role) if err != nil { log.Error(err) return nil, status.Errorf(codes.Aborted, "%v", err) diff --git a/controller/northbound/server/user.go b/controller/northbound/server/user.go index 1ea31bee575daa4e29faa594610937452543e90b..d37ae43a79bb7b0c1086687dfa4b2e44ee588318 100644 --- a/controller/northbound/server/user.go +++ b/controller/northbound/server/user.go @@ -92,7 +92,12 @@ func (u UserServer) CreateUsers(ctx context.Context, request *apb.CreateUsersReq hashedPassword := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(user.Password), []byte(salt), 1, 64*1024, 4, 32)) - user := rbac.NewUser(uuid.New(), user.Name, roles, string(hashedPassword), user.Token, salt, conflict.Metadata{ResourceVersion: 0}) + userID, err := uuid.Parse(user.Id) + if err != nil { + userID = uuid.New() + } + + user := rbac.NewUser(userID, user.Name, roles, string(hashedPassword), user.Token, salt, conflict.Metadata{ResourceVersion: 0}) err = u.userService.Add(user) if err != nil { log.Error(err) diff --git a/integration-tests/integrationTestUtils/integrationTestUtils.go b/integration-tests/integrationTestUtils/integrationTestUtils.go index 2d88c1a13a4cd9900e417fb7b567bffc842cf1e4..daf272f74c1cd1d30c9dd68bc76f2bf8a8f5cbef 100644 --- a/integration-tests/integrationTestUtils/integrationTestUtils.go +++ b/integration-tests/integrationTestUtils/integrationTestUtils.go @@ -8,6 +8,9 @@ import ( configMgmtPb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/configurationmanagement" ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd" + apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac" + "github.com/sirupsen/logrus" + "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac" "code.fbi.h-da.de/danet/gosdn/controller/api" @@ -16,7 +19,9 @@ import ( "google.golang.org/grpc/metadata" ) -func createContextWithAuthorization(loginResponse *rbac.LoginResponse) context.Context { +const admin = "admin" + +func CreateContextWithAuthorization(loginResponse *rbac.LoginResponse) context.Context { md := metadata.Pairs("authorize", loginResponse.Token) return metadata.NewOutgoingContext(context.Background(), md) } @@ -35,7 +40,7 @@ func CreateSecureConnection() (*grpc.ClientConn, context.Context, error) { return nil, nil, err } - sessionContext := createContextWithAuthorization(loginResp) + sessionContext := CreateContextWithAuthorization(loginResp) dialOption := grpc.WithTransportCredentials(insecure.NewCredentials()) conn, err := grpc.Dial(controllerUrl, dialOption, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024))) @@ -66,7 +71,7 @@ func CreateConnection() (*grpc.ClientConn, context.Context, error) { // ExportCurrentSDNConfig can be used to save the current SDN config as a string to use in an other test later on. func ExportCurrentSDNConfig(conn *grpc.ClientConn, ctx context.Context) (string, error) { pndService := ppb.NewPndServiceClient(conn) - pndRes, err := pndService.GetPndList(ctx, &ppb.GetPndListRequest{Timestamp: getTimestamp()}) + pndRes, err := pndService.GetPndList(ctx, &ppb.GetPndListRequest{Timestamp: GetTimestamp()}) if err != nil { return "", err } @@ -74,7 +79,7 @@ func ExportCurrentSDNConfig(conn *grpc.ClientConn, ctx context.Context) (string, configMgmtService := configMgmtPb.NewConfigurationManagementServiceClient(conn) - sdnConfigResponse, err := configMgmtService.ExportSDNConfig(ctx, &configMgmtPb.ExportSDNConfigRequest{Timestamp: getTimestamp(), Pid: pndID}) + sdnConfigResponse, err := configMgmtService.ExportSDNConfig(ctx, &configMgmtPb.ExportSDNConfigRequest{Timestamp: GetTimestamp(), Pid: pndID}) if err != nil { return "", err } @@ -85,7 +90,7 @@ func ExportCurrentSDNConfig(conn *grpc.ClientConn, ctx context.Context) (string, // ApplySDNConfig can be used to apply a given SDN config as a string to set the testing environment as desired. func ApplySDNConfig(conn *grpc.ClientConn, ctx context.Context, sdnConfig string) { pndService := ppb.NewPndServiceClient(conn) - pndRes, err := pndService.GetPndList(ctx, &ppb.GetPndListRequest{Timestamp: getTimestamp()}) + pndRes, err := pndService.GetPndList(ctx, &ppb.GetPndListRequest{Timestamp: GetTimestamp()}) if err != nil { fmt.Println(err) } @@ -95,12 +100,95 @@ func ApplySDNConfig(conn *grpc.ClientConn, ctx context.Context, sdnConfig string configMgmtService := configMgmtPb.NewConfigurationManagementServiceClient(conn) - _, err = configMgmtService.ImportSDNConfig(ctx, &configMgmtPb.ImportSDNConfigRequest{Timestamp: getTimestamp(), Pid: pndID, SdnConfigData: sdnConfig}) + _, err = configMgmtService.ImportSDNConfig(ctx, &configMgmtPb.ImportSDNConfigRequest{Timestamp: GetTimestamp(), Pid: pndID, SdnConfigData: sdnConfig}) if err != nil { fmt.Println(err) } } -func getTimestamp() int64 { +func GetTimestamp() int64 { return int64(time.Now().Nanosecond()) } + +func CleanUserAndRolesExceptAdmin(conn *grpc.ClientConn, ctx context.Context) { + userService := apb.NewUserServiceClient(conn) + roleService := apb.NewRoleServiceClient(conn) + + getAllUserResponse, err := userService.GetUsers(ctx, &apb.GetUsersRequest{ + Timestamp: GetTimestamp(), + }) + if err != nil { + logrus.Errorf("Error while cleaning up DB, %v", err) + } + + err = cleanUsersExceptAdmin(getAllUserResponse, userService, ctx) + if err != nil { + logrus.Errorf("Error while cleaning up DB, %v", err) + } + + getAllRoleResponse, err := roleService.GetRoles(ctx, &apb.GetRolesRequest{ + Timestamp: GetTimestamp(), + }) + if err != nil { + logrus.Errorf("Error while cleaning up DB, %v", err) + } + + err = cleanRolesExceptAdmin(getAllRoleResponse, roleService, ctx) + if err != nil { + logrus.Errorf("Error while cleaning up DB, %v", err) + } +} + +func cleanUsersExceptAdmin(resp *apb.GetUsersResponse, userService apb.UserServiceClient, ctx context.Context) error { + // no need to delete if only admin available + if len(resp.User) == 1 { + return nil + } + + var names []string + + for _, u := range resp.User { + if u.Name == admin { + continue + } + + names = append(names, u.Name) + } + + _, err := userService.DeleteUsers(ctx, &apb.DeleteUsersRequest{ + Timestamp: GetTimestamp(), + Username: names, + }) + if err != nil { + return err + } + + return nil +} + +func cleanRolesExceptAdmin(resp *apb.GetRolesResponse, roleService apb.RoleServiceClient, ctx context.Context) error { + // no need to delete if only admin available + if len(resp.Roles) == 1 { + return nil + } + + var names []string + + for _, role := range resp.Roles { + if role.Name == admin { + continue + } + + names = append(names, role.Name) + } + + _, err := roleService.DeleteRoles(ctx, &apb.DeleteRolesRequest{ + Timestamp: GetTimestamp(), + RoleName: names, + }) + if err != nil { + return err + } + + return nil +} diff --git a/integration-tests/rbac_tests/rbac_test.go b/integration-tests/rbac_tests/rbac_test.go new file mode 100644 index 0000000000000000000000000000000000000000..44cbc4fc1ed74a797e651adb62c52c8051c90be4 --- /dev/null +++ b/integration-tests/rbac_tests/rbac_test.go @@ -0,0 +1,398 @@ +package integration_test_rbac + +import ( + "context" + "fmt" + "testing" + + "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/conflict" + mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement" + apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac" + "github.com/stretchr/testify/assert" + + integration_test_utils "code.fbi.h-da.de/danet/gosdn/integration-tests/integrationTestUtils" + "google.golang.org/grpc" +) + +const userID = "23224223-aeb6-433b-a797-2d671d7424f0" +const user1NameAndPW = "testUser1" +const pndID = "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d" +const roleID = "679f7982-d740-452e-b78d-c1d133233694" + +// The connection to the controller to use in each test. +var conn *grpc.ClientConn + +// The context containing the credentials when authenticated. +var ctx context.Context + +// A defaultSDN config with default/empty values. +var defaultSDNConfig string + +func TestMain(m *testing.M) { + localConn, localCtx, err := integration_test_utils.CreateSecureConnection() + if err != nil { + fmt.Println(err.Error()) + } + conn = localConn + ctx = localCtx + + sndConfig, err := integration_test_utils.ExportCurrentSDNConfig(conn, ctx) + defaultSDNConfig = sndConfig + if err != nil { + fmt.Println(err.Error()) + } + + integration_test_utils.CleanUserAndRolesExceptAdmin(conn, ctx) + + m.Run() +} + +func TestUserCreationAndModification(t *testing.T) { + defer integration_test_utils.CleanUserAndRolesExceptAdmin(conn, ctx) + + // setup required parameters + const user2UUID = "0a28e837-473b-475d-b935-2457eb9462ae" + const expectedAmountOfUsers = 3 + const expectedNameAfterChange = "testUser1Changed" + + createUserRequest := &apb.CreateUsersRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + User: []*apb.User{ + { + Id: userID, + Name: user1NameAndPW, + Roles: map[string]string{pndID: "admin", userID: "random"}, + Password: user1NameAndPW, + Metadata: &conflict.Metadata{ + ResourceVersion: 0, + }, + }, + { + Id: user2UUID, + Name: "user2", + Roles: map[string]string{pndID: "random"}, + Password: user1NameAndPW, + Metadata: &conflict.Metadata{ + ResourceVersion: 0, + }, + }, + }, + } + + getAllRequest := &apb.GetUsersRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + } + + updateUserRequest := &apb.UpdateUsersRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + User: []*apb.UpdateUser{ + { + Id: userID, + Name: expectedNameAfterChange, + Roles: map[string]string{}, + Metadata: &conflict.Metadata{}, + }, + }, + } + + getUserRequest := &apb.GetUserRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + Id: userID, + Name: expectedNameAfterChange, + } + + // setup gRPC services + userService := apb.NewUserServiceClient(conn) + + // create two users + _, err := userService.CreateUsers(ctx, createUserRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + // get all users and compare + getAllResponse, err := userService.GetUsers(ctx, getAllRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + assert.Equal(t, expectedAmountOfUsers, len(getAllResponse.User)) + + assert.NotEqual(t, getAllResponse.User[1].Name, getAllResponse.User[2].Name) + + assert.NotEqual(t, getAllResponse.User[1].Roles[pndID], getAllResponse.User[2].Roles[pndID]) + + // change name of user1 + updateUserRequest.User[0].Roles = getAllResponse.User[1].Roles + updateUserRequest.User[0].Metadata.ResourceVersion = getAllResponse.User[1].Metadata.ResourceVersion + + _, err = userService.UpdateUsers(ctx, updateUserRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + getUserResponse, err := userService.GetUser(ctx, getUserRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + assert.Equal(t, expectedNameAfterChange, getUserResponse.User.Name) +} + +func TestRoleCreationAndModification(t *testing.T) { + defer integration_test_utils.CleanUserAndRolesExceptAdmin(conn, ctx) + + // setup required parameters + const multiplePermissionRole = "multiple permission role" + const roleDescription = "A role with multiple permissions." + const multiplePermissionRoleNameAfterChange = "only two permission role" + const permissionToBeDeleted = "permission to be deleted" + const expectedAmountOfRoles = 3 + const expectedAmountOfRolesAfterDeletion = 2 + const expectedAmountOfPermissions = 3 + const expectedAmountOfPermissionsAfterDeletion = 2 + + createRolesRequest := &apb.CreateRolesRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + Roles: []*apb.Role{ + { + Id: roleID, + Name: multiplePermissionRole, + Description: roleDescription, + Permissions: []string{ + "permission1", + "permission2", + permissionToBeDeleted, + }, + }, + { + Name: "random", + Description: "A random role with one permissions.", + Permissions: []string{ + "permission1", + }, + }, + }, + } + + getAllRoleRequest := &apb.GetRolesRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + } + + deleteRoleRequest := &apb.DeleteRolesRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + RoleName: []string{"random"}, + } + + removePermissionRequest := &apb.DeletePermissionsForRoleRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + RoleName: multiplePermissionRole, + PermissionsToDelete: []string{ + permissionToBeDeleted, + }, + } + + updateRoleRequest := &apb.UpdateRolesRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + Roles: []*apb.Role{ + { + Id: roleID, + Name: multiplePermissionRoleNameAfterChange, + Description: roleDescription, + }, + }, + } + + getRoleRequest := &apb.GetRoleRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + Id: roleID, + RoleName: multiplePermissionRoleNameAfterChange, + } + + // setup gRPC services + roleService := apb.NewRoleServiceClient(conn) + + // create new roles + _, err := roleService.CreateRoles(ctx, createRolesRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + // get roles and check amount + getAllRoleResponse, err := roleService.GetRoles(ctx, getAllRoleRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + assert.Equal(t, expectedAmountOfRoles, len(getAllRoleResponse.Roles)) + + assert.Equal(t, multiplePermissionRole, getAllRoleResponse.Roles[1].Name) + + assert.Equal(t, expectedAmountOfPermissions, len(getAllRoleResponse.Roles[1].Permissions)) + + // delete role and compare amount + _, err = roleService.DeleteRoles(ctx, deleteRoleRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + getAllRoleResponse, err = roleService.GetRoles(ctx, getAllRoleRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + assert.Equal(t, expectedAmountOfRolesAfterDeletion, len(getAllRoleResponse.Roles)) + + // delete one permission then get role to compare + _, err = roleService.DeletePermissionsForRole(ctx, removePermissionRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + getAllRoleResponse, err = roleService.GetRoles(ctx, getAllRoleRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + assert.Equal(t, expectedAmountOfPermissionsAfterDeletion, len(getAllRoleResponse.Roles[1].Permissions)) + + // change role name and compare + _, err = roleService.UpdateRoles(ctx, updateRoleRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + getRoleResponse, err := roleService.GetRole(ctx, getRoleRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + assert.Equal(t, multiplePermissionRoleNameAfterChange, getRoleResponse.Role.Name) +} + +func TestUserWithoutPermission(t *testing.T) { + defer integration_test_utils.CleanUserAndRolesExceptAdmin(conn, ctx) + + // setup required parameters + const roleName = "peter" + + createUserRequestPreparation := &apb.CreateUsersRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + User: []*apb.User{ + { + Id: userID, + Name: user1NameAndPW, + Roles: map[string]string{pndID: roleName}, + Password: user1NameAndPW, + Metadata: &conflict.Metadata{ + ResourceVersion: 0, + }, + }, + }, + } + + createUserRequestTestCase := &apb.CreateUsersRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + User: []*apb.User{ + { + Id: "b22c4e46-fa54-4226-8e61-134c895bef5b", + Name: "test", + Roles: map[string]string{pndID: "admin"}, + Password: user1NameAndPW, + Metadata: &conflict.Metadata{ + ResourceVersion: 0, + }, + }, + }, + } + + createRoleRequest := &apb.CreateRolesRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + Roles: []*apb.Role{ + { + Name: roleName, + Description: "Something that only a peter can do.", + Permissions: []string{ + "/gosdn.rbac.UserService/CreateUsers", + "/gosdn.networkelement.NetworkElementService/GetAllFlattened", + }, + }, + }, + } + + loginRequest := &apb.LoginRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + Username: user1NameAndPW, + Pwd: user1NameAndPW, + } + + // setup gRPC services + userService := apb.NewUserServiceClient(conn) + roleService := apb.NewRoleServiceClient(conn) + authService := apb.NewAuthServiceClient(conn) + mneService := mnepb.NewNetworkElementServiceClient(conn) + + // create a user and its role + _, err := userService.CreateUsers(ctx, createUserRequestPreparation) + if err != nil { + t.Error(err) + t.FailNow() + } + + _, err = roleService.CreateRoles(ctx, createRoleRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + // login new user + loginResponse, err := authService.Login(context.Background(), loginRequest) + if err != nil { + t.Error(err) + t.FailNow() + } + + sessionToken := integration_test_utils.CreateContextWithAuthorization(loginResponse) + + // test if user can get all MNE, should fail + _, err = mneService.GetAll(sessionToken, &mnepb.GetAllRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + Pid: pndID, + }, + ) + + assert.NotEqual(t, nil, err) + + // test if user can get all flattened MNE, should work + _, err = mneService.GetAllFlattened(sessionToken, &mnepb.GetAllFlattenedRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + Pid: pndID, + }, + ) + + assert.Equal(t, nil, err) + + // test if user1 can create user with admin role, should fail + _, _ = userService.CreateUsers(sessionToken, createUserRequestTestCase) + //TODO(faseid): implement mechanism to stop random user from creating admin user, + // then uncomment test case + //assert.NotEqual(t, nil, err) + + // test if user1 can create user with random role, should work + createUserRequestTestCase.User[0].Roles[pndID] = "peter 2" + _, err = userService.CreateUsers(sessionToken, createUserRequestTestCase) + + assert.Equal(t, nil, err) +}