diff --git a/Makefile b/Makefile index bdd980b1c2536c0635118a9dee2dbb5c50307afc..288f8dd941c3f969c78815aab47113f70c401a8b 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ generate-certs: generate-gnmi-target-certs generate-gosdn-certs integration-tests: generate-certs containerize-gosdn containerize-plugin-registry docker-compose -f dev_env_data/docker-compose/integration-test_docker-compose.yml down docker-compose -f dev_env_data/docker-compose/integration-test_docker-compose.yml up -d - go test -p 1 ./integration-tests/* + go test -p 1 -count=1 ./integration-tests/* docker-compose -f dev_env_data/docker-compose/integration-test_docker-compose.yml down integration-tests-debug-up: generate-certs containerize-gosdn containerize-plugin-registry diff --git a/controller/api/apiUtil_test.go b/controller/api/apiUtil_test.go index 78ae66d8bfbcc64121370f53f05840feaf702f6f..ca87dfcc32963c0dc338434236d68d8b51aa80d5 100644 --- a/controller/api/apiUtil_test.go +++ b/controller/api/apiUtil_test.go @@ -76,14 +76,15 @@ func createTestUsers() error { testUserPWD := createHashedAndSaltedPassword("user", salt) testRandPWD := createHashedAndSaltedPassword("aurelius", salt) + wrongTokens := []string{"wrong token"} users := []rbacImpl.User{ {UserID: uuid.MustParse(adminID), UserName: "testAdmin", Roles: adminRoleMap, Password: testAdminPWD}, {UserID: uuid.MustParse(userID), UserName: "testUser", Roles: userRoleMap, Password: testUserPWD}, - {UserID: uuid.New(), UserName: "testRandom", Roles: randomRoleMap, Password: testRandPWD, Token: "wrong token"}, + {UserID: uuid.New(), UserName: "testRandom", Roles: randomRoleMap, Password: testRandPWD, Tokens: wrongTokens}, } for _, u := range users { - err := userService.Add(rbacImpl.NewUser(u.ID(), u.Name(), u.Roles, u.Password, "", salt, conflict.Metadata{ResourceVersion: 0})) + err := userService.Add(rbacImpl.NewUser(u.ID(), u.Name(), u.Roles, u.Password, wrongTokens, salt, conflict.Metadata{ResourceVersion: 0})) if err != nil { return err } @@ -146,7 +147,7 @@ func createTestUserToken(userName string, validTokenRequired bool) (string, erro if err != nil { return token, err } - user.SetToken(token) + user.AddToken(token) err = userService.Update(user) if err != nil { diff --git a/controller/config/config.go b/controller/config/config.go index 29f8a9f28b2405298559720e474b04132357c089..cb2dc65c776ac3d3a216a36962f220a61b9497e1 100644 --- a/controller/config/config.go +++ b/controller/config/config.go @@ -19,6 +19,8 @@ const ( defaultJWTDuration = time.Hour * 24 jwtSecretKey = "jwtSecret" gNMISubscriptionsFilePathKey = "gNMISubscriptionsPath" + maxTokensPerUserKey = "maxTokensPerUser" + defaultMaxTokensPerUser = 100 // RabbitMQ Broker. amqpPrefixKey = "amqpPrefix" @@ -81,6 +83,9 @@ var CertFilePath string // KeyFilePath si the path to the private key that the controller should use for TLS connections. var KeyFilePath string +// MaxTokensPerUser is the maximum number of tokens a user can have. This determines the maximum of concurrent logged in sessions per user. +var MaxTokensPerUser int + // Init gets called on module import. func Init() { err := InitializeConfig() @@ -107,9 +112,9 @@ func InitializeConfig() error { setLogLevel() - DatabaseConnection = getStringFromViper(databaseConnectionKey) + DatabaseConnection = viper.GetString(databaseConnectionKey) - FilesystemPathToStores = getStringFromViper(filesystemPathToStores) + FilesystemPathToStores = viper.GetString(filesystemPathToStores) if FilesystemPathToStores == "" { FilesystemPathToStores = "stores" } @@ -121,15 +126,20 @@ func InitializeConfig() error { JWTSecret = viper.GetString(jwtSecretKey) - GNMISubscriptionsFilePath = getStringFromViper(gNMISubscriptionsFilePathKey) + MaxTokensPerUser = viper.GetInt(maxTokensPerUserKey) + if MaxTokensPerUser == 0 { + MaxTokensPerUser = defaultMaxTokensPerUser + } + + GNMISubscriptionsFilePath = viper.GetString(gNMISubscriptionsFilePathKey) loadAMQPConfig() - CAFilePath = getStringFromViper(tlsCACertFileKey) + CAFilePath = viper.GetString(tlsCACertFileKey) - CertFilePath = getStringFromViper(tlsCertFileKey) + CertFilePath = viper.GetString(tlsCertFileKey) - KeyFilePath = getStringFromViper(tlsKeyFileKey) + KeyFilePath = viper.GetString(tlsKeyFileKey) if err := viper.WriteConfig(); err != nil { return err @@ -161,12 +171,6 @@ func getUUIDFromViper(viperKey string) (uuid.UUID, error) { return parsedUUID, nil } -func getStringFromViper(viperKey string) string { - stringFromViper := viper.GetString(viperKey) - - return stringFromViper -} - func setChangeTimeout() error { e := os.Getenv(changeTimeoutKey) if e != "" { @@ -202,9 +206,9 @@ func getDurationFromViper(viperKey, unit string) (time.Duration, error) { } func loadAMQPConfig() { - AMQPPrefix = getStringFromViper(amqpPrefixKey) - AMQPUser = getStringFromViper(amqpUserKey) - AMQPPassword = getStringFromViper(amqpPasswordKey) - AMQPHost = getStringFromViper(amqpHostKey) - AMQPPort = getStringFromViper(amqpPortKey) + AMQPPrefix = viper.GetString(amqpPrefixKey) + AMQPUser = viper.GetString(amqpUserKey) + AMQPPassword = viper.GetString(amqpPasswordKey) + AMQPHost = viper.GetString(amqpHostKey) + AMQPPort = viper.GetString(amqpPortKey) } diff --git a/controller/configs/basic-docker-compose.toml b/controller/configs/basic-docker-compose.toml index 2708de32e81c6101ffcf2da807de72ee86065d4d..d14da89b04f9d107f22f9e237f0e68cf36ed2df8 100644 --- a/controller/configs/basic-docker-compose.toml +++ b/controller/configs/basic-docker-compose.toml @@ -16,6 +16,7 @@ plugin-folder = 'plugins' plugin-registry = 'plugin-registry:55057' security = 'insecure' socket = ':55055' +maxTokensPerUser = 100 tlscacertfile = '/ssl/ca.crt' tlscertfile = '/ssl/certs/gosdn-selfsigned.crt' tlskeyfile = '/ssl/private/gosdn-selfsigned.key' diff --git a/controller/configs/containerlab-gosdn.toml.example b/controller/configs/containerlab-gosdn.toml.example index e5ed6652053ef3f64a929e863cf05a8f07758032..4b3e0668ae77b65fc85b0ff2681efa018f174f67 100644 --- a/controller/configs/containerlab-gosdn.toml.example +++ b/controller/configs/containerlab-gosdn.toml.example @@ -10,6 +10,7 @@ gNMISubscriptionsPath = "configs/gNMISubscriptions.txt" tlscertfile = '/ssl/certs/gosdn-selfsigned.crt' tlskeyfile = '/ssl/private/gosdn-selfsigned.key' tlscacertfile = '/ssl/ca.crt' +maxTokensPerUser = 100 amqpPrefix = "amqp://" amqpUser = "guest" diff --git a/controller/configs/development-gosdn.toml.example b/controller/configs/development-gosdn.toml.example index 4845c082c2acb6476c48b4e313ee8578a13d3c87..56bc0f26184b2fcd9fd3e5787afe56c0ce4664e0 100644 --- a/controller/configs/development-gosdn.toml.example +++ b/controller/configs/development-gosdn.toml.example @@ -15,6 +15,7 @@ log-level = 'debug' plugin-folder = 'plugins' security = 'insecure' socket = ':55055' +maxTokensPerUser = 100 tlscertfile = '/ssl/certs/gosdn-selfsigned.crt' tlskeyfile = '/ssl/private/gosdn-selfsigned.key' tlscacertfile = '/ssl/ca.crt' diff --git a/controller/configs/integration-test-gosdn.toml b/controller/configs/integration-test-gosdn.toml index 2708de32e81c6101ffcf2da807de72ee86065d4d..d14da89b04f9d107f22f9e237f0e68cf36ed2df8 100644 --- a/controller/configs/integration-test-gosdn.toml +++ b/controller/configs/integration-test-gosdn.toml @@ -16,6 +16,7 @@ plugin-folder = 'plugins' plugin-registry = 'plugin-registry:55057' security = 'insecure' socket = ':55055' +maxTokensPerUser = 100 tlscacertfile = '/ssl/ca.crt' tlscertfile = '/ssl/certs/gosdn-selfsigned.crt' tlskeyfile = '/ssl/private/gosdn-selfsigned.key' diff --git a/controller/controller.go b/controller/controller.go index 4cb78c20be7ac1d1179b3c26b0b5fcda8631bfe9..65d692517d2a1d5005919b63e9ead1b431b2e9e2 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -10,6 +10,7 @@ import ( "os/signal" "sync" "syscall" + "time" "github.com/google/uuid" "github.com/sethvargo/go-password/password" @@ -172,6 +173,10 @@ func initialize() error { return err } + if err := deleteAllExpiredUserTokens(); err != nil { + return err + } + if err := startGrpc(); err != nil { return err } @@ -430,7 +435,7 @@ func ensureDefaultUserExists() error { hashedPassword := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(usedPassword), []byte(salt), 1, 64*1024, 4, 32)) - err = c.userService.Add(rbacImpl.NewUser(uuid.New(), defaultUserName, map[string]string{config.BasePndUUID.String(): "admin"}, string(hashedPassword), "", salt, conflict.Metadata{ResourceVersion: 0})) + err = c.userService.Add(rbacImpl.NewUser(uuid.New(), defaultUserName, map[string]string{config.BasePndUUID.String(): "admin"}, string(hashedPassword), []string{}, salt, conflict.Metadata{ResourceVersion: 0})) if err != nil { return err } @@ -441,6 +446,42 @@ func ensureDefaultUserExists() error { return nil } +func deleteAllExpiredUserTokens() error { + var usersToUpdate []rbac.User + // Temporary create JWT manager just to evaluate tokens here + jwtManager := rbacImpl.NewJWTManager(config.JWTSecret, config.JWTDuration) + + users, err := c.userService.GetAll() + if err != nil { + return fmt.Errorf("error getting all users while deleting expired user tokens: %w", err) + } + + for _, user := range users { + tokens := user.GetTokens() + for _, token := range tokens { + claims, err := jwtManager.GetClaimsFromToken(token) + if err != nil { + return fmt.Errorf("error getting claims from token while deleting expired user tokens: %w", err) + } + if claims.ExpiresAt < time.Now().Unix() { + err := user.RemoveToken(token) + if err != nil { + return fmt.Errorf("error removing token while deleting expired user tokens: %w", err) + } + usersToUpdate = append(usersToUpdate, user) + } + } + } + + for _, user := range usersToUpdate { + err := c.userService.Update(user) + if err != nil { + return fmt.Errorf("error updating user while deleting expired user tokens: %w", err) + } + } + return nil +} + // Run calls initialize to start the controller. func Run(ctx context.Context) error { var initError error diff --git a/controller/interfaces/rbac/user.go b/controller/interfaces/rbac/user.go index 2116e73be7c3346c43281812e167bc33d2504a5b..1dfe1d64a31233fc15209e9d819702cc24ea623c 100644 --- a/controller/interfaces/rbac/user.go +++ b/controller/interfaces/rbac/user.go @@ -12,8 +12,10 @@ type User interface { Name() string GetRoles() map[string]string GetPassword() string - GetToken() string - SetToken(string) + GetTokens() []string + SetTokens([]string) + AddToken(string) + RemoveToken(string) error GetSalt() string GetMetadata() conflict.Metadata } @@ -24,7 +26,7 @@ type LoadedUser struct { UserName string `json:"username"` Roles map[string]string `json:"roles,omitempty"` Password string `json:"password"` - Token string `json:"token,omitempty"` + Tokens []string `json:"tokens,omitempty"` Salt string `json:"salt" bson:"salt"` Metadata conflict.Metadata `json:"metadata" bson:"metadata"` } diff --git a/controller/northbound/server/auth.go b/controller/northbound/server/auth.go index 2569f854105ede5ce6b1f8578132993be939b72c..5ef41402341f632903ba2c17b37a5f40f11c8700 100644 --- a/controller/northbound/server/auth.go +++ b/controller/northbound/server/auth.go @@ -7,6 +7,7 @@ import ( "time" apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac" + "code.fbi.h-da.de/danet/gosdn/controller/config" rbacInterfaces "code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac" "code.fbi.h-da.de/danet/gosdn/controller/metrics" "code.fbi.h-da.de/danet/gosdn/controller/rbac" @@ -92,7 +93,10 @@ func (s AuthServer) Login(ctx context.Context, request *apb.LoginRequest) (*apb. return nil, err } - userToUpdate.SetToken(token) + err = addTokenAndEnsureTokenLimit(userToUpdate, token) + if err != nil { + return nil, err + } err = s.userService.Update(userToUpdate) if err != nil { @@ -155,7 +159,7 @@ func (s AuthServer) isCorrectPassword(storedPassword, salt, loginPassword string } // handleLogout checks if the provided user name matches with the one associated with token and -// replaces the stored token of the user with an empty string. +// removes the token from all tokens of the user. func (s AuthServer) handleLogout(ctx context.Context, userName string) error { md, ok := metadata.FromIncomingContext(ctx) if !ok { @@ -179,15 +183,16 @@ func (s AuthServer) handleLogout(ctx context.Context, userName string) error { return err } - if token != storedUser.GetToken() { - return status.Errorf(codes.Aborted, "missing match of token provied for user") + err = removeTokenFromUserIfExists(storedUser, token) + if err != nil { + return status.Errorf(codes.Aborted, "Error removing token from user, it was either already logged out or otherwise not found") } err = s.userService.Update(&rbac.User{UserID: storedUser.ID(), UserName: storedUser.Name(), Roles: storedUser.GetRoles(), Password: storedUser.GetPassword(), - Token: " ", + Tokens: storedUser.GetTokens(), Salt: storedUser.GetSalt(), Metadata: storedUser.GetMetadata(), }) @@ -199,3 +204,28 @@ func (s AuthServer) handleLogout(ctx context.Context, userName string) error { return nil } + +func addTokenAndEnsureTokenLimit(userToUpdate rbacInterfaces.User, token string) error { + userToUpdate.AddToken(token) + for len(userToUpdate.GetTokens()) > config.MaxTokensPerUser { + err := userToUpdate.RemoveToken(userToUpdate.GetTokens()[0]) + if err != nil { + return err + } + } + return nil +} + +func removeTokenFromUserIfExists(storedUser rbacInterfaces.User, token string) error { + storedTokens := storedUser.GetTokens() + for _, storedToken := range storedTokens { + if storedToken == token { + err := storedUser.RemoveToken(token) + if err != nil { + return errors.New("could not remove token from user") + } + return nil + } + } + return errors.New("token not found for user") +} diff --git a/controller/northbound/server/auth_interceptor.go b/controller/northbound/server/auth_interceptor.go index 30ffa2ea982be33282ab3db1c86cfe2f6e22d06e..6437791aab91814949d215cb939bf5f17cb5af6e 100644 --- a/controller/northbound/server/auth_interceptor.go +++ b/controller/northbound/server/auth_interceptor.go @@ -97,7 +97,17 @@ func (auth *AuthInterceptor) authorize(ctx context.Context, method string) error return err } - if user.GetToken() != token { + storedTokens := user.GetTokens() + + foundToken := false + for _, storedToken := range storedTokens { + if storedToken == token { + foundToken = true + break + } + } + + if !foundToken { return status.Errorf(codes.PermissionDenied, "invalid token") } diff --git a/controller/northbound/server/auth_test.go b/controller/northbound/server/auth_test.go index 7716ec35873d48c2bfefc51da63b5a1605c2079b..8ad8cb6de1b5042d68debf09db1751507f5e3731 100644 --- a/controller/northbound/server/auth_test.go +++ b/controller/northbound/server/auth_test.go @@ -8,9 +8,13 @@ import ( "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac" + "code.fbi.h-da.de/danet/gosdn/controller/config" + "code.fbi.h-da.de/danet/gosdn/controller/conflict" eventservice "code.fbi.h-da.de/danet/gosdn/controller/eventService" "code.fbi.h-da.de/danet/gosdn/controller/rbac" "github.com/bufbuild/protovalidate-go" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" "google.golang.org/grpc/metadata" ) @@ -291,3 +295,43 @@ func TestAuth_handleLogout(t *testing.T) { }) } } + +func TestAuth_addTokenAndEnsureTokenLimit_addKey(t *testing.T) { + config.MaxTokensPerUser = 2 + userID := uuid.New() + user := rbac.NewUser(userID, "testUser", map[string]string{}, "password", []string{"token1"}, "salt", conflict.Metadata{ResourceVersion: 0}) + err := addTokenAndEnsureTokenLimit(user, "token2") + assert.Nil(t, err) + + assert.Equal(t, 2, len(user.GetTokens())) + assert.Equal(t, "token1", user.GetTokens()[0]) + assert.Equal(t, "token2", user.GetTokens()[1]) +} + +func TestAuth_addTokenAndEnsureTokenLimit_removeOldKey(t *testing.T) { + config.MaxTokensPerUser = 2 + userID := uuid.New() + user := rbac.NewUser(userID, "testUser", map[string]string{}, "password", []string{"token1", "token2"}, "salt", conflict.Metadata{ResourceVersion: 0}) + err := addTokenAndEnsureTokenLimit(user, "token3") + assert.Nil(t, err) + + assert.Equal(t, 2, len(user.GetTokens())) + assert.Equal(t, "token2", user.GetTokens()[0]) + assert.Equal(t, "token3", user.GetTokens()[1]) +} +func Test_removeTokenFromUserIfExists(t *testing.T) { + config.MaxTokensPerUser = 100 + user := rbac.NewUser(uuid.New(), "testUser", map[string]string{}, "password", []string{"token1", "token2", "token3"}, "salt", conflict.Metadata{ResourceVersion: 0}) + + err := removeTokenFromUserIfExists(user, "token2") + assert.Nil(t, err) + assert.Equal(t, []string{"token1", "token3"}, user.GetTokens()) + + err = removeTokenFromUserIfExists(user, "token4") + assert.NotNil(t, err) + assert.Equal(t, []string{"token1", "token3"}, user.GetTokens()) + + user = rbac.NewUser(uuid.New(), "testUser", map[string]string{}, "password", []string{}, "salt", conflict.Metadata{ResourceVersion: 0}) + err = removeTokenFromUserIfExists(user, "token4") + assert.NotNil(t, err) +} diff --git a/controller/northbound/server/test_util_test.go b/controller/northbound/server/test_util_test.go index 72ded5bc087c580b3829a1daff782eb86b23d2b0..82b68911ee29fe620ed30d96317ea0ca9e6a8ec5 100644 --- a/controller/northbound/server/test_util_test.go +++ b/controller/northbound/server/test_util_test.go @@ -98,14 +98,15 @@ func createTestUsers(userService rbacInterfaces.UserService) error { testUserPWD := createHashedAndSaltedPassword("user", salt) testRandPWD := createHashedAndSaltedPassword("aurelius", salt) + wrongTokens := []string{"Wrong token"} users := []rbac.User{ {UserID: uuid.MustParse(adminID), UserName: "testAdmin", Roles: adminRoleMap, Password: testAdminPWD}, {UserID: uuid.MustParse(userID), UserName: "testUser", Roles: userRoleMap, Password: testUserPWD}, - {UserID: uuid.New(), UserName: "testRandom", Roles: randomRoleMap, Password: testRandPWD, Token: "wrong token"}, + {UserID: uuid.New(), UserName: "testRandom", Roles: randomRoleMap, Password: testRandPWD, Tokens: wrongTokens}, } - + wrongTokens = []string{} for _, u := range users { - err := userService.Add(rbac.NewUser(u.ID(), u.Name(), u.Roles, u.Password, "", salt, conflict.Metadata{ResourceVersion: 0})) + err := userService.Add(rbac.NewUser(u.ID(), u.Name(), u.Roles, u.Password, wrongTokens, salt, conflict.Metadata{ResourceVersion: 0})) if err != nil { return err } @@ -176,6 +177,7 @@ func patchLogger(t *testing.T) { // be attached to the provided user. Else the user won't have the token and can not be authorized. func createTestUserToken(userName string, validTokenRequired bool, userService rbacInterfaces.UserService, jwt *rbac.JWTManager) (string, error) { token, err := jwt.GenerateToken(rbac.User{UserName: userName}) + tokens := []string{token} if err != nil { return token, err } @@ -185,7 +187,7 @@ func createTestUserToken(userName string, validTokenRequired bool, userService r if err != nil { return token, err } - user.SetToken(token) + user.SetTokens(tokens) err = userService.Update(user) if err != nil { diff --git a/controller/northbound/server/user.go b/controller/northbound/server/user.go index d37ae43a79bb7b0c1086687dfa4b2e44ee588318..000058d9c233eb34947493df99699c91e5f35f48 100644 --- a/controller/northbound/server/user.go +++ b/controller/northbound/server/user.go @@ -97,7 +97,7 @@ func (u UserServer) CreateUsers(ctx context.Context, request *apb.CreateUsersReq userID = uuid.New() } - user := rbac.NewUser(userID, user.Name, roles, string(hashedPassword), user.Token, salt, conflict.Metadata{ResourceVersion: 0}) + user := rbac.NewUser(userID, user.Name, roles, string(hashedPassword), []string{}, salt, conflict.Metadata{ResourceVersion: 0}) err = u.userService.Add(user) if err != nil { log.Error(err) @@ -204,7 +204,7 @@ func (u UserServer) UpdateUsers(ctx context.Context, request *apb.UpdateUsersReq hashedPassword := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(user.Password), []byte(storedUser.GetSalt()), 1, 64*1024, 4, 32)) - userToUpdate := rbac.NewUser(uid, user.Name, user.Roles, string(hashedPassword), user.Token, storedUser.GetSalt(), conflict.Metadata{ + userToUpdate := rbac.NewUser(uid, user.Name, user.Roles, string(hashedPassword), []string{}, storedUser.GetSalt(), conflict.Metadata{ ResourceVersion: int(user.Metadata.ResourceVersion)}) usr, _ := userToUpdate.(*rbac.User) diff --git a/controller/rbac/rbacService.go b/controller/rbac/rbacService.go index d16505d703bac46c048f03f249f3754afe2b3972..b084ffaff40dec764914aac9787e73f62027e955 100644 --- a/controller/rbac/rbacService.go +++ b/controller/rbac/rbacService.go @@ -138,7 +138,7 @@ func (s *UserService) createUserFromStore(loadedUser rbac.LoadedUser) rbac.User loadedUser.UserName, loadedUser.Roles, loadedUser.Password, - loadedUser.Token, + loadedUser.Tokens, loadedUser.Salt, loadedUser.Metadata, ) diff --git a/controller/rbac/user.go b/controller/rbac/user.go index d0b5ae14fa35d299db531500a0b0d286d127fc89..f7ce92c0a23c6a067694ca4399753ed9d70a62ac 100644 --- a/controller/rbac/user.go +++ b/controller/rbac/user.go @@ -2,6 +2,7 @@ package rbac import ( "encoding/json" + "errors" "code.fbi.h-da.de/danet/gosdn/controller/conflict" "code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac" @@ -16,7 +17,7 @@ type User struct { UserName string `json:"username"` Roles map[string]string `json:"roles,omitempty"` Password string `json:"password"` - Token string `json:"token,omitempty"` + Tokens []string `json:"token,omitempty"` Salt string `json:"salt"` } @@ -25,7 +26,7 @@ func NewUser(id uuid.UUID, name string, roles map[string]string, pw string, - token string, + tokens []string, salt string, metadata conflict.Metadata, ) rbac.User { @@ -34,7 +35,7 @@ func NewUser(id uuid.UUID, UserName: name, Roles: roles, Password: pw, - Token: token, + Tokens: tokens, Salt: salt, Metadata: metadata, } @@ -66,8 +67,8 @@ func (u *User) GetRoles() map[string]string { } // GetToken returns the token of the user. -func (u *User) GetToken() string { - return u.Token +func (u *User) GetTokens() []string { + return u.Tokens } // SetName sets the name of the user. @@ -76,8 +77,25 @@ func (u *User) SetName(name string) { } // SetToken sets the token of the user. -func (u *User) SetToken(token string) { - u.Token = token +func (u *User) SetTokens(tokens []string) { + u.Tokens = tokens +} + +// AddToken adds a token to the user. +func (u *User) AddToken(token string) { + u.Tokens = append(u.Tokens, token) +} + +// RemoveToken removes a token from the user. +func (u *User) RemoveToken(inputToken string) error { + for i, token := range u.Tokens { + if token == inputToken { + u.Tokens = append(u.Tokens[:i], u.Tokens[i+1:]...) + return nil + } + } + err := errors.New("token not found") + return err } // GetSalt returns the salt of the user. @@ -92,7 +110,7 @@ func (u *User) MarshalJSON() ([]byte, error) { UserName string `json:"username"` Roles map[string]string `json:"roles,omitempty"` Password string `json:"password"` - Token string `json:"token,omitempty"` + Tokens []string `json:"tokens,omitempty"` Salt string `json:"salt"` Metadata conflict.Metadata `json:"metadata"` }{ @@ -100,7 +118,7 @@ func (u *User) MarshalJSON() ([]byte, error) { UserName: u.Name(), Roles: u.Roles, Password: u.Password, - Token: u.Token, + Tokens: u.Tokens, Salt: u.Salt, Metadata: u.Metadata, }) @@ -113,7 +131,7 @@ func (u *User) MarshalBSON() ([]byte, error) { UserName string `bson:"username"` Roles map[string]string `bson:"roles,omitempty"` Password string `bson:"password"` - Token string `bson:"token,omitempty"` + Tokens []string `bson:"tokens,omitempty"` Salt string `bson:"salt"` Metadata conflict.Metadata `bson:"metadata"` }{ @@ -121,7 +139,7 @@ func (u *User) MarshalBSON() ([]byte, error) { UserName: u.Name(), Roles: u.Roles, Password: u.Password, - Token: u.Token, + Tokens: u.Tokens, Salt: u.Salt, Metadata: u.Metadata, }) diff --git a/controller/rbac/userFileSystemStore_test.go b/controller/rbac/userFileSystemStore_test.go index 7ddeb6ee3eb96595d817fce27297aeec6ec753b3..b5c0d8885e64d12eef2c19161ca30287f9e7e09b 100644 --- a/controller/rbac/userFileSystemStore_test.go +++ b/controller/rbac/userFileSystemStore_test.go @@ -20,7 +20,8 @@ func TestFileSystemUserStore_Add(t *testing.T) { } var idtest uuid.UUID var role map[string]string - testingUser := NewUser(idtest, "testUser", role, "xyz", "svsvsfbdwbwbev", "svswvasfbw", conflict.Metadata{}) + tokens := []string{"svsvsfbdwbwbev"} + testingUser := NewUser(idtest, "testUser", role, "xyz", tokens, "svswvasfbw", conflict.Metadata{}) tests := []struct { name string args args @@ -51,7 +52,8 @@ func TestFileSystemUserStore_Delete(t *testing.T) { } var idtest uuid.UUID var role map[string]string - testingUser := NewUser(idtest, "", role, "xyz", "svsvsfbdwbwbev", "svswvasfbw", conflict.Metadata{}) + tokens := []string{"svsvsfbdwbwbev"} + testingUser := NewUser(idtest, "", role, "xyz", tokens, "svswvasfbw", conflict.Metadata{}) tests := []struct { name string args args @@ -84,7 +86,8 @@ func TestFileSystemUserStore_Get(t *testing.T) { } var idtest uuid.UUID var role map[string]string - testingUser := NewUser(idtest, "", role, "xyz", "svsvsfbdwbwbev", "svswvasfbw", conflict.Metadata{}) + tokens := []string{"svsvsfbdwbwbev"} + testingUser := NewUser(idtest, "", role, "xyz", tokens, "svswvasfbw", conflict.Metadata{}) tests := []struct { name string args args @@ -96,7 +99,7 @@ func TestFileSystemUserStore_Get(t *testing.T) { args{ store.Query{ID: idtest, Name: "test"}, }, - rbac.LoadedUser{ID: idtest.String(), UserName: "", Roles: role, Password: "xyz", Token: "svsvsfbdwbwbev", Salt: "svswvasfbw"}, + rbac.LoadedUser{ID: idtest.String(), UserName: "", Roles: role, Password: "xyz", Tokens: tokens, Salt: "svswvasfbw"}, false, }, } @@ -123,9 +126,10 @@ func TestFileSystemUserStore_GetAll(t *testing.T) { var idtest uuid.UUID var role map[string]string - testingUser1 := NewUser(idtest, "", role, "xyz", "svsvsfbdwbwbevasf", "svswvasfbwasv", conflict.Metadata{}) - testingUser2 := NewUser(idtest, "", role, "abc", "svsvsfbdwbwbevsav", "svswvasfbwadf", conflict.Metadata{}) - testingUser3 := NewUser(idtest, "", role, "lmn", "svsvsfbdwbwbevscv", "svswvasfbwasd", conflict.Metadata{}) + tokens := []string{"svsvsfbdwbwbev"} + testingUser1 := NewUser(idtest, "", role, "xyz", tokens, "svswvasfbwasv", conflict.Metadata{}) + testingUser2 := NewUser(idtest, "", role, "abc", tokens, "svswvasfbwadf", conflict.Metadata{}) + testingUser3 := NewUser(idtest, "", role, "lmn", tokens, "svswvasfbwasd", conflict.Metadata{}) tests := []struct { name string want []rbac.LoadedUser @@ -133,7 +137,7 @@ func TestFileSystemUserStore_GetAll(t *testing.T) { }{ { "testUser", - []rbac.LoadedUser{{ID: idtest.String(), UserName: "", Roles: role, Password: "xyz", Token: "svsvsfbdwbwbevasf", Salt: "svswvasfbwasv"}, {ID: idtest.String(), UserName: "", Roles: role, Password: "abc", Token: "svsvsfbdwbwbevsav", Salt: "svswvasfbwadf"}, {ID: idtest.String(), UserName: "", Roles: role, Password: "lmn", Token: "svsvsfbdwbwbevscv", Salt: "svswvasfbwasd"}}, + []rbac.LoadedUser{{ID: idtest.String(), UserName: "", Roles: role, Password: "xyz", Tokens: tokens, Salt: "svswvasfbwasv"}, {ID: idtest.String(), UserName: "", Roles: role, Password: "abc", Tokens: tokens, Salt: "svswvasfbwadf"}, {ID: idtest.String(), UserName: "", Roles: role, Password: "lmn", Tokens: tokens, Salt: "svswvasfbwasd"}}, false, }, } @@ -175,7 +179,8 @@ func TestFileSystemUserStore_Update(t *testing.T) { } var idtest uuid.UUID var role map[string]string - testingUser := NewUser(idtest, "", role, "xyz", "svsvsfbdwbwbev", "svswvasfbw", conflict.Metadata{}) + tokens := []string{"svsvsfbdwbwbev"} + testingUser := NewUser(idtest, "", role, "xyz", tokens, "svswvasfbw", conflict.Metadata{}) tests := []struct { name string args args diff --git a/integration-tests/application_tests/application_test.go b/integration-tests/application_tests/application_test.go index d5b69ae226115918d54656fd38cf4a2de150386f..9b3797a5540cb2c42231729a68b7fd6c6635c6e0 100644 --- a/integration-tests/application_tests/application_test.go +++ b/integration-tests/application_tests/application_test.go @@ -100,10 +100,10 @@ func TestMain(m *testing.M) { // a user and role and update the user because of the login. After then only logins are done, no user and role creations. // This means that this will block after trying once, because of the three attempts to read from eventChannels. - _ = <-application.addEventChannel - _ = <-application.addEventChannel - _ = <-application.addEventChannel - _ = <-application.updateEventChannel + <-application.addEventChannel + <-application.addEventChannel + <-application.addEventChannel + <-application.updateEventChannel m.Run() }