diff --git a/cmd/climc/shell/domains.go b/cmd/climc/shell/domains.go index 2dd58bc0ba..03a72910d2 100644 --- a/cmd/climc/shell/domains.go +++ b/cmd/climc/shell/domains.go @@ -15,11 +15,8 @@ package shell import ( - "fmt" - "yunion.io/x/jsonutils" - api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" ) @@ -72,30 +69,7 @@ func init() { printObject(result) return nil }) - R(&DomainDetailOptions{}, "domain-config-show", "Show detail of a domain config", func(s *mcclient.ClientSession, args *DomainDetailOptions) error { - objId, err := modules.Domains.GetId(s, args.ID, nil) - if err != nil { - return err - } - config, err := modules.Domains.GetConfig(s, objId) - if err != nil { - return err - } - fmt.Println(config.PrettyString()) - return nil - }) - R(&DomainDetailOptions{}, "domain-config-delete", "Delete a domain config", func(s *mcclient.ClientSession, args *DomainDetailOptions) error { - objId, err := modules.Domains.GetId(s, args.ID, nil) - if err != nil { - return err - } - config, err := modules.Domains.DeleteConfig(s, objId) - if err != nil { - return err - } - printObject(config) - return nil - }) + /* R(&DomainDetailOptions{}, "domain-config-sql", "Config a domain with SQL driver", func(s *mcclient.ClientSession, args *DomainDetailOptions) error { config := jsonutils.NewDict() config.Add(jsonutils.NewString("sql"), "config", "identity", "driver") @@ -111,26 +85,6 @@ func init() { return nil }) */ - type DomainConfigLDAPOptions struct { - ID string `help:"ID of domain to config" json:"-"` - api.SDomainLDAPConfigOptions - } - R(&DomainConfigLDAPOptions{}, "domain-config-ldap", "Config a domain with LDAP driver", func(s *mcclient.ClientSession, args *DomainConfigLDAPOptions) error { - config := jsonutils.NewDict() - config.Add(jsonutils.NewString("ldap"), "config", "identity", "driver") - config.Add(jsonutils.Marshal(args), "config", "ldap") - objId, err := modules.Domains.GetId(s, args.ID, nil) - if err != nil { - return err - } - nconf, err := modules.Domains.UpdateConfig(s, objId, config) - if err != nil { - return err - } - fmt.Println(nconf.PrettyString()) - return nil - }) - type DomainCreateOptions struct { NAME string `help:"Name of domain"` Desc string `help:"Description"` diff --git a/cmd/climc/shell/identityproviders.go b/cmd/climc/shell/identityproviders.go new file mode 100644 index 0000000000..a5cc0cdeba --- /dev/null +++ b/cmd/climc/shell/identityproviders.go @@ -0,0 +1,205 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "fmt" + + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" + + "yunion.io/x/jsonutils" +) + +func init() { + type IdentityProviderListOptions struct { + options.BaseListOptions + } + R(&IdentityProviderListOptions{}, "idp-list", "List all identity provider", func(s *mcclient.ClientSession, args *IdentityProviderListOptions) error { + params, err := options.ListStructToParams(args) + if err != nil { + return err + } + results, err := modules.IdentityProviders.List(s, params) + if err != nil { + return err + } + printList(results, modules.IdentityProviders.GetColumns(s)) + return nil + }) + + type IdentityProviderDetailOptions struct { + ID string `help:"Id or name of identity provider to show"` + } + R(&IdentityProviderDetailOptions{}, "idp-show", "Show details of idp", func(s *mcclient.ClientSession, args *IdentityProviderDetailOptions) error { + detail, err := modules.IdentityProviders.Get(s, args.ID, nil) + if err != nil { + return err + } + printObject(detail) + return nil + }) + + R(&IdentityProviderDetailOptions{}, "idp-config-show", "Show detail of a domain config", func(s *mcclient.ClientSession, args *IdentityProviderDetailOptions) error { + conf, err := modules.IdentityProviders.GetSpecific(s, args.ID, "config", nil) + if err != nil { + return err + } + fmt.Println(conf.PrettyString()) + return nil + }) + + R(&IdentityProviderDetailOptions{}, "idp-enable", "Enable an identity provider", func(s *mcclient.ClientSession, args *IdentityProviderDetailOptions) error { + idp, err := modules.IdentityProviders.PerformAction(s, args.ID, "enable", nil) + if err != nil { + return err + } + printObject(idp) + return nil + }) + + R(&IdentityProviderDetailOptions{}, "idp-disable", "Disable an identity provider", func(s *mcclient.ClientSession, args *IdentityProviderDetailOptions) error { + idp, err := modules.IdentityProviders.PerformAction(s, args.ID, "disable", nil) + if err != nil { + return err + } + printObject(idp) + return nil + }) + + R(&IdentityProviderDetailOptions{}, "idp-delete", "Delete an identity provider", func(s *mcclient.ClientSession, args *IdentityProviderDetailOptions) error { + idp, err := modules.IdentityProviders.Delete(s, args.ID, nil) + if err != nil { + return err + } + printObject(idp) + return nil + }) + + R(&IdentityProviderDetailOptions{}, "idp-sync", "Sync an identity provider", func(s *mcclient.ClientSession, args *IdentityProviderDetailOptions) error { + idp, err := modules.IdentityProviders.PerformAction(s, args.ID, "sync", nil) + if err != nil { + return err + } + printObject(idp) + return nil + }) + + type IdentityProviderConfigLDAPOptions struct { + ID string `help:"ID of idp to config" json:"-"` + api.SLDAPIdpConfigOptions + } + R(&IdentityProviderConfigLDAPOptions{}, "idp-config-ldap", "Config an Identity provider with LDAP driver", func(s *mcclient.ClientSession, args *IdentityProviderConfigLDAPOptions) error { + config := jsonutils.NewDict() + config.Add(jsonutils.Marshal(args), "config", "ldap") + nconf, err := modules.IdentityProviders.PerformAction(s, args.ID, "config", config) + if err != nil { + return err + } + fmt.Println(nconf.PrettyString()) + return nil + }) + + type IdentityProviderCreateOptions struct { + NAME string `help:"name of identity provider" json:"-"` + api.SLDAPIdpConfigOptions + } + R(&IdentityProviderCreateOptions{}, "idp-create-ldap", "Create an identity provider with LDAP driver", func(s *mcclient.ClientSession, args *IdentityProviderCreateOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.NAME), "name") + params.Add(jsonutils.NewString("ldap"), "driver") + params.Add(jsonutils.Marshal(args), "config", "ldap") + + idp, err := modules.IdentityProviders.Create(s, params) + if err != nil { + return err + } + printObject(idp) + return nil + }) + + type IdentityProviderConfigLDAPSingleDomainOptions struct { + ID string `help:"ID of idp to config" json:"-"` + api.SLDAPIdpConfigSingleDomainOptions + } + R(&IdentityProviderConfigLDAPSingleDomainOptions{}, "idp-config-ldap-single-domain", "Config an Identity provider with LDAP driver/single domain template", func(s *mcclient.ClientSession, args *IdentityProviderConfigLDAPSingleDomainOptions) error { + config := jsonutils.NewDict() + config.Add(jsonutils.Marshal(args), "config", "ldap") + nconf, err := modules.IdentityProviders.PerformAction(s, args.ID, "config", config) + if err != nil { + return err + } + fmt.Println(nconf.PrettyString()) + return nil + }) + + type IdentityProviderCreateLDAPSingleDomainOptions struct { + NAME string `help:"name of identity provider" json:"-"` + TEMPLATE string `help:"configuration template name" choices:"msad_one_domain|openldap_one_domain" json:"-"` + api.SLDAPIdpConfigSingleDomainOptions + } + R(&IdentityProviderCreateLDAPSingleDomainOptions{}, "idp-create-ldap-single-domain", "Create an identity provider with LDAP driver/single domain template", func(s *mcclient.ClientSession, args *IdentityProviderCreateLDAPSingleDomainOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.NAME), "name") + params.Add(jsonutils.NewString("ldap"), "driver") + params.Add(jsonutils.NewString(args.TEMPLATE), "template") + params.Add(jsonutils.Marshal(args), "config", "ldap") + + idp, err := modules.IdentityProviders.Create(s, params) + if err != nil { + return err + } + printObject(idp) + return nil + }) + + type IdentityProviderConfigLDAPMultiDomainOptions struct { + ID string `help:"ID of idp to config" json:"-"` + api.SLDAPIdpConfigMultiDomainOptions + } + R(&IdentityProviderConfigLDAPMultiDomainOptions{}, "idp-config-ldap-multi-domain", "Config an Identity provider with LDAP driver/multi domain template", func(s *mcclient.ClientSession, args *IdentityProviderConfigLDAPMultiDomainOptions) error { + config := jsonutils.NewDict() + config.Add(jsonutils.Marshal(args), "config", "ldap") + nconf, err := modules.IdentityProviders.PerformAction(s, args.ID, "config", config) + if err != nil { + return err + } + fmt.Println(nconf.PrettyString()) + return nil + }) + + type IdentityProviderCreateLDAPMultiDomainOptions struct { + NAME string `help:"name of identity provider" json:"-"` + TEMPLATE string `help:"configuration template name" choices:"msad_multi_domain" json:"-"` + api.SLDAPIdpConfigMultiDomainOptions + } + R(&IdentityProviderCreateLDAPMultiDomainOptions{}, "idp-create-ldap-multi-domain", "Create an identity provider with LDAP driver/single domain template", func(s *mcclient.ClientSession, args *IdentityProviderCreateLDAPMultiDomainOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.NAME), "name") + params.Add(jsonutils.NewString("ldap"), "driver") + params.Add(jsonutils.NewString(args.TEMPLATE), "template") + params.Add(jsonutils.Marshal(args), "config", "ldap") + + idp, err := modules.IdentityProviders.Create(s, params) + if err != nil { + return err + } + printObject(idp) + return nil + }) + +} diff --git a/cmd/climc/shell/snapshot_policy.go b/cmd/climc/shell/snapshot_policy.go index 8049cc1391..5324592dba 100644 --- a/cmd/climc/shell/snapshot_policy.go +++ b/cmd/climc/shell/snapshot_policy.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/cmd/climc/shell/users.go b/cmd/climc/shell/users.go index 95d901d807..80c16114f6 100644 --- a/cmd/climc/shell/users.go +++ b/cmd/climc/shell/users.go @@ -263,7 +263,16 @@ func init() { Enabled bool `help:"Enabled"` Disabled bool `help:"Disabled"` - DefaultProject string `help:"Default project"` + SystemAccount bool `help:"Turn on is_system_account"` + NotSystemAccount bool `help:"Turn off is_system_account"` + + AllowWebConsole bool `help:"Turn on allow_web_console"` + DisallowWebConsole bool `help:"Turn off allow_web_console"` + + EnableMfa bool `help:"turn on enable_mfa"` + DisableMfa bool `help:"turn off enable_mfa"` + + // DefaultProject string `help:"Default project"` // Option []string `help:"User options"` } R(&UserUpdateOptions{}, "user-update", "Update a user", func(s *mcclient.ClientSession, args *UserUpdateOptions) error { @@ -303,25 +312,40 @@ func init() { } else if !args.Enabled && args.Disabled { params.Add(jsonutils.JSONFalse, "enabled") } - if len(args.DefaultProject) > 0 { - projId, err := modules.Projects.GetId(s, args.DefaultProject, nil) - if err != nil { - return err - } - params.Add(jsonutils.NewString(projId), "default_project_id") + if args.SystemAccount { + params.Add(jsonutils.JSONTrue, "is_system_account") + } else if args.NotSystemAccount { + params.Add(jsonutils.JSONFalse, "is_system_account") } - /* - if len(args.Option) > 0 { - uoptions := jsonutils.NewDict() - for _, opt := range args.Option { - pos := strings.IndexByte(opt, ':') - key := opt[:pos] - val := opt[pos+1:] - uoptions.Add(jsonutils.NewString(val), key) - } - params.Add(uoptions, "_resource_options") - } - */ + if args.AllowWebConsole { + params.Add(jsonutils.JSONTrue, "allow_web_console") + } else if args.DisallowWebConsole { + params.Add(jsonutils.JSONFalse, "allow_web_console") + } + if args.EnableMfa { + params.Add(jsonutils.JSONTrue, "enable_mfa") + } else if args.DisableMfa { + params.Add(jsonutils.JSONFalse, "enable_mfa") + } + // if len(args.DefaultProject) > 0 { + // projId, err := modules.Projects.GetId(s, args.DefaultProject, nil) + // if err != nil { + // return err + // } + // params.Add(jsonutils.NewString(projId), "default_project_id") + // } + // + // if len(args.Option) > 0 { + // uoptions := jsonutils.NewDict() + // for _, opt := range args.Option { + // pos := strings.IndexByte(opt, ':') + // key := opt[:pos] + // val := opt[pos+1:] + // uoptions.Add(jsonutils.NewString(val), key) + // } + // params.Add(uoptions, "_resource_options") + // } + // user, err := modules.UsersV3.Patch(s, uid, params) if err != nil { return err diff --git a/cmd/ldapcli/shell/search.go b/cmd/ldapcli/shell/search.go index a226ca37d4..c460c4d38a 100644 --- a/cmd/ldapcli/shell/search.go +++ b/cmd/ldapcli/shell/search.go @@ -17,16 +17,29 @@ package shell import ( "fmt" "strings" + + "gopkg.in/ldap.v3" + + api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/util/ldaputils" "yunion.io/x/onecloud/pkg/util/shellutils" ) +func queryScope(scope string) int { + if scope == api.QueryScopeOne { + return ldap.ScopeSingleLevel + } else { + return ldap.ScopeWholeSubtree + } +} + func init() { type LdapSearchOptions struct { Base string `help:"base DN, e.g. OU=tech,DC=example,DC=com"` Objectclass string `help:"objectclass, e.g. organizationalPerson"` Search []string `help:"search conditions, in format of field:value"` Field []string `help:"retrieve field info"` + Scope string `help:"query scope" choices:"one|sub" default:"sub"` } shellutils.R(&LdapSearchOptions{}, "search", "search ldap", func(cli *ldaputils.SLDAPClient, args *LdapSearchOptions) error { search := make(map[string]string) @@ -37,7 +50,7 @@ func init() { } search[s[:colonPos]] = s[(colonPos + 1):] } - entries, err := cli.Search(args.Base, args.Objectclass, search, args.Field) + entries, err := cli.Search(args.Base, args.Objectclass, search, "", args.Field, queryScope(args.Scope)) if err != nil { return err } @@ -54,9 +67,10 @@ func init() { ACCOUNT string `help:"account name to auth"` PASSWORD string `help:"Password to auth"` Field []string `help:"retrieve field info"` + Scope string `help:"query scope" choices:"one|sub" default:"sub"` } shellutils.R(&LdapAuthOptions{}, "auth", "authenticate against ldap", func(cli *ldaputils.SLDAPClient, args *LdapAuthOptions) error { - entry, err := cli.Authenticate(args.Base, args.Objectclass, args.ATTR, args.ACCOUNT, args.PASSWORD, args.Field) + entry, err := cli.Authenticate(args.Base, args.Objectclass, args.ATTR, args.ACCOUNT, args.PASSWORD, "", args.Field, queryScope(args.Scope)) if err != nil { return err } diff --git a/pkg/apis/identity/config.go b/pkg/apis/identity/config.go index 92580eddc4..2ca1c1b5bb 100644 --- a/pkg/apis/identity/config.go +++ b/pkg/apis/identity/config.go @@ -14,18 +14,51 @@ package identity +import "yunion.io/x/jsonutils" + const ( QueryScopeOne = "one" QUeryScopeSub = "sub" ) -type SDomainLDAPConfigOptions struct { +type TIdentityProviderConfigs map[string]map[string]jsonutils.JSONObject + +type SLDAPIdpConfigBaseOptions struct { + Url string `json:"url,omitempty" help:"LDAP server URL" required:"true"` + Suffix string `json:"suffix,omitempty" required:"true"` + User string `json:"user,omitempty" required:"true"` + Password string `json:"password,omitempty" required:"true"` +} + +type SLDAPIdpConfigSingleDomainOptions struct { + SLDAPIdpConfigBaseOptions + + UserTreeDN string `json:"user_tree_dn,omitempty" help:"Base user tree distinguished name" required:"true"` + GroupTreeDN string `json:"group_tree_dn,omitempty" help:"Base group tree distinguished name" required:"true"` +} + +type SLDAPIdpConfigMultiDomainOptions struct { + SLDAPIdpConfigBaseOptions + + DomainTreeDN string `json:"domain_tree_dn,omitempty" help:"Base domain tree distinguished name" required:"true"` +} + +type SLDAPIdpConfigOptions struct { Url string `json:"url,omitempty" help:"LDAP server URL" required:"true"` Suffix string `json:"suffix,omitempty" required:"true"` - QueryScope string `json:"query_scope,omitempty" help:"Query scope, either one or sub" choices:"one|sub" default:"sub"` - PageSize int `json:"page_size,omitzero" help:"Page size, default 20" default:"20"` - User string `json:"user,omitempty"` - Password string `json:"password,omitempty"` + QueryScope string `json:"query_scope,omitempty" help:"Query scope, either one or sub" choices:"one|sub"` + // PageSize int `json:"page_size,omitzero" help:"Page size, default 20" default:"20"` + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` + + ImportDomain bool `json:"import_domain,omitfalse"` + + DomainTreeDN string `json:"domain_tree_dn,omitempty" help:"Domain tree root node dn(distinguished name)"` + DomainFilter string `json:"domain_filter,omitempty"` + DomainObjectclass string `json:"domain_objectclass,omitempty"` + DomainIdAttribute string `json:"domain_id_attribute,omitempty"` + DomainNameAttribute string `json:"domain_name_attribute,omitempty"` + DomainQueryScope string `json:"domain_query_scope,omitempty" help:"Query scope, either one or sub" choices:"one|sub"` UserTreeDN string `json:"user_tree_dn,omitempty" help:"User tree distinguished name"` UserFilter string `json:"user_filter,omitempty"` @@ -37,6 +70,7 @@ type SDomainLDAPConfigOptions struct { UserEnabledDefault string `json:"user_enabled_default,omitempty"` UserEnabledInvert bool `json:"user_enabled_invert,allowfalse"` UserAdditionalAttribute []string `json:"user_additional_attribute_mapping,omitempty" token:"user_additional_attribute"` + UserQueryScope string `json:"user_query_scope,omitempty" help:"Query scope, either one or sub" choices:"one|sub"` GroupTreeDN string `json:"group_tree_dn,omitempty" help:"Group tree distinguished name"` GroupFilter string `json:"group_filter,omitempty"` @@ -45,4 +79,19 @@ type SDomainLDAPConfigOptions struct { GroupNameAttribute string `json:"group_name_attribute,omitempty"` GroupMemberAttribute string `json:"group_member_attribute,omitempty"` GroupMembersAreIds bool `json:"group_members_are_ids,allowfalse"` + GroupQueryScope string `json:"group_query_scope,omitempty" help:"Query scope, either one or sub" choices:"one|sub"` } + +const ( + IdpTemplateMSSingleDomain = "msad_one_domain" + IdpTemplateMSMultiDomain = "msad_multi_domain" + IdpTemplateOpenLDAPSingleDomain = "openldap_one_domain" +) + +var ( + IdpTemplateDriver = map[string]string{ + IdpTemplateMSSingleDomain: IdentityDriverLDAP, + IdpTemplateMSMultiDomain: IdentityDriverLDAP, + IdpTemplateOpenLDAPSingleDomain: IdentityDriverLDAP, + } +) diff --git a/pkg/apis/identity/consts.go b/pkg/apis/identity/consts.go index fa56aa9c76..4d54c2fd66 100644 --- a/pkg/apis/identity/consts.go +++ b/pkg/apis/identity/consts.go @@ -20,6 +20,10 @@ const ( DEFAULT_DOMAIN_ID = "default" DEFAULT_DOMAIN_NAME = "Default" + DefaultRemoteDomainId = "default_domain" + + DEFAULT_IDP_ID = DEFAULT_DOMAIN_ID + SystemAdminUser = "sysadmin" SystemAdminProject = "system" SystemAdminRole = "admin" @@ -44,11 +48,26 @@ const ( KeystoneDomainRoot = "<>" - IdMappingEntityUser = "user" - IdMappingEntityGroup = "group" + IdMappingEntityUser = "user" + IdMappingEntityGroup = "group" + IdMappingEntityDomain = "domain" IdentityDriverSQL = "sql" IdentityDriverLDAP = "ldap" + + IdentityDriverStatusConnected = "connected" + IdentityDriverStatusDisconnected = "disconnected" + IdentityDriverStatusDeleting = "deleting" + + IdentityProviderSyncLocal = "local" + IdentityProviderSyncFull = "full" + IdentityProviderSyncOnAuth = "auth" + + IdentitySyncStatusQueued = "queued" + IdentitySyncStatusSyncing = "syncing" + IdentitySyncStatusIdle = "idle" + + MinimalSyncIntervalSeconds = 5 * 60 // 5 minutes ) var ( diff --git a/pkg/apis/identity/usrext.go b/pkg/apis/identity/usrext.go new file mode 100644 index 0000000000..bb931753fc --- /dev/null +++ b/pkg/apis/identity/usrext.go @@ -0,0 +1,34 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package identity + +import "time" + +type SUserExtended struct { + Id string + Name string + Enabled bool + DefaultProjectId string + CreatedAt time.Time + LastActiveAt time.Time + DomainId string + + LocalId int + DomainName string + DomainEnabled bool + IsLocal bool + IdpId string + IdpName string +} diff --git a/pkg/cloudcommon/cronman/cronman.go b/pkg/cloudcommon/cronman/cronman.go index 8ba15c1b49..57bb8f07d8 100644 --- a/pkg/cloudcommon/cronman/cronman.go +++ b/pkg/cloudcommon/cronman/cronman.go @@ -28,6 +28,10 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/auth" ) +var ( + DefaultAdminSessionGenerator = auth.AdminCredential +) + type TCronJobFunction func(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) var manager *SCronJobManager @@ -227,7 +231,7 @@ func (job *SCronJob) runJobInWorker(isStart bool) { // log.Debugf("Cron job: %s started", job.Name) ctx := context.Background() - ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_APPNAME, "Region-Cron-Service") - userCred := auth.AdminCredential() + ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_APPNAME, "Cron-Service") + userCred := DefaultAdminSessionGenerator() job.job(ctx, userCred, isStart) } diff --git a/pkg/cloudcommon/object/doc.go b/pkg/cloudcommon/object/doc.go index 9235036b73..ac9e878b84 100644 --- a/pkg/cloudcommon/object/doc.go +++ b/pkg/cloudcommon/object/doc.go @@ -1 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package object // import "yunion.io/x/onecloud/pkg/cloudcommon/object" diff --git a/pkg/cloudcommon/options/options.go b/pkg/cloudcommon/options/options.go index bcf835d9c7..1c8d14f5b4 100644 --- a/pkg/cloudcommon/options/options.go +++ b/pkg/cloudcommon/options/options.go @@ -63,6 +63,8 @@ type BaseOptions struct { RbacPolicySyncPeriodSeconds int `help:"policy sync interval in seconds, default 15 minutes" default:"900"` RbacPolicySyncFailedRetrySeconds int `help:"seconds to wait after a failed sync, default 30 seconds" default:"30"` + IsSlaveNode bool `help:"Region service slave node"` + structarg.BaseOptions } diff --git a/pkg/cloudcommon/userdata/doc.go b/pkg/cloudcommon/userdata/doc.go index ffaf90462f..40f1a7909c 100644 --- a/pkg/cloudcommon/userdata/doc.go +++ b/pkg/cloudcommon/userdata/doc.go @@ -1 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package userdata // import "yunion.io/x/onecloud/pkg/cloudcommon/userdata" diff --git a/pkg/cloudcommon/userdata/userdata.go b/pkg/cloudcommon/userdata/userdata.go index 0aed691610..15f5054b66 100644 --- a/pkg/cloudcommon/userdata/userdata.go +++ b/pkg/cloudcommon/userdata/userdata.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package userdata import ( diff --git a/pkg/cloudcommon/userdata/userdata_test.go b/pkg/cloudcommon/userdata/userdata_test.go index 03b7a99ed8..f6491bcb69 100644 --- a/pkg/cloudcommon/userdata/userdata_test.go +++ b/pkg/cloudcommon/userdata/userdata_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package userdata import "testing" diff --git a/pkg/cloudprovider/eip.go b/pkg/cloudprovider/eip.go index 6153f448bc..4a47c67b4e 100644 --- a/pkg/cloudprovider/eip.go +++ b/pkg/cloudprovider/eip.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package cloudprovider type SEip struct { diff --git a/pkg/cloudprovider/snapshot_policy.go b/pkg/cloudprovider/snapshot_policy.go index acb1d6266c..434c5f6d88 100644 --- a/pkg/cloudprovider/snapshot_policy.go +++ b/pkg/cloudprovider/snapshot_policy.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package cloudprovider import "strconv" diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 8482fa8ac9..ed7784ab8d 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -1084,7 +1084,7 @@ func (manager *SDiskManager) SyncDisks(ctx context.Context, userCred mcclient.To for i := 0; i < len(added); i += 1 { extId := added[i].GetGlobalId() - _disk, err := manager.FetchByExternalId(extId) + _disk, err := db.FetchByExternalId(manager, extId) if err != nil && err != sql.ErrNoRows { //主要是显示duplicate err及 general err,方便排错 msg := fmt.Errorf("failed to found disk by external Id %s error: %v", extId, err) @@ -1123,7 +1123,7 @@ func (self *SDisk) syncDiskStorage(ctx context.Context, userCred mcclient.TokenC return err } storageExtId := istorage.GetGlobalId() - storage, err := StorageManager.FetchByExternalId(storageExtId) + storage, err := db.FetchByExternalId(StorageManager, storageExtId) if err != nil { log.Errorf("failed to found storage by istorage %s error: %v", storageExtId, err) return err diff --git a/pkg/compute/models/elasticips.go b/pkg/compute/models/elasticips.go index aed9958054..0d45e944e1 100644 --- a/pkg/compute/models/elasticips.go +++ b/pkg/compute/models/elasticips.go @@ -265,7 +265,7 @@ func (manager *SElasticipManager) SyncEips(ctx context.Context, userCred mcclien } if vm != nil { guest := vm.(*SGuest) - result := guest.SyncVMEip(ctx, userCred, provider, added[i], projectId) + result := guest.SyncVMEip(ctx, userCred, provider, added[i], syncOwnerId) if result.IsError() { syncResult.UpdateError(fmt.Errorf(result.Result())) } else { diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 5e66c1c6e6..57f8665b2f 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -1852,7 +1852,7 @@ func (self *SHost) SyncHostVMs(ctx context.Context, userCred mcclient.TokenCrede } for i := 0; i < len(added); i += 1 { - vm, err := GuestManager.FetchByExternalId(added[i].GetGlobalId()) + vm, err := db.FetchByExternalId(GuestManager, added[i].GetGlobalId()) if err != nil && err != sql.ErrNoRows { log.Errorf("failed to found guest by externalId %s error: %v", added[i].GetGlobalId(), err) continue @@ -1864,7 +1864,7 @@ func (self *SHost) SyncHostVMs(ctx context.Context, userCred mcclient.TokenCrede log.Errorf("failed to found ihost from vm %s", added[i].GetGlobalId()) continue } - _host, err := HostManager.FetchByExternalId(ihost.GetGlobalId()) + _host, err := db.FetchByExternalId(HostManager, ihost.GetGlobalId()) if err != nil { log.Errorf("failed to found host by externalId %s", ihost.GetGlobalId()) continue diff --git a/pkg/compute/options/options.go b/pkg/compute/options/options.go index 517c0ea340..63d733a9f6 100644 --- a/pkg/compute/options/options.go +++ b/pkg/compute/options/options.go @@ -105,8 +105,6 @@ type ComputeOptions struct { DisconnectedCloudAccountRetryProbeIntervalHours int `help:"interval to wait to probe status of a disconnected cloud account" default:"24"` - IsSlaveNode bool `help:"Region service slave node"` - SCapabilityOptions common_options.CommonOptions common_options.DBOptions diff --git a/pkg/compute/tasks/snapshot_policy_delete_task.go b/pkg/compute/tasks/snapshot_policy_delete_task.go index 4a75ce1025..9f0a7663c9 100644 --- a/pkg/compute/tasks/snapshot_policy_delete_task.go +++ b/pkg/compute/tasks/snapshot_policy_delete_task.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package tasks import ( diff --git a/pkg/compute/tasks/snapshotpolicy_create_task.go b/pkg/compute/tasks/snapshotpolicy_create_task.go index caebbad2e9..bbc0c1efc7 100644 --- a/pkg/compute/tasks/snapshotpolicy_create_task.go +++ b/pkg/compute/tasks/snapshotpolicy_create_task.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package tasks import ( diff --git a/pkg/image/tasks/image_probe_task.go b/pkg/image/tasks/image_probe_task.go index 357fd9a295..623a5c0acd 100644 --- a/pkg/image/tasks/image_probe_task.go +++ b/pkg/image/tasks/image_probe_task.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package tasks import ( diff --git a/pkg/keystone/driver/base.go b/pkg/keystone/driver/base.go index 4bad16acf7..d66701a806 100644 --- a/pkg/keystone/driver/base.go +++ b/pkg/keystone/driver/base.go @@ -15,36 +15,51 @@ package driver import ( - "yunion.io/x/onecloud/pkg/keystone/models" + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/cloudcommon/object" ) -type SBaseDomainDriver struct { - virtual interface{} +var ( + idpDriverClasses = make(map[string]IIdentityBackendClass) +) - config models.TDomainConfigs - domainId string +func RegisterDriverClass(drv IIdentityBackendClass) { + idpDriverClasses[drv.Name()] = drv } -func (base *SBaseDomainDriver) IIdentityBackend() IIdentityBackend { - return base.virtual.(IIdentityBackend) -} - -func NewBaseDomainDriver(domainId string, conf models.TDomainConfigs) SBaseDomainDriver { - return SBaseDomainDriver{ - domainId: domainId, - config: conf, +func GetDriverClass(drv string) IIdentityBackendClass { + if cls, ok := idpDriverClasses[drv]; ok { + return cls } + return nil } -func GetDriver(domainId string, conf models.TDomainConfigs) (IIdentityBackend, error) { - if ident, ok := conf["identity"]; ok { - if driverJson, ok := ident["driver"]; ok { - driver, _ := driverJson.GetString() - switch driver { - case "ldap": - return NewLDAPDriver(domainId, conf) - } - } +func GetDriver(driver string, idpId, idpName, template string, conf api.TIdentityProviderConfigs) (IIdentityBackend, error) { + drvCls := GetDriverClass(driver) + if drvCls == nil { + return nil, ErrNoSuchDriver } - return NewSQLDriver(domainId, conf) + return drvCls.NewDriver(idpId, idpName, template, conf) +} + +type SBaseIdentityDriver struct { + object.SObject + + Config api.TIdentityProviderConfigs + IdpId string + IdpName string + Template string +} + +func (base *SBaseIdentityDriver) IIdentityBackend() IIdentityBackend { + return base.GetVirtualObject().(IIdentityBackend) +} + +func NewBaseIdentityDriver(idpId, idpName, template string, conf api.TIdentityProviderConfigs) (SBaseIdentityDriver, error) { + drv := SBaseIdentityDriver{} + drv.IdpId = idpId + drv.IdpName = idpName + drv.Template = template + drv.Config = conf + return drv, nil } diff --git a/pkg/keystone/driver/driver.go b/pkg/keystone/driver/driver.go index 85c731295e..0e7fcb6ef6 100644 --- a/pkg/keystone/driver/driver.go +++ b/pkg/keystone/driver/driver.go @@ -17,24 +17,19 @@ package driver import ( "context" - "yunion.io/x/onecloud/pkg/keystone/models" + api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/mcclient" ) +type IIdentityBackendClass interface { + SingletonInstance() bool + SyncMethod() string + Name() string + NewDriver(idpId, idpName, template string, conf api.TIdentityProviderConfigs) (IIdentityBackend, error) +} + type IIdentityBackend interface { - Authenticate(ctx context.Context, identity mcclient.SAuthenticationIdentity) (*models.SUserExtended, error) -} - -type SUserInfo struct { - DN string - Id string - Name string - Enabled bool - Extra map[string]string -} - -type SGroupInfo struct { - Id string - Name string - Members []string + Authenticate(ctx context.Context, identity mcclient.SAuthenticationIdentity) (*api.SUserExtended, error) + Sync(ctx context.Context) error + Probe(ctx context.Context) error } diff --git a/pkg/keystone/driver/errors.go b/pkg/keystone/driver/errors.go new file mode 100644 index 0000000000..fd86c7936c --- /dev/null +++ b/pkg/keystone/driver/errors.go @@ -0,0 +1,21 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import "yunion.io/x/pkg/errors" + +const ( + ErrNoSuchDriver = errors.Error("no such driver") +) diff --git a/pkg/keystone/driver/ldap/class.go b/pkg/keystone/driver/ldap/class.go new file mode 100644 index 0000000000..ac7211f23a --- /dev/null +++ b/pkg/keystone/driver/ldap/class.go @@ -0,0 +1,42 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ldap + +import ( + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/keystone/driver" +) + +type SLDAPDriverClass struct{} + +func (self *SLDAPDriverClass) SingletonInstance() bool { + return false +} + +func (self *SLDAPDriverClass) SyncMethod() string { + return api.IdentityProviderSyncFull +} + +func (self *SLDAPDriverClass) NewDriver(idpId, idpName, template string, conf api.TIdentityProviderConfigs) (driver.IIdentityBackend, error) { + return NewLDAPDriver(idpId, idpName, template, conf) +} + +func (self *SLDAPDriverClass) Name() string { + return api.IdentityDriverLDAP +} + +func init() { + driver.RegisterDriverClass(&SLDAPDriverClass{}) +} diff --git a/pkg/keystone/driver/ldap/doc.go b/pkg/keystone/driver/ldap/doc.go new file mode 100644 index 0000000000..48baf3a5df --- /dev/null +++ b/pkg/keystone/driver/ldap/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ldap // import "yunion.io/x/onecloud/pkg/keystone/driver/ldap" diff --git a/pkg/keystone/driver/ldap/info.go b/pkg/keystone/driver/ldap/info.go new file mode 100644 index 0000000000..6f9074e065 --- /dev/null +++ b/pkg/keystone/driver/ldap/info.go @@ -0,0 +1,32 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ldap + +type SDomainInfo struct { + DN string + Id string + Name string +} + +type SUserInfo struct { + SDomainInfo + Enabled bool + Extra map[string]string +} + +type SGroupInfo struct { + SDomainInfo + Members []string +} diff --git a/pkg/keystone/driver/ldap.go b/pkg/keystone/driver/ldap/ldap.go similarity index 52% rename from pkg/keystone/driver/ldap.go rename to pkg/keystone/driver/ldap/ldap.go index c257065f6e..566a6fea07 100644 --- a/pkg/keystone/driver/ldap.go +++ b/pkg/keystone/driver/ldap/ldap.go @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -package driver +package ldap import ( "context" - "database/sql" "strconv" "strings" @@ -26,28 +25,29 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/utils" 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/keystone/driver" "yunion.io/x/onecloud/pkg/keystone/models" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/ldaputils" ) type SLDAPDriver struct { - SBaseDomainDriver - ldapConfig *api.SDomainLDAPConfigOptions + driver.SBaseIdentityDriver + + ldapConfig *api.SLDAPIdpConfigOptions } -func NewLDAPDriver(domainId string, conf models.TDomainConfigs) (IIdentityBackend, error) { - drv := SLDAPDriver{ - SBaseDomainDriver: NewBaseDomainDriver(domainId, conf), +func NewLDAPDriver(idpId, idpName, template string, conf api.TIdentityProviderConfigs) (driver.IIdentityBackend, error) { + base, err := driver.NewBaseIdentityDriver(idpId, idpName, template, conf) + if err != nil { + return nil, errors.Wrap(err, "NewBaseIdentityDriver") } - drv.virtual = &drv - err := drv.prepareConfig() + drv := SLDAPDriver{SBaseIdentityDriver: base} + drv.SetVirtualObject(&drv) + err = drv.prepareConfig() if err != nil { return nil, errors.Wrap(err, "prepareConfig") } @@ -56,26 +56,58 @@ func NewLDAPDriver(domainId string, conf models.TDomainConfigs) (IIdentityBacken func (self *SLDAPDriver) prepareConfig() error { if self.ldapConfig == nil { - conf := api.SDomainLDAPConfigOptions{} - confJson := jsonutils.Marshal(self.config["ldap"]) + conf := api.SLDAPIdpConfigOptions{} + switch self.Template { + case api.IdpTemplateMSSingleDomain: + conf = MicrosoftActiveDirectorySingleDomainTemplate + case api.IdpTemplateMSMultiDomain: + conf = MicrosoftActiveDirectoryMultipleDomainTemplate + case api.IdpTemplateOpenLDAPSingleDomain: + conf = OpenLdapSingleDomainTemplate + } + confJson := jsonutils.Marshal(self.Config["ldap"]) err := confJson.Unmarshal(&conf) if err != nil { return errors.Wrap(err, "json.Unmarshal") } - log.Debugf("%s %s %#v", self.config, confJson, self.ldapConfig) + log.Debugf("%s %s %#v", self.Config, confJson, self.ldapConfig) self.ldapConfig = &conf } return nil } -func (self *SLDAPDriver) queryScope() int { - if self.ldapConfig.QueryScope == api.QueryScopeOne { +func queryScope(scope string) int { + if scope == api.QueryScopeOne { return ldap.ScopeSingleLevel } else { return ldap.ScopeWholeSubtree } } +func (self *SLDAPDriver) userQueryScope() int { + scope := self.ldapConfig.UserQueryScope + if len(scope) == 0 { + scope = self.ldapConfig.QueryScope + } + return queryScope(scope) +} + +func (self *SLDAPDriver) groupQueryScope() int { + scope := self.ldapConfig.GroupQueryScope + if len(scope) == 0 { + scope = self.ldapConfig.QueryScope + } + return queryScope(scope) +} + +func (self *SLDAPDriver) domainQueryScope() int { + scope := self.ldapConfig.DomainQueryScope + if len(scope) == 0 { + scope = self.ldapConfig.QueryScope + } + return queryScope(scope) +} + func (self *SLDAPDriver) userAttributeList() []string { attrs := []string{ "dn", @@ -92,8 +124,34 @@ func (self *SLDAPDriver) userAttributeList() []string { return attrs } +func (self *SLDAPDriver) groupAttributeList() []string { + return []string{ + "dn", + self.ldapConfig.GroupIdAttribute, + self.ldapConfig.GroupNameAttribute, + self.ldapConfig.GroupMemberAttribute, + } +} + +func (self *SLDAPDriver) domainAttributeList() []string { + return []string{ + "dn", + self.ldapConfig.DomainIdAttribute, + self.ldapConfig.DomainNameAttribute, + } +} + +func (self *SLDAPDriver) entry2Domain(entry *ldap.Entry) SDomainInfo { + info := SDomainInfo{} + info.DN = entry.DN + info.Id = entry.GetAttributeValue(self.ldapConfig.DomainIdAttribute) + info.Name = entry.GetAttributeValue(self.ldapConfig.DomainNameAttribute) + return info +} + func (self *SLDAPDriver) entry2Group(entry *ldap.Entry) SGroupInfo { info := SGroupInfo{} + info.DN = entry.DN info.Id = entry.GetAttributeValue(self.ldapConfig.GroupIdAttribute) info.Name = entry.GetAttributeValue(self.ldapConfig.GroupNameAttribute) info.Members = entry.GetAttributeValues(self.ldapConfig.GroupMemberAttribute) @@ -114,11 +172,12 @@ func (self *SLDAPDriver) entry2User(entry *ldap.Entry) SUserInfo { if (enabled & self.ldapConfig.UserEnabledMask) != 0 { info.Enabled = true } - } else if self.ldapConfig.UserEnabledInvert { - info.Enabled = !utils.ToBool(enabledStr) } else { info.Enabled = utils.ToBool(enabledStr) } + if self.ldapConfig.UserEnabledInvert { + info.Enabled = !info.Enabled + } info.Extra = make(map[string]string) for _, m := range self.ldapConfig.UserAdditionalAttribute { parts := strings.Split(m, ":") @@ -129,15 +188,6 @@ func (self *SLDAPDriver) entry2User(entry *ldap.Entry) SUserInfo { return info } -func (self *SLDAPDriver) groupAttributeList() []string { - return []string{ - "dn", - self.ldapConfig.GroupIdAttribute, - self.ldapConfig.GroupNameAttribute, - self.ldapConfig.GroupMemberAttribute, - } -} - func (self *SLDAPDriver) getClient() (*ldaputils.SLDAPClient, error) { cli := ldaputils.NewLDAPClient( self.ldapConfig.Url, @@ -153,6 +203,13 @@ func (self *SLDAPDriver) getClient() (*ldaputils.SLDAPClient, error) { return cli, nil } +func (self *SLDAPDriver) getDomainTreeDN() string { + if len(self.ldapConfig.DomainTreeDN) > 0 { + return self.ldapConfig.DomainTreeDN + } + return self.ldapConfig.Suffix +} + func (self *SLDAPDriver) getUserTreeDN() string { if len(self.ldapConfig.UserTreeDN) > 0 { return self.ldapConfig.UserTreeDN @@ -167,131 +224,55 @@ func (self *SLDAPDriver) getGroupTreeDN() string { return self.ldapConfig.Suffix } -func (self *SLDAPDriver) Authenticate(ctx context.Context, ident mcclient.SAuthenticationIdentity) (*models.SUserExtended, error) { +func (self *SLDAPDriver) Authenticate(ctx context.Context, ident mcclient.SAuthenticationIdentity) (*api.SUserExtended, error) { cli, err := self.getClient() if err != nil { return nil, errors.Wrap(err, "getClient") } defer cli.Close() - username := ident.Password.User.Name + usrExt, err := models.UserManager.FetchUserExtended( + ident.Password.User.Id, + ident.Password.User.Name, + ident.Password.User.Domain.Id, + ident.Password.User.Domain.Name, + ) + if err != nil { + return nil, errors.Wrap(err, "UserManager.FetchUserExtended") + } + + var userTreeDN string + if self.ldapConfig.ImportDomain { + idMap, err := models.IdmappingManager.FetchEntity(usrExt.DomainId, api.IdMappingEntityDomain) + if err != nil { + return nil, errors.Wrap(err, "IdmappingManager.FetchEntity for domain") + } + userTreeDN = idMap.IdpEntityId + } else { + userTreeDN = self.getUserTreeDN() + } + + usrIdmap, err := models.IdmappingManager.FetchEntity(usrExt.Id, api.IdMappingEntityUser) + if err != nil { + return nil, errors.Wrap(err, "IdmappingManager.FetchEntity for user") + } + username := usrIdmap.IdpEntityId password := ident.Password.User.Password - entry, err := cli.Authenticate( - self.getUserTreeDN(), + _, err = cli.Authenticate( + userTreeDN, self.ldapConfig.UserObjectclass, self.ldapConfig.UserIdAttribute, username, password, - self.userAttributeList()) + self.ldapConfig.UserFilter, + nil, + self.userQueryScope(), + ) if err != nil { log.Errorf("LDAP AUTH error: %s", err) return nil, errors.Wrap(err, "Authenticate error") } - userinfo := self.entry2User(entry) - groups := self.fetchUserGroups(cli, userinfo) - - return self.syncUserDB(ctx, models.GetDefaultAdminCred(), userinfo, groups) -} - -func (self *SLDAPDriver) fetchUserGroups(cli *ldaputils.SLDAPClient, userinfo SUserInfo) []SGroupInfo { - cond := make(map[string]string) - if self.ldapConfig.GroupMembersAreIds { - cond[self.ldapConfig.GroupMemberAttribute] = userinfo.Id - } else { - cond[self.ldapConfig.GroupMemberAttribute] = userinfo.DN - } - entries, _ := cli.Search(self.getGroupTreeDN(), - self.ldapConfig.GroupObjectclass, - cond, - self.groupAttributeList(), - ) - groups := make([]SGroupInfo, len(entries)) - for i := range entries { - groups[i] = self.entry2Group(entries[i]) - } - return groups -} - -func copyUserInfo(ui SUserInfo, nonLocal *models.SNonlocalUser, user *models.SUser) { - user.Name = ui.Id - if ui.Enabled { - user.Enabled = tristate.True - } else { - user.Enabled = tristate.False - } - user.Id = nonLocal.UserId - user.DomainId = nonLocal.DomainId - if val, ok := ui.Extra["email"]; ok && len(val) > 0 { - user.Email = val - } - if val, ok := ui.Extra["displayname"]; ok && len(val) > 0 { - user.Displayname = val - } - if val, ok := ui.Extra["mobile"]; ok && len(val) > 0 { - user.Mobile = val - } -} - -func registerNonlocalUser(ctx context.Context, ui SUserInfo, nonLocal *models.SNonlocalUser) error { - lockman.LockRawObject(ctx, models.UserManager.Keyword(), nonLocal.UserId) - defer lockman.ReleaseRawObject(ctx, models.UserManager.Keyword(), nonLocal.UserId) - - userObj, err := db.NewModelObject(models.UserManager) - if err != nil { - return errors.Wrap(err, "db.NewModelObject") - } - user := userObj.(*models.SUser) - q := models.UserManager.Query().Equals("id", nonLocal.UserId) - err = q.First(user) - if err != nil && err != sql.ErrNoRows { - return errors.Wrap(err, "Query") - } - if err == nil { - // update - _, err := db.Update(user, func() error { - copyUserInfo(ui, nonLocal, user) - return nil - }) - if err != nil { - return errors.Wrap(err, "Update") - } - } else { - // insert - copyUserInfo(ui, nonLocal, user) - err = models.UserManager.TableSpec().Insert(user) - if err != nil { - return errors.Wrap(err, "Insert") - } - } - return nil -} - -func (self *SLDAPDriver) syncUserDB(ctx context.Context, userCred mcclient.TokenCredential, ui SUserInfo, groups []SGroupInfo) (*models.SUserExtended, error) { - nonLocalUser, err := models.NonlocalUserManager.Register(ctx, self.domainId, ui.Id) - if err != nil { - return nil, errors.Wrap(err, "models.NonlocalUserManager.Register") - } - - // insert nonlocal user - err = registerNonlocalUser(ctx, ui, nonLocalUser) - if err != nil { - return nil, errors.Wrap(err, "registerNonlocalUser") - } - - // sync group - groupIds := make([]string, 0) - for i := range groups { - grp, err := models.GroupManager.RegisterExternalGroup(ctx, self.domainId, groups[i].Id, groups[i].Name) - if err != nil { - log.Errorf("models.GroupManager.RegisterExternalGroup fail %s", err) - } else { - groupIds = append(groupIds, grp.Id) - } - } - - models.UsergroupManager.SyncUserGroups(ctx, userCred, nonLocalUser.UserId, groupIds) - - return models.UserManager.FetchUserExtended(nonLocalUser.UserId, "", "", "") + return usrExt, nil } diff --git a/pkg/keystone/driver/ldap/sync.go b/pkg/keystone/driver/ldap/sync.go new file mode 100644 index 0000000000..7025f67f4f --- /dev/null +++ b/pkg/keystone/driver/ldap/sync.go @@ -0,0 +1,269 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ldap + +import ( + "context" + "database/sql" + "fmt" + + "github.com/pkg/errors" + + "yunion.io/x/pkg/tristate" + + 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/keystone/models" + "yunion.io/x/onecloud/pkg/util/ldaputils" +) + +func (self *SLDAPDriver) Probe(ctx context.Context) error { + cli, err := self.getClient() + if err != nil { + return errors.Wrap(err, "getClient") + } + defer cli.Close() + return nil +} + +func (self *SLDAPDriver) Sync(ctx context.Context) error { + cli, err := self.getClient() + if err != nil { + return errors.Wrap(err, "getClient") + } + defer cli.Close() + + if self.ldapConfig.ImportDomain { + return self.syncDomains(ctx, cli) + } else { + return self.syncSingleDomain(ctx, cli) + } +} + +func (self *SLDAPDriver) syncSingleDomain(ctx context.Context, cli *ldaputils.SLDAPClient) error { + domainInfo := SDomainInfo{DN: self.ldapConfig.Suffix, Id: api.DefaultRemoteDomainId, Name: self.IdpName} + domain, err := self.syncDomainInfo(ctx, domainInfo) + if err != nil { + return errors.Wrap(err, "syncDomainInfo") + } + userIdMap, err := self.syncUsers(ctx, cli, domain.Id, self.getUserTreeDN()) + if err != nil { + return errors.Wrap(err, "syncUsers") + } + err = self.syncGroups(ctx, cli, domain.Id, self.getGroupTreeDN(), userIdMap) + if err != nil { + return errors.Wrap(err, "syncGroups") + } + return nil +} + +func (self *SLDAPDriver) syncDomains(ctx context.Context, cli *ldaputils.SLDAPClient) error { + entries, err := cli.Search(self.getDomainTreeDN(), + self.ldapConfig.DomainObjectclass, + nil, + self.ldapConfig.DomainFilter, + self.domainAttributeList(), + self.domainQueryScope(), + ) + if err != nil { + return errors.Wrap(err, "searchLDAP") + } + for i := range entries { + domainInfo := self.entry2Domain(entries[i]) + domain, err := self.syncDomainInfo(ctx, domainInfo) + if err != nil { + return errors.Wrap(err, "syncDomainInfo") + } + userIdMap, err := self.syncUsers(ctx, cli, domain.Id, domainInfo.DN) + if err != nil { + return errors.Wrap(err, "syncUsers") + } + err = self.syncGroups(ctx, cli, domain.Id, domainInfo.DN, userIdMap) + if err != nil { + return errors.Wrap(err, "syncGroups") + } + } + return nil +} + +func (self *SLDAPDriver) syncDomainInfo(ctx context.Context, info SDomainInfo) (*models.SDomain, error) { + domainId, err := models.IdmappingManager.RegisterIdMap(ctx, self.IdpId, info.Id, api.IdMappingEntityDomain) + if err != nil { + return nil, errors.Wrap(err, "IdmappingManager.RegisterIdMap") + } + + domain, err := models.DomainManager.FetchDomainById(domainId) + if err != nil && err != sql.ErrNoRows { + return nil, errors.Wrap(err, "DomainManager.FetchDomainById") + } + if err == nil { + return domain, nil + } + + lockman.LockClass(ctx, models.DomainManager, "") + lockman.ReleaseClass(ctx, models.DomainManager, "") + + domain = &models.SDomain{} + domain.SetModelManager(models.DomainManager, domain) + domain.Id = domainId + newName, err := db.GenerateName(models.DomainManager, nil, info.Name) + if err != nil { + return nil, errors.Wrap(err, "GenerateName") + } + domain.Name = newName + domain.Enabled = tristate.True + domain.IsDomain = tristate.True + domain.DomainId = api.KeystoneDomainRoot + domain.Description = fmt.Sprintf("domain for %s", info.DN) + err = models.DomainManager.TableSpec().Insert(domain) + if err != nil { + return nil, errors.Wrap(err, "insert") + } + + return domain, nil +} + +func (self *SLDAPDriver) syncUsers(ctx context.Context, cli *ldaputils.SLDAPClient, domainId string, baseDN string) (map[string]string, error) { + entries, err := cli.Search(baseDN, + self.ldapConfig.UserObjectclass, + nil, + self.ldapConfig.UserFilter, + self.userAttributeList(), + self.userQueryScope(), + ) + if err != nil { + return nil, errors.Wrap(err, "searchLDAP") + } + userIdMap := make(map[string]string) + for i := range entries { + userInfo := self.entry2User(entries[i]) + userId, err := self.syncUserDB(ctx, userInfo, domainId) + if err != nil { + return nil, errors.Wrap(err, "syncUserDB") + } + if self.ldapConfig.GroupMembersAreIds { + userIdMap[userInfo.Id] = userId + } else { + userIdMap[userInfo.DN] = userId + } + } + return userIdMap, nil +} + +func copyUserInfo(ui SUserInfo, userId string, domainId string, user *models.SUser) { + user.Id = userId + user.Name = ui.Name + if ui.Enabled { + user.Enabled = tristate.True + } else { + user.Enabled = tristate.False + } + user.DomainId = domainId + if val, ok := ui.Extra["email"]; ok && len(val) > 0 { + user.Email = val + } + if val, ok := ui.Extra["displayname"]; ok && len(val) > 0 { + user.Displayname = val + } + if val, ok := ui.Extra["mobile"]; ok && len(val) > 0 { + user.Mobile = val + } +} + +func registerNonlocalUser(ctx context.Context, ui SUserInfo, userId string, domainId string) error { + lockman.LockRawObject(ctx, models.UserManager.Keyword(), userId) + defer lockman.ReleaseRawObject(ctx, models.UserManager.Keyword(), userId) + + userObj, err := db.NewModelObject(models.UserManager) + if err != nil { + return errors.Wrap(err, "db.NewModelObject") + } + user := userObj.(*models.SUser) + q := models.UserManager.Query().Equals("id", userId) + err = q.First(user) + if err != nil && err != sql.ErrNoRows { + return errors.Wrap(err, "Query user") + } + if err == nil { + // update + _, err := db.Update(user, func() error { + copyUserInfo(ui, userId, domainId, user) + return nil + }) + if err != nil { + return errors.Wrap(err, "Update") + } + } else { + // new user + copyUserInfo(ui, userId, domainId, user) + err = models.UserManager.TableSpec().Insert(user) + if err != nil { + return errors.Wrap(err, "Insert") + } + } + return nil +} + +func (self *SLDAPDriver) syncUserDB(ctx context.Context, ui SUserInfo, domainId string) (string, error) { + userId, err := models.IdmappingManager.RegisterIdMap(ctx, self.IdpId, ui.Id, api.IdMappingEntityUser) + if err != nil { + return "", errors.Wrap(err, "models.IdmappingManager.RegisterIdMap") + } + + // insert nonlocal user + err = registerNonlocalUser(ctx, ui, userId, domainId) + if err != nil { + return "", errors.Wrap(err, "registerNonlocalUser") + } + + return userId, nil +} + +func (self *SLDAPDriver) syncGroups(ctx context.Context, cli *ldaputils.SLDAPClient, domainId string, baseDN string, userIdMap map[string]string) error { + entries, err := cli.Search(baseDN, + self.ldapConfig.GroupObjectclass, + nil, + self.ldapConfig.GroupFilter, + self.groupAttributeList(), + self.groupQueryScope(), + ) + if err != nil { + return errors.Wrap(err, "searchLDAP") + } + for i := range entries { + groupInfo := self.entry2Group(entries[i]) + err := self.syncGroupDB(ctx, groupInfo, domainId, userIdMap) + if err != nil { + return errors.Wrap(err, "syncGroupDB") + } + } + return nil +} + +func (self *SLDAPDriver) syncGroupDB(ctx context.Context, groupInfo SGroupInfo, domainId string, userIdMap map[string]string) error { + grp, err := models.GroupManager.RegisterExternalGroup(ctx, self.IdpId, domainId, groupInfo.Id, groupInfo.Name) + if err != nil { + return errors.Wrap(err, "GroupManager.RegisterExternalGroup") + } + userIds := make([]string, 0) + for _, userExtId := range groupInfo.Members { + if uid, ok := userIdMap[userExtId]; ok { + userIds = append(userIds, uid) + } + } + models.UsergroupManager.SyncGroupUsers(ctx, models.GetDefaultAdminCred(), grp.Id, userIds) + return nil +} diff --git a/pkg/keystone/driver/ldap/template.go b/pkg/keystone/driver/ldap/template.go new file mode 100644 index 0000000000..1c335e3c9d --- /dev/null +++ b/pkg/keystone/driver/ldap/template.go @@ -0,0 +1,93 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ldap + +import ( + api "yunion.io/x/onecloud/pkg/apis/identity" +) + +var ( + MicrosoftActiveDirectorySingleDomainTemplate = api.SLDAPIdpConfigOptions{ + ImportDomain: false, + UserObjectclass: "organizationalPerson", + UserIdAttribute: "sAMAccountName", + UserNameAttribute: "sAMAccountName", + UserEnabledAttribute: "userAccountControl", + UserEnabledMask: 2, + UserEnabledDefault: "512", + UserEnabledInvert: true, + UserAdditionalAttribute: []string{ + "displayName:displayname", + "telephoneNumber:mobile", + "mail:email", + }, + UserQueryScope: "sub", + GroupObjectclass: "group", + GroupIdAttribute: "sAMAccountName", + GroupNameAttribute: "name", + GroupMemberAttribute: "member", + GroupMembersAreIds: false, + GroupQueryScope: "sub", + } + + MicrosoftActiveDirectoryMultipleDomainTemplate = api.SLDAPIdpConfigOptions{ + ImportDomain: true, + DomainObjectclass: "organizationalUnit", + DomainIdAttribute: "distinguishedName", + DomainNameAttribute: "name", + DomainQueryScope: "one", + UserObjectclass: "organizationalPerson", + UserIdAttribute: "sAMAccountName", + UserNameAttribute: "sAMAccountName", + UserEnabledAttribute: "userAccountControl", + UserEnabledMask: 2, + UserEnabledDefault: "512", + UserEnabledInvert: true, + UserAdditionalAttribute: []string{ + "displayName:displayname", + "telephoneNumber:mobile", + "mail:email", + }, + UserQueryScope: "sub", + GroupObjectclass: "group", + GroupIdAttribute: "sAMAccountName", + GroupNameAttribute: "name", + GroupMemberAttribute: "member", + GroupMembersAreIds: false, + GroupQueryScope: "sub", + } + + OpenLdapSingleDomainTemplate = api.SLDAPIdpConfigOptions{ + ImportDomain: false, + UserObjectclass: "person", + UserIdAttribute: "uid", + UserNameAttribute: "uid", + UserEnabledAttribute: "nsAccountLock", + UserEnabledDefault: "FALSE", + UserEnabledInvert: true, + UserAdditionalAttribute: []string{ + "displayName:displayname", + "mobile:mobile", + "mail:email", + }, + UserQueryScope: "sub", + GroupObjectclass: "ipausergroup", + GroupIdAttribute: "cn", + GroupNameAttribute: "cn", + GroupMemberAttribute: "member", + GroupMembersAreIds: false, + GroupQueryScope: "sub", + } +) diff --git a/pkg/keystone/driver/sql/class.go b/pkg/keystone/driver/sql/class.go new file mode 100644 index 0000000000..5ac9806b53 --- /dev/null +++ b/pkg/keystone/driver/sql/class.go @@ -0,0 +1,42 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sql + +import ( + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/keystone/driver" +) + +type SSQLDriverClass struct{} + +func (self *SSQLDriverClass) SingletonInstance() bool { + return true +} + +func (self *SSQLDriverClass) SyncMethod() string { + return api.IdentityProviderSyncLocal +} + +func (self *SSQLDriverClass) NewDriver(idpId, idpName, template string, conf api.TIdentityProviderConfigs) (driver.IIdentityBackend, error) { + return NewSQLDriver(idpId, idpName, template, conf) +} + +func (self *SSQLDriverClass) Name() string { + return api.IdentityDriverSQL +} + +func init() { + driver.RegisterDriverClass(&SSQLDriverClass{}) +} diff --git a/pkg/keystone/driver/sql/doc.go b/pkg/keystone/driver/sql/doc.go new file mode 100644 index 0000000000..e97dcf3a92 --- /dev/null +++ b/pkg/keystone/driver/sql/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sql // import "yunion.io/x/onecloud/pkg/keystone/driver/sql" diff --git a/pkg/keystone/driver/sql.go b/pkg/keystone/driver/sql/sql.go similarity index 63% rename from pkg/keystone/driver/sql.go rename to pkg/keystone/driver/sql/sql.go index a8df31e200..0dc467c622 100644 --- a/pkg/keystone/driver/sql.go +++ b/pkg/keystone/driver/sql/sql.go @@ -12,30 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -package driver +package sql import ( "context" "github.com/pkg/errors" + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/keystone/driver" "yunion.io/x/onecloud/pkg/keystone/models" "yunion.io/x/onecloud/pkg/mcclient" ) type SSQLDriver struct { - SBaseDomainDriver + driver.SBaseIdentityDriver } -func NewSQLDriver(domainId string, conf models.TDomainConfigs) (IIdentityBackend, error) { - drv := SSQLDriver{ - NewBaseDomainDriver(domainId, conf), +func NewSQLDriver(idpId, idpName, template string, conf api.TIdentityProviderConfigs) (driver.IIdentityBackend, error) { + base, err := driver.NewBaseIdentityDriver(idpId, idpName, template, conf) + if err != nil { + return nil, err } - drv.virtual = &drv + drv := SSQLDriver{base} + drv.SetVirtualObject(&drv) return &drv, nil } -func (sql *SSQLDriver) Authenticate(ctx context.Context, ident mcclient.SAuthenticationIdentity) (*models.SUserExtended, error) { +func (sql *SSQLDriver) Authenticate(ctx context.Context, ident mcclient.SAuthenticationIdentity) (*api.SUserExtended, error) { usrExt, err := models.UserManager.FetchUserExtended( ident.Password.User.Id, ident.Password.User.Name, @@ -45,9 +49,17 @@ func (sql *SSQLDriver) Authenticate(ctx context.Context, ident mcclient.SAuthent if err != nil { return nil, errors.Wrap(err, "UserManager.FetchUserExtended") } - err = usrExt.VerifyPassword(ident.Password.User.Password) + err = models.VerifyPassword(usrExt, ident.Password.User.Password) if err != nil { return nil, errors.Wrap(err, "usrExt.VerifyPassword") } return usrExt, nil } + +func (sql *SSQLDriver) Sync(ctx context.Context) error { + return nil +} + +func (sql *SSQLDriver) Probe(ctx context.Context) error { + return nil +} diff --git a/pkg/keystone/models/assignments.go b/pkg/keystone/models/assignments.go index b03bea63e2..763c60a177 100644 --- a/pkg/keystone/models/assignments.go +++ b/pkg/keystone/models/assignments.go @@ -262,6 +262,9 @@ func (manager *SAssignmentManager) projectAddUser(ctx context.Context, userCred } func (manager *SAssignmentManager) projectRemoveUser(ctx context.Context, userCred mcclient.TokenCredential, project *SProject, user *SUser, role *SRole) error { + if project.IsAdminProject() && user.IsAdminUser() && role.IsSystemRole() { + return httperrors.NewForbiddenError("sysadmin is protected") + } err := manager.remove(api.AssignmentUserProject, user.Id, project.Id, role.Id) if err == nil { db.OpsLog.LogEvent(user, db.ACT_DETACH, project.GetShortDesc(ctx), userCred) diff --git a/pkg/keystone/models/configs.go b/pkg/keystone/models/configs.go index a082459f89..f299ed517b 100644 --- a/pkg/keystone/models/configs.go +++ b/pkg/keystone/models/configs.go @@ -23,6 +23,7 @@ import ( "yunion.io/x/jsonutils" + api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -71,15 +72,15 @@ func init() { type SConfigOption struct { db.SResourceBase - DomainId string `width:"64" charset:"ascii" primary:"true"` - Group string `width:"255" charset:"utf8" primary:"true"` - Option string `width:"255" charset:"utf8" primary:"true"` + IdpId string `name:"domain_id" width:"64" charset:"ascii" primary:"true"` + Group string `width:"255" charset:"utf8" primary:"true"` + Option string `width:"255" charset:"utf8" primary:"true"` Value jsonutils.JSONObject `nullable:"false"` } -func (manager *SConfigOptionManager) fetchConfigs(domainId string, groups []string, options []string) (TConfigOptions, error) { - q := manager.Query().Equals("domain_id", domainId) +func (manager *SConfigOptionManager) fetchConfigs(idpId string, groups []string, options []string) (TConfigOptions, error) { + q := manager.Query().Equals("domain_id", idpId) if len(groups) > 0 { q = q.In("group", groups) } @@ -95,8 +96,8 @@ func (manager *SConfigOptionManager) fetchConfigs(domainId string, groups []stri return opts, nil } -func config2map(opts []SConfigOption) TDomainConfigs { - conf := make(TDomainConfigs) +func config2map(opts []SConfigOption) api.TIdentityProviderConfigs { + conf := make(api.TIdentityProviderConfigs) for i := range opts { opt := opts[i] if _, ok := conf[opt.Group]; !ok { @@ -107,12 +108,12 @@ func config2map(opts []SConfigOption) TDomainConfigs { return conf } -func (manager *SConfigOptionManager) deleteConfig(ctx context.Context, userCred mcclient.TokenCredential, domainId string) error { - return manager.syncConfig(ctx, userCred, domainId, nil) +func (manager *SConfigOptionManager) deleteConfig(ctx context.Context, userCred mcclient.TokenCredential, idpId string) error { + return manager.syncConfig(ctx, userCred, idpId, nil) } -func (manager *SConfigOptionManager) syncConfig(ctx context.Context, userCred mcclient.TokenCredential, domainId string, newOpts TConfigOptions) error { - oldOpts, err := manager.fetchConfigs(domainId, nil, nil) +func (manager *SConfigOptionManager) syncConfig(ctx context.Context, userCred mcclient.TokenCredential, idpId string, newOpts TConfigOptions) error { + oldOpts, err := manager.fetchConfigs(idpId, nil, nil) if err != nil { return errors.Wrap(err, "fetchOldConfigs") } @@ -143,15 +144,13 @@ func (manager *SConfigOptionManager) syncConfig(ctx context.Context, userCred mc return nil } -type TDomainConfigs map[string]map[string]jsonutils.JSONObject - -func (conf TDomainConfigs) getConfigOptions(domainId string, sensitiveList map[string]string) (TConfigOptions, TConfigOptions) { +func getConfigOptions(conf api.TIdentityProviderConfigs, idpId string, sensitiveList map[string]string) (TConfigOptions, TConfigOptions) { options := make(TConfigOptions, 0) sensitive := make(TConfigOptions, 0) for group, groupConf := range conf { for optKey, optVal := range groupConf { opt := SConfigOption{} - opt.DomainId = domainId + opt.IdpId = idpId opt.Group = group opt.Option = optKey opt.Value = optVal @@ -227,3 +226,14 @@ func compareConfigOptions(opts1, opts2 TConfigOptions) (deleted, updated1, updat } return } + +func (manager *SConfigOptionManager) getDriver(idStr string) (string, error) { + opts, err := manager.fetchConfigs(idStr, []string{"identity"}, []string{"driver"}) + if err != nil { + return "", errors.Wrap(err, "WhitelistedConfigManager.fetchConfigs") + } + if len(opts) == 1 { + return opts[0].Value.GetString() + } + return api.IdentityDriverSQL, nil +} diff --git a/pkg/keystone/models/domains.go b/pkg/keystone/models/domains.go index 7d5aeaeed9..3a214d361c 100644 --- a/pkg/keystone/models/domains.go +++ b/pkg/keystone/models/domains.go @@ -18,15 +18,13 @@ import ( "context" "database/sql" - "github.com/pkg/errors" - "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/tristate" "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/identity" - "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" @@ -60,7 +58,8 @@ type SDomain struct { Enabled tristate.TriState `nullable:"false" default:"true" list:"admin" update:"admin" create:"admin_optional"` IsDomain tristate.TriState `default:"false" nullable:"false" create:"admin_optional"` - // ParentId string `width:"64" charset:"ascii" index:"true" list:"admin" create:"admin_optional"` + // IdpId string `token:"parent_id" width:"64" charset:"ascii" index:"true" list:"admin"` + DomainId string `width:"64" charset:"ascii" default:"default" nullable:"false" index:"true"` } @@ -171,38 +170,6 @@ func (manager *SDomainManager) ListItemFilter(ctx context.Context, q *sqlchemy.S return q, nil } -func (self *SDomain) AllowGetDetailsConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return db.IsAdminAllowGetSpec(userCred, self, "config") -} - -func (self *SDomain) GetDetailsConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { - appParams := appsrv.AppContextGetParams(ctx) - appParams.OverrideResponseBodyWrapper = true - - conf, err := self.GetConfig(false) - if err != nil { - return nil, err - } - result := jsonutils.NewDict() - result.Add(jsonutils.Marshal(conf), "config") - return result, nil -} - -func (self *SDomain) GetConfig(all bool) (TDomainConfigs, error) { - opts, err := WhitelistedConfigManager.fetchConfigs(self.Id, nil, nil) - if err != nil { - return nil, err - } - if all { - opts2, err := SensitiveConfigManager.fetchConfigs(self.Id, nil, nil) - if err != nil { - return nil, err - } - opts = append(opts, opts2...) - } - return config2map(opts), nil -} - func (domain *SDomain) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { // domain.ParentId = api.KeystoneDomainRoot domain.DomainId = api.KeystoneDomainRoot @@ -215,6 +182,16 @@ func (domain *SDomain) GetProjectCount() (int, error) { return q.CountWithError() } +func (domain *SDomain) GetRoleCount() (int, error) { + q := RoleManager.Query().Equals("domain_id", domain.Id) + return q.CountWithError() +} + +func (domain *SDomain) GetPolicyCount() (int, error) { + q := PolicyManager.Query().Equals("domain_id", domain.Id) + return q.CountWithError() +} + func (domain *SDomain) GetUserCount() (int, error) { q := UserManager.Query().Equals("domain_id", domain.Id) return q.CountWithError() @@ -225,47 +202,60 @@ func (domain *SDomain) GetGroupCount() (int, error) { return q.CountWithError() } -func (domain *SDomain) ValidateDeleteCondition(ctx context.Context) error { +func (domain *SDomain) ValidatePurgeCondition(ctx context.Context) error { + if domain.Enabled.IsTrue() { + return httperrors.NewInvalidStatusError("domain is enabled") + } projCnt, _ := domain.GetProjectCount() if projCnt > 0 { - return httperrors.NewNotEmptyError("domain is in use") + return httperrors.NewNotEmptyError("domain is in use by project") } - usrCnt, _ := domain.GetUserCount() - if usrCnt > 0 { - return httperrors.NewNotEmptyError("domain is in use") + roleCnt, _ := domain.GetRoleCount() + if roleCnt > 0 { + return httperrors.NewNotEmptyError("domain is in use by role") } - grpCnt, _ := domain.GetGroupCount() - if grpCnt > 0 { - return httperrors.NewNotEmptyError("domain is in use") + policyCnt, _ := domain.GetPolicyCount() + if policyCnt > 0 { + return httperrors.NewNotEmptyError("domain is in use by policy") } if domain.Id == api.DEFAULT_DOMAIN_ID { return httperrors.NewForbiddenError("cannot delete default domain") } + return nil +} + +func (domain *SDomain) ValidateDeleteCondition(ctx context.Context) error { + // usrCnt, _ := domain.GetUserCount() + // if usrCnt > 0 { + // return httperrors.NewNotEmptyError("domain is in use") + // } + // grpCnt, _ := domain.GetGroupCount() + // if grpCnt > 0 { + // return httperrors.NewNotEmptyError("domain is in use") + // } + err := domain.ValidatePurgeCondition(ctx) + if err != nil { + return err + } + if domain.IsReadOnly() { + return httperrors.NewForbiddenError("readonly") + } return domain.SStandaloneResourceBase.ValidateDeleteCondition(ctx) } -func (domain *SDomain) GetDriver() string { - drv, _ := domain.getDriver() - return drv +func (domain *SDomain) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + if domain.Id == api.DEFAULT_DOMAIN_ID { + return nil, httperrors.NewForbiddenError("default domain is protected") + } + return domain.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data) } -func (domain *SDomain) getDriver() (string, error) { - opts, err := WhitelistedConfigManager.fetchConfigs(domain.Id, []string{"identity"}, []string{"driver"}) - if err != nil { - return "", errors.Wrap(err, "WhitelistedConfigManager.fetchConfigs") - } - if len(opts) == 1 { - return opts[0].Value.GetString() - } - return api.IdentityDriverSQL, nil -} - -func (domain *SDomain) isReadOnly() bool { +/*func (domain *SDomain) isReadOnly() bool { if domain.GetDriver() == api.IdentityDriverSQL { return false } return true -} +}*/ func (domain *SDomain) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { extra := domain.SStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query) @@ -281,10 +271,10 @@ func (domain *SDomain) GetExtraDetails(ctx context.Context, userCred mcclient.To } func domainExtra(domain *SDomain, extra *jsonutils.JSONDict) *jsonutils.JSONDict { - if domain.isReadOnly() { - extra.Add(jsonutils.JSONTrue, "readonly") - } - extra.Add(jsonutils.NewString(domain.GetDriver()), "driver") + // idp, _ := domain.GetIdentityProvider() + // if idp != nil { + // extra.Add(jsonutils.NewString(idp.Name), "driver") + // } usrCnt, _ := domain.GetUserCount() extra.Add(jsonutils.NewInt(int64(usrCnt)), "user_count") @@ -295,40 +285,66 @@ func domainExtra(domain *SDomain, extra *jsonutils.JSONDict) *jsonutils.JSONDict return extra } -func (domain *SDomain) AllowUpdateConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) bool { - return db.IsAdminAllowUpdateSpec(userCred, domain, "config") +func (domain *SDomain) getUsers() ([]SUser, error) { + q := UserManager.Query().Equals("domain_id", domain.Id) + usrs := make([]SUser, 0) + err := db.FetchModelObjects(UserManager, q, &usrs) + if err != nil && err != sql.ErrNoRows { + return nil, errors.Wrap(err, "FetchModelObjects") + } + return usrs, nil } -func (domain *SDomain) UpdateConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (jsonutils.JSONObject, error) { - opts := TDomainConfigs{} - err := data.Unmarshal(&opts, "config") - if err != nil { - return nil, httperrors.NewInputParameterError("invalid input data") +func (domain *SDomain) getGroups() ([]SGroup, error) { + q := GroupManager.Query().Equals("domain_id", domain.Id) + grps := make([]SGroup, 0) + err := db.FetchModelObjects(GroupManager, q, &grps) + if err != nil && err != sql.ErrNoRows { + return nil, errors.Wrap(err, "FetchModelObjects") } - whiteListedOpts, sensitiveOpts := opts.getConfigOptions(domain.Id, api.SensitiveDomainConfigMap) - err = WhitelistedConfigManager.syncConfig(ctx, userCred, domain.Id, whiteListedOpts) - if err != nil { - return nil, httperrors.NewInternalServerError("WhitelistedConfigManager.syncConfig fail %s", err) - } - err = SensitiveConfigManager.syncConfig(ctx, userCred, domain.Id, sensitiveOpts) - if err != nil { - return nil, httperrors.NewInternalServerError("SensitiveConfigManager.syncConfig fail %s", err) - } - return domain.GetDetailsConfig(ctx, userCred, query) + return grps, nil } -func (domain *SDomain) AllowDeleteConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) bool { - return db.IsAdminAllowDeleteSpec(userCred, domain, "config") +func (domain *SDomain) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + usrs, err := domain.getUsers() + if err != nil { + return errors.Wrap(err, "domain.getUsers") + } + for i := range usrs { + err = usrs[i].ValidatePurgeCondition(ctx) + if err != nil { + return errors.Wrap(err, "usr.ValidatePurgeCondition") + } + err = usrs[i].purge(ctx, userCred) + if err != nil { + return errors.Wrap(err, "usr.purge") + } + } + grps, err := domain.getGroups() + if err != nil { + return errors.Wrap(err, "domain.getGroups") + } + for i := range grps { + err = grps[i].ValidatePurgeCondition(ctx) + if err != nil { + return errors.Wrap(err, "grp.ValidatePurgeCondition") + } + err = grps[i].purge(ctx, userCred) + if err != nil { + return errors.Wrap(err, "grp.purge") + } + } + return domain.Delete(ctx, userCred) } -func (domain *SDomain) DeleteConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (jsonutils.JSONObject, error) { - err := WhitelistedConfigManager.deleteConfig(ctx, userCred, domain.Id) - if err != nil { - return nil, httperrors.NewInternalServerError("WhitelistedConfigManager.syncConfig fail %s", err) +func (domain *SDomain) getIdmapping() (*SIdmapping, error) { + return IdmappingManager.FetchEntity(domain.Id, api.IdMappingEntityDomain) +} + +func (domain *SDomain) IsReadOnly() bool { + idmap, _ := domain.getIdmapping() + if idmap != nil { + return true } - err = SensitiveConfigManager.deleteConfig(ctx, userCred, domain.Id) - if err != nil { - return nil, httperrors.NewInternalServerError("SensitiveConfigManager.syncConfig fail %s", err) - } - return domain.GetDetailsConfig(ctx, userCred, query) + return false } diff --git a/pkg/keystone/models/groups.go b/pkg/keystone/models/groups.go index 206df95160..dec4de3113 100644 --- a/pkg/keystone/models/groups.go +++ b/pkg/keystone/models/groups.go @@ -16,13 +16,13 @@ package models import ( "context" - - "yunion.io/x/jsonutils" - "yunion.io/x/sqlchemy" - "database/sql" - "github.com/pkg/errors" + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" @@ -122,18 +122,39 @@ func (group *SGroup) GetProjectCount() (int, error) { return q.CountWithError() } -func (group *SGroup) ValidateDeleteCondition(ctx context.Context) error { - usrCnt, _ := group.GetUserCount() - if usrCnt > 0 { - return httperrors.NewNotEmptyError("group contains user") - } +func (group *SGroup) ValidatePurgeCondition(ctx context.Context) error { prjCnt, _ := group.GetProjectCount() if prjCnt > 0 { return httperrors.NewNotEmptyError("group joins project") } + return nil +} + +func (group *SGroup) ValidateDeleteCondition(ctx context.Context) error { + // usrCnt, _ := group.GetUserCount() + // if usrCnt > 0 { + // return httperrors.NewNotEmptyError("group contains user") + // } + err := group.ValidatePurgeCondition(ctx) + if err != nil { + return err + } + if group.IsReadOnly() { + return httperrors.NewForbiddenError("readonly") + } return group.SIdentityBaseResource.ValidateDeleteCondition(ctx) } +func (group *SGroup) PostDelete(ctx context.Context, userCred mcclient.TokenCredential) { + group.SIdentityBaseResource.PostDelete(ctx, userCred) + + err := UsergroupManager.delete("", group.Id) + if err != nil { + log.Errorf("PasswordManager.delete fail %s", err) + return + } +} + func (group *SGroup) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { extra := group.SIdentityBaseResource.GetCustomizeColumns(ctx, userCred, query) return groupExtra(group, extra) @@ -155,11 +176,11 @@ func groupExtra(group *SGroup, extra *jsonutils.JSONDict) *jsonutils.JSONDict { return extra } -func (manager *SGroupManager) RegisterExternalGroup(ctx context.Context, domainId string, groupId string, groupName string) (*SGroup, error) { - lockman.LockClass(ctx, manager, domainId) - defer lockman.ReleaseClass(ctx, manager, domainId) +func (manager *SGroupManager) RegisterExternalGroup(ctx context.Context, idpId string, domainId string, groupId string, groupName string) (*SGroup, error) { + lockman.LockClass(ctx, manager, idpId) + defer lockman.ReleaseClass(ctx, manager, idpId) - pubId, err := IdmappingManager.registerIdMap(ctx, domainId, groupId, api.IdMappingEntityGroup) + pubId, err := IdmappingManager.RegisterIdMap(ctx, idpId, groupId, api.IdMappingEntityGroup) if err != nil { return nil, errors.Wrap(err, "IdmappingManager.registerIdMap") } @@ -175,7 +196,7 @@ func (manager *SGroupManager) RegisterExternalGroup(ctx context.Context, domainI if err == sql.ErrNoRows { group.Id = pubId group.DomainId = domainId - group.Name = groupId + group.Name = groupName group.Displayname = groupName err = manager.TableSpec().Insert(&group) @@ -199,10 +220,26 @@ func (manager *SGroupManager) fetchGroupById(gid string) *SGroup { return nil } -func (manager *SGroupManager) IsDomainReadonly(domain *SDomain) bool { - return domain.isReadOnly() -} - func (manager *SGroupManager) NamespaceScope() rbacutils.TRbacScope { return rbacutils.ScopeDomain } + +func (group *SGroup) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + err := UsergroupManager.delete("", group.Id) + if err != nil { + return errors.Wrap(err, "UsergroupManager.delete") + } + return group.Delete(ctx, userCred) +} + +func (group *SGroup) getIdmapping() (*SIdmapping, error) { + return IdmappingManager.FetchEntity(group.Id, api.IdMappingEntityGroup) +} + +func (group *SGroup) IsReadOnly() bool { + idmap, _ := group.getIdmapping() + if idmap != nil { + return true + } + return false +} diff --git a/pkg/keystone/models/id_mappings.go b/pkg/keystone/models/id_mappings.go index 0e1b46f399..107a9c9cb0 100644 --- a/pkg/keystone/models/id_mappings.go +++ b/pkg/keystone/models/id_mappings.go @@ -27,14 +27,14 @@ import ( ) type SIdmappingManager struct { - db.SModelBaseManager + db.SResourceBaseManager } var IdmappingManager *SIdmappingManager func init() { IdmappingManager = &SIdmappingManager{ - SModelBaseManager: db.NewModelBaseManager( + SResourceBaseManager: db.NewResourceBaseManager( SIdmapping{}, "id_mapping", "id_mapping", @@ -56,20 +56,24 @@ func init() { */ type SIdmapping struct { - db.SModelBase + db.SResourceBase - PublicId string `width:"64" charset:"ascii" nullable:"false" primary:"true"` - DomainId string `width:"64" charset:"ascii" nullable:"false" index:"true"` - LocalId string `width:"64" charset:"ascii" nullable:"false"` - EntityType string `width:"10" charset:"ascii" nullable:"false"` + PublicId string `width:"64" charset:"ascii" nullable:"false" primary:"true"` + IdpId string `name:"domain_id" width:"64" charset:"ascii" nullable:"false" index:"true"` + IdpEntityId string `name:"local_id" width:"128" charset:"utf8" nullable:"false"` + EntityType string `width:"10" charset:"ascii" nullable:"false"` } -func (manager *SIdmappingManager) registerIdMap(ctx context.Context, domainId string, localId string, entityType string) (string, error) { - key := fmt.Sprintf("%s-%s-%s", entityType, domainId, localId) +func (manager *SIdmappingManager) RegisterIdMap(ctx context.Context, idpId string, entityId string, entityType string) (string, error) { + return manager.RegisterIdMapWithId(ctx, idpId, entityId, entityType, "") +} + +func (manager *SIdmappingManager) RegisterIdMapWithId(ctx context.Context, idpId string, entityId string, entityType string, publicId string) (string, error) { + key := fmt.Sprintf("%s-%s-%s", entityType, idpId, entityId) lockman.LockRawObject(ctx, manager.Keyword(), key) defer lockman.ReleaseRawObject(ctx, manager.Keyword(), key) - q := manager.Query().Equals("domain_id", domainId).Equals("local_id", localId).Equals("entity_type", entityType) + q := manager.Query().Equals("domain_id", idpId).Equals("local_id", entityId).Equals("entity_type", entityType) mapping := SIdmapping{} err := q.First(&mapping) @@ -77,14 +81,17 @@ func (manager *SIdmappingManager) registerIdMap(ctx context.Context, domainId st return "", errors.Wrap(err, "Query") } if err == sql.ErrNoRows { - u1, _ := uuid.NewV4() - u2, _ := uuid.NewV4() - mapping.PublicId = u1.Format(uuid.StyleWithoutDash) + u2.Format(uuid.StyleWithoutDash) - mapping.DomainId = domainId - mapping.LocalId = localId + if len(publicId) == 0 { + u1, _ := uuid.NewV4() + u2, _ := uuid.NewV4() + publicId = u1.Format(uuid.StyleWithoutDash) + u2.Format(uuid.StyleWithoutDash) + } + mapping.PublicId = publicId + mapping.IdpId = idpId + mapping.IdpEntityId = entityId mapping.EntityType = entityType - err = manager.TableSpec().Insert(&mapping) + err = manager.TableSpec().InsertOrUpdate(&mapping) if err != nil { return "", errors.Wrap(err, "Insert") } @@ -92,3 +99,32 @@ func (manager *SIdmappingManager) registerIdMap(ctx context.Context, domainId st return mapping.PublicId, nil } + +func (manager *SIdmappingManager) FetchEntity(idStr string, entType string) (*SIdmapping, error) { + q := manager.Query().Equals("public_id", idStr).Equals("entity_type", entType) + idMap := SIdmapping{} + idMap.SetModelManager(manager, &idMap) + err := q.First(&idMap) + if err != nil { + return nil, err + } + return &idMap, nil +} + +func (manager *SIdmappingManager) deleteByIdpId(idpId string) error { + q := manager.Query().Equals("domain_id", idpId) + idmappings := make([]SIdmapping, 0) + err := db.FetchModelObjects(manager, q, &idmappings) + if err != nil && err != sql.ErrNoRows { + return errors.Wrap(err, "FetchModelObjects") + } + for i := range idmappings { + _, err = db.Update(&idmappings[i], func() error { + return idmappings[i].MarkDelete() + }) + if err != nil { + return errors.Wrap(err, "markdelete") + } + } + return nil +} diff --git a/pkg/keystone/models/identity_provider.go b/pkg/keystone/models/identity_provider.go index 889da828d8..bd04ecf002 100644 --- a/pkg/keystone/models/identity_provider.go +++ b/pkg/keystone/models/identity_provider.go @@ -15,13 +15,27 @@ package models import ( - "yunion.io/x/pkg/tristate" + "context" + "database/sql" + "time" + "yunion.io/x/pkg/errors" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/keystone/driver" + "yunion.io/x/onecloud/pkg/keystone/options" + "yunion.io/x/onecloud/pkg/mcclient" ) type SIdentityProviderManager struct { - db.SStandaloneResourceBaseManager + db.SEnabledStatusStandaloneResourceBaseManager } var ( @@ -30,7 +44,7 @@ var ( func init() { IdentityProviderManager = &SIdentityProviderManager{ - SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( + SEnabledStatusStandaloneResourceBaseManager: db.NewEnabledStatusStandaloneResourceBaseManager( SIdentityProvider{}, "identity_provider", "identity_provider", @@ -53,8 +67,571 @@ desc identity_provider; */ type SIdentityProvider struct { - db.SStandaloneResourceBase + db.SEnabledStatusStandaloneResourceBase - Enabled tristate.TriState `nullable:"false" default:"true"` - DomainId string `width:"64" charset:"ascii" nullable:"false" index:"true"` + Driver string `width:"32" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` + Template string `width:"32" charset:"ascii" nullable:"true" list:"admin" create:"admin_optional"` + + ErrorCount int `list:"admin"` + + SyncStatus string `width:"10" charset:"ascii" default:"idle" list:"admin"` + LastSync time.Time `list:"admin"` // = Column(DateTime, nullable=True) + LastSyncEndAt time.Time `list:"admin"` + + SyncIntervalSeconds int `create:"admin_optional" update:"admin"` +} + +func (manager *SIdentityProviderManager) InitializeData() error { + cnt, err := manager.Query().CountWithError() + if err != nil { + return errors.Wrap(err, "CountWithError") + } + if cnt > 0 { + return nil + } + + // copy domains + // first create a sql provider + sqldrv := SIdentityProvider{} + sqldrv.SetModelManager(manager, &sqldrv) + sqldrv.Id = api.DEFAULT_IDP_ID + sqldrv.Name = api.IdentityDriverSQL + sqldrv.Enabled = true + sqldrv.Status = api.IdentityDriverStatusConnected + sqldrv.Driver = api.IdentityDriverSQL + sqldrv.Description = "Default sql identity provider" + err = manager.TableSpec().Insert(&sqldrv) + if err != nil { + return errors.Wrap(err, "insert default sql driver") + } + + // then, insert all none-sql domain drivers + q := DomainManager.Query().NotEquals("id", api.KeystoneDomainRoot) + domains := make([]SDomain, 0) + err = db.FetchModelObjects(DomainManager, q, &domains) + if err != nil { + return errors.Wrap(err, "query domains") + } + + for i := range domains { + driver, err := WhitelistedConfigManager.getDriver(domains[i].Id) + if err != nil { + // get driver fail + return errors.Wrap(err, "WhitelistedConfigManager.getDriver") + } + if driver == api.IdentityDriverSQL { + // sql driver, skip + continue + } + + drv := SIdentityProvider{} + drv.SetModelManager(manager, &drv) + drv.Id = domains[i].Id // identical ID with domain, for backward compatibility + drv.Name = domains[i].Name + drv.Enabled = domains[i].Enabled.Bool() + drv.Status = api.IdentityDriverStatusDisconnected + drv.Driver = driver + drv.Description = domains[i].Description + err = manager.TableSpec().Insert(&drv) + if err != nil { + return errors.Wrap(err, "insert driver") + } + _, err = IdmappingManager.RegisterIdMapWithId(context.Background(), drv.Id, api.DefaultRemoteDomainId, api.IdMappingEntityDomain, domains[i].Id) + if err != nil { + return errors.Wrap(err, "RegisterIdMapWithId") + } + } + return nil +} + +func (ident *SIdentityProvider) SetSyncStatus(ctx context.Context, userCred mcclient.TokenCredential, status string) error { + if status != ident.SyncStatus { + _, err := db.UpdateWithLock(ctx, ident, func() error { + ident.SyncStatus = status + switch status { + case api.IdentitySyncStatusQueued: + ident.LastSync = time.Now().UTC() + ident.LastSyncEndAt = time.Time{} + case api.IdentitySyncStatusSyncing: + ident.LastSync = time.Now().UTC() + case api.IdentitySyncStatusIdle: + ident.LastSyncEndAt = time.Now().UTC() + } + return nil + }) + if err != nil { + return errors.Wrap(err, "updateWithLock") + } + } + return nil +} + +func (ident *SIdentityProvider) MarkConnected(ctx context.Context, userCred mcclient.TokenCredential) error { + _, err := db.UpdateWithLock(ctx, ident, func() error { + ident.ErrorCount = 0 + return nil + }) + if err != nil { + return err + } + return ident.SetStatus(userCred, api.IdentityDriverStatusConnected, "") +} + +func (ident *SIdentityProvider) MarkDisconnected(ctx context.Context, userCred mcclient.TokenCredential) error { + _, err := db.UpdateWithLock(ctx, ident, func() error { + ident.ErrorCount = ident.ErrorCount + 1 + return nil + }) + if err != nil { + return err + } + return ident.SetStatus(userCred, api.IdentityDriverStatusDisconnected, "") +} + +func (self *SIdentityProvider) AllowGetDetailsConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsAdminAllowGetSpec(userCred, self, "config") +} + +func (self *SIdentityProvider) GetDetailsConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { + conf, err := self.GetConfig(false) + if err != nil { + return nil, err + } + result := jsonutils.NewDict() + result.Add(jsonutils.Marshal(conf), "config") + return result, nil +} + +func (self *SIdentityProvider) GetConfig(all bool) (api.TIdentityProviderConfigs, error) { + opts, err := WhitelistedConfigManager.fetchConfigs(self.Id, nil, nil) + if err != nil { + return nil, err + } + if all { + opts2, err := SensitiveConfigManager.fetchConfigs(self.Id, nil, nil) + if err != nil { + return nil, err + } + opts = append(opts, opts2...) + } + return config2map(opts), nil +} + +func (ident *SIdentityProvider) AllowPerformConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) 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) { + 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.TIdentityProviderConfigs{} + err := data.Unmarshal(&opts, "config") + if err != nil { + return nil, httperrors.NewInputParameterError("invalid input data") + } + + err = ident.saveConfig(ctx, userCred, opts) + if err != nil { + return nil, httperrors.NewInternalServerError("saveConfig fail %s", err) + } + ident.MarkDisconnected(ctx, userCred) + submitIdpSyncTask(ctx, userCred, ident) + return ident.GetDetailsConfig(ctx, userCred, query) +} + +func (ident *SIdentityProvider) saveConfig(ctx context.Context, userCred mcclient.TokenCredential, opts api.TIdentityProviderConfigs) error { + whiteListedOpts, sensitiveOpts := getConfigOptions(opts, ident.Id, api.SensitiveDomainConfigMap) + err := WhitelistedConfigManager.syncConfig(ctx, userCred, ident.Id, whiteListedOpts) + if err != nil { + return errors.Wrap(err, "WhitelistedConfigManager.syncConfig") + } + err = SensitiveConfigManager.syncConfig(ctx, userCred, ident.Id, sensitiveOpts) + if err != nil { + return errors.Wrap(err, "SensitiveConfigManager.syncConfig") + } + return nil +} + +func (manager *SIdentityProviderManager) getDriveInstanceCount(drvName string) (int, error) { + 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) { + var drvName string + + template, _ := data.GetString("template") + if len(template) > 0 { + if _, ok := api.IdpTemplateDriver[template]; !ok { + return nil, httperrors.NewInputParameterError("invalid template") + } + drvName = api.IdpTemplateDriver[template] + } else { + drvName, _ = data.GetString("driver") + if len(drvName) == 0 { + return nil, httperrors.NewInputParameterError("missing driver") + } + } + + drvCls := driver.GetDriverClass(drvName) + if drvCls == nil { + return nil, httperrors.NewInputParameterError("driver %s not supported", drvName) + } + + if drvCls.SingletonInstance() { + cnt, err := manager.getDriveInstanceCount(drvName) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + if cnt >= 1 { + return nil, httperrors.NewConflictError("driver %s already exists", drvName) + } + } + + if data.Contains("sync_interval_seconds") { + secs, _ := data.Int("sync_interval_seconds") + if secs < api.MinimalSyncIntervalSeconds { + data.Set("sync_interval_seconds", jsonutils.NewInt(int64(api.MinimalSyncIntervalSeconds))) + } + } + + opts := api.TIdentityProviderConfigs{} + err := data.Unmarshal(&opts, "config") + if err != nil { + return nil, httperrors.NewInputParameterError("parse config error: %s", err) + } + return manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data) +} + +func (ident *SIdentityProvider) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + ident.Enabled = true + return ident.SEnabledStatusStandaloneResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) +} + +func (ident *SIdentityProvider) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + ident.SEnabledStatusStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data) + + opts := api.TIdentityProviderConfigs{} + err := data.Unmarshal(&opts, "config") + if err != nil { + log.Errorf("parse config error %s", err) + return + } + err = ident.saveConfig(ctx, userCred, opts) + if err != nil { + log.Errorf("saveConfig fail %s", err) + return + } + + submitIdpSyncTask(ctx, userCred, ident) + return +} + +func (manager *SIdentityProviderManager) fetchEnabledProviders() ([]SIdentityProvider, error) { + q := manager.Query().IsTrue("enabled") + providers := make([]SIdentityProvider, 0) + err := db.FetchModelObjects(manager, q, &providers) + if err != nil { + return nil, errors.Wrap(err, "FetchModelObjects") + } + return providers, nil +} + +func (self *SIdentityProvider) CanSync() bool { + if self.SyncStatus == api.IdentitySyncStatusQueued || self.SyncStatus == api.IdentitySyncStatusSyncing { + if self.LastSync.IsZero() || time.Now().Sub(self.LastSync) > 1800*time.Second { + return true + } else { + return false + } + } else { + return true + } +} + +func (self *SIdentityProvider) getSyncIntervalSeconds() int { + if self.SyncIntervalSeconds == 0 { + return options.Options.DefaultSyncIntervalSeoncds + } + return self.SyncIntervalSeconds +} + +func (self *SIdentityProvider) NeedSync() bool { + if self.Driver != api.IdentityProviderSyncFull { + return false + } + if !self.LastSync.IsZero() && time.Now().Sub(self.LastSync) < time.Duration(self.getSyncIntervalSeconds())*time.Second { + return false + } + + return true +} + +func (self *SIdentityProvider) AllowPerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "sync") +} + +func (self *SIdentityProvider) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !self.Enabled { + return nil, httperrors.NewInvalidStatusError("Account disabled") + } + if self.CanSync() { + submitIdpSyncTask(ctx, userCred, self) + } + return nil, nil +} + +func (self *SIdentityProvider) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := self.SEnabledStatusStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query) + return self.getMoreDetails(extra) +} + +func (self *SIdentityProvider) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + extra, err := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query) + if err != nil { + return nil, err + } + return self.getMoreDetails(extra), nil +} + +func (self *SIdentityProvider) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { + extra = db.FetchModelExtraCountProperties(self, extra) + extra.Set("sync_interval_seconds", jsonutils.NewInt(int64(self.getSyncIntervalSeconds()))) + return extra +} + +func (self *SIdentityProvider) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + if data.Contains("sync_interval_seconds") { + secs, _ := data.Int("sync_interval_seconds") + if secs < api.MinimalSyncIntervalSeconds { + data.Set("sync_interval_seconds", jsonutils.NewInt(int64(api.MinimalSyncIntervalSeconds))) + } + } + return self.SEnabledStatusStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data) +} + +func (self *SIdentityProvider) GetUserCount() (int, error) { + if self.Driver == api.IdentityDriverSQL { + return self.getLocalUserCount() + } else { + return self.getNonlocalUserCount() + } +} + +func (self *SIdentityProvider) getNonlocalUserCount() (int, error) { + users := UserManager.Query().SubQuery() + idmaps := IdmappingManager.Query().SubQuery() + + q := users.Query() + q = q.LeftJoin(idmaps, sqlchemy.AND( + sqlchemy.Equals(users.Field("id"), idmaps.Field("public_id")), + sqlchemy.Equals(idmaps.Field("entity_type"), api.IdMappingEntityUser), + )) + q = q.Filter(sqlchemy.Equals(idmaps.Field("domain_id"), self.Id)) + + return q.CountWithError() +} + +func (self *SIdentityProvider) getLocalUserCount() (int, error) { + subq := IdmappingManager.Query("public_id").Equals("entity_type", api.IdMappingEntityUser) + q := UserManager.Query().NotIn("id", subq.SubQuery()) + return q.CountWithError() +} + +func (self *SIdentityProvider) GetGroupCount() (int, error) { + if self.Driver == api.IdentityDriverSQL { + return self.getLocalGroupCount() + } else { + return self.getNonlocalGroupCount() + } +} + +func (self *SIdentityProvider) getNonlocalGroupCount() (int, error) { + groups := GroupManager.Query().SubQuery() + idmaps := IdmappingManager.Query().SubQuery() + + q := groups.Query() + q = q.LeftJoin(idmaps, sqlchemy.AND( + sqlchemy.Equals(groups.Field("id"), idmaps.Field("public_id")), + sqlchemy.Equals(idmaps.Field("entity_type"), api.IdMappingEntityGroup), + )) + q = q.Filter(sqlchemy.Equals(idmaps.Field("domain_id"), self.Id)) + + return q.CountWithError() +} + +func (self *SIdentityProvider) getLocalGroupCount() (int, error) { + subq := IdmappingManager.Query("public_id").Equals("entity_type", api.IdMappingEntityGroup) + q := GroupManager.Query().NotIn("id", subq.SubQuery()) + return q.CountWithError() +} + +func (self *SIdentityProvider) GetDomainCount() (int, error) { + if self.Driver == api.IdentityDriverSQL { + return self.getLocalDomainCount() + } else { + return self.getNonlocalDomainCount() + } +} + +func (self *SIdentityProvider) getDomainQuery() *sqlchemy.SQuery { + if self.Driver == api.IdentityDriverSQL { + return self.getLocalDomainQuery() + } else { + return self.getNonlocalDomainQuery() + } +} + +func (self *SIdentityProvider) getNonlocalDomainCount() (int, error) { + q := self.getNonlocalDomainQuery() + return q.CountWithError() +} + +func (self *SIdentityProvider) getNonlocalDomainQuery() *sqlchemy.SQuery { + domains := DomainManager.Query().SubQuery() + idmaps := IdmappingManager.Query().SubQuery() + + q := domains.Query() + q = q.Join(idmaps, sqlchemy.AND( + sqlchemy.Equals(domains.Field("id"), idmaps.Field("public_id")), + sqlchemy.Equals(idmaps.Field("entity_type"), api.IdMappingEntityDomain), + )) + q = q.Filter(sqlchemy.NotEquals(domains.Field("id"), api.KeystoneDomainRoot)) + q = q.Filter(sqlchemy.Equals(idmaps.Field("domain_id"), self.Id)) + + return q +} + +func (self *SIdentityProvider) getLocalDomainCount() (int, error) { + q := self.getLocalDomainQuery() + return q.CountWithError() +} + +func (self *SIdentityProvider) getLocalDomainQuery() *sqlchemy.SQuery { + subq := IdmappingManager.Query("public_id").Equals("entity_type", api.IdMappingEntityDomain) + q := DomainManager.Query().NotIn("id", subq.SubQuery()).NotEquals("id", api.KeystoneDomainRoot) + return q +} + +func (self *SIdentityProvider) GetProjectCount() (int, error) { + subq := self.getDomainQuery().SubQuery() + q := ProjectManager.Query().In("domain_id", subq.Query(subq.Field("id")).SubQuery()) + return q.CountWithError() +} + +func (self *SIdentityProvider) GetRoleCount() (int, error) { + subq := self.getDomainQuery().SubQuery() + q := RoleManager.Query().In("domain_id", subq.Query(subq.Field("id")).SubQuery()) + return q.CountWithError() +} + +func (self *SIdentityProvider) GetPolicyCount() (int, error) { + subq := self.getDomainQuery().SubQuery() + q := PolicyManager.Query().In("domain_id", subq.Query(subq.Field("id")).SubQuery()) + return q.CountWithError() +} + +func (self *SIdentityProvider) ValidateDeleteCondition(ctx context.Context) error { + if self.Driver == api.IdentityDriverSQL { + return httperrors.NewForbiddenError("cannot delete default SQL identity provider") + } + prjCnt, err := self.GetProjectCount() + if err != nil { + return httperrors.NewGeneralError(err) + } + if prjCnt > 0 { + return httperrors.NewConflictError("identity provider with projects") + } + domains, err := self.getDomains() + if err != nil { + return httperrors.NewGeneralError(err) + } + for i := range domains { + if domains[i].Enabled.IsTrue() { + return httperrors.NewInvalidStatusError("domain %s should be disabled", domains[i].Name) + } + } + return self.SEnabledStatusStandaloneResourceBase.ValidateDeleteCondition(ctx) +} + +func (self *SIdentityProvider) ValidateUpdateCondition(ctx context.Context) error { + if self.SyncStatus != api.IdentitySyncStatusIdle { + return httperrors.NewConflictError("cannot update in sync status") + } + return self.SEnabledStatusStandaloneResourceBase.ValidateUpdateCondition(ctx) +} + +func (self *SIdentityProvider) getDomains() ([]SDomain, error) { + q := self.getDomainQuery() + domains := make([]SDomain, 0) + err := db.FetchModelObjects(DomainManager, q, &domains) + if err != nil && err != sql.ErrNoRows { + return nil, errors.Wrap(err, "FetchModelObjects") + } + return domains, nil +} + +func (ident *SIdentityProvider) deleteConfig(ctx context.Context, userCred mcclient.TokenCredential) error { + err := WhitelistedConfigManager.deleteConfig(ctx, userCred, ident.Id) + if err != nil { + return errors.Wrap(err, "WhitelistedConfigManager.deleteConfig") + } + err = SensitiveConfigManager.deleteConfig(ctx, userCred, ident.Id) + if err != nil { + return errors.Wrap(err, "SensitiveConfigManager.deleteConfig") + } + return nil +} + +func (self *SIdentityProvider) Purge(ctx context.Context, userCred mcclient.TokenCredential) error { + domains, err := self.getDomains() + if err != nil { + return errors.Wrap(err, "getDomains") + } + for i := range domains { + err = domains[i].ValidatePurgeCondition(ctx) + if err != nil { + return errors.Wrap(err, "domain.ValidateDeleteCondition") + } + err = domains[i].purge(ctx, userCred) + if err != nil { + return errors.Wrap(err, "purge domain") + } + } + err = self.deleteConfig(ctx, userCred) + if err != nil { + return errors.Wrap(err, "self.deleteConfig") + } + err = IdmappingManager.deleteByIdpId(self.Id) + if err != nil { + return errors.Wrap(err, "self.deleteIdmappings") + } + return self.RealDelete(ctx, userCred) +} + +func (self *SIdentityProvider) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + log.Infof("SIdentityProvider delete do nothing") + return nil +} + +func (self *SIdentityProvider) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SEnabledStatusStandaloneResourceBase.Delete(ctx, userCred) +} + +func (self *SIdentityProvider) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.startDeleteIdentityProviderTask(ctx, userCred, "") +} + +func (self *SIdentityProvider) startDeleteIdentityProviderTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + self.SetStatus(userCred, api.IdentityDriverStatusDeleting, "") + + task, err := taskman.TaskManager.NewTask(ctx, "IdentityProviderDeleteTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil } diff --git a/pkg/keystone/models/identitybase.go b/pkg/keystone/models/identitybase.go index 8f790d6352..98c7857236 100644 --- a/pkg/keystone/models/identitybase.go +++ b/pkg/keystone/models/identitybase.go @@ -147,9 +147,12 @@ func (manager *SIdentityBaseResourceManager) FetchOwnerId(ctx context.Context, d func (manager *SIdentityBaseResourceManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { domain, _ := DomainManager.FetchDomainById(ownerId.GetProjectDomainId()) - if manager.GetIIdentityModelManager().IsDomainReadonly(domain) { - return nil, httperrors.NewForbiddenError("domain is readonly") + if domain.Enabled.IsFalse() { + return nil, httperrors.NewInvalidStatusError("domain is disabled") } + // if manager.GetIIdentityModelManager().IsDomainReadonly(domain) { + // return nil, httperrors.NewForbiddenError("domain is readonly") + // } return manager.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data) } @@ -166,7 +169,7 @@ func (manager *SIdentityBaseResourceManager) FetchCustomizeColumns(ctx context.C domainIds = stringutils2.Append(domainIds, idStr) } } - if len(fields) == 0 || fields.Contains("domain") || fields.Contains("domain_readonly") { + if len(fields) == 0 || fields.Contains("domain") { domains := fetchDomain(domainIds) if domains != nil { for i := range rows { @@ -176,13 +179,6 @@ func (manager *SIdentityBaseResourceManager) FetchCustomizeColumns(ctx context.C if len(fields) == 0 || fields.Contains("domain") { rows[i].Add(jsonutils.NewString(domain.Name), "domain") } - if len(fields) == 0 || fields.Contains("domain_readonly") { - if domain.isReadOnly() { - rows[i].Add(jsonutils.JSONTrue, "domain_readonly") - } else { - rows[i].Add(jsonutils.JSONFalse, "domain_readonly") - } - } } } } diff --git a/pkg/keystone/models/initdb.go b/pkg/keystone/models/initdb.go index 2d812c4909..f3cb4c4e7f 100644 --- a/pkg/keystone/models/initdb.go +++ b/pkg/keystone/models/initdb.go @@ -31,6 +31,7 @@ func InitDB() error { EndpointManager, PolicyManager, + IdentityProviderManager, DomainManager, ProjectManager, RoleManager, diff --git a/pkg/keystone/models/nonlocal_users.go b/pkg/keystone/models/nonlocal_users.go index 41fa626d93..8ffced067a 100644 --- a/pkg/keystone/models/nonlocal_users.go +++ b/pkg/keystone/models/nonlocal_users.go @@ -15,15 +15,7 @@ package models import ( - "context" - "database/sql" - "fmt" - - "github.com/pkg/errors" - - api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" ) type SNonlocalUserManager struct { @@ -62,6 +54,7 @@ type SNonlocalUser struct { UserId string `width:"64" charset:"ascii" nullable:"false" index:"true"` } +/* func (manager *SNonlocalUserManager) Register(ctx context.Context, domainId string, name string) (*SNonlocalUser, error) { key := fmt.Sprintf("%s-%s", domainId, name) lockman.LockRawObject(ctx, manager.Keyword(), key) @@ -97,3 +90,4 @@ func (manager *SNonlocalUserManager) Register(ctx context.Context, domainId stri return nonlocalUser, nil } +*/ diff --git a/pkg/keystone/models/sync.go b/pkg/keystone/models/sync.go new file mode 100644 index 0000000000..69c1fcd4e8 --- /dev/null +++ b/pkg/keystone/models/sync.go @@ -0,0 +1,66 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/log" + + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/keystone/driver" + "yunion.io/x/onecloud/pkg/mcclient" +) + +func AutoSyncIdentityProviderTask(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + idps, err := IdentityProviderManager.fetchEnabledProviders() + if err != nil { + log.Errorf("FetchEnabledProviders fail %s", err) + return + } + if isStart { + for i := range idps { + idps[i].SetSyncStatus(ctx, userCred, api.IdentitySyncStatusIdle) + } + } + for i := range idps { + err = syncIdentityProvider(ctx, userCred, &idps[i]) + if err != nil { + log.Errorf("Fail to sync identityprovider %s: %s", idps[i].Name, err) + } + } +} + +func syncIdentityProvider(ctx context.Context, userCred mcclient.TokenCredential, idp *SIdentityProvider) error { + if idp.SyncStatus != api.IdentitySyncStatusIdle { + return nil + } + + if !idp.CanSync() { + return nil + } + + if !idp.NeedSync() { + return nil + } + + drvCls := driver.GetDriverClass(idp.Driver) + if drvCls.SyncMethod() == api.IdentityProviderSyncLocal { + // skip, no need to sync + return nil + } + submitIdpSyncTask(ctx, userCred, idp) + return nil +} diff --git a/pkg/keystone/models/syncworker.go b/pkg/keystone/models/syncworker.go new file mode 100644 index 0000000000..465ab77188 --- /dev/null +++ b/pkg/keystone/models/syncworker.go @@ -0,0 +1,75 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/log" + + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/keystone/driver" + "yunion.io/x/onecloud/pkg/mcclient" +) + +var ( + syncWorker *appsrv.SWorkerManager +) + +func InitSyncWorkers() { + syncWorker = appsrv.NewWorkerManager( + "identityProviderSyncWorkerManager", + 1, + 2048, + true, + ) +} + +func submitIdpSyncTask(ctx context.Context, userCred mcclient.TokenCredential, idp *SIdentityProvider) { + idp.SetSyncStatus(ctx, userCred, api.IdentitySyncStatusQueued) + syncWorker.Run(func() { + idp.SetSyncStatus(ctx, userCred, api.IdentitySyncStatusSyncing) + defer idp.SetSyncStatus(ctx, userCred, api.IdentitySyncStatusIdle) + + conf, err := idp.GetConfig(true) + if err != nil { + log.Errorf("GetConfig for idp %s fail %s", idp.Name, err) + idp.MarkDisconnected(ctx, userCred) + return + } + driver, err := driver.GetDriver(idp.Driver, idp.Id, idp.Name, idp.Template, conf) + if err != nil { + log.Errorf("GetDriver for idp %s fail %s", idp.Name, err) + idp.MarkDisconnected(ctx, userCred) + return + } + err = driver.Probe(ctx) + if err != nil { + log.Errorf("Probe for idp %s fail %s", idp.Name, err) + idp.MarkDisconnected(ctx, userCred) + return + } + + idp.MarkConnected(ctx, userCred) + + err = driver.Sync(ctx) + if err != nil { + log.Errorf("Sync for idp %s fail %s", idp.Name, err) + return + } + + }, nil, nil) +} diff --git a/pkg/keystone/models/user_group_memberships.go b/pkg/keystone/models/user_group_memberships.go index fa72511363..727706b9b5 100644 --- a/pkg/keystone/models/user_group_memberships.go +++ b/pkg/keystone/models/user_group_memberships.go @@ -88,6 +88,21 @@ func (manager *SUsergroupManager) getUserGroupIds(userId string) []string { return groupIds } +func (manager *SUsergroupManager) getGroupUserIds(groupId string) []string { + members := make([]SUsergroupMembership, 0) + q := manager.Query().Equals("group_id", groupId) + err := db.FetchModelObjects(manager, q, &members) + if err != nil { + log.Errorf("getGroupUserIds fail %s", err) + return nil + } + userIds := make([]string, len(members)) + for i := range members { + userIds[i] = members[i].UserId + } + return userIds +} + func (manager *SUsergroupManager) SyncUserGroups(ctx context.Context, userCred mcclient.TokenCredential, userId string, groupIds []string) { oldGroupIds := manager.getUserGroupIds(userId) sort.Strings(oldGroupIds) @@ -106,6 +121,30 @@ func (manager *SUsergroupManager) SyncUserGroups(ctx context.Context, userCred m } } +func (manager *SUsergroupManager) SyncGroupUsers(ctx context.Context, userCred mcclient.TokenCredential, groupId string, userIds []string) { + oldUserIds := manager.getGroupUserIds(groupId) + sort.Strings(oldUserIds) + sort.Strings(userIds) + + deleted, _, added := stringutils2.Split(stringutils2.SSortedStrings(oldUserIds), stringutils2.SSortedStrings(userIds)) + + grp := GroupManager.fetchGroupById(groupId) + if grp != nil { + for _, uid := range deleted { + usr, _ := UserManager.fetchUserById(uid) + if usr != nil { + manager.remove(ctx, userCred, usr, grp) + } + } + for _, uid := range added { + usr, _ := UserManager.fetchUserById(uid) + if usr != nil { + manager.add(ctx, userCred, usr, grp) + } + } + } +} + func (manager *SUsergroupManager) remove(ctx context.Context, userCred mcclient.TokenCredential, usr *SUser, grp *SGroup) error { q := manager.Query().Equals("user_id", usr.Id).Equals("group_id", grp.Id) membership := SUsergroupMembership{} @@ -156,3 +195,27 @@ func (manager *SUsergroupManager) add(ctx context.Context, userCred mcclient.Tok db.OpsLog.LogEvent(user, db.ACT_ATTACH, group.GetShortDesc(ctx), userCred) return nil } + +func (manager *SUsergroupManager) delete(userId string, groupId string) error { + q := manager.Query() + if len(userId) > 0 { + q = q.Equals("user_id", userId) + } + if len(groupId) > 0 { + q = q.Equals("group_id", groupId) + } + memberships := make([]SUsergroupMembership, 0) + err := db.FetchModelObjects(manager, q, &memberships) + if err != nil { + return errors.Wrap(err, "Query") + } + for i := range memberships { + _, err = db.Update(&memberships[i], func() error { + return memberships[i].MarkDelete() + }) + if err != nil { + return errors.Wrap(err, "MarkDelete") + } + } + return nil +} diff --git a/pkg/keystone/models/users.go b/pkg/keystone/models/users.go index 7c5f9886f3..774ec45734 100644 --- a/pkg/keystone/models/users.go +++ b/pkg/keystone/models/users.go @@ -87,7 +87,7 @@ type SUser struct { DefaultProjectId string `width:"64" charset:"ascii" nullable:"true"` AllowWebConsole tristate.TriState `nullable:"false" default:"true" list:"admin" update:"admin" create:"admin_optional"` - EnableMfa tristate.TriState `nullable:"false" default:"false" list:"admin" update:"admin" create:"admin_optional"` + EnableMfa tristate.TriState `nullable:"false" default:"true" list:"admin" update:"admin" create:"admin_optional"` } func (manager *SUserManager) GetContextManagers() [][]db.IModelManager { @@ -194,34 +194,19 @@ func (manager *SUserManager) initSysUser() error { return nil } -type SUserExtended struct { - Id string - Name string - Enabled bool - DefaultProjectId string - CreatedAt time.Time - LastActiveAt time.Time - DomainId string - - LocalId int - LocalName string - NonlocalName string - DomainName string - IsLocal bool -} - /* Fetch extended userinfo by Id or name + domainId or name + domainName */ -func (manager *SUserManager) FetchUserExtended(userId, userName, domainId, domainName string) (*SUserExtended, error) { +func (manager *SUserManager) FetchUserExtended(userId, userName, domainId, domainName string) (*api.SUserExtended, error) { if len(userId) == 0 && len(userName) == 0 { return nil, sqlchemy.ErrEmptyQuery } localUsers := LocalUserManager.Query().SubQuery() - nonlocalUsers := NonlocalUserManager.Query().SubQuery() + // nonlocalUsers := NonlocalUserManager.Query().SubQuery() users := UserManager.Query().SubQuery() domains := DomainManager.Query().SubQuery() + idmappings := IdmappingManager.Query().SubQuery() q := users.Query( users.Field("id"), @@ -231,21 +216,20 @@ func (manager *SUserManager) FetchUserExtended(userId, userName, domainId, domai users.Field("last_active_at"), users.Field("domain_id"), localUsers.Field("id", "local_id"), - localUsers.Field("name", "local_name"), - nonlocalUsers.Field("name", "nonlocal_name"), domains.Field("name", "domain_name"), + domains.Field("enabled", "domain_enabled"), + idmappings.Field("domain_id", "idp_id"), + idmappings.Field("local_id", "idp_name"), ) + q = q.Join(domains, sqlchemy.Equals(users.Field("domain_id"), domains.Field("id"))) q = q.LeftJoin(localUsers, sqlchemy.Equals(localUsers.Field("user_id"), users.Field("id"))) - q = q.LeftJoin(nonlocalUsers, sqlchemy.Equals(nonlocalUsers.Field("user_id"), users.Field("id"))) + q = q.LeftJoin(idmappings, sqlchemy.Equals(users.Field("id"), idmappings.Field("public_id"))) if len(userId) > 0 { q = q.Filter(sqlchemy.Equals(users.Field("id"), userId)) } else if len(userName) > 0 { - q = q.Filter(sqlchemy.OR( - sqlchemy.Equals(localUsers.Field("name"), userName), - sqlchemy.Equals(nonlocalUsers.Field("name"), userName), - )) + q = q.Filter(sqlchemy.Equals(users.Field("name"), userName)) if len(domainId) == 0 && len(domainName) == 0 { domainId = api.DEFAULT_DOMAIN_ID } @@ -256,31 +240,29 @@ func (manager *SUserManager) FetchUserExtended(userId, userName, domainId, domai } } - extUser := SUserExtended{} + extUser := api.SUserExtended{} err := q.First(&extUser) if err != nil { return nil, err } - if len(extUser.NonlocalName) > 0 { + if len(extUser.IdpName) > 0 { extUser.IsLocal = false - extUser.Name = extUser.NonlocalName } else { extUser.IsLocal = true - extUser.Name = extUser.LocalName } return &extUser, nil } -func (user *SUserExtended) VerifyPassword(passwd string) error { +func VerifyPassword(user *api.SUserExtended, passwd string) error { if user.IsLocal { - return user.localUserVerifyPassword(passwd) + return localUserVerifyPassword(user, passwd) } else { return fmt.Errorf("not implemented") } } -func (user *SUserExtended) localUserVerifyPassword(passwd string) error { +func localUserVerifyPassword(user *api.SUserExtended, passwd string) error { passes, err := PasswordManager.fetchByLocaluserId(user.LocalId) if err != nil { return err @@ -339,11 +321,12 @@ func (manager *SUserManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQu } func (user *SUser) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - if data.Contains("name") { - if user.IsAdminUser() { - return nil, httperrors.NewForbiddenError("cannot alter name of system user") - } + if user.IsAdminUser() { + return nil, httperrors.NewForbiddenError("system admin user is protected") } + // if user.IsReadOnly() { + // return nil, httperrors.NewForbiddenError("readonly") + // } return user.SEnabledIdentityBaseResource.ValidateUpdateData(ctx, userCred, query, data) } @@ -440,11 +423,7 @@ func (user *SUser) PostUpdate(ctx context.Context, userCred mcclient.TokenCreden } } -func (user *SUser) ValidateDeleteCondition(ctx context.Context) error { - grpCnt, _ := user.GetGroupCount() - if grpCnt > 0 { - return httperrors.NewNotEmptyError("group contains user") - } +func (user *SUser) ValidatePurgeCondition(ctx context.Context) error { prjCnt, _ := user.GetProjectCount() if prjCnt > 0 { return httperrors.NewNotEmptyError("user joins project") @@ -452,6 +431,21 @@ func (user *SUser) ValidateDeleteCondition(ctx context.Context) error { if user.IsAdminUser() { return httperrors.NewForbiddenError("cannot delete system user") } + return nil +} + +func (user *SUser) ValidateDeleteCondition(ctx context.Context) error { + // grpCnt, _ := user.GetGroupCount() + // if grpCnt > 0 { + // return httperrors.NewNotEmptyError("group contains user") + // } + err := user.ValidatePurgeCondition(ctx) + if err != nil { + return err + } + if user.IsReadOnly() { + return httperrors.NewForbiddenError("readonly") + } return user.SIdentityBaseResource.ValidateDeleteCondition(ctx) } @@ -469,12 +463,15 @@ func (user *SUser) PostDelete(ctx context.Context, userCred mcclient.TokenCreden log.Errorf("PasswordManager.delete fail %s", err) return } + + err = UsergroupManager.delete(user.Id, "") + if err != nil { + log.Errorf("UsergroupManager.delete fail %s", err) + return + } } func (user *SUser) UpdateInContext(ctx context.Context, userCred mcclient.TokenCredential, ctxObjs []db.IModel, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if user.GetDomain().isReadOnly() { - return nil, httperrors.NewForbiddenError("readonly domain") - } if len(ctxObjs) != 1 { return nil, httperrors.NewInputParameterError("not supported update context") } @@ -489,9 +486,6 @@ func (user *SUser) UpdateInContext(ctx context.Context, userCred mcclient.TokenC } func (user *SUser) DeleteInContext(ctx context.Context, userCred mcclient.TokenCredential, ctxObjs []db.IModel, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if user.GetDomain().isReadOnly() { - return nil, httperrors.NewForbiddenError("readonly domain") - } if len(ctxObjs) != 1 { return nil, httperrors.NewInputParameterError("not supported update context") } @@ -566,10 +560,39 @@ func tokenV3LoginSession(token *mcclient.TokenCredentialV3) sLoginSession { return s } -func (manager *SUserManager) IsDomainReadonly(domain *SDomain) bool { - return domain.isReadOnly() -} - func (manager *SUserManager) NamespaceScope() rbacutils.TRbacScope { return rbacutils.ScopeDomain } + +func (user *SUser) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + localUser, err := LocalUserManager.delete(user.Id, user.DomainId) + if err != nil { + return errors.Wrap(err, "LocalUserManager.delete") + } + + if localUser != nil { + err = PasswordManager.delete(localUser.Id) + if err != nil { + return errors.Wrap(err, "PasswordManager.delete") + } + } + + err = UsergroupManager.delete(user.Id, "") + if err != nil { + return errors.Wrap(err, "UsergroupManager.delete") + } + + return user.Delete(ctx, userCred) +} + +func (user *SUser) getIdmapping() (*SIdmapping, error) { + return IdmappingManager.FetchEntity(user.Id, api.IdMappingEntityUser) +} + +func (user *SUser) IsReadOnly() bool { + idmap, _ := user.getIdmapping() + if idmap != nil { + return true + } + return false +} diff --git a/pkg/keystone/options/options.go b/pkg/keystone/options/options.go index 0813e7c5a6..fd437938e5 100644 --- a/pkg/keystone/options/options.go +++ b/pkg/keystone/options/options.go @@ -30,6 +30,10 @@ type SKeystoneOptions struct { CredentialKeyRepository string `help:"fernet key repo directory for credentials" token:"credential_key_repository"` BootstrapAdminUserPassword string `help:"bootstreap sysadmin user password" default:"sysadmin"` + + AutoSyncIntervalSeconds int `help:"frequency to check auto sync tasks" default:"30"` + + DefaultSyncIntervalSeoncds int `help:"frequency to do auto sync tasks" default:"900"` } var ( diff --git a/pkg/keystone/service/handlers.go b/pkg/keystone/service/handlers.go index 79d7436106..be7c33d65a 100644 --- a/pkg/keystone/service/handlers.go +++ b/pkg/keystone/service/handlers.go @@ -18,6 +18,7 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/appsrv/dispatcher" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/keystone/models" "yunion.io/x/onecloud/pkg/keystone/tokens" "yunion.io/x/onecloud/pkg/keystone/usages" @@ -32,14 +33,14 @@ func initHandlers(app *appsrv.Application) { // quotas.AddQuotaHandler(models.QuotaManager, API_VERSION, app) usages.AddUsageHandler(API_VERSION, app) - // taskman.AddTaskHandler(API_VERSION, app) + taskman.AddTaskHandler(API_VERSION, app) tokens.AddHandler(app) for _, manager := range []db.IModelManager{ - // taskman.TaskManager, - // taskman.SubTaskManager, - // taskman.TaskObjectManager, + taskman.TaskManager, + taskman.SubTaskManager, + taskman.TaskObjectManager, db.Metadata, models.SensitiveConfigManager, models.WhitelistedConfigManager, @@ -72,6 +73,7 @@ func initHandlers(app *appsrv.Application) { models.AssignmentManager, models.PolicyManager, models.CredentialManager, + models.IdentityProviderManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/keystone/service/service.go b/pkg/keystone/service/service.go index 7d7478ae11..bba2c95a11 100644 --- a/pkg/keystone/service/service.go +++ b/pkg/keystone/service/service.go @@ -16,6 +16,7 @@ package service import ( "os" + "time" _ "github.com/go-sql-driver/mysql" "github.com/golang-plus/uuid" @@ -25,6 +26,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon" app_common "yunion.io/x/onecloud/pkg/cloudcommon/app" + "yunion.io/x/onecloud/pkg/cloudcommon/cronman" "yunion.io/x/onecloud/pkg/cloudcommon/db" common_options "yunion.io/x/onecloud/pkg/cloudcommon/options" "yunion.io/x/onecloud/pkg/cloudcommon/policy" @@ -34,6 +36,10 @@ import ( "yunion.io/x/onecloud/pkg/keystone/tokens" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/util/logclient" + + _ "yunion.io/x/onecloud/pkg/keystone/driver/ldap" + _ "yunion.io/x/onecloud/pkg/keystone/driver/sql" + _ "yunion.io/x/onecloud/pkg/keystone/tasks" ) func keystoneUUIDGenerator() string { @@ -46,6 +52,9 @@ func StartService() { db.DefaultUUIDGenerator = keystoneUUIDGenerator policy.DefaultPolicyFetcher = localPolicyFetcher logclient.DefaultSessionGenerator = models.GetDefaultClientSession + cronman.DefaultAdminSessionGenerator = models.GetDefaultAdminCred + + models.InitSyncWorkers() opts := &options.Options common_options.ParseOptions(opts, os.Args, "keystone.conf", api.SERVICE_TYPE) @@ -72,10 +81,14 @@ func StartService() { app_common.InitBaseAuth(&opts.BaseOptions) - // cron := cronman.GetCronJobManager(true) - // cron.AddJob1("CleanPendingDeleteImages", time.Duration(options.Options.PendingDeleteCheckSeconds)*time.Second, models.ImageManager.CleanPendingDeleteImages) + if !opts.IsSlaveNode { + cron := cronman.GetCronJobManager(true) - // cron.Start() + cron.AddJob1WithStartRun("AutoSyncIdentityProviderTask", time.Duration(opts.AutoSyncIntervalSeconds)*time.Second, models.AutoSyncIdentityProviderTask, true) + + cron.Start() + defer cron.Stop() + } cloudcommon.AppDBInit(app) diff --git a/pkg/keystone/tasks/doc.go b/pkg/keystone/tasks/doc.go new file mode 100644 index 0000000000..8d67831c65 --- /dev/null +++ b/pkg/keystone/tasks/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks // import "yunion.io/x/onecloud/pkg/keystone/tasks" diff --git a/pkg/keystone/tasks/identity_provider_delete_task.go b/pkg/keystone/tasks/identity_provider_delete_task.go new file mode 100644 index 0000000000..c928ae3dcb --- /dev/null +++ b/pkg/keystone/tasks/identity_provider_delete_task.go @@ -0,0 +1,46 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/keystone/models" +) + +type IdentityProviderDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(IdentityProviderDeleteTask{}) +} + +func (self *IdentityProviderDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + idp := obj.(*models.SIdentityProvider) + + err := idp.Purge(ctx, self.UserCred) + if err != nil { + self.SetStageFailed(ctx, fmt.Sprintf("purge failed %s", err)) + return + } + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/keystone/tokens/auth.go b/pkg/keystone/tokens/auth.go index 45774c8d6f..08468df928 100644 --- a/pkg/keystone/tokens/auth.go +++ b/pkg/keystone/tokens/auth.go @@ -16,11 +16,12 @@ package tokens import ( "context" + "database/sql" "time" - "github.com/pkg/errors" - + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/keystone/driver" @@ -29,15 +30,15 @@ import ( "yunion.io/x/onecloud/pkg/mcclient" ) -func authUserByTokenV2(ctx context.Context, input mcclient.SAuthenticationInputV2) (*models.SUserExtended, error) { +func authUserByTokenV2(ctx context.Context, input mcclient.SAuthenticationInputV2) (*api.SUserExtended, error) { return authUserByToken(ctx, input.Auth.Token.Id) } -func authUserByTokenV3(ctx context.Context, input mcclient.SAuthenticationInputV3) (*models.SUserExtended, error) { +func authUserByTokenV3(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, error) { return authUserByToken(ctx, input.Auth.Identity.Token.Id) } -func authUserByToken(ctx context.Context, tokenStr string) (*models.SUserExtended, error) { +func authUserByToken(ctx context.Context, tokenStr string) (*api.SUserExtended, error) { token := SAuthToken{} err := token.ParseFernetToken(tokenStr) if err != nil { @@ -46,7 +47,7 @@ func authUserByToken(ctx context.Context, tokenStr string) (*models.SUserExtende return models.UserManager.FetchUserExtended(token.UserId, "", "", "") } -func authUserByPasswordV2(ctx context.Context, input mcclient.SAuthenticationInputV2) (*models.SUserExtended, error) { +func authUserByPasswordV2(ctx context.Context, input mcclient.SAuthenticationInputV2) (*api.SUserExtended, error) { ident := mcclient.SAuthenticationIdentity{} ident.Methods = []string{api.AUTH_METHOD_PASSWORD} ident.Password.User.Name = input.Auth.PasswordCredentials.Username @@ -55,32 +56,100 @@ func authUserByPasswordV2(ctx context.Context, input mcclient.SAuthenticationInp return authUserByIdentity(ctx, ident) } -func authUserByIdentityV3(ctx context.Context, input mcclient.SAuthenticationInputV3) (*models.SUserExtended, error) { +func authUserByIdentityV3(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, error) { return authUserByIdentity(ctx, input.Auth.Identity) } -func authUserByIdentity(ctx context.Context, ident mcclient.SAuthenticationIdentity) (*models.SUserExtended, error) { - domain, err := models.DomainManager.FetchDomain(ident.Password.User.Domain.Id, ident.Password.User.Domain.Name) - if err != nil { - return nil, errors.Wrap(err, "DomainManager.FetchDomain") +func authUserByIdentity(ctx context.Context, ident mcclient.SAuthenticationIdentity) (*api.SUserExtended, error) { + var idpId string + + if len(ident.Password.User.Name) == 0 && len(ident.Password.User.Id) == 0 && len(ident.Password.User.Domain.Id) == 0 && len(ident.Password.User.Domain.Name) == 0 { + return nil, ErrEmptyAuth } - conf, err := domain.GetConfig(true) - if err != nil { - return nil, errors.Wrap(err, "domain.GetConfig") + if len(ident.Password.User.Name) > 0 && len(ident.Password.User.Id) == 0 && len(ident.Password.User.Domain.Id) == 0 && len(ident.Password.User.Domain.Name) == 0 { + q := models.UserManager.Query().Equals("name", ident.Password.User.Name) + usrCnt, err := q.CountWithError() + if err != nil { + return nil, errors.Wrap(err, "Query user by name") + } + if usrCnt > 1 { + return nil, sqlchemy.ErrDuplicateEntry + } else if usrCnt == 0 { + /*idp, err := models.IdentityProviderManager.GetAutoCreateUserProvider() + if err != nil { + return nil, errors.Wrap(err, "IdentityProviderManager.GetAutoCreateUserProvider") + } + idpId = idp.Id + */ + return nil, sqlchemy.ErrEmptyQuery + } else { + // userCnt == 1 + usr := models.SUser{} + usr.SetModelManager(models.UserManager, &usr) + err := q.First(&usr) + if err != nil { + return nil, errors.Wrap(err, "Query user") + } + idmap, err := models.IdmappingManager.FetchEntity(usr.Id, api.IdMappingEntityUser) + if err != nil { + return nil, errors.Wrap(err, "IdmappingManager.FetchEntity") + } + idpId = idmap.IdpId + } + } else { + usrExt, err := models.UserManager.FetchUserExtended(ident.Password.User.Id, ident.Password.User.Name, + ident.Password.User.Domain.Id, ident.Password.User.Domain.Name) + if err != nil && err != sql.ErrNoRows { + return nil, errors.Wrap(err, "UserManager.FetchUserExtended") + } + + if err == sql.ErrNoRows { + // no such user locally, query domain idp + domain, err := models.DomainManager.FetchDomain(ident.Password.User.Domain.Id, ident.Password.User.Domain.Name) + if err != nil { + return nil, errors.Wrap(err, "DomainManager.FetchDomain") + } + mapping, err := models.IdmappingManager.FetchEntity(domain.Id, api.IdMappingEntityDomain) + if err != nil { + return nil, errors.Wrap(err, "IdmappingManager.FetchEntity") + } + idpId = mapping.IdpId + } else { + // user exists, query user's idp + idpId = usrExt.IdpId + } } - domainDriver, err := driver.GetDriver(domain.Id, conf) - if err != nil { - return nil, errors.Wrapf(err, "driver.GetDriver") + + if len(idpId) == 0 { + idpId = api.DEFAULT_IDP_ID } - usrExt, err := domainDriver.Authenticate(ctx, ident) + idpObj, err := models.IdentityProviderManager.FetchById(idpId) if err != nil { - return nil, errors.Wrap(err, "domainDriver.Authenticate") + return nil, errors.Wrap(err, "IdentityProviderManager.FetchById") } - return usrExt, nil + + idp := idpObj.(*models.SIdentityProvider) + + conf, err := idp.GetConfig(true) + if err != nil { + return nil, errors.Wrap(err, "GetConfig") + } + + backend, err := driver.GetDriver(idp.Driver, idp.Id, idp.Name, idp.Template, conf) + if err != nil { + return nil, errors.Wrap(err, "driver.GetDriver") + } + + usr, err := backend.Authenticate(ctx, ident) + if err != nil { + return nil, errors.Wrap(err, "Authenticate") + } + + return usr, nil } func AuthenticateV3(ctx context.Context, input mcclient.SAuthenticationInputV3) (*mcclient.TokenCredentialV3, error) { - var user *models.SUserExtended + var user *api.SUserExtended var err error if len(input.Auth.Identity.Methods) != 1 { return nil, ErrInvalidAuthMethod @@ -107,6 +176,11 @@ func AuthenticateV3(ctx context.Context, input mcclient.SAuthenticationInputV3) if !user.Enabled { return nil, ErrUserDisabled } + + if !user.DomainEnabled { + return nil, ErrDomainDisabled + } + token := SAuthToken{} token.UserId = user.Id token.Method = method @@ -152,7 +226,7 @@ func AuthenticateV3(ctx context.Context, input mcclient.SAuthenticationInputV3) } func AuthenticateV2(ctx context.Context, input mcclient.SAuthenticationInputV2) (*mcclient.TokenCredentialV2, error) { - var user *models.SUserExtended + var user *api.SUserExtended var err error var method string if len(input.Auth.Token.Id) > 0 { @@ -178,6 +252,11 @@ func AuthenticateV2(ctx context.Context, input mcclient.SAuthenticationInputV2) if !user.Enabled { return nil, ErrUserDisabled } + + if !user.DomainEnabled { + return nil, ErrDomainDisabled + } + token := SAuthToken{} token.UserId = user.Id token.Method = method diff --git a/pkg/keystone/tokens/errors.go b/pkg/keystone/tokens/errors.go index 7e08387922..7f97b53f7a 100644 --- a/pkg/keystone/tokens/errors.go +++ b/pkg/keystone/tokens/errors.go @@ -14,15 +14,16 @@ package tokens -import "github.com/pkg/errors" +import "yunion.io/x/pkg/errors" -var ( - ErrVerMismatch = errors.New("version mismatch") - ErrProjectDisabled = errors.New("project disabled") - ErrUserDisabled = errors.New("user disabled") - ErrExpiredToken = errors.New("expired token") - ErrInvalidFernetToken = errors.New("invalid fernet token") - ErrInvalidAuthMethod = errors.New("invalid auth methods") - ErrUserNotFound = errors.New("user not found") - ErrDomainDisabled = errors.New("domain is disabled") +const ( + ErrVerMismatch = errors.Error("version mismatch") + ErrProjectDisabled = errors.Error("project disabled") + ErrUserDisabled = errors.Error("user disabled") + ErrExpiredToken = errors.Error("expired token") + ErrInvalidFernetToken = errors.Error("invalid fernet token") + ErrInvalidAuthMethod = errors.Error("invalid auth methods") + ErrUserNotFound = errors.Error("user not found") + ErrDomainDisabled = errors.Error("domain is disabled") + ErrEmptyAuth = errors.Error("empty auth request") ) diff --git a/pkg/keystone/tokens/token.go b/pkg/keystone/tokens/token.go index ae3c92a619..563c087829 100644 --- a/pkg/keystone/tokens/token.go +++ b/pkg/keystone/tokens/token.go @@ -21,6 +21,7 @@ import ( "github.com/pkg/errors" + api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/keystone/keys" "yunion.io/x/onecloud/pkg/keystone/models" "yunion.io/x/onecloud/pkg/keystone/options" @@ -218,7 +219,7 @@ func (t *SAuthToken) getRoles() ([]models.SRole, error) { func (t *SAuthToken) getTokenV3( ctx context.Context, - user *models.SUserExtended, + user *api.SUserExtended, project *models.SProjectExtended, domain *models.SDomain, ) (*mcclient.TokenCredentialV3, error) { @@ -277,7 +278,7 @@ func (t *SAuthToken) getTokenV3( func (t *SAuthToken) getTokenV2( ctx context.Context, - user *models.SUserExtended, + user *api.SUserExtended, project *models.SProjectExtended, ) (*mcclient.TokenCredentialV2, error) { token := mcclient.TokenCredentialV2{} diff --git a/pkg/mcclient/modules/mod_domains.go b/pkg/mcclient/modules/mod_domains.go index f6b294bb52..076cf98e0e 100644 --- a/pkg/mcclient/modules/mod_domains.go +++ b/pkg/mcclient/modules/mod_domains.go @@ -14,259 +14,10 @@ package modules -import ( - "fmt" - - "yunion.io/x/jsonutils" - "yunion.io/x/log" - - "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/util/httputils" -) - type DomainManager struct { ResourceManager } -func (this *DomainManager) GetConfig(s *mcclient.ClientSession, domain string) (jsonutils.JSONObject, error) { - url := fmt.Sprintf("/domains/%s/config", domain) - return this._get(s, url, "config") -} - -func (this *DomainManager) UpdateConfig(s *mcclient.ClientSession, domain string, config jsonutils.JSONObject) (jsonutils.JSONObject, error) { - result, e := this._updateConfig(s, domain, config) - if e != nil { - return result, e - } - driver, e := config.Get("config", "identity", "driver") - if e == nil { - body := jsonutils.NewDict() - body.Add(driver, "driver") - this.Patch(s, domain, body) - } - return result, e -} - -func (this *DomainManager) _updateConfig(s *mcclient.ClientSession, domain string, config jsonutils.JSONObject) (jsonutils.JSONObject, error) { - driver, e := config.GetString("config", "identity", "driver") - if e != nil { - return nil, httperrors.NewInputParameterError("Malformed domain configuration %s", driver) - } - if driver != "ldap" { - return nil, httperrors.NewInputParameterError("Invalid driver: %s, ONLY ldap is supported", driver) - } - url := fmt.Sprintf("/domains/%s/config", domain) - ret, e := this._patch(s, url, config, "config") - if e != nil { - je, ok := e.(*httputils.JSONClientError) - if ok && je.Code == 404 { - return this._put(s, url, config, "config") - } else { - return nil, e - } - } else { - return ret, nil - } -} - -func (this *DomainManager) DeleteConfig(s *mcclient.ClientSession, domain string) (jsonutils.JSONObject, error) { - if domain == "default" { - return nil, httperrors.NewForbiddenError("domain %s did not allowed deleted", domain) - } - - result, e := this._deleteConfig(s, domain) - if e != nil { - return result, e - } - body := jsonutils.NewDict() - body.Add(jsonutils.NewString(""), "driver") - this.Patch(s, domain, body) - return result, e -} - -func (this *DomainManager) _deleteConfig(s *mcclient.ClientSession, domain string) (jsonutils.JSONObject, error) { - url := fmt.Sprintf("/domains/%s/config", domain) - return this._delete(s, url, nil, "config") -} - -func (this *DomainManager) GetDomainConfig(s *mcclient.ClientSession, domain string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - - ret := jsonutils.NewDict() - - domain_detail, err := this.Get(s, domain, nil) - if err != nil { - return ret, err - } - - config, err := this.GetConfig(s, domain) - if err != nil { - // for empty domain config - log.Infof("err fetch domain config for %s with error: %s", domain, err) - config = jsonutils.NewDict() - } - - ret.Add(domain_detail, "domain") - ret.Add(config, "config") - return ret, nil -} - -func (this *DomainManager) DoDomainConfigUpdate(s *mcclient.ClientSession, domain string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - // params example: - // { - // "config": { - // "identity": { - // "driver": "ldap" - // }, - // "ldap": { - // "group_id_attribute": "cn", - // "group_member_attribute": "member", - // "group_name_attribute": "cn", - // "group_objectclass": "ipausergroup", - // "group_tree_dn": "CN=groups,CN=accounts,DC=ipa,DC=yunionyun,DC=com", - // "page_size": 20, - // "query_scope": "sub", - // "suffix": "DC=ipa,DC=yunionyun,DC=com", - // "url": "ldap://192.168.0.222", - // "user": "UID=dcadmin,CN=users,CN=accounts,DC=ipa,DC=yunionyun,DC=com", - // "user_additional_attribute_mapping": [ - // "displayName:displayname", - // "telephoneNumber:mobile" - // ], - // "user_enabled_attribute": "nsAccountLock", - // "user_enabled_default": "FALSE", - // "user_enabled_invert": true, - // "user_enabled_mask": 0, - // "user_id_attribute": "uid", - // "user_name_attribute": "uid", - // "user_objectclass": "person", - // "user_tree_dn": "CN=users,CN=accounts,DC=ipa,DC=yunionyun,DC=com" - // } - // }, - // "domain": { - // "description": "SqnkThciWBq7", - // "enabled": true, - // "name": "os8vFdmqlgji-delete-free" - // } - // } - - ret := jsonutils.NewDict() - - _domain, err := params.Get("domain") - if err != nil { - return ret, httperrors.NewMissingParameterError("domain") - } - name, _ := _domain.GetString("name") - if domain == "default" && name != "Default" { - return nil, httperrors.NewUnsupportOperationError("domain %s did not allowed update Name", domain) - } - - _domain, err = this.Patch(s, domain, _domain) - if err != nil { - return ret, err - } - - config := jsonutils.NewDict() - _config, _ := params.Get("config") - if _config == nil { - _config = jsonutils.NewDict() - } - _driver, _ := _config.GetString("identity", "driver") - - if _driver == "ldap" { - config.Add(_config, "config") - log.Infof("to update config: %s", config) - _config, err = this.UpdateConfig(s, domain, config) - if err != nil { - return ret, err - } - ret.Add(_config, "config") - } - - ret.Add(_domain, "domain") - - return ret, nil -} - -func (this *DomainManager) DoDomainConfigCreate(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - ret := jsonutils.NewDict() - _domain, err := params.Get("domain") - - if err != nil { - return ret, httperrors.NewMissingParameterError("domain") - } - - _domain, err = this.Create(s, _domain) - if err != nil { - return ret, err - } - - objId, err := _domain.GetString("id") - if err != nil { - return ret, err - } - - config := jsonutils.NewDict() - _config, _ := params.Get("config") - _driver, _ := _config.Get("identity") - - if _driver != nil { - config.Add(_config, "config") - _config, err = this.UpdateConfig(s, objId, config) - if err != nil { - return ret, err - } - ret.Add(_config, "config") - } - - ret.Add(_domain, "domain") - return ret, nil -} - -func (this *DomainManager) DoDomainConfigDelete(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - ret := jsonutils.NewDict() - - ids, _ := params.GetArray("ids") - domains := jsonutils.JSONArray2StringArray(ids) - - for _, domain := range domains { - objId, err := this.GetId(s, domain, nil) - if err != nil { - return ret, err - } - - defer func() { - if err := recover(); err != nil { - this.DeleteConfig(s, objId) - this.Delete(s, objId, nil) - } - }() - - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(objId), "domain_id") - - detail, err := this.GetById(s, domain, nil) - if err != nil { - log.Errorf("got domain detail error: %v", err) - return ret, httperrors.NewResourceNotFoundError("%s %s not find", "Domain", domain) - } - - driver, _ := detail.GetString("driver") - if driver != "ldap" { - if result, err := UsersV3.List(s, params); err != nil { - log.Errorf("user list got error: %v", err) - return ret, httperrors.NewInternalServerError("fetching user list failed: %s", err) - } else if len(result.Data) > 0 { - return ret, httperrors.NewForbiddenError("cannot delete: there still exists %d user related with domain %s.", len(result.Data), objId) - } - } - - this.DeleteConfig(s, objId) - this.Delete(s, objId, nil) - } - return ret, nil -} - var ( Domains DomainManager ) diff --git a/pkg/mcclient/modules/mod_identityproviders.go b/pkg/mcclient/modules/mod_identityproviders.go new file mode 100644 index 0000000000..0edd1d7d76 --- /dev/null +++ b/pkg/mcclient/modules/mod_identityproviders.go @@ -0,0 +1,221 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +type IdentityProviderManager struct { + ResourceManager +} + +var ( + IdentityProviders IdentityProviderManager +) + +/* +func (this *IdentityProviderManager) GetConfig(s *mcclient.ClientSession, idpId string) (jsonutils.JSONObject, error) { + return this.GetSpecific(s, idpId, "config", nil) +} + +func (this *IdentityProviderManager) UpdateConfig(s *mcclient.ClientSession, idpId string, config jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return this.PerformAction(s, idpId, "config", config) +} + +func (this *IdentityProviderManager) GetIdpConfig(s *mcclient.ClientSession, idpId string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + ret := jsonutils.NewDict() + + idpDetail, err := this.Get(s, idpId, nil) + if err != nil { + return ret, err + } + + config, err := this.GetConfig(s, idpId) + if err != nil { + // for empty domain config + log.Infof("err fetch domain config for %s with error: %s", idpId, err) + config = jsonutils.NewDict() + } + + ret.Add(idpDetail, "domain") + ret.Add(config, "config") + return ret, nil +} + +func (this *IdentityProviderManager) DoIdpConfigUpdate(s *mcclient.ClientSession, idpId string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + // params example: + // { + // "config": { + // "identity": { + // "driver": "ldap" + // }, + // "ldap": { + // "group_id_attribute": "cn", + // "group_member_attribute": "member", + // "group_name_attribute": "cn", + // "group_objectclass": "ipausergroup", + // "group_tree_dn": "CN=groups,CN=accounts,DC=ipa,DC=yunionyun,DC=com", + // "page_size": 20, + // "query_scope": "sub", + // "suffix": "DC=ipa,DC=yunionyun,DC=com", + // "url": "ldap://192.168.0.222", + // "user": "UID=dcadmin,CN=users,CN=accounts,DC=ipa,DC=yunionyun,DC=com", + // "user_additional_attribute_mapping": [ + // "displayName:displayname", + // "telephoneNumber:mobile" + // ], + // "user_enabled_attribute": "nsAccountLock", + // "user_enabled_default": "FALSE", + // "user_enabled_invert": true, + // "user_enabled_mask": 0, + // "user_id_attribute": "uid", + // "user_name_attribute": "uid", + // "user_objectclass": "person", + // "user_tree_dn": "CN=users,CN=accounts,DC=ipa,DC=yunionyun,DC=com" + // } + // }, + // "domain": { + // "description": "SqnkThciWBq7", + // "enabled": true, + // "name": "os8vFdmqlgji-delete-free" + // } + // } + + ret := jsonutils.NewDict() + + idp, err := params.Get("identity_provider") + if err != nil { + return ret, httperrors.NewMissingParameterError("domain") + } + name, _ := idp.GetString("name") + if domain == "default" && name != "Default" { + return nil, httperrors.NewUnsupportOperationError("domain %s did not allowed update Name", domain) + } + + domain, err = this.Update(s, idpId, idp) + if err != nil { + return ret, err + } + + config := jsonutils.NewDict() + _config, _ := params.Get("config") + if _config == nil { + _config = jsonutils.NewDict() + } + _driver, _ := _config.GetString("identity", "driver") + + if _driver == "ldap" { + config.Add(_config, "config") + log.Infof("to update config: %s", config) + _config, err = this.UpdateConfig(s, domain, config) + if err != nil { + return ret, err + } + ret.Add(_config, "config") + } + + ret.Add(_domain, "domain") + + return ret, nil +} + +func (this *DomainManager) DoDomainConfigCreate(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + ret := jsonutils.NewDict() + _domain, err := params.Get("domain") + + if err != nil { + return ret, httperrors.NewMissingParameterError("domain") + } + + _domain, err = this.Create(s, _domain) + if err != nil { + return ret, err + } + + objId, err := _domain.GetString("id") + if err != nil { + return ret, err + } + + config := jsonutils.NewDict() + _config, _ := params.Get("config") + _driver, _ := _config.Get("identity") + + if _driver != nil { + config.Add(_config, "config") + _config, err = this.UpdateConfig(s, objId, config) + if err != nil { + return ret, err + } + ret.Add(_config, "config") + } + + ret.Add(_domain, "domain") + return ret, nil +} + + +func (this *DomainManager) DoDomainConfigDelete(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + ret := jsonutils.NewDict() + + ids, _ := params.GetArray("ids") + domains := jsonutils.JSONArray2StringArray(ids) + + for _, domain := range domains { + objId, err := this.GetId(s, domain, nil) + if err != nil { + return ret, err + } + + defer func() { + if err := recover(); err != nil { + this.DeleteConfig(s, objId) + this.Delete(s, objId, nil) + } + }() + + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(objId), "domain_id") + + detail, err := this.GetById(s, domain, nil) + if err != nil { + log.Errorf("got domain detail error: %v", err) + return ret, httperrors.NewResourceNotFoundError("%s %s not find", "Domain", domain) + } + + driver, _ := detail.GetString("driver") + if driver != "ldap" { + if result, err := UsersV3.List(s, params); err != nil { + log.Errorf("user list got error: %v", err) + return ret, httperrors.NewInternalServerError("fetching user list failed: %s", err) + } else if len(result.Data) > 0 { + return ret, httperrors.NewForbiddenError("cannot delete: there still exists %d user related with domain %s.", len(result.Data), objId) + } + } + + this.DeleteConfig(s, objId) + this.Delete(s, objId, nil) + } + return ret, nil +} +*/ + +func init() { + IdentityProviders = IdentityProviderManager{ + NewIdentityV3Manager("identity_provider", + "identity_providers", + []string{}, + []string{"ID", "Name", "Driver", "Template", "Enabled", "Status", "Sync_Status", "Error_count", "Sync_Interval_Seconds"}), + } + + register(&IdentityProviders) +} diff --git a/pkg/mcclient/modules/mod_users.go b/pkg/mcclient/modules/mod_users.go index 3901ad59d8..269891ab36 100644 --- a/pkg/mcclient/modules/mod_users.go +++ b/pkg/mcclient/modules/mod_users.go @@ -16,10 +16,8 @@ package modules import ( "fmt" - "strings" "yunion.io/x/jsonutils" - "yunion.io/x/log" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -71,6 +69,7 @@ func (this *UserManagerV3) GetProjectsRPC(s *mcclient.ClientSession, uid string, return ListResult2JSON(ret), nil } +/* func (this *UserManagerV3) GetIsLdapUser(s *mcclient.ClientSession, uid string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { ret := jsonutils.NewDict() ret.Add(jsonutils.JSONFalse, "isldap") @@ -98,7 +97,7 @@ func (this *UserManagerV3) GetIsLdapUser(s *mcclient.ClientSession, uid string, } return ret, nil -} +}*/ func (this *UserManagerV3) _groupAction(s *mcclient.ClientSession, gid, uid, action string, ch chan int) error { @@ -187,7 +186,7 @@ func init() { UsersV3 = UserManagerV3{NewIdentityV3Manager("user", "users", []string{}, - []string{"ID", "Name", "Domain_Id", "Domain", "domain_readonly", + []string{"ID", "Name", "Domain_Id", "Domain", "readonly", "idp_id", "Enabled", "Email", "Mobile", "Displayname", "is_system_account", "allow_web_console", "enable_mfa"})} diff --git a/pkg/multicloud/disk_base.go b/pkg/multicloud/disk_base.go index ea352b5eac..01cfc0f86e 100644 --- a/pkg/multicloud/disk_base.go +++ b/pkg/multicloud/disk_base.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package multicloud type SDisk struct{} diff --git a/pkg/multicloud/doc.go b/pkg/multicloud/doc.go index 2513ce679b..ad57b3cd07 100644 --- a/pkg/multicloud/doc.go +++ b/pkg/multicloud/doc.go @@ -1 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package multicloud // import "yunion.io/x/onecloud/pkg/multicloud" diff --git a/pkg/multicloud/region_base.go b/pkg/multicloud/region_base.go index e30dcacdc6..e701fac48b 100644 --- a/pkg/multicloud/region_base.go +++ b/pkg/multicloud/region_base.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package multicloud import ( diff --git a/pkg/scheduler/models/doc.go b/pkg/scheduler/models/doc.go new file mode 100644 index 0000000000..f31ea5617b --- /dev/null +++ b/pkg/scheduler/models/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models // import "yunion.io/x/onecloud/pkg/scheduler/models" diff --git a/pkg/util/aliyun/snapshot_policy.go b/pkg/util/aliyun/snapshot_policy.go index 694786ee87..8dfa178dee 100644 --- a/pkg/util/aliyun/snapshot_policy.go +++ b/pkg/util/aliyun/snapshot_policy.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package aliyun import ( diff --git a/pkg/util/ldaputils/ldaputils.go b/pkg/util/ldaputils/ldaputils.go index b4aad2d82a..91932ff923 100644 --- a/pkg/util/ldaputils/ldaputils.go +++ b/pkg/util/ldaputils/ldaputils.go @@ -78,10 +78,10 @@ func (cli *SLDAPClient) Close() { } } -func (cli *SLDAPClient) Authenticate(baseDN string, objClass string, uidAttr string, uname string, passwd string, fields []string) (*ldap.Entry, error) { +func (cli *SLDAPClient) Authenticate(baseDN string, objClass string, uidAttr string, uname string, passwd string, filter string, fields []string, queryScope int) (*ldap.Entry, error) { attrMap := make(map[string]string) attrMap[uidAttr] = uname - entries, err := cli.Search(baseDN, objClass, attrMap, fields) + entries, err := cli.Search(baseDN, objClass, attrMap, filter, fields, queryScope) if err != nil { return nil, errors.WithMessage(err, "Search") } @@ -100,7 +100,7 @@ func (cli *SLDAPClient) Authenticate(baseDN string, objClass string, uidAttr str return entry, nil } -func (cli *SLDAPClient) Search(base string, objClass string, condition map[string]string, fields []string) ([]*ldap.Entry, error) { +func (cli *SLDAPClient) Search(base string, objClass string, condition map[string]string, filter string, fields []string, queryScope int) ([]*ldap.Entry, error) { searches := strings.Builder{} if len(condition) == 0 && len(objClass) == 0 { searches.WriteString("(objectClass=*)") @@ -117,15 +117,22 @@ func (cli *SLDAPClient) Search(base string, objClass string, condition map[strin searches.WriteString(v) searches.WriteString(")") } + if len(filter) > 0 && strings.HasPrefix(filter, "(") && strings.HasSuffix(filter, ")") { + searches.WriteString(filter) + } searchStr := fmt.Sprintf("(&%s)", searches.String()) if len(base) == 0 { base = cli.baseDN } + if queryScope != ldap.ScopeWholeSubtree && queryScope != ldap.ScopeSingleLevel && queryScope != ldap.ScopeBaseObject { + queryScope = ldap.ScopeWholeSubtree + } + searchRequest := ldap.NewSearchRequest( base, // The base dn to search - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + queryScope, ldap.NeverDerefAliases, 0, 0, false, searchStr, fields, // A list attributes to retrieve nil, diff --git a/pkg/util/openstack/eip.go b/pkg/util/openstack/eip.go index a36a3dbc5b..6a6d08ab56 100644 --- a/pkg/util/openstack/eip.go +++ b/pkg/util/openstack/eip.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package openstack import ( diff --git a/pkg/util/sysutils/privilege.go b/pkg/util/sysutils/privilege.go index ef9c44a066..c045bac740 100644 --- a/pkg/util/sysutils/privilege.go +++ b/pkg/util/sysutils/privilege.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package sysutils import "os" diff --git a/pkg/util/zstack/cluster.go b/pkg/util/zstack/cluster.go index 8f7f5b5f7d..18188f056f 100644 --- a/pkg/util/zstack/cluster.go +++ b/pkg/util/zstack/cluster.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack type SCluster struct { diff --git a/pkg/util/zstack/configuration.go b/pkg/util/zstack/configuration.go index dbbb29212c..787852d987 100644 --- a/pkg/util/zstack/configuration.go +++ b/pkg/util/zstack/configuration.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack type SConfiguration struct { diff --git a/pkg/util/zstack/disk.go b/pkg/util/zstack/disk.go index 11c372f731..a32d21322a 100644 --- a/pkg/util/zstack/disk.go +++ b/pkg/util/zstack/disk.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/disk_offering.go b/pkg/util/zstack/disk_offering.go index 34a2b9608f..99ef46b9ba 100644 --- a/pkg/util/zstack/disk_offering.go +++ b/pkg/util/zstack/disk_offering.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/doc.go b/pkg/util/zstack/doc.go index 9344a60cd9..31367450ff 100644 --- a/pkg/util/zstack/doc.go +++ b/pkg/util/zstack/doc.go @@ -1 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack // import "yunion.io/x/onecloud/pkg/util/zstack" diff --git a/pkg/util/zstack/eip.go b/pkg/util/zstack/eip.go index 3d07f44400..bdb639107a 100644 --- a/pkg/util/zstack/eip.go +++ b/pkg/util/zstack/eip.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/host.go b/pkg/util/zstack/host.go index 0ad502f8b4..ba564151d6 100644 --- a/pkg/util/zstack/host.go +++ b/pkg/util/zstack/host.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/image.go b/pkg/util/zstack/image.go index bd15e703a2..f8e5d929ba 100644 --- a/pkg/util/zstack/image.go +++ b/pkg/util/zstack/image.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/image_server.go b/pkg/util/zstack/image_server.go index e4bbfb0a0e..0588df13f1 100644 --- a/pkg/util/zstack/image_server.go +++ b/pkg/util/zstack/image_server.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack type ImageServers []SImageServer diff --git a/pkg/util/zstack/instance.go b/pkg/util/zstack/instance.go index 3c82802788..6617611362 100644 --- a/pkg/util/zstack/instance.go +++ b/pkg/util/zstack/instance.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/instancenic.go b/pkg/util/zstack/instancenic.go index 9b2f8518c0..ec6bea9031 100644 --- a/pkg/util/zstack/instancenic.go +++ b/pkg/util/zstack/instancenic.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/network.go b/pkg/util/zstack/network.go index 1d539059df..79dd6be7d3 100644 --- a/pkg/util/zstack/network.go +++ b/pkg/util/zstack/network.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/network_service.go b/pkg/util/zstack/network_service.go index d29b012d39..919654db5d 100644 --- a/pkg/util/zstack/network_service.go +++ b/pkg/util/zstack/network_service.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/offering.go b/pkg/util/zstack/offering.go index 1775721027..5146da00fb 100644 --- a/pkg/util/zstack/offering.go +++ b/pkg/util/zstack/offering.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/provider/doc.go b/pkg/util/zstack/provider/doc.go index 8a52516f3c..ce16d2f678 100644 --- a/pkg/util/zstack/provider/doc.go +++ b/pkg/util/zstack/provider/doc.go @@ -1 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package provider // import "yunion.io/x/onecloud/pkg/util/zstack/provider" diff --git a/pkg/util/zstack/region.go b/pkg/util/zstack/region.go index 766e1f11aa..e32fc5face 100644 --- a/pkg/util/zstack/region.go +++ b/pkg/util/zstack/region.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/securitygroup.go b/pkg/util/zstack/securitygroup.go index 85d957b20f..5e4c614c9a 100644 --- a/pkg/util/zstack/securitygroup.go +++ b/pkg/util/zstack/securitygroup.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/shell/cluster.go b/pkg/util/zstack/shell/cluster.go index 5b13cb05b1..afffe54857 100644 --- a/pkg/util/zstack/shell/cluster.go +++ b/pkg/util/zstack/shell/cluster.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/configration.go b/pkg/util/zstack/shell/configration.go index f29eed2ce4..b92bc96d90 100644 --- a/pkg/util/zstack/shell/configration.go +++ b/pkg/util/zstack/shell/configration.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/disk.go b/pkg/util/zstack/shell/disk.go index c5aa945200..11b92736c2 100644 --- a/pkg/util/zstack/shell/disk.go +++ b/pkg/util/zstack/shell/disk.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/disk_offering.go b/pkg/util/zstack/shell/disk_offering.go index 7e30ec8eb3..40c49f645c 100644 --- a/pkg/util/zstack/shell/disk_offering.go +++ b/pkg/util/zstack/shell/disk_offering.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/doc.go b/pkg/util/zstack/shell/doc.go index cd762b7aeb..803a1a372e 100644 --- a/pkg/util/zstack/shell/doc.go +++ b/pkg/util/zstack/shell/doc.go @@ -1 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell // import "yunion.io/x/onecloud/pkg/util/zstack/shell" diff --git a/pkg/util/zstack/shell/eip.go b/pkg/util/zstack/shell/eip.go index d6d0cde148..c8e851e311 100644 --- a/pkg/util/zstack/shell/eip.go +++ b/pkg/util/zstack/shell/eip.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/host.go b/pkg/util/zstack/shell/host.go index 83d6344ba0..acc5fc862c 100644 --- a/pkg/util/zstack/shell/host.go +++ b/pkg/util/zstack/shell/host.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/image.go b/pkg/util/zstack/shell/image.go index ca1edb2249..3998656c4f 100644 --- a/pkg/util/zstack/shell/image.go +++ b/pkg/util/zstack/shell/image.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/image_server.go b/pkg/util/zstack/shell/image_server.go index fda97bdfb0..5cd71f8fb0 100644 --- a/pkg/util/zstack/shell/image_server.go +++ b/pkg/util/zstack/shell/image_server.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/instance.go b/pkg/util/zstack/shell/instance.go index d3e35c5ac6..5d7d123dcc 100644 --- a/pkg/util/zstack/shell/instance.go +++ b/pkg/util/zstack/shell/instance.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/instance_offering.go b/pkg/util/zstack/shell/instance_offering.go index 7757e644e0..b9f257edb5 100644 --- a/pkg/util/zstack/shell/instance_offering.go +++ b/pkg/util/zstack/shell/instance_offering.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/network.go b/pkg/util/zstack/shell/network.go index e7c5faf1bb..16b8eebb0e 100644 --- a/pkg/util/zstack/shell/network.go +++ b/pkg/util/zstack/shell/network.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/region.go b/pkg/util/zstack/shell/region.go index 088e77aa71..2abe7a6594 100644 --- a/pkg/util/zstack/shell/region.go +++ b/pkg/util/zstack/shell/region.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/securitygroup.go b/pkg/util/zstack/shell/securitygroup.go index 45ddca51e9..352ff7e09d 100644 --- a/pkg/util/zstack/shell/securitygroup.go +++ b/pkg/util/zstack/shell/securitygroup.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/snapshot.go b/pkg/util/zstack/shell/snapshot.go index 6c3ebc4e9a..5fbc3cf222 100644 --- a/pkg/util/zstack/shell/snapshot.go +++ b/pkg/util/zstack/shell/snapshot.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/storage.go b/pkg/util/zstack/shell/storage.go index c54c766f96..27ebff5f2d 100644 --- a/pkg/util/zstack/shell/storage.go +++ b/pkg/util/zstack/shell/storage.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/tag.go b/pkg/util/zstack/shell/tag.go index c1248de53d..b31e9f1880 100644 --- a/pkg/util/zstack/shell/tag.go +++ b/pkg/util/zstack/shell/tag.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/vip.go b/pkg/util/zstack/shell/vip.go index 0f27845f30..e944692547 100644 --- a/pkg/util/zstack/shell/vip.go +++ b/pkg/util/zstack/shell/vip.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/wire.go b/pkg/util/zstack/shell/wire.go index 9ac1f78c8a..d46cf41a8c 100644 --- a/pkg/util/zstack/shell/wire.go +++ b/pkg/util/zstack/shell/wire.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/shell/zone.go b/pkg/util/zstack/shell/zone.go index d9142c33a4..2d4b239589 100644 --- a/pkg/util/zstack/shell/zone.go +++ b/pkg/util/zstack/shell/zone.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package shell import ( diff --git a/pkg/util/zstack/snapshot.go b/pkg/util/zstack/snapshot.go index e9bab53f83..d70b0cefe5 100644 --- a/pkg/util/zstack/snapshot.go +++ b/pkg/util/zstack/snapshot.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/storage.go b/pkg/util/zstack/storage.go index 5da8dfa0ca..36123ae988 100644 --- a/pkg/util/zstack/storage.go +++ b/pkg/util/zstack/storage.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/storage_local.go b/pkg/util/zstack/storage_local.go index 74a84219e7..0e998e8007 100644 --- a/pkg/util/zstack/storage_local.go +++ b/pkg/util/zstack/storage_local.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/storagecache.go b/pkg/util/zstack/storagecache.go index a605b4797f..2c6dc30b53 100644 --- a/pkg/util/zstack/storagecache.go +++ b/pkg/util/zstack/storagecache.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/systag.go b/pkg/util/zstack/systag.go index e6565cb60c..167a582d53 100644 --- a/pkg/util/zstack/systag.go +++ b/pkg/util/zstack/systag.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack type SSysTag struct { diff --git a/pkg/util/zstack/vip.go b/pkg/util/zstack/vip.go index 0e16a4d542..6396f28521 100644 --- a/pkg/util/zstack/vip.go +++ b/pkg/util/zstack/vip.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/vpc.go b/pkg/util/zstack/vpc.go index 5b78d4009f..17c0a510ac 100644 --- a/pkg/util/zstack/vpc.go +++ b/pkg/util/zstack/vpc.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/wire.go b/pkg/util/zstack/wire.go index 0855fae2c0..d99952edc4 100644 --- a/pkg/util/zstack/wire.go +++ b/pkg/util/zstack/wire.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/zone.go b/pkg/util/zstack/zone.go index 9d494888be..a9ab560213 100644 --- a/pkg/util/zstack/zone.go +++ b/pkg/util/zstack/zone.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/zstack.go b/pkg/util/zstack/zstack.go index 517a5684f2..ce5b7c3fe2 100644 --- a/pkg/util/zstack/zstack.go +++ b/pkg/util/zstack/zstack.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import ( diff --git a/pkg/util/zstack/zstack_const.go b/pkg/util/zstack/zstack_const.go index 27dbec2f53..b2fd77f26c 100644 --- a/pkg/util/zstack/zstack_const.go +++ b/pkg/util/zstack/zstack_const.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package zstack import "time"