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/controller.go b/controller/controller.go index 4cb78c20be7ac1d1179b3c26b0b5fcda8631bfe9..1ae253a72db34fb7e2353b1b64cad430e65d33aa 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -430,7 +430,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 } 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..a10e7a49d6a315ebca45ce8972b4b81a4af5c663 100644 --- a/controller/northbound/server/auth.go +++ b/controller/northbound/server/auth.go @@ -92,7 +92,7 @@ func (s AuthServer) Login(ctx context.Context, request *apb.LoginRequest) (*apb. return nil, err } - userToUpdate.SetToken(token) + userToUpdate.AddToken(token) err = s.userService.Update(userToUpdate) if err != nil { @@ -155,7 +155,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. +// removed 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,7 +179,17 @@ func (s AuthServer) handleLogout(ctx context.Context, userName string) error { return err } - if token != storedUser.GetToken() { + storedTokens := storedUser.GetTokens() + foundToken := false + for _, storedToken := range storedTokens { + if storedToken == token { + storedUser.RemoveToken(token) + foundToken = true + break + } + } + + if !foundToken { return status.Errorf(codes.Aborted, "missing match of token provied for user") } @@ -187,7 +197,7 @@ func (s AuthServer) handleLogout(ctx context.Context, userName string) error { UserName: storedUser.Name(), Roles: storedUser.GetRoles(), Password: storedUser.GetPassword(), - Token: " ", + Tokens: storedUser.GetTokens(), Salt: storedUser.GetSalt(), Metadata: storedUser.GetMetadata(), }) 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/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