mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
feature: cas user auto join designated project
This commit is contained in:
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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+)</%s>`, 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
|
||||
}
|
||||
|
||||
@@ -46,3 +46,17 @@ func TestXmlUnmarshal(t *testing.T) {
|
||||
t.Logf("%#v", casresp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchAttribute(t *testing.T) {
|
||||
xmlstr := `<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
|
||||
<cas:authenticationSuccess>
|
||||
<cas:user>casuser</cas:user>
|
||||
<cas:proj>casproj</cas:proj>
|
||||
</cas:authenticationSuccess>
|
||||
</cas:serviceResponse>`
|
||||
got := fetchAttribute([]byte(xmlstr), "cas:proj")
|
||||
want := "casproj"
|
||||
if got != want {
|
||||
t.Errorf("want %s got %s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user