diff --git a/pkg/apiserver/domain/model/system_info.go b/pkg/apiserver/domain/model/system_info.go index 794dff9ef..d920249b7 100644 --- a/pkg/apiserver/domain/model/system_info.go +++ b/pkg/apiserver/domain/model/system_info.go @@ -32,10 +32,16 @@ const ( // SystemInfo systemInfo model type SystemInfo struct { BaseModel - InstallID string `json:"installID"` - EnableCollection bool `json:"enableCollection"` - LoginType string `json:"loginType"` - StatisticInfo StatisticInfo `json:"statisticInfo,omitempty"` + InstallID string `json:"installID"` + EnableCollection bool `json:"enableCollection"` + StatisticInfo StatisticInfo `json:"statisticInfo,omitempty"` + LoginType string `json:"loginType"` + DexUserDefaultProjects []ProjectRef `json:"projects"` +} + +type ProjectRef struct { + Name string `json:"name"` + Roles []string `json:"roles"` } // UpdateDexConfig update dex config diff --git a/pkg/apiserver/domain/model/user.go b/pkg/apiserver/domain/model/user.go index a44ad2806..35678047a 100644 --- a/pkg/apiserver/domain/model/user.go +++ b/pkg/apiserver/domain/model/user.go @@ -48,6 +48,7 @@ type User struct { LastLoginTime time.Time `json:"lastLoginTime,omitempty"` // UserRoles binding the platform level roles UserRoles []string `json:"userRoles"` + DexSub string `json:"dexSub,omitempty"` } // TableName return custom table name @@ -74,6 +75,9 @@ func (u *User) Index() map[string]string { if u.Email != "" { index["email"] = u.Email } + if u.DexSub != "" { + index["dexSub"] = u.DexSub + } return index } diff --git a/pkg/apiserver/domain/service/authentication.go b/pkg/apiserver/domain/service/authentication.go index 434485dc4..fd17914ce 100644 --- a/pkg/apiserver/domain/service/authentication.go +++ b/pkg/apiserver/domain/service/authentication.go @@ -68,10 +68,12 @@ type AuthenticationService interface { } type authenticationServiceImpl struct { - SysService SystemInfoService `inject:""` - UserService UserService `inject:""` - Store datastore.DataStore `inject:"datastore"` - KubeClient client.Client `inject:"kubeClient"` + SysService SystemInfoService `inject:""` + UserService UserService `inject:""` + ProjectService ProjectService `inject:""` + SystemInfoService SystemInfoService `inject:""` + Store datastore.DataStore `inject:"datastore"` + KubeClient client.Client `inject:"kubeClient"` } // NewAuthenticationService new authentication service @@ -84,8 +86,10 @@ type authHandler interface { } type dexHandlerImpl struct { - idToken *oidc.IDToken - Store datastore.DataStore + idToken *oidc.IDToken + Store datastore.DataStore + projectService ProjectService + systemInfoService SystemInfoService } type localHandlerImpl struct { @@ -124,8 +128,9 @@ func (a *authenticationServiceImpl) newDexHandler(ctx context.Context, req apisv return nil, err } return &dexHandlerImpl{ - idToken: idToken, - Store: a.Store, + idToken: idToken, + Store: a.Store, + projectService: a.ProjectService, }, nil } @@ -433,31 +438,66 @@ func (a *authenticationServiceImpl) GetLoginType(ctx context.Context) (*apisv1.G func (d *dexHandlerImpl) login(ctx context.Context) (*apisv1.UserBase, error) { var claims struct { Email string `json:"email"` - Name string `json:"name"` + // Name End-User's full name in displayable form including all name parts, possibly including titles and suffixes, ordered according to the End-User's locale and preferences. + Name string `json:"name"` + // Subject - Identifier for the End-User at the Issuer. + Sub string `json:"sub"` } if err := d.idToken.Claims(&claims); err != nil { return nil, err } - - user := &model.User{Email: claims.Email} - userBase := &apisv1.UserBase{Email: claims.Email, Name: claims.Name} - users, err := d.Store.List(ctx, user, &datastore.ListOptions{}) - if err != nil { - return nil, err + var users []datastore.Entity + var err error + if claims.Email != "" { + user := &model.User{Email: claims.Email} + users, err = d.Store.List(ctx, user, &datastore.ListOptions{}) + if err != nil { + return nil, err + } } + if len(users) == 0 && claims.Sub != "" { + // Support query the user by the subject + user := &model.User{DexSub: claims.Sub} + users, err = d.Store.List(ctx, user, &datastore.ListOptions{}) + if err != nil { + return nil, err + } + } + var userBase *apisv1.UserBase if len(users) > 0 { u := users[0].(*model.User) u.LastLoginTime = time.Now() if err := d.Store.Put(ctx, u); err != nil { return nil, err } - userBase.Name = u.Name - } else if err := d.Store.Add(ctx, &model.User{ - Email: claims.Email, - Name: claims.Name, - LastLoginTime: time.Now(), - }); err != nil { - return nil, err + userBase = convertUserBase(u) + } else { + user := &model.User{ + Email: claims.Email, + Name: claims.Sub, + DexSub: claims.Sub, + Alias: claims.Name, + LastLoginTime: time.Now(), + } + if err := d.Store.Add(ctx, user); err != nil { + return nil, err + } + systemInfo, err := d.systemInfoService.GetSystemInfo(ctx) + if err != nil { + log.Logger.Errorf("failed to get the system info %s", err.Error()) + } + if systemInfo != nil { + for _, project := range systemInfo.DexUserDefaultProjects { + _, err := d.projectService.AddProjectUser(ctx, project.Name, apisv1.AddProjectUserRequest{ + UserName: claims.Sub, + UserRoles: project.Roles, + }) + if err != nil { + log.Logger.Errorf("failed to add a user to project %s", err.Error()) + } + } + } + userBase = convertUserBase(user) } return userBase, nil diff --git a/pkg/apiserver/domain/service/authentication_test.go b/pkg/apiserver/domain/service/authentication_test.go index 45e7f92f2..457c075b9 100644 --- a/pkg/apiserver/domain/service/authentication_test.go +++ b/pkg/apiserver/domain/service/authentication_test.go @@ -45,10 +45,11 @@ import ( var _ = Describe("Test authentication service functions", func() { var ( - authService *authenticationServiceImpl - userService *userServiceImpl - sysService *systemInfoServiceImpl - ds datastore.DataStore + authService *authenticationServiceImpl + userService *userServiceImpl + sysService *systemInfoServiceImpl + projectService ProjectService + ds datastore.DataStore ) BeforeEach(func() { @@ -59,42 +60,86 @@ var _ = Describe("Test authentication service functions", func() { authService = &authenticationServiceImpl{KubeClient: k8sClient, Store: ds} sysService = &systemInfoServiceImpl{Store: ds, KubeClient: k8sClient} userService = &userServiceImpl{Store: ds, SysService: sysService} + projectService = NewTestProjectService(ds, k8sClient) }) It("Test Dex login", func() { testIDToken := &oidc.IDToken{} patch := ApplyMethod(reflect.TypeOf(testIDToken), "Claims", func(_ *oidc.IDToken, v interface{}) error { - return json.Unmarshal([]byte(`{"email":"test@test.com","name":"test"}`), v) + return json.Unmarshal([]byte(`{"email":"test@test.com", "name":"show name", "sub": "testuser"}`), v) }) defer patch.Reset() + + err := sysService.Init(context.TODO()) + Expect(err).Should(BeNil()) + err = userService.Init(context.TODO()) + Expect(err).Should(BeNil()) + err = projectService.Init(context.TODO()) + Expect(err).Should(BeNil()) + + _, err = sysService.UpdateSystemInfo(context.TODO(), apisv1.SystemInfoRequest{ + LoginType: "local", + DexUserDefaultProjects: []model.ProjectRef{{ + Name: "default", + Roles: []string{"app-developer"}, + }}, + }) + Expect(err).Should(BeNil()) + dexHandler := dexHandlerImpl{ - idToken: testIDToken, - Store: ds, + idToken: testIDToken, + Store: ds, + projectService: projectService, + systemInfoService: sysService, } resp, err := dexHandler.login(context.Background()) Expect(err).Should(BeNil()) Expect(resp.Email).Should(Equal("test@test.com")) - Expect(resp.Name).Should(Equal("test")) + Expect(resp.Name).Should(Equal("testuser")) + Expect(resp.Alias).Should(Equal("show name")) + + projects, err := projectService.ListUserProjects(context.TODO(), "testuser") + Expect(err).Should(BeNil()) + Expect(len(projects)).Should(Equal(1)) user := &model.User{ - Name: "test", + Name: "testuser", } err = ds.Get(context.Background(), user) Expect(err).Should(BeNil()) Expect(user.Email).Should(Equal("test@test.com")) existUser := &model.User{ - Name: "test", + Name: "testuser", } err = ds.Delete(context.Background(), existUser) Expect(err).Should(BeNil()) - existUser.Name = "exist-user" - existUser.Email = "test@test.com" + + existUser = &model.User{ + Name: "exist-user", + Email: "test@test.com", + } err = ds.Add(context.Background(), existUser) Expect(err).Should(BeNil()) resp, err = dexHandler.login(context.Background()) Expect(err).Should(BeNil()) Expect(resp.Email).Should(Equal("test@test.com")) Expect(resp.Name).Should(Equal("exist-user")) + + err = ds.Delete(context.Background(), existUser) + Expect(err).Should(BeNil()) + + existUser = &model.User{ + Name: "zhangsan", + Email: "test2@test.com", + DexSub: "testuser", + } + err = ds.Add(context.Background(), existUser) + Expect(err).Should(BeNil()) + resp, err = dexHandler.login(context.Background()) + Expect(err).Should(BeNil()) + Expect(resp.Email).Should(Equal("test2@test.com")) + Expect(resp.Name).Should(Equal("zhangsan")) + }) It("Test local login", func() { diff --git a/pkg/apiserver/domain/service/system_info.go b/pkg/apiserver/domain/service/system_info.go index f8acafe08..f4c85faa0 100644 --- a/pkg/apiserver/domain/service/system_info.go +++ b/pkg/apiserver/domain/service/system_info.go @@ -113,7 +113,8 @@ func (u systemInfoServiceImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1. CreateTime: info.CreateTime, UpdateTime: time.Now(), }, - StatisticInfo: info.StatisticInfo, + StatisticInfo: info.StatisticInfo, + DexUserDefaultProjects: sysInfo.DexUserDefaultProjects, } if sysInfo.LoginType == model.LoginTypeDex { @@ -166,9 +167,10 @@ func (u systemInfoServiceImpl) Init(ctx context.Context) error { func convertInfoToBase(info *model.SystemInfo) v1.SystemInfo { return v1.SystemInfo{ - PlatformID: info.InstallID, - EnableCollection: info.EnableCollection, - LoginType: info.LoginType, - InstallTime: info.CreateTime, + PlatformID: info.InstallID, + EnableCollection: info.EnableCollection, + LoginType: info.LoginType, + InstallTime: info.CreateTime, + DexUserDefaultProjects: info.DexUserDefaultProjects, } } diff --git a/pkg/apiserver/interfaces/api/dto/v1/types.go b/pkg/apiserver/interfaces/api/dto/v1/types.go index 11cceae91..0321a5079 100644 --- a/pkg/apiserver/interfaces/api/dto/v1/types.go +++ b/pkg/apiserver/interfaces/api/dto/v1/types.go @@ -1194,10 +1194,11 @@ type SystemInfoResponse struct { // SystemInfo system info type SystemInfo struct { - PlatformID string `json:"platformID"` - EnableCollection bool `json:"enableCollection"` - LoginType string `json:"loginType"` - InstallTime time.Time `json:"installTime,omitempty"` + PlatformID string `json:"platformID"` + EnableCollection bool `json:"enableCollection"` + LoginType string `json:"loginType" validate:"oneof=dex local"` + InstallTime time.Time `json:"installTime,omitempty"` + DexUserDefaultProjects []model.ProjectRef `json:"dexUserDefaultProjects,omitempty"` } // StatisticInfo generated by cronJob running in backend @@ -1214,9 +1215,10 @@ type StatisticInfo struct { // SystemInfoRequest request by update SystemInfo type SystemInfoRequest struct { - EnableCollection bool `json:"enableCollection"` - LoginType string `json:"loginType"` - VelaAddress string `json:"velaAddress,omitempty"` + EnableCollection bool `json:"enableCollection"` + LoginType string `json:"loginType"` + VelaAddress string `json:"velaAddress,omitempty"` + DexUserDefaultProjects []model.ProjectRef `json:"dexUserDefaultProjects,omitempty"` } // SystemVersion contains KubeVela version