From eac60b484bbec9e43cab4883c27936d4ec214609 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Tue, 14 Apr 2020 07:55:52 +0800 Subject: [PATCH] feature: cas user auto join designated project --- pkg/apis/identity/cas.go | 12 +++ pkg/apis/identity/config.go | 16 ++++ pkg/apis/identity/identityprovider.go | 25 ++++++ pkg/keystone/driver/cas/cas.go | 66 +++++++++++++++ pkg/keystone/driver/cas/cas_test.go | 14 ++++ pkg/keystone/driver/cas/class.go | 43 ++++++++++ pkg/keystone/driver/driver.go | 1 + pkg/keystone/driver/ldap/class.go | 7 ++ pkg/keystone/driver/sql/class.go | 7 ++ pkg/keystone/models/assignments.go | 2 +- pkg/keystone/models/identity_provider.go | 101 +++++++++++------------ pkg/keystone/models/projects.go | 52 ++++++++++-- pkg/keystone/models/projects_test.go | 2 +- pkg/keystone/models/roles.go | 2 +- pkg/keystone/models/users.go | 2 +- 15 files changed, 291 insertions(+), 61 deletions(-) diff --git a/pkg/apis/identity/cas.go b/pkg/apis/identity/cas.go index 711c7aef1c..912dbaec59 100644 --- a/pkg/apis/identity/cas.go +++ b/pkg/apis/identity/cas.go @@ -14,8 +14,20 @@ package identity +import "yunion.io/x/pkg/tristate" + type SCASIdpConfigOptions struct { // https://cas.example.org/cas/ CASServerURL string `json:"cas_server_url"` Service string `json:"service"` + + CasProjectAttribute string `json:"cas_project_attribute"` + + AutoCreateCasProject tristate.TriState `json:"auto_create_cas_project"` + + DefaultCasProjectId string `json:"default_cas_project_id"` + + CasRoleAttribute string `json:"cas_role_attribute"` + + DefaultCasRoleId string `json:"default_cas_role_id"` } diff --git a/pkg/apis/identity/config.go b/pkg/apis/identity/config.go index 6ad51de9a0..eba5b23c19 100644 --- a/pkg/apis/identity/config.go +++ b/pkg/apis/identity/config.go @@ -93,3 +93,19 @@ var ( IdpTemplateOpenLDAPSingleDomain: IdentityDriverLDAP, } ) + +type PerformConfigInput struct { + // 更新配置的方式 + // example: update + // + // | action | 含义 | + // |---------|-----------------------------------------------| + // | update | 增量更新配置 | + // | remove | 删除指定配置 | + // | replace | 全量替换配置,如果action为空,则默认为replace | + // + Action string `json:"action"` + + // 配置信息 + Config TConfigs `json:"config"` +} diff --git a/pkg/apis/identity/identityprovider.go b/pkg/apis/identity/identityprovider.go index e991796105..1c83cd28e7 100644 --- a/pkg/apis/identity/identityprovider.go +++ b/pkg/apis/identity/identityprovider.go @@ -29,3 +29,28 @@ type IdentityProviderDetails struct { ProjectCount int `json:"project_count,allowempty"` GroupCount int `json:"group_count,allowempty"` } + +type IdentityProviderCreateInput struct { + apis.EnabledStatusStandaloneResourceCreateInput + + // 后端驱动名称 + Driver string `json:"driver"` + + // 模板名称 + Template string `json:"template"` + + // 默认导入用户和组的域 + TargetDomainId string `json:"target_domain_id"` + // swagger:ignore + // Deprecated + TargetDomain string `json:"target_domain" "yunion:deprecated-by":"target_domain_id"` + + // 新建域的时候是否自动新建第一个项目 + AutoCreateProject *bool `json:"auto_create_project"` + + // 自动同步间隔,单位:秒 + SyncIntervalSeconds *int `json:"sync_interval_seconds"` + + // 配置信息 + Config TConfigs `json:"config"` +} diff --git a/pkg/keystone/driver/cas/cas.go b/pkg/keystone/driver/cas/cas.go index d8ad061956..6cdc883be6 100644 --- a/pkg/keystone/driver/cas/cas.go +++ b/pkg/keystone/driver/cas/cas.go @@ -16,8 +16,11 @@ package cas import ( "context" + "database/sql" "encoding/xml" "fmt" + "regexp" + "strings" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -144,9 +147,72 @@ func (self *SCASDriver) Authenticate(ctx context.Context, ident mcclient.SAuthen if err != nil { return nil, errors.Wrap(err, "models.UserManager.FetchUserExtended") } + + self.userTryJoinProject(ctx, usr, domain, resp) + return extUser, nil } +func (self *SCASDriver) userTryJoinProject(ctx context.Context, usr *models.SUser, domain *models.SDomain, resp []byte) { + var err error + var targetProject *models.SProject + if len(self.casConfig.CasProjectAttribute) > 0 { + projName := fetchAttribute(resp, self.casConfig.CasProjectAttribute) + if len(projName) > 0 { + targetProject, err = models.ProjectManager.FetchProject("", projName, domain.Id, domain.Name) + if err != nil { + log.Errorf("fetch project %s fail %s", projName, err) + if errors.Cause(err) == sql.ErrNoRows && self.casConfig.AutoCreateCasProject.IsTrue() { + targetProject, err = models.ProjectManager.NewProject(ctx, projName, "cas project", domain) + if err != nil { + log.Errorf("auto create project %s fail %s", projName, err) + } + } + } + } + } + if targetProject == nil && len(self.casConfig.DefaultCasProjectId) > 0 { + targetProject, err = models.ProjectManager.FetchProjectById(self.casConfig.DefaultCasProjectId) + if err != nil { + log.Errorf("fetch default project %s fail %s", self.casConfig.DefaultCasProjectId, err) + } + } + if targetProject != nil { + // put user in project + var targetRole *models.SRole + if len(self.casConfig.CasRoleAttribute) > 0 { + roleName := fetchAttribute(resp, self.casConfig.CasRoleAttribute) + if len(roleName) > 0 { + targetRole, err = models.RoleManager.FetchRole("", roleName, domain.Id, domain.Name) + if err != nil { + log.Errorf("fetch role %s fail %s", roleName, err) + } + } + } + if targetRole == nil && len(self.casConfig.DefaultCasRoleId) > 0 { + targetRole, err = models.RoleManager.FetchRoleById(self.casConfig.DefaultCasRoleId) + if err != nil { + log.Errorf("fetch default role %s fail %s", self.casConfig.DefaultCasRoleId, err) + } + } + if targetRole != nil { + err = models.AssignmentManager.ProjectAddUser(ctx, models.GetDefaultAdminCred(), targetProject, usr, targetRole) + if err != nil { + log.Errorf("CAS user join project fail %s", err) + } + } + } +} + +func fetchAttribute(heystack []byte, name string) string { + pattern := regexp.MustCompile(fmt.Sprintf(`<%s>(\w+)`, name, name)) + result := pattern.FindAllStringSubmatch(string(heystack), -1) + if len(result) > 0 && len(result[0]) > 1 { + return strings.TrimSpace(result[0][1]) + } + return "" +} + func (self *SCASDriver) Sync(ctx context.Context) error { return nil } diff --git a/pkg/keystone/driver/cas/cas_test.go b/pkg/keystone/driver/cas/cas_test.go index 1c979db318..96b9a865e7 100644 --- a/pkg/keystone/driver/cas/cas_test.go +++ b/pkg/keystone/driver/cas/cas_test.go @@ -46,3 +46,17 @@ func TestXmlUnmarshal(t *testing.T) { t.Logf("%#v", casresp) } } + +func TestFetchAttribute(t *testing.T) { + xmlstr := ` + + casuser + casproj + +` + got := fetchAttribute([]byte(xmlstr), "cas:proj") + want := "casproj" + if got != want { + t.Errorf("want %s got %s", want, got) + } +} diff --git a/pkg/keystone/driver/cas/class.go b/pkg/keystone/driver/cas/class.go index 473db9cf60..9e70fa4b5b 100644 --- a/pkg/keystone/driver/cas/class.go +++ b/pkg/keystone/driver/cas/class.go @@ -15,8 +15,17 @@ package cas import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/keystone/driver" + "yunion.io/x/onecloud/pkg/keystone/models" + "yunion.io/x/onecloud/pkg/mcclient" ) type SCASDriverClass struct{} @@ -37,6 +46,40 @@ func (self *SCASDriverClass) Name() string { return api.IdentityDriverCAS } +func (self *SCASDriverClass) ValidateConfig(ctx context.Context, userCred mcclient.TokenCredential, tconf api.TConfigs) (api.TConfigs, error) { + + conf := api.SCASIdpConfigOptions{} + confJson := jsonutils.Marshal(tconf["cas"]) + err := confJson.Unmarshal(&conf) + if err != nil { + return tconf, errors.Wrap(err, "unmarshal config") + } + if len(conf.DefaultCasProjectId) > 0 { + obj, err := models.ProjectManager.FetchByIdOrName(userCred, conf.DefaultCasProjectId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return tconf, errors.Wrapf(httperrors.ErrResourceNotFound, "project %s", conf.DefaultCasProjectId) + } else { + return tconf, errors.Wrap(err, "FetchProjectById") + } + } + tconf["cas"]["default_cas_project_id"] = jsonutils.NewString(obj.GetId()) + } + if len(conf.DefaultCasRoleId) > 0 { + obj, err := models.RoleManager.FetchByIdOrName(userCred, conf.DefaultCasRoleId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return tconf, errors.Wrapf(httperrors.ErrResourceNotFound, "role %s", conf.DefaultCasRoleId) + } else { + return tconf, errors.Wrap(err, "FetchRoleById") + } + } + tconf["cas"]["default_cas_role_id"] = jsonutils.NewString(obj.GetId()) + } + + return tconf, nil +} + func init() { driver.RegisterDriverClass(&SCASDriverClass{}) } diff --git a/pkg/keystone/driver/driver.go b/pkg/keystone/driver/driver.go index 6fa44e8335..17bc72bc37 100644 --- a/pkg/keystone/driver/driver.go +++ b/pkg/keystone/driver/driver.go @@ -26,6 +26,7 @@ type IIdentityBackendClass interface { SyncMethod() string Name() string NewDriver(idpId, idpName, template, targetDomainId string, autoCreateProject bool, conf api.TConfigs) (IIdentityBackend, error) + ValidateConfig(ctx context.Context, userCred mcclient.TokenCredential, conf api.TConfigs) (api.TConfigs, error) } type IIdentityBackend interface { diff --git a/pkg/keystone/driver/ldap/class.go b/pkg/keystone/driver/ldap/class.go index 7a6c80e220..02e9243ab4 100644 --- a/pkg/keystone/driver/ldap/class.go +++ b/pkg/keystone/driver/ldap/class.go @@ -15,8 +15,11 @@ package ldap import ( + "context" + api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/keystone/driver" + "yunion.io/x/onecloud/pkg/mcclient" ) type SLDAPDriverClass struct{} @@ -37,6 +40,10 @@ func (self *SLDAPDriverClass) Name() string { return api.IdentityDriverLDAP } +func (self *SLDAPDriverClass) ValidateConfig(ctx context.Context, userCred mcclient.TokenCredential, conf api.TConfigs) (api.TConfigs, error) { + return conf, nil +} + func init() { driver.RegisterDriverClass(&SLDAPDriverClass{}) } diff --git a/pkg/keystone/driver/sql/class.go b/pkg/keystone/driver/sql/class.go index 5297f6db71..34e14006e9 100644 --- a/pkg/keystone/driver/sql/class.go +++ b/pkg/keystone/driver/sql/class.go @@ -15,8 +15,11 @@ package sql import ( + "context" + api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/keystone/driver" + "yunion.io/x/onecloud/pkg/mcclient" ) type SSQLDriverClass struct{} @@ -37,6 +40,10 @@ func (self *SSQLDriverClass) Name() string { return api.IdentityDriverSQL } +func (self *SSQLDriverClass) ValidateConfig(ctx context.Context, userCred mcclient.TokenCredential, conf api.TConfigs) (api.TConfigs, error) { + return conf, nil +} + func init() { driver.RegisterDriverClass(&SSQLDriverClass{}) } diff --git a/pkg/keystone/models/assignments.go b/pkg/keystone/models/assignments.go index 0fdd7687f0..6e9f7d199b 100644 --- a/pkg/keystone/models/assignments.go +++ b/pkg/keystone/models/assignments.go @@ -242,7 +242,7 @@ func (manager *SAssignmentManager) fetchProjectUserIdsQuery(projId string) *sqlc return union.Query() } -func (manager *SAssignmentManager) projectAddUser(ctx context.Context, userCred mcclient.TokenCredential, project *SProject, user *SUser, role *SRole) error { +func (manager *SAssignmentManager) ProjectAddUser(ctx context.Context, userCred mcclient.TokenCredential, project *SProject, user *SUser, role *SRole) error { err := db.ValidateCreateDomainId(project.DomainId) if err != nil { return err diff --git a/pkg/keystone/models/identity_provider.go b/pkg/keystone/models/identity_provider.go index a68201a50f..880208509f 100644 --- a/pkg/keystone/models/identity_provider.go +++ b/pkg/keystone/models/identity_provider.go @@ -26,7 +26,6 @@ import ( "yunion.io/x/pkg/tristate" "yunion.io/x/sqlchemy" - "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/consts" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -221,23 +220,30 @@ func (self *SIdentityProvider) GetDetailsConfig(ctx context.Context, userCred mc return result, nil } -func (ident *SIdentityProvider) AllowPerformConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) bool { +func (ident *SIdentityProvider) getDriverClass() driver.IIdentityBackendClass { + return driver.GetDriverClass(ident.Driver) +} + +func (ident *SIdentityProvider) AllowPerformConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.PerformConfigInput) bool { return db.IsAdminAllowUpdateSpec(userCred, ident, "config") } -func (ident *SIdentityProvider) PerformConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (jsonutils.JSONObject, error) { +func (ident *SIdentityProvider) PerformConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.PerformConfigInput) (jsonutils.JSONObject, error) { if ident.Status == api.IdentityDriverStatusConnected && ident.Enabled { return nil, httperrors.NewInvalidStatusError("cannot update config when enabled and connected") } if ident.SyncStatus != api.IdentitySyncStatusIdle { return nil, httperrors.NewInvalidStatusError("cannot update config when not idle") } - opts := api.TConfigs{} - err := data.Unmarshal(&opts, "config") + + var err error + input.Config, err = ident.getDriverClass().ValidateConfig(ctx, userCred, input.Config) if err != nil { - return nil, httperrors.NewInputParameterError("invalid input data") + return nil, errors.Wrap(err, "ValidateConfig") } - action, _ := data.GetString("action") + + opts := input.Config + action := input.Action err = saveConfigs(action, ident, opts, nil, nil, api.SensitiveDomainConfigMap) if err != nil { return nil, httperrors.NewInternalServerError("saveConfig fail %s", err) @@ -251,74 +257,77 @@ func (manager *SIdentityProviderManager) getDriveInstanceCount(drvName string) ( return manager.Query().Equals("driver", drvName).CountWithError() } -func (manager *SIdentityProviderManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { +func (manager *SIdentityProviderManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input api.IdentityProviderCreateInput, +) (api.IdentityProviderCreateInput, error) { var drvName string - template, _ := data.GetString("template") + template := input.Template if len(template) > 0 { if _, ok := api.IdpTemplateDriver[template]; !ok { - return nil, httperrors.NewInputParameterError("invalid template") + return input, httperrors.NewInputParameterError("invalid template") } drvName = api.IdpTemplateDriver[template] - data.Set("driver", jsonutils.NewString(drvName)) + input.Driver = drvName } else { - drvName, _ = data.GetString("driver") + drvName = input.Driver if len(drvName) == 0 { - return nil, httperrors.NewInputParameterError("missing driver") + return input, httperrors.NewInputParameterError("missing driver") } } drvCls := driver.GetDriverClass(drvName) if drvCls == nil { - return nil, httperrors.NewInputParameterError("driver %s not supported", drvName) + return input, httperrors.NewInputParameterError("driver %s not supported", drvName) } if drvCls.SingletonInstance() { cnt, err := manager.getDriveInstanceCount(drvName) if err != nil { - return nil, httperrors.NewGeneralError(err) + return input, httperrors.NewGeneralError(err) } if cnt >= 1 { - return nil, httperrors.NewConflictError("driver %s already exists", drvName) + return input, httperrors.NewConflictError("driver %s already exists", drvName) } } - if data.Contains("sync_interval_seconds") { - secs, _ := data.Int("sync_interval_seconds") + if input.SyncIntervalSeconds != nil { + secs := *input.SyncIntervalSeconds if secs < api.MinimalSyncIntervalSeconds { - data.Set("sync_interval_seconds", jsonutils.NewInt(int64(api.MinimalSyncIntervalSeconds))) + secs = api.MinimalSyncIntervalSeconds + input.SyncIntervalSeconds = &secs } } - targetDomainStr, _ := data.GetString("target_domain") + targetDomainStr := input.TargetDomainId if len(targetDomainStr) > 0 { domain, err := DomainManager.FetchDomainById(targetDomainStr) if err != nil { if err == sql.ErrNoRows { - return nil, httperrors.NewResourceNotFoundError2(DomainManager.Keyword(), targetDomainStr) + return input, httperrors.NewResourceNotFoundError2(DomainManager.Keyword(), targetDomainStr) } else { - return nil, httperrors.NewGeneralError(err) + return input, httperrors.NewGeneralError(err) } } - data.Set("target_domain_id", jsonutils.NewString(domain.Id)) + input.TargetDomainId = domain.Id } - opts := api.TConfigs{} - err := data.Unmarshal(&opts, "config") + var err error + input.Config, err = drvCls.ValidateConfig(ctx, userCred, input.Config) if err != nil { - return nil, httperrors.NewInputParameterError("parse config error: %s", err) + return input, errors.Wrap(err, "ValidateConfig") } - input := apis.EnabledStatusStandaloneResourceCreateInput{} - err = data.Unmarshal(&input) + + input.EnabledStatusStandaloneResourceCreateInput, err = manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledStatusStandaloneResourceCreateInput) if err != nil { - return nil, httperrors.NewInternalServerError("unmarshal StandaloneResourceCreateInput fail %s", err) + return input, errors.Wrap(err, "SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData") } - input, err = manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input) - if err != nil { - return nil, err - } - data.Update(jsonutils.Marshal(input)) - return data, nil + + return input, nil } func (ident *SIdentityProvider) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { @@ -736,23 +745,13 @@ func (self *SIdentityProvider) SyncOrCreateDomain(ctx context.Context, extId str } if self.AutoCreateProject.IsTrue() && consts.GetNonDefaultDomainProjects() { - project := &SProject{} - project.SetModelManager(ProjectManager, project) - projectName := NormalizeProjectName(fmt.Sprintf("%s_default_project", extName)) - newName, err := db.GenerateName(ProjectManager, nil, projectName) + _, err := ProjectManager.NewProject(ctx, + fmt.Sprintf("%s_default_project", extName), + fmt.Sprintf("Default project for domain %s", extName), + domain, + ) if err != nil { - // ignore the error - log.Errorf("db.GenerateName error %s for default domain project %s", err, projectName) - newName = projectName - } - project.Name = newName - project.DomainId = domain.Id - project.Description = fmt.Sprintf("Default project for domain %s", extName) - project.IsDomain = tristate.False - project.ParentId = domain.Id - err = ProjectManager.TableSpec().Insert(project) - if err != nil { - log.Errorf("ProjectManager.Insert fail %s", err) + log.Errorf("ProjectManager.NewProject fail %s", err) } } diff --git a/pkg/keystone/models/projects.go b/pkg/keystone/models/projects.go index 650992dfe6..218dfbf475 100644 --- a/pkg/keystone/models/projects.go +++ b/pkg/keystone/models/projects.go @@ -29,10 +29,12 @@ import ( api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/keystone/options" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/pinyinutils" + "yunion.io/x/onecloud/pkg/util/rbacutils" ) type SProjectManager struct { @@ -321,12 +323,21 @@ func (proj *SProject) getExternalResources() (map[string]int, time.Time, error) func NormalizeProjectName(name string) string { name = pinyinutils.Text2Pinyin(name) - for _, illChar := range []string{ - "/", ".", " ", - } { - name = strings.Replace(name, illChar, "", -1) + newName := strings.Builder{} + lastSlash := false + for _, c := range name { + if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') { + newName.WriteRune(c) + lastSlash = false + } else if c >= 'A' && c <= 'Z' { + newName.WriteRune(c - 'A' + 'a') + lastSlash = false + } else if !lastSlash { + newName.WriteRune('-') + lastSlash = true + } } - return name + return newName.String() } func (manager *SProjectManager) FetchUserProjects(userId string) ([]SProjectExtended, error) { @@ -430,7 +441,7 @@ func (project *SProject) PerformJoin( for i := range users { for j := range roles { - err = AssignmentManager.projectAddUser(ctx, userCred, project, users[i], roles[j]) + err = AssignmentManager.ProjectAddUser(ctx, userCred, project, users[i], roles[j]) if err != nil { return nil, httperrors.NewGeneralError(err) } @@ -518,3 +529,32 @@ func (project *SProject) PerformLeave( } return nil, nil } + +func (manager *SProjectManager) NewProject(ctx context.Context, name string, desc string, domain *SDomain) (*SProject, error) { + lockman.LockClass(ctx, manager, domain.Id) + defer lockman.ReleaseClass(ctx, manager, domain.Id) + + project := &SProject{} + project.SetModelManager(ProjectManager, project) + projectName := NormalizeProjectName(name) + ownerId := &db.SOwnerId{} + if manager.NamespaceScope() == rbacutils.ScopeDomain { + ownerId.DomainId = domain.Id + } + newName, err := db.GenerateName(ProjectManager, ownerId, projectName) + if err != nil { + // ignore the error + log.Errorf("db.GenerateName error %s for default domain project %s", err, projectName) + newName = projectName + } + project.Name = newName + project.DomainId = domain.Id + project.Description = desc + project.IsDomain = tristate.False + project.ParentId = domain.Id + err = ProjectManager.TableSpec().Insert(project) + if err != nil { + return nil, errors.Wrap(err, "Insert") + } + return project, nil +} diff --git a/pkg/keystone/models/projects_test.go b/pkg/keystone/models/projects_test.go index 09fc767e63..eb7bb3bad3 100644 --- a/pkg/keystone/models/projects_test.go +++ b/pkg/keystone/models/projects_test.go @@ -24,7 +24,7 @@ func TestNormalizeProjectName(t *testing.T) { Want string }{ {"分公司1", "fengongsi1"}, - {"集团/分公司/项目A", "jituanfengongsixiangmuA"}, + {"集团/分公司/项目A", "jituan-fengongsi-xiangmua"}, } for _, c := range cases { got := NormalizeProjectName(c.In) diff --git a/pkg/keystone/models/roles.go b/pkg/keystone/models/roles.go index c079d538f8..bb51cb4bc9 100644 --- a/pkg/keystone/models/roles.go +++ b/pkg/keystone/models/roles.go @@ -278,7 +278,7 @@ func (role *SRole) UpdateInContext(ctx context.Context, userCred mcclient.TokenC } switch obj := ctxObjs[1].(type) { case *SUser: - return nil, AssignmentManager.projectAddUser(ctx, userCred, project, obj, role) + return nil, AssignmentManager.ProjectAddUser(ctx, userCred, project, obj, role) case *SGroup: return nil, AssignmentManager.projectAddGroup(ctx, userCred, project, obj, role) default: diff --git a/pkg/keystone/models/users.go b/pkg/keystone/models/users.go index b8243c860b..694204aeac 100644 --- a/pkg/keystone/models/users.go +++ b/pkg/keystone/models/users.go @@ -796,7 +796,7 @@ func joinProjects(ident db.IModel, isUser bool, ctx context.Context, userCred mc for i := range projects { for j := range roles { if isUser { - err = AssignmentManager.projectAddUser(ctx, userCred, projects[i], ident.(*SUser), roles[j]) + err = AssignmentManager.ProjectAddUser(ctx, userCred, projects[i], ident.(*SUser), roles[j]) } else { err = AssignmentManager.projectAddGroup(ctx, userCred, projects[i], ident.(*SGroup), roles[j]) }