Feat: support to init the roles of the user which login by dex

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
This commit is contained in:
barnettZQG
2022-07-24 14:20:01 +08:00
parent 573f60170c
commit 9a255cdbc8
6 changed files with 149 additions and 50 deletions
+10 -4
View File
@@ -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
+4
View File
@@ -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
}
+62 -22
View File
@@ -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
@@ -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() {
+7 -5
View File
@@ -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,
}
}
+9 -7
View File
@@ -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