feature: saml for cloudid

This commit is contained in:
Qu Xuan
2020-11-17 18:02:37 +08:00
parent 7123a8fc28
commit d4edbbb610
101 changed files with 4502 additions and 483 deletions
+448 -18
View File
@@ -17,8 +17,10 @@ package models
import (
"context"
"database/sql"
"fmt"
"net/http"
"net/url"
"strings"
"golang.org/x/net/http/httpproxy"
@@ -67,9 +69,11 @@ type SCloudaccount struct {
db.SStandaloneResourceBase
db.SDomainizedResourceBase
Provider string `width:"64" charset:"ascii" list:"domain"`
Brand string `width:"64" charset:"utf8" nullable:"true" list:"domain"`
IamLoginUrl string `width:"512" charset:"ascii"`
AccountId string `width:"128" charset:"utf8" nullable:"true" list:"domain" create:"domain_optional"`
Provider string `width:"64" charset:"ascii" list:"domain"`
Brand string `width:"64" charset:"utf8" nullable:"true" list:"domain"`
IamLoginUrl string `width:"512" charset:"ascii"`
SAMLAuth tristate.TriState `nullable:"false" list:"domain" default:"false"`
}
func (manager *SCloudaccountManager) GetResourceCount() ([]db.SScopeResourceCount, error) {
@@ -206,6 +210,34 @@ func (self *SCloudaccount) removeCloudgroupcaches(ctx context.Context, userCred
return nil
}
func (self *SCloudaccount) removeSAMLProviders(ctx context.Context, userCred mcclient.TokenCredential) error {
samls, err := self.GetSAMLProviders()
if err != nil {
return errors.Wrap(err, "GetSAMLProviders")
}
for i := range samls {
err = samls[i].RealDelete(ctx, userCred)
if err != nil {
return errors.Wrap(err, "samls[i].RealDelete")
}
}
return nil
}
func (self *SCloudaccount) removeCloudroles(ctx context.Context, userCred mcclient.TokenCredential) error {
roles, err := self.GetCloudroles()
if err != nil {
return errors.Wrapf(err, "GetCloudroles")
}
for i := range roles {
err = roles[i].RealDelete(ctx, userCred)
if err != nil {
return errors.Wrapf(err, "roles.RealDelete")
}
}
return nil
}
func (self *SCloudaccount) syncRemoveCloudaccount(ctx context.Context, userCred mcclient.TokenCredential) error {
err := self.syncRemoveClouduser(ctx, userCred)
if err != nil {
@@ -222,9 +254,48 @@ func (self *SCloudaccount) syncRemoveCloudaccount(ctx context.Context, userCred
return errors.Wrap(err, "removeCloudgroupcaches")
}
err = self.removeSAMLProviders(ctx, userCred)
if err != nil {
return errors.Wrapf(err, "removeSAMLProviders")
}
err = self.removeCloudroles(ctx, userCred)
if err != nil {
return errors.Wrapf(err, "removeCloudroles")
}
err = self.removeSamluser(ctx, userCred)
if err != nil {
return errors.Wrapf(err, "syncRemoveSamluser")
}
return self.Delete(ctx, userCred)
}
func (self *SCloudaccount) GetSamlusers() ([]SSamluser, error) {
q := SamluserManager.Query().Equals("cloudaccount_id", self.Id)
users := []SSamluser{}
err := db.FetchModelObjects(SamluserManager, q, &users)
if err != nil {
return nil, errors.Wrapf(err, "db.FetchModelObjects")
}
return users, nil
}
func (self *SCloudaccount) removeSamluser(ctx context.Context, userCred mcclient.TokenCredential) error {
users, err := self.GetSamlusers()
if err != nil {
return errors.Wrapf(err, "GetSamusers")
}
for i := range users {
err = users[i].Delete(ctx, userCred)
if err != nil {
return errors.Wrapf(err, "delete %s(%s)", users[i].Name, users[i].Id)
}
}
return nil
}
func (self *SCloudaccount) syncRemoveClouduser(ctx context.Context, userCred mcclient.TokenCredential) error {
users, err := self.getCloudusers()
if err != nil {
@@ -248,6 +319,9 @@ func (manager *SCloudaccountManager) newFromICloudaccount(ctx context.Context, u
if err != nil {
return nil, errors.Wrap(err, "Insert")
}
if account.SAMLAuth.IsTrue() {
account.StartSAMLProviderCreateTask(ctx, userCred)
}
return account, nil
}
@@ -258,11 +332,14 @@ func (self *SCloudaccount) syncWithICloudaccount(ctx context.Context, userCred m
self.DomainId = account.DomainId
self.Brand = account.Brand
self.IamLoginUrl = account.IamLoginUrl
self.SAMLAuth = account.SAMLAuth
self.AccountId = account.AccountId
return nil
})
if err != nil {
return errors.Wrap(err, "db.UpdateWithLock")
}
self.StartSAMLProviderCreateTask(ctx, userCred)
return nil
}
@@ -278,6 +355,16 @@ func (manager *SCloudaccountManager) SyncCloudaccounts(ctx context.Context, user
}
}
func (self *SCloudaccount) StartSyncSamlProvidersTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
params := jsonutils.NewDict()
task, err := taskman.TaskManager.NewTask(ctx, "SyncSAMLProvidersTask", self, userCred, params, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
task.ScheduleRun(nil)
return nil
}
func (self SCloudaccount) GetGlobalId() string {
return self.Id
}
@@ -407,6 +494,8 @@ func (account *SCloudDelegate) GetProvider() (cloudprovider.ICloudProvider, erro
Account: account.Account,
Secret: passwd,
ProxyFunc: proxyFunc,
AccountId: account.Id,
})
}
@@ -463,14 +552,12 @@ func (self *SCloudaccount) SyncCloudusers(ctx context.Context, userCred mcclient
}
for i := 0; i < len(removed); i++ {
if len(removed[i].ExternalId) > 0 {
err = removed[i].RealDelete(ctx, userCred)
if err != nil {
result.DeleteError(err)
continue
}
result.Delete()
err = removed[i].RealDelete(ctx, userCred)
if err != nil {
result.DeleteError(err)
continue
}
result.Delete()
}
for i := 0; i < len(commondb); i++ {
@@ -860,7 +947,7 @@ func (self *SCloudaccount) syncCloudprovider(ctx context.Context, userCred mccli
for i := 0; i < len(removed); i++ {
err = removed[i].Delete(ctx, userCred)
if err != nil {
result.AddError(err)
result.DeleteError(err)
continue
}
result.Delete()
@@ -970,6 +1057,180 @@ func (manager *SCloudaccountManager) SyncCloudidResources(ctx context.Context, u
}
}
func (self *SCloudaccount) IsSAMLProviderValid() (*SSAMLProvider, bool) {
provider, err := self.RegisterSAMProvider()
if err != nil {
return provider, false
}
if len(provider.ExternalId) == 0 {
return provider, false
}
return provider, true
}
func (self *SCloudaccount) RegisterSAMProvider() (*SSAMLProvider, error) {
if len(options.Options.ApiServer) == 0 {
return nil, fmt.Errorf("empty api server")
}
sps, err := self.GetSAMLProviders()
if err != nil {
return nil, errors.Wrapf(err, "GetSAMLProviders")
}
for i := range sps {
if sps[i].EntityId == options.Options.ApiServer {
return &sps[i], nil
}
}
sp := &SSAMLProvider{}
sp.SetModelManager(SAMLProviderManager, sp)
sp.Name = func() string {
name := strings.TrimPrefix(options.Options.ApiServer, "https://")
name = strings.TrimPrefix(name, "http://")
return name
}()
sp.EntityId = options.Options.ApiServer
sp.CloudaccountId = self.Id
sp.Status = api.SAML_PROVIDER_STATUS_CREATING
metadata := SamlIdpInstance().GetMetadata(self.Id).String()
sp.MetadataDocument = metadata
err = SAMLProviderManager.TableSpec().Insert(context.TODO(), sp)
if err != nil {
return nil, errors.Wrapf(err, "Insert")
}
return sp, nil
}
func (self *SCloudaccount) StartSAMLProviderCreateTask(ctx context.Context, userCred mcclient.TokenCredential) error {
if self.SAMLAuth.IsFalse() {
return nil
}
sp, valid := self.IsSAMLProviderValid()
if valid {
return nil
}
return sp.StartSAMLProviderCreateTask(ctx, userCred, "")
}
func (manager *SCloudaccountManager) SyncSAMLProviders(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
accounts, err := manager.GetSupportCloudIdAccounts()
if err != nil {
log.Errorf("GetSupportCloudIdAccounts error: %v", err)
return
}
for i := range accounts {
err = accounts[i].StartSyncSamlProvidersTask(ctx, userCred, "")
if err != nil {
log.Errorf("StartSyncSamlProvidersTask for account %s(%s) error: %v", accounts[i].Name, accounts[i].Provider, err)
}
}
}
func (manager *SCloudaccountManager) SyncCloudroles(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
accounts, err := manager.GetSupportCloudIdAccounts()
if err != nil {
log.Errorf("GetSupportCloudIdAccounts error: %v", err)
return
}
for i := range accounts {
err = accounts[i].StartSyncCloudrolesTask(ctx, userCred, "")
if err != nil {
log.Errorf("StartSyncCloudrolesTask for account %s(%s) error: %v", accounts[i].Name, accounts[i].Provider, err)
}
}
}
func (self *SCloudaccount) StartSyncCloudrolesTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
params := jsonutils.NewDict()
task, err := taskman.TaskManager.NewTask(ctx, "SyncCloudrolesTask", self, userCred, params, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
task.ScheduleRun(nil)
return nil
}
func (self *SCloudaccount) GetSAMLProviders() ([]SSAMLProvider, error) {
q := SAMLProviderManager.Query().Equals("cloudaccount_id", self.Id)
samls := []SSAMLProvider{}
err := db.FetchModelObjects(SAMLProviderManager, q, &samls)
if err != nil {
return nil, errors.Wrapf(err, "db.FetchModelObjects")
}
return samls, nil
}
func (self *SCloudaccount) SyncSAMLProviders(ctx context.Context, userCred mcclient.TokenCredential, samls []cloudprovider.ICloudSAMLProvider) compare.SyncResult {
result := compare.SyncResult{}
dbSamls, err := self.GetSAMLProviders()
if err != nil {
result.Error(errors.Wrap(err, "GetSAMLProviders"))
return result
}
removed := make([]SSAMLProvider, 0)
commondb := make([]SSAMLProvider, 0)
commonext := make([]cloudprovider.ICloudSAMLProvider, 0)
added := make([]cloudprovider.ICloudSAMLProvider, 0)
err = compare.CompareSets(dbSamls, samls, &removed, &commondb, &commonext, &added)
if err != nil {
result.Error(errors.Wrap(err, "compare.CompareSets"))
return result
}
for i := 0; i < len(removed); i++ {
err = removed[i].RealDelete(ctx, userCred)
if err != nil {
result.DeleteError(err)
continue
}
result.Delete()
}
for i := 0; i < len(commondb); i++ {
err = commondb[i].SyncWithCloudSAMLProvider(ctx, userCred, commonext[i])
if err != nil {
result.UpdateError(err)
continue
}
result.Update()
}
for i := 0; i < len(added); i++ {
err = self.newFromCloudSAMLProvider(ctx, userCred, added[i])
if err != nil {
result.AddError(err)
continue
}
result.Add()
}
return result
}
func (self *SCloudaccount) newFromCloudSAMLProvider(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudSAMLProvider) error {
saml := &SSAMLProvider{}
saml.SetModelManager(SAMLProviderManager, saml)
saml.Name = ext.GetName()
saml.ExternalId = ext.GetGlobalId()
saml.DomainId = self.DomainId
saml.CloudaccountId = self.Id
metadata, err := ext.GetMetadataDocument()
if err != nil {
log.Errorf("failed to get metadata from %s(%s) error: %v", self.Name, self.Provider, err)
}
saml.Status = ext.GetStatus()
if metadata != nil {
saml.EntityId = metadata.EntityId
saml.MetadataDocument = metadata.String()
}
if saml.EntityId != options.Options.ApiServer {
saml.Status = api.SAML_PROVIDER_STATUS_NOT_MATCH
}
return SAMLProviderManager.TableSpec().Insert(ctx, saml)
}
func (self *SCloudaccount) StartSyncCloudIdResourcesTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
params := jsonutils.NewDict()
task, err := taskman.TaskManager.NewTask(ctx, "SyncCloudIdResourcesTask", self, userCred, params, parentTaskId, "", nil)
@@ -999,14 +1260,12 @@ func (self *SCloudaccount) SyncCloudgroupcaches(ctx context.Context, userCred mc
}
for i := 0; i < len(removed); i++ {
if len(removed[i].ExternalId) > 0 { // 只删除云上已经删除过的组
err = removed[i].RealDelete(ctx, userCred)
if err != nil {
result.DeleteError(err)
continue
}
result.Delete()
err = removed[i].RealDelete(ctx, userCred)
if err != nil {
result.DeleteError(err)
continue
}
result.Delete()
}
for i := 0; i < len(commondb); i++ {
@@ -1335,3 +1594,174 @@ func (self *SCloudaccount) SyncSystemCloudpoliciesForCloud(ctx context.Context,
log.Infof("Sync %s(%s) system policies for user %s result: %s", self.Name, self.Provider, clouduser.Name, result.Result())
return nil
}
func (self *SCloudaccount) GetLocalUserCloudroles(userId, spId string) ([]SCloudrole, error) {
roles := []SCloudrole{}
q := CloudroleManager.Query().Equals("cloudaccount_id", self.Id).Equals("owner_id", userId).Equals("saml_provider_id", spId)
err := db.FetchModelObjects(CloudroleManager, q, &roles)
if err != nil {
return nil, errors.Wrapf(err, "db.FetchModelObjects")
}
return roles, nil
}
func (self *SCloudaccount) RegisterCloudrole(userId, spId string) (*SCloudrole, error) {
roles, err := self.GetLocalUserCloudroles(userId, spId)
if err != nil {
return nil, errors.Wrapf(err, "GetLocalUserCloudroles")
}
if len(roles) > 0 {
return &roles[0], nil
}
user, err := db.UserCacheManager.FetchById(userId)
if err != nil {
return nil, errors.Wrapf(err, "UserCacheManager.FetchById(%s)", userId)
}
role := &SCloudrole{}
role.SetModelManager(CloudroleManager, role)
role.CloudaccountId = self.Id
role.OwnerId = userId
role.SAMLProviderId = spId
role.Name = user.GetName()
role.Status = api.CLOUD_ROLE_STATUS_CREATING
role.DomainId = self.DomainId
return role, CloudroleManager.TableSpec().Insert(context.TODO(), role)
}
func (self *SCloudaccount) GetCloudrole(userId string) (*SCloudrole, error) {
sp, valid := self.IsSAMLProviderValid()
if !valid {
return nil, fmt.Errorf("SAMLProvider for account %s not ready", self.Id)
}
return self.RegisterCloudrole(userId, sp.Id)
}
func (self *SCloudaccount) SyncRole(userId string) (*SCloudrole, error) {
role, err := self.GetCloudrole(userId)
if err != nil {
return nil, errors.Wrapf(err, "GetCloudrole")
}
err = role.SyncRoles()
if err != nil {
return nil, errors.Wrapf(err, "SyncRoles")
}
return role, nil
}
func (self *SCloudaccount) GetCloudroles() ([]SCloudrole, error) {
roles := []SCloudrole{}
q := CloudroleManager.Query()
err := db.FetchModelObjects(CloudroleManager, q, &roles)
if err != nil {
return nil, errors.Wrapf(err, "db.FetchModelObjects")
}
return roles, nil
}
func (self *SCloudaccount) newCloudrole(ctx context.Context, userCred mcclient.TokenCredential, iRole cloudprovider.ICloudrole) error {
role := &SCloudrole{}
role.SetModelManager(CloudroleManager, role)
role.Name = iRole.GetName()
role.ExternalId = iRole.GetGlobalId()
role.Document = iRole.GetDocument()
if spId := iRole.GetSAMLProvider(); len(spId) > 0 {
sp, _ := db.FetchByExternalIdAndManagerId(SAMLProviderManager, spId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
return q.Equals("cloudaccount_id", self.Id)
})
if sp != nil {
role.SAMLProviderId = sp.GetId()
}
}
role.CloudaccountId = self.Id
role.Status = api.CLOUD_ROLE_STATUS_AVAILABLE
return CloudroleManager.TableSpec().Insert(ctx, role)
}
func (self *SCloudaccount) SyncCloudroles(ctx context.Context, userCred mcclient.TokenCredential, exts []cloudprovider.ICloudrole) compare.SyncResult {
result := compare.SyncResult{}
roles, err := self.GetCloudroles()
if err != nil {
result.Error(errors.Wrapf(err, "GetCloudroles"))
return result
}
removed := make([]SCloudrole, 0)
commondb := make([]SCloudrole, 0)
commonext := make([]cloudprovider.ICloudrole, 0)
added := make([]cloudprovider.ICloudrole, 0)
err = compare.CompareSets(roles, exts, &removed, &commondb, &commonext, &added)
if err != nil {
result.Error(errors.Wrapf(err, "compare.CompareSets"))
return result
}
for i := 0; i < len(removed); i++ {
err = removed[i].RealDelete(ctx, userCred)
if err != nil {
result.DeleteError(err)
continue
}
result.Delete()
}
for i := 0; i < len(commondb); i++ {
err = commondb[i].syncWithCloudrole(ctx, userCred, commonext[i])
if err != nil {
result.UpdateError(err)
continue
}
result.Update()
}
for i := 0; i < len(added); i++ {
err := self.newCloudrole(ctx, userCred, added[i])
if err != nil {
result.AddError(err)
continue
}
result.Add()
}
return result
}
func (self *SCloudaccount) GetUserCloudgroups(userId string) ([]string, error) {
ret := []string{}
q := CloudgroupManager.Query()
samlusers := SamluserManager.Query("cloudgroup_id").Equals("owner_id", userId).Equals("cloudaccount_id", self.Id).SubQuery()
q = q.In("id", samlusers)
groups := []SCloudgroup{}
err := db.FetchModelObjects(CloudgroupManager, q, &groups)
if err != nil {
return nil, errors.Wrapf(err, "db.FetchModelObjects")
}
if len(groups) == 0 {
return ret, nil
}
for i := range groups {
cache, err := CloudgroupcacheManager.Register(&groups[i], self)
if err != nil {
return []string{}, errors.Wrapf(err, "group cache Register")
}
if len(cache.ExternalId) > 0 {
ret = append(ret, cache.Name)
} else {
s := auth.GetAdminSession(context.TODO(), options.Options.Region, "")
_, err = cache.GetOrCreateICloudgroup(context.TODO(), s.GetToken())
if err != nil {
return []string{}, errors.Wrapf(err, "GetOrCreateICloudgroup")
}
cache, err := CloudgroupcacheManager.Register(&groups[i], self)
if err != nil {
return []string{}, errors.Wrapf(err, "group cache Register")
}
ret = append(ret, cache.Name)
}
}
return ret, nil
}
+28
View File
@@ -255,9 +255,37 @@ func (self *SCloudgroup) RealDelete(ctx context.Context, userCred mcclient.Token
if err != nil {
return errors.Wrap(err, "remoteUsers")
}
err = self.removeSamlusers()
if err != nil {
return errors.Wrapf(err, "removeSamlusers")
}
return self.SStatusInfrasResourceBase.Delete(ctx, userCred)
}
func (self *SCloudgroup) GetSamlusers() ([]SSamluser, error) {
q := SamluserManager.Query().Equals("cloudgroup_id", self.Id)
users := []SSamluser{}
err := db.FetchModelObjects(SamluserManager, q, &users)
if err != nil {
return nil, errors.Wrapf(err, "db.FetchModelObjects")
}
return users, nil
}
func (self *SCloudgroup) removeSamlusers() error {
users, err := self.GetSamlusers()
if err != nil {
return errors.Wrap(err, "GetSamlusers")
}
for i := range users {
err = users[i].Delete(context.TODO(), nil)
if err != nil {
return errors.Wrapf(err, "rm saml user(%s)", users[i].Id)
}
}
return nil
}
func (self *SCloudgroup) removeUsers() error {
users, err := self.GetCloudusers()
if err != nil {
+1
View File
@@ -283,6 +283,7 @@ func (self *SCloudgroupcache) GetOrCreateICloudgroup(ctx context.Context, userCr
return nil, errors.Wrap(err, "CreateICloudgroup")
}
_, err = db.Update(self, func() error {
self.Name = groupName
self.ExternalId = iGroup.GetGlobalId()
self.Status = api.CLOUD_GROUP_CACHE_STATUS_AVAILABLE
return nil
+90
View File
@@ -0,0 +1,90 @@
// 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"
"database/sql"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/reflectutils"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/cloudid"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
type SCloudgroupResourceBaseManager struct {
}
type SCloudgroupResourceBase struct {
CloudgroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"`
}
func (self *SCloudgroupJointsBase) GetCloudgroup() (*SCloudgroup, error) {
group, err := CloudgroupManager.FetchById(self.CloudgroupId)
if err != nil {
return nil, errors.Wrap(err, "FetchById")
}
return group.(*SCloudgroup), nil
}
func (manager *SCloudgroupResourceBaseManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, groupCred mcclient.TokenCredential, query api.CloudgroupResourceListInput) (*sqlchemy.SQuery, error) {
if len(query.CloudgroupId) > 0 {
group, err := CloudgroupManager.FetchByIdOrName(nil, query.CloudgroupId)
if err != nil {
if err == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError2("cloudgroup", query.CloudgroupId)
}
return nil, httperrors.NewGeneralError(err)
}
q = q.Equals("cloudgroup_id", group.GetId())
}
return q, nil
}
func (manager *SCloudgroupResourceBaseManager) FetchCustomizeColumns(
ctx context.Context,
groupCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.CloudgroupResourceDetails {
rows := make([]api.CloudgroupResourceDetails, len(objs))
groupIds := make([]string, len(objs))
for i := range objs {
var base *SCloudgroupResourceBase
err := reflectutils.FindAnonymouStructPointer(objs[i], &base)
if err != nil {
log.Errorf("Cannot find SCloudgroupResourceBase in %#v: %s", objs[i], err)
} else if base != nil && len(base.CloudgroupId) > 0 {
groupIds[i] = base.CloudgroupId
}
}
groupMaps, err := db.FetchIdNameMap2(CloudgroupManager, groupIds)
if err != nil {
return rows
}
for i := range rows {
rows[i].Cloudgroup, _ = groupMaps[groupIds[i]]
}
return rows
}
+261
View File
@@ -0,0 +1,261 @@
// 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"
"fmt"
"strconv"
"strings"
"gopkg.in/fatih/set.v0"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/cloudid"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
type SCloudroleManager struct {
db.SStatusInfrasResourceBaseManager
db.SExternalizedResourceBaseManager
SCloudaccountResourceBaseManager
SAMLProviderResourceBaseManager
}
var CloudroleManager *SCloudroleManager
func init() {
CloudroleManager = &SCloudroleManager{
SStatusInfrasResourceBaseManager: db.NewStatusInfrasResourceBaseManager(
SCloudrole{},
"cloudroles_tbl",
"cloudrole",
"cloudroles",
),
}
CloudroleManager.SetVirtualObject(CloudroleManager)
}
type SCloudrole struct {
db.SEnabledStatusInfrasResourceBase
db.SExternalizedResourceBase
SCloudaccountResourceBase
SAMLProviderResourceBase
Document *jsonutils.JSONDict `length:"long" charset:"ascii" list:"domain" update:"domain" create:"domain_required"`
OwnerId string `width:"128" charset:"ascii" index:"true" list:"user" nullable:"false" create:"optional"`
}
// 公有云角色列表
func (manager *SCloudroleManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.CloudroleListInput) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SStatusInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusInfrasResourceBaseListInput)
if err != nil {
return nil, err
}
q, err = manager.SCloudaccountResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudaccountResourceListInput)
if err != nil {
return nil, err
}
return q, nil
}
// 获取公有云角色详情
func (self *SCloudrole) GetExtraDetails(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
isList bool,
) (api.CloudroleDetails, error) {
return api.CloudroleDetails{}, nil
}
func (manager *SCloudroleManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.CloudroleDetails {
rows := make([]api.CloudroleDetails, len(objs))
infRows := manager.SStatusInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
acRows := manager.SCloudaccountResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for i := range rows {
rows[i] = api.CloudroleDetails{
StatusInfrasResourceBaseDetails: infRows[i],
CloudaccountResourceDetails: acRows[i],
}
}
return rows
}
// 删除公有云角色
func (self *SCloudrole) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
return self.StartCloudroleDeleteTask(ctx, userCred, false, "")
}
func (self *SCloudrole) StartCloudroleDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, purge bool, parentTaskId string) error {
params := jsonutils.NewDict()
params.Add(jsonutils.NewBool(purge), "purge")
task, err := taskman.TaskManager.NewTask(ctx, "CloudroleDeleteTask", self, userCred, params, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
self.SetStatus(userCred, api.CLOUD_ROLE_STATUS_DELETING, "")
task.ScheduleRun(nil)
return nil
}
func (self *SCloudrole) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsDomainAllowPerform(userCred, self, "purge")
}
// 清除角色(不删除云上资源)
func (self *SCloudrole) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudrolePurgeInput) (jsonutils.JSONObject, error) {
return nil, self.StartCloudroleDeleteTask(ctx, userCred, true, "")
}
func (self *SCloudrole) GetICloudrole() (cloudprovider.ICloudrole, error) {
account, err := self.GetCloudaccount()
if err != nil {
return nil, errors.Wrapf(err, "GetCloudaccount")
}
provider, err := account.GetProvider()
if err != nil {
return nil, errors.Wrapf(err, "GetProvider")
}
if len(self.ExternalId) > 0 {
iRole, err := provider.GetICloudroleById(self.ExternalId)
if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound {
return nil, errors.Wrapf(err, "GetICloudroleById(%s)", self.ExternalId)
}
if err == nil {
return iRole, nil
}
}
sp, err := self.GetSAMLProvider()
if err != nil {
return nil, errors.Wrapf(err, "GetSAMLProvider")
}
for i := 0; i < 10; i++ {
_, err := provider.GetICloudroleByName(self.Name)
if err != nil && errors.Cause(err) == cloudprovider.ErrNotFound {
break
}
info := strings.Split(self.Name, "-")
num, err := strconv.Atoi(info[len(info)-1])
if err != nil {
info = append(info, "1")
} else {
info[len(info)-1] = fmt.Sprintf("%d", num+1)
}
self.Name = strings.Join(info, "-")
}
opts := &cloudprovider.SRoleCreateOptions{
Name: self.Name,
Desc: self.Description,
SAMLProvider: sp.ExternalId,
}
iRole, err := provider.CreateICloudrole(opts)
if err != nil {
return nil, errors.Wrapf(err, "CreateICloudrole")
}
db.Update(self, func() error {
self.ExternalId = iRole.GetGlobalId()
self.Name = iRole.GetName()
self.Document = iRole.GetDocument()
self.Status = api.CLOUD_ROLE_STATUS_AVAILABLE
return nil
})
return iRole, nil
}
func (self *SCloudrole) GetCloudpolicies() ([]SCloudpolicy, error) {
q := CloudpolicyManager.Query()
samlUsers := SamluserManager.Query("cloudgroup_id").Equals("owner_id", self.OwnerId).Equals("cloudaccount_id", self.CloudaccountId).SubQuery()
groups := CloudgroupManager.Query("id").In("id", samlUsers)
gp := CloudgroupPolicyManager.Query("cloudpolicy_id").In("cloudgroup_id", groups).SubQuery()
q = q.In("id", gp)
policies := []SCloudpolicy{}
err := db.FetchModelObjects(CloudpolicyManager, q, &policies)
if err != nil {
return nil, errors.Wrapf(err, "db.FetchModelObjects")
}
return policies, nil
}
func (self *SCloudrole) SyncRoles() error {
iRole, err := self.GetICloudrole()
if err != nil {
return errors.Wrapf(err, "GetICloudrole")
}
policies, err := self.GetCloudpolicies()
if err != nil {
return errors.Wrapf(err, "GetICloudpolicies")
}
local := set.New(set.ThreadSafe)
for i := range policies {
if policies[i].PolicyType == api.CLOUD_POLICY_TYPE_SYSTEM {
local.Add(policies[i].ExternalId)
} else {
}
}
iPolicies, err := iRole.GetICloudpolicies()
if err != nil {
return errors.Wrapf(err, "GetICloudpolicies")
}
remote := set.New(set.ThreadSafe)
for i := range iPolicies {
remote.Add(iPolicies[i].GetGlobalId())
}
for _, id := range set.Difference(remote, local).List() {
err = iRole.DetachPolicy(id.(string))
if err != nil {
return errors.Wrapf(err, "DetachPolicy(%s)", id)
}
}
for _, id := range set.Difference(local, remote).List() {
err = iRole.AttachPolicy(id.(string))
if err != nil {
return errors.Wrapf(err, "AttachPolicy(%s)", id)
}
}
return nil
}
func (self *SCloudrole) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
return self.SStatusInfrasResourceBase.Delete(ctx, userCred)
}
func (self *SCloudrole) syncWithCloudrole(ctx context.Context, userCred mcclient.TokenCredential, iRole cloudprovider.ICloudrole) error {
_, err := db.Update(self, func() error {
self.Name = self.GetName()
self.Document = iRole.GetDocument()
self.Status = api.CLOUD_ROLE_STATUS_AVAILABLE
return nil
})
return err
}
+212
View File
@@ -0,0 +1,212 @@
// 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"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/cloudid"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/cloudid/options"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/samlutils"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
type SSAMLProviderManager struct {
db.SStatusInfrasResourceBaseManager
db.SExternalizedResourceBaseManager
SCloudaccountResourceBaseManager
}
var SAMLProviderManager *SSAMLProviderManager
func init() {
SAMLProviderManager = &SSAMLProviderManager{
SStatusInfrasResourceBaseManager: db.NewStatusInfrasResourceBaseManager(
SSAMLProvider{},
"saml_provider_tbl",
"saml_provider",
"saml_providers",
),
}
SAMLProviderManager.SetVirtualObject(SAMLProviderManager)
}
type SSAMLProvider struct {
db.SStatusInfrasResourceBase
db.SExternalizedResourceBase
SCloudaccountResourceBase
EntityId string `get:"domain" create:"domain_optional" list:"domain"`
MetadataDocument string `get:"domain" create:"domain_optional"`
AuthUrl string `width:"512" charset:"ascii" get:"domain" list:"domain"`
}
func (manager *SSAMLProviderManager) GetIVirtualModelManager() db.IVirtualModelManager {
return manager.GetVirtualObject().(db.IVirtualModelManager)
}
func (manager *SSAMLProviderManager) GetResourceCount() ([]db.SScopeResourceCount, error) {
return nil, nil
}
func (manager *SSAMLProviderManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject {
accountId, _ := data.GetString("cloudaccount_id")
return jsonutils.Marshal(map[string]string{"cloudaccount_id": accountId})
}
func (manager *SSAMLProviderManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery {
accountId, _ := values.GetString("cloudaccount_id")
if len(accountId) > 0 {
q = q.Equals("cloudaccount_id", accountId)
}
return q
}
// 创建云账号的身份提供商
func (manager *SSAMLProviderManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.SAMLProviderCreateInput) (api.SAMLProviderCreateInput, error) {
if len(input.CloudaccountId) == 0 {
return input, httperrors.NewMissingParameterError("cloudaccount_id")
}
_, err := validators.ValidateModel(userCred, CloudaccountManager, &input.CloudaccountId)
if err != nil {
return input, err
}
input.EntityId = options.Options.ApiServer
if len(input.EntityId) == 0 {
return input, httperrors.NewResourceNotReadyError("not set api_server")
}
input.Name = strings.TrimPrefix(input.EntityId, "https://")
input.Name = strings.TrimPrefix(input.Name, "http://")
input.MetadataDocument = SamlIdpInstance().GetMetadata(input.CloudaccountId).String()
input.StatusInfrasResourceBaseCreateInput, err = manager.SStatusInfrasResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StatusInfrasResourceBaseCreateInput)
if err != nil {
return input, err
}
return input, nil
}
func (self *SSAMLProvider) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
self.StartSAMLProviderCreateTask(ctx, userCred, "")
}
// 公有云身份提供商列表
func (manager *SSAMLProviderManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.SAMLProviderListInput) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SStatusInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusInfrasResourceBaseListInput)
if err != nil {
return nil, err
}
q, err = manager.SCloudaccountResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudaccountResourceListInput)
if err != nil {
return nil, err
}
return q, nil
}
// 删除
func (self *SSAMLProvider) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
params := jsonutils.NewDict()
return self.StartSAMLProviderDeleteTask(ctx, userCred, params, "")
}
func (self *SSAMLProvider) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
return nil
}
func (self *SSAMLProvider) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
return self.SStatusInfrasResourceBase.Delete(ctx, userCred)
}
func (self *SSAMLProvider) StartSAMLProviderDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error {
task, err := taskman.TaskManager.NewTask(ctx, "SAMLProviderDeleteTask", self, userCred, data, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
self.SetStatus(userCred, api.SAML_PROVIDER_STATUS_DELETING, "")
task.ScheduleRun(nil)
return nil
}
func (self *SSAMLProvider) StartSAMLProviderCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
task, err := taskman.TaskManager.NewTask(ctx, "SAMLProviderCreateTask", self, userCred, nil, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
self.SetStatus(userCred, api.SAML_PROVIDER_STATUS_CREATING, "")
task.ScheduleRun(nil)
return nil
}
func (self *SSAMLProvider) syncRemove(ctx context.Context, userCred mcclient.TokenCredential) error {
return self.RealDelete(ctx, userCred)
}
func (self *SSAMLProvider) SyncWithCloudSAMLProvider(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudSAMLProvider) error {
_, err := db.Update(self, func() error {
self.ExternalId = ext.GetGlobalId()
self.AuthUrl = ext.GetAuthUrl()
self.Status = ext.GetStatus()
metadata, err := ext.GetMetadataDocument()
if err != nil {
log.Errorf("failed to get metadata for %s error: %v", self.Name, err)
}
if metadata != nil {
self.EntityId = metadata.EntityId
self.MetadataDocument = metadata.String()
}
if self.EntityId != options.Options.ApiServer {
self.Status = api.SAML_PROVIDER_STATUS_NOT_MATCH
}
return nil
})
return err
}
func (self *SSAMLProvider) GetMetadataDocument() (samlutils.EntityDescriptor, error) {
return samlutils.ParseMetadata([]byte(self.MetadataDocument))
}
func (manager *SSAMLProviderManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.SAMLProviderDetails {
rows := make([]api.SAMLProviderDetails, len(objs))
infRows := manager.SStatusInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
acRows := manager.SCloudaccountResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for i := range rows {
rows[i] = api.SAMLProviderDetails{
StatusInfrasResourceBaseDetails: infRows[i],
CloudaccountResourceDetails: acRows[i],
}
}
return rows
}
@@ -12,7 +12,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package providers
package models
import (
"context"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/samlutils"
"yunion.io/x/onecloud/pkg/util/samlutils/idp"
)
type SamlInstance func() *idp.SSAMLIdpInstance
var (
SamlIdpInstance SamlInstance = nil
)
type ICloudSAMLLoginDriver interface {
GetEntityID() string
GetMetadataFilename() string
GetMetadataUrl() string
GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error)
GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccoutId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error)
}
var (
driverTable = make(map[string]ICloudSAMLLoginDriver)
@@ -12,19 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package providers
package models
import (
"yunion.io/x/onecloud/pkg/util/samlutils"
"yunion.io/x/onecloud/pkg/util/samlutils/idp"
)
import "yunion.io/x/pkg/errors"
type ICloudSAMLLoginDriver interface {
GetEntityID() string
type SAMLProviderResourceBaseManager struct {
}
GetMetadataFilename() string
GetMetadataUrl() string
type SAMLProviderResourceBase struct {
SAMLProviderId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" json:"saml_provider_id"`
}
GetIdpInitiatedLoginData(idpId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error)
GetSpInitiatedLoginData(idpId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error)
func (self *SAMLProviderResourceBase) GetSAMLProvider() (*SSAMLProvider, error) {
sp, err := SAMLProviderManager.FetchById(self.SAMLProviderId)
if err != nil {
return nil, errors.Wrap(err, "SAMLProviderManager.FetchById")
}
return sp.(*SSAMLProvider), nil
}
+172
View File
@@ -0,0 +1,172 @@
// 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/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/cloudid"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
type SSamluserManager struct {
db.SStatusDomainLevelUserResourceBaseManager
SCloudgroupResourceBaseManager
SCloudaccountResourceBaseManager
}
var SamluserManager *SSamluserManager
func init() {
SamluserManager = &SSamluserManager{
SStatusDomainLevelUserResourceBaseManager: db.NewStatusDomainLevelUserResourceBaseManager(
SSamluser{},
"samlusers_tbl",
"samluser",
"samlusers",
),
}
SamluserManager.SetVirtualObject(SamluserManager)
}
type SSamluser struct {
db.SStatusDomainLevelUserResourceBase
SCloudgroupResourceBase
SCloudaccountResourceBase
}
func (manager *SSamluserManager) GetResourceCount() ([]db.SScopeResourceCount, error) {
q := manager.Query()
domainCnt, err := db.CalculateResourceCount(q, "domain_id")
if err != nil {
return nil, errors.Wrap(err, "CalculateResourceCount.domain_id")
}
q = manager.Query()
userCnt, err := db.CalculateResourceCount(q, "owner_id")
if err != nil {
return nil, errors.Wrap(err, "CalculateResourceCount.owner_id")
}
return append(domainCnt, userCnt...), nil
}
func (manager *SSamluserManager) GetIVirtualModelManager() db.IVirtualModelManager {
return manager.GetVirtualObject().(db.IVirtualModelManager)
}
func (manager *SSamluserManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject {
groupId, _ := data.GetString("cloudgroup_id")
accountId, _ := data.GetString("cloudaccount_id")
return jsonutils.Marshal(map[string]string{"cloudgroup_id": groupId, "cloudaccount_id": accountId})
}
func (manager *SSamluserManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery {
groupId, _ := values.GetString("cloudgroup_id")
accountId, _ := values.GetString("cloudaccount_id")
if len(groupId) > 0 {
q = q.Equals("cloudgroup_id", groupId)
}
if len(accountId) > 0 {
q = q.Equals("cloudaccount_id", accountId)
}
return q
}
// SAML认证用户列表
func (manager *SSamluserManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.SamluserListInput) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SStatusDomainLevelUserResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusDomainLevelUserResourceListInput)
if err != nil {
return nil, err
}
q, err = manager.SCloudgroupResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudgroupResourceListInput)
if err != nil {
return nil, err
}
q, err = manager.SCloudaccountResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudaccountResourceListInput)
if err != nil {
return nil, err
}
return q, nil
}
// 创建SAML认证用户
func (manager *SSamluserManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.SamluserCreateInput) (api.SamluserCreateInput, error) {
if len(input.OwnerId) > 0 {
user, err := db.UserCacheManager.FetchUserById(ctx, input.OwnerId)
if err != nil {
return input, errors.Wrapf(err, "FetchUserById")
}
input.OwnerId = user.Id
input.Name = user.Name
} else {
input.OwnerId = userCred.GetUserId()
input.Name = userCred.GetUserName()
}
_group, err := validators.ValidateModel(userCred, CloudgroupManager, &input.CloudgroupId)
if err != nil {
return input, err
}
group := _group.(*SCloudgroup)
sq := CloudgroupManager.Query("id").Equals("provider", group.Provider).SubQuery()
q := manager.Query().Equals("owner_id", input.OwnerId).In("cloudgroup_id", sq)
groups := []SCloudgroup{}
err = db.FetchModelObjects(CloudgroupManager, q, &groups)
if err != nil {
return input, httperrors.NewGeneralError(errors.Wrapf(err, "db.FetchModelObjects"))
}
if len(groups) > 0 {
return input, httperrors.NewConflictError("user %s has already in other %s group", input.Name, group.Provider)
}
_account, err := validators.ValidateModel(userCred, CloudaccountManager, &input.CloudaccountId)
if err != nil {
return input, err
}
account := _account.(*SCloudaccount)
if account.Provider != group.Provider {
return input, httperrors.NewConflictError("account %s and group %s not with same provider %s", account.Name, group.Name)
}
input.Status = api.SAML_USER_STATUS_AVAILABLE
return input, nil
}
func (manager *SSamluserManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.SamluserDetails {
rows := make([]api.SamluserDetails, len(objs))
userRows := manager.SStatusDomainLevelUserResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
groupRows := manager.SCloudgroupResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
acRows := manager.SCloudaccountResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for i := range rows {
rows[i] = api.SamluserDetails{
StatusDomainLevelUserResourceDetails: userRows[i],
CloudgroupResourceDetails: groupRows[i],
CloudaccountResourceDetails: acRows[i],
}
}
return rows
}
+2
View File
@@ -23,8 +23,10 @@ type SCloudIdOptions struct {
common_options.DBOptions
CloudaccountSyncIntervalMinutes int `help:"frequency to sync region cloudaccount task" default:"3"`
SAMLProviderSyncIntervalHours int `help:"frequency to sync account saml provider task" default:"3"`
SystemPoliciesSyncIntervalHours int `help:"frequency to sync region cloudaccount task" default:"24"`
CloudIdResourceSyncIntervalHours int `help:"frequency to sync region cloudpolicy task" default:"3"`
CloudroleSyncIntervalHours int `help:"frequency to sync region cloudroles task" default:"12"`
CloudSAMLMetadataPath string `help:"path to store SAML sp metadata file of cloud providers" default:"/opt/yunion/share/saml/sp-metadata"`
}
+106
View File
@@ -13,3 +13,109 @@
// limitations under the License.
package saml // import "yunion.io/x/onecloud/pkg/cloudid/saml"
/*
+-----------------+ +----------------+
| CloudId Service | | Region Service |
+-----------------+ +----------------+
+----------+
|EnableSaml|
+-------------------+ +------------+ +----------+
| Cloudaccount | |Cloudaccount|
| (enabled|disable) | +------------+ +-----------+
+----________-------+ |DisableSaml|
_______/ \_______ +-----------+
___/ \____
+--------------+ +--------------+
| SamlProvider | | SamlProvider |
| (available) | | (not match) |
+--------------+ +--------------+
Saml Check CronJob Task
+-------+
| Start |
+---|---+
|
|
v
+------------------------+ Yes +----------------------------------------+ No +---------------------+
| Is account enable saml | ----------------> | Is account has available saml provider |----------------> | Create saml provider|
+------------------------+ +----------------------------------------+ +---------------------+
| | |
| No | Yes |
| | |
v | |
+------+ | |
| End | <-----------------------------------------------<----------------------------------------------------
+------+
Saml Auth Login
+-------+
| Start |
+-------+
|
|
v
+-----------------------+ Yes +-----------------------------------------------+
|Is account enable saml |-------------> |Prepare tmp Role and set expired time for user |
+-----------------------+ +-----------------------------------------------+
| No |
| |
| |
| v
| +------------------+
| | Auth for console |
| +------------------+
| |
| |
v |
+------+ |
| End |<----------------------------------------------
+------+
*/
+6
View File
@@ -20,6 +20,7 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudid/options"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/util/samlutils"
@@ -55,6 +56,7 @@ func initSAMLInstance() error {
return errors.Wrap(err, "samlutils.NewSAMLInstance")
}
models.SamlIdpInstance = SAMLIdpInstance
return nil
}
@@ -65,6 +67,10 @@ func SAMLInstance() *samlutils.SSAMLInstance {
return saml
}
func SAMLIdpInstance() *idp.SSAMLIdpInstance {
return idpInstance
}
func IsSAMLEnabled() bool {
return saml != nil
}
+6 -6
View File
@@ -24,8 +24,8 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudid/options"
"yunion.io/x/onecloud/pkg/cloudid/saml/providers"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/util/httputils"
@@ -38,11 +38,11 @@ func initSAMLIdp(app *appsrv.Application, prefix string) error {
token := auth.FetchUserCredential(ctx, nil)
log.Debugf("Recive SP initiated Login: %s", sp.GetEntityId())
data := samlutils.SSAMLSpInitiatedLoginData{}
driver := providers.FindDriver(sp.GetEntityId())
driver := models.FindDriver(sp.GetEntityId())
if driver == nil {
return data, errors.Wrapf(httperrors.ErrResourceNotFound, "entityID %s not found", sp.GetEntityId())
}
data, err := driver.GetSpInitiatedLoginData(idpId, token.GetUserId(), sp)
data, err := driver.GetSpInitiatedLoginData(ctx, token, idpId, sp)
if err != nil {
return data, errors.Wrap(err, "driver.GetSpInitiatedLoginData")
}
@@ -62,11 +62,11 @@ func initSAMLIdp(app *appsrv.Application, prefix string) error {
token := auth.FetchUserCredential(ctx, nil)
log.Debugf("Recive IDP initiated Login: %s", sp.GetEntityId())
data := samlutils.SSAMLIdpInitiatedLoginData{}
driver := providers.FindDriver(sp.GetEntityId())
driver := models.FindDriver(sp.GetEntityId())
if driver == nil {
return data, errors.Wrapf(httperrors.ErrResourceNotFound, "entityID %s not found", sp.GetEntityId())
}
data, err := driver.GetIdpInitiatedLoginData(idpId, token.GetUserId(), sp)
data, err := driver.GetIdpInitiatedLoginData(ctx, token, idpId, sp)
if err != nil {
return data, errors.Wrap(err, "driver.GetIdpInitiatedLoginData")
}
@@ -78,7 +78,7 @@ func initSAMLIdp(app *appsrv.Application, prefix string) error {
}
idpInst := idp.NewIdpInstance(saml, spFunc, idpFunc, logoutFunc)
for entityId, drvFactory := range providers.AllDrivers() {
for entityId, drvFactory := range models.AllDrivers() {
filePath := path.Join(options.Options.CloudSAMLMetadataPath, drvFactory.GetMetadataFilename())
metaBytes, err := ioutil.ReadFile(filePath)
if err != nil || len(metaBytes) == 0 {
+5 -2
View File
@@ -15,13 +15,16 @@
package aliyun
import (
"context"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/samlutils"
"yunion.io/x/onecloud/pkg/util/samlutils/idp"
)
func (d *SAliyunSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
func (d *SAliyunSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
// TODO
data := samlutils.SSAMLIdpInitiatedLoginData{}
data.NameId = "ecsossreadonly"
@@ -41,7 +44,7 @@ func (d *SAliyunSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userI
return data, nil
}
func (d *SAliyunSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
func (d *SAliyunSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
// not supported
return samlutils.SSAMLSpInitiatedLoginData{}, errors.ErrNotSupported
}
+2 -2
View File
@@ -15,7 +15,7 @@
package aliyun
import (
"yunion.io/x/onecloud/pkg/cloudid/saml/providers"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
@@ -34,5 +34,5 @@ func (d *SAliyunSAMLDriver) GetMetadataUrl() string {
}
func init() {
providers.Register(&SAliyunSAMLDriver{})
models.Register(&SAliyunSAMLDriver{})
}
+39 -7
View File
@@ -15,17 +15,49 @@
package aws
import (
"context"
"database/sql"
"fmt"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/samlutils"
"yunion.io/x/onecloud/pkg/util/samlutils/idp"
)
func (d *SAWSSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
// TODO
func (d *SAWSSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
data := samlutils.SSAMLIdpInitiatedLoginData{}
data.NameId = "ec2s3readonly"
_account, err := models.CloudaccountManager.FetchById(cloudAccountId)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return data, httperrors.NewResourceNotFoundError("cloudaccount", cloudAccountId)
}
return data, httperrors.NewGeneralError(err)
}
account := _account.(*models.SCloudaccount)
if account.Provider != api.CLOUD_PROVIDER_AWS {
return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_AWS)
}
if account.SAMLAuth.IsFalse() {
return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id)
}
SAMLProvider, valid := account.IsSAMLProviderValid()
if !valid {
return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id)
}
role, err := account.SyncRole(userCred.GetUserId())
if err != nil {
return data, httperrors.NewGeneralError(errors.Wrapf(err, "SyncRole"))
}
data.NameId = userCred.GetUserName()
data.NameIdFormat = samlutils.NAME_ID_FORMAT_PERSISTENT
data.AudienceRestriction = "https://signin.aws.amazon.com/saml"
for _, v := range []struct {
@@ -36,17 +68,17 @@ func (d *SAWSSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId s
{
name: "https://aws.amazon.com/SAML/Attributes/Role",
friendlyName: "RoleEntitlement",
value: "arn:aws:iam::285906155448:role/ec2s3readonly,arn:aws:iam::285906155448:saml-provider/saml.yunion.cn",
value: fmt.Sprintf("%s,%s", role.ExternalId, SAMLProvider.ExternalId),
},
{
name: "https://aws.amazon.com/SAML/Attributes/RoleSessionName",
friendlyName: "RoleSessionName",
value: "ec2s3readonly",
value: userCred.GetUserId(),
},
{
name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.3",
friendlyName: "eduPersonOrgDN",
value: "ec2s3readonly",
value: userCred.GetUserName(),
},
} {
data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
@@ -60,7 +92,7 @@ func (d *SAWSSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId s
return data, nil
}
func (d *SAWSSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
func (d *SAWSSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
// not supported
return samlutils.SSAMLSpInitiatedLoginData{}, errors.ErrNotSupported
}
+2 -2
View File
@@ -15,7 +15,7 @@
package aws
import (
"yunion.io/x/onecloud/pkg/cloudid/saml/providers"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
@@ -34,5 +34,5 @@ func (d *SAWSSAMLDriver) GetMetadataUrl() string {
}
func init() {
providers.Register(&SAWSSAMLDriver{})
models.Register(&SAWSSAMLDriver{})
}
+38 -7
View File
@@ -15,17 +15,48 @@
package awscn
import (
"context"
"database/sql"
"fmt"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/samlutils"
"yunion.io/x/onecloud/pkg/util/samlutils/idp"
)
func (d *SAWSCNSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
// TODO
func (d *SAWSCNSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
data := samlutils.SSAMLIdpInitiatedLoginData{}
_account, err := models.CloudaccountManager.FetchById(cloudAccountId)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return data, httperrors.NewResourceNotFoundError("cloudaccount", cloudAccountId)
}
return data, httperrors.NewGeneralError(err)
}
account := _account.(*models.SCloudaccount)
if account.Provider != api.CLOUD_PROVIDER_AWS {
return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_AWS)
}
if account.SAMLAuth.IsFalse() {
return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id)
}
data.NameId = "ec2s3readonly"
SAMLProvider, valid := account.IsSAMLProviderValid()
if !valid {
return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id)
}
role, err := account.SyncRole(userCred.GetUserId())
if err != nil {
return data, httperrors.NewGeneralError(errors.Wrapf(err, "SyncRole"))
}
data.NameId = userCred.GetUserName()
data.NameIdFormat = samlutils.NAME_ID_FORMAT_PERSISTENT
data.AudienceRestriction = "https://signin.amazonaws.cn/saml"
for _, v := range []struct {
@@ -36,17 +67,17 @@ func (d *SAWSCNSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId
{
name: "https://aws.amazon.com/SAML/Attributes/Role",
friendlyName: "RoleEntitlement",
value: "arn:aws-cn:iam::248697896586:role/ec2s3readonly,arn:aws-cn:iam::248697896586:saml-provider/saml.yunion.io",
value: fmt.Sprintf("%s,%s", role.ExternalId, SAMLProvider.ExternalId),
},
{
name: "https://aws.amazon.com/SAML/Attributes/RoleSessionName",
friendlyName: "RoleSessionName",
value: "ec2s3readonly",
value: userCred.GetUserId(),
},
{
name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.3",
friendlyName: "eduPersonOrgDN",
value: "ec2s3readonly",
value: userCred.GetUserName(),
},
} {
data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
@@ -60,7 +91,7 @@ func (d *SAWSCNSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId
return data, nil
}
func (d *SAWSCNSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
func (d *SAWSCNSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
// not supported
return samlutils.SSAMLSpInitiatedLoginData{}, errors.ErrNotSupported
}
+2 -2
View File
@@ -15,7 +15,7 @@
package awscn
import (
"yunion.io/x/onecloud/pkg/cloudid/saml/providers"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
@@ -34,5 +34,5 @@ func (d *SAWSCNSAMLDriver) GetMetadataUrl() string {
}
func init() {
providers.Register(&SAWSCNSAMLDriver{})
models.Register(&SAWSCNSAMLDriver{})
}
+5 -2
View File
@@ -15,19 +15,22 @@
package google
import (
"context"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/samlutils"
"yunion.io/x/onecloud/pkg/util/samlutils/idp"
)
func (d *SGoogleSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
func (d *SGoogleSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
// not supported
data := samlutils.SSAMLIdpInitiatedLoginData{}
return data, httperrors.ErrNotSupported
}
func (d *SGoogleSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
func (d *SGoogleSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
// TODO
data := samlutils.SSAMLSpInitiatedLoginData{}
+2 -2
View File
@@ -15,7 +15,7 @@
package google
import (
"yunion.io/x/onecloud/pkg/cloudid/saml/providers"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
@@ -34,5 +34,5 @@ func (d *SGoogleSAMLDriver) GetMetadataUrl() string {
}
func init() {
providers.Register(&SGoogleSAMLDriver{})
models.Register(&SGoogleSAMLDriver{})
}
+43 -8
View File
@@ -15,33 +15,68 @@
package huawei
import (
"context"
"database/sql"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/samlutils"
"yunion.io/x/onecloud/pkg/util/samlutils/idp"
)
func (d *SHuaweiSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
func (d *SHuaweiSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
// not supported
data := samlutils.SSAMLIdpInitiatedLoginData{}
return data, httperrors.ErrNotSupported
}
func (d *SHuaweiSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
// TODO
func (d *SHuaweiSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
data := samlutils.SSAMLSpInitiatedLoginData{}
data.NameId = "yunionoss"
_account, err := models.CloudaccountManager.FetchById(cloudAccountId)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return data, httperrors.NewResourceNotFoundError("cloudaccount", cloudAccountId)
}
return data, httperrors.NewGeneralError(err)
}
account := _account.(*models.SCloudaccount)
if account.Provider != api.CLOUD_PROVIDER_HUAWEI {
return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_HUAWEI)
}
if account.SAMLAuth.IsFalse() {
return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id)
}
_, valid := account.IsSAMLProviderValid()
if !valid {
return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id)
}
groups, err := account.GetUserCloudgroups(userCred.GetUserId())
if err != nil {
return data, httperrors.NewGeneralError(errors.Wrapf(err, "GetUserCloudgroups"))
}
if len(groups) == 0 {
return data, httperrors.NewResourceNotFoundError("no available group found")
}
data.NameId = userCred.GetUserName()
data.NameIdFormat = samlutils.NAME_ID_FORMAT_TRANSIENT
data.AudienceRestriction = sp.GetEntityId()
for k, v := range map[string]string{
"User": "ec2admin",
"Group": "ec2admin",
for k, v := range map[string][]string{
"User": []string{userCred.GetUserName()},
"Groups": groups,
} {
data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
Name: k, FriendlyName: k,
NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
Values: []string{v},
Values: v,
})
}
+2 -2
View File
@@ -15,7 +15,7 @@
package huawei
import (
"yunion.io/x/onecloud/pkg/cloudid/saml/providers"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
@@ -34,5 +34,5 @@ func (d *SHuaweiSAMLDriver) GetMetadataUrl() string {
}
func init() {
providers.Register(&SHuaweiSAMLDriver{})
models.Register(&SHuaweiSAMLDriver{})
}
+71 -10
View File
@@ -15,15 +15,51 @@
package qcloud
import (
"context"
"database/sql"
"fmt"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/samlutils"
"yunion.io/x/onecloud/pkg/util/samlutils/idp"
)
func (d *SQcloudSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
// TODO
func (d *SQcloudSAMLDriver) GetIdpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
data := samlutils.SSAMLIdpInitiatedLoginData{}
data.NameId = "cvmcosreadonly"
_account, err := models.CloudaccountManager.FetchById(cloudAccountId)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return data, httperrors.NewResourceNotFoundError("cloudaccount", cloudAccountId)
}
return data, httperrors.NewGeneralError(err)
}
account := _account.(*models.SCloudaccount)
if account.Provider != api.CLOUD_PROVIDER_QCLOUD {
return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_QCLOUD)
}
if account.SAMLAuth.IsFalse() {
return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id)
}
SAMLProvider, valid := account.IsSAMLProviderValid()
if !valid {
return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id)
}
role, err := account.SyncRole(userCred.GetUserId())
if err != nil {
return data, httperrors.NewGeneralError(errors.Wrapf(err, "SyncRole"))
}
roleStr := fmt.Sprintf("qcs::cam::uin/%s:roleName/%s,qcs::cam::uin/%s:saml-provider/%s", account.AccountId, role.ExternalId, account.AccountId, SAMLProvider.ExternalId)
data.NameId = role.Name
data.NameIdFormat = samlutils.NAME_ID_FORMAT_TRANSIENT
data.AudienceRestriction = "https://cloud.tencent.com"
for _, v := range []struct {
@@ -34,12 +70,12 @@ func (d *SQcloudSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userI
{
name: "https://cloud.tencent.com/SAML/Attributes/Role",
friendlyName: "RoleEntitlement",
value: "qcs::cam::uin/100008182714:roleName/cvmcosreadonly,qcs::cam::uin/100008182714:saml-provider/saml.yunion.io",
value: roleStr,
},
{
name: "https://cloud.tencent.com/SAML/Attributes/RoleSessionName",
friendlyName: "RoleSessionName",
value: "cvmcosreadonly",
value: role.Name,
},
} {
data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
@@ -53,11 +89,36 @@ func (d *SQcloudSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userI
return data, nil
}
func (d *SQcloudSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
// not supported
func (d *SQcloudSAMLDriver) GetSpInitiatedLoginData(ctx context.Context, userCred mcclient.TokenCredential, cloudAccountId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
data := samlutils.SSAMLSpInitiatedLoginData{}
_account, err := models.CloudaccountManager.FetchById(cloudAccountId)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return data, httperrors.NewResourceNotFoundError("cloudaccount", cloudAccountId)
}
return data, httperrors.NewGeneralError(err)
}
account := _account.(*models.SCloudaccount)
if account.Provider != api.CLOUD_PROVIDER_QCLOUD {
return data, httperrors.NewClientError("cloudaccount %s is %s not %s", account.Id, account.Provider, api.CLOUD_PROVIDER_QCLOUD)
}
if account.SAMLAuth.IsFalse() {
return data, httperrors.NewNotSupportedError("cloudaccount %s not open saml auth", account.Id)
}
data.NameId = "cvmcosreadonly"
SAMLProvider, valid := account.IsSAMLProviderValid()
if !valid {
return data, httperrors.NewResourceNotReadyError("SAMLProvider for account %s not ready", account.Id)
}
role, err := account.SyncRole(userCred.GetUserId())
if err != nil {
return data, httperrors.NewGeneralError(errors.Wrapf(err, "SyncRole"))
}
roleStr := fmt.Sprintf("qcs::cam::uin/%s:roleName/%s,qcs::cam::uin/%s:saml-provider/%s", account.AccountId, role.ExternalId, account.AccountId, SAMLProvider.ExternalId)
data.NameId = role.Name
data.NameIdFormat = samlutils.NAME_ID_FORMAT_TRANSIENT
data.AudienceRestriction = "https://cloud.tencent.com"
for _, v := range []struct {
@@ -68,12 +129,12 @@ func (d *SQcloudSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId
{
name: "https://cloud.tencent.com/SAML/Attributes/Role",
friendlyName: "RoleEntitlement",
value: "qcs::cam::uin/100008182714:roleName/cvmcosreadonly,qcs::cam::uin/100008182714:saml-provider/saml.yunion.io",
value: roleStr,
},
{
name: "https://cloud.tencent.com/SAML/Attributes/RoleSessionName",
friendlyName: "RoleSessionName",
value: "cvmcosreadonly",
value: role.Name,
},
} {
data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
+2 -2
View File
@@ -15,7 +15,7 @@
package qcloud
import (
"yunion.io/x/onecloud/pkg/cloudid/saml/providers"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
@@ -34,5 +34,5 @@ func (d *SQcloudSAMLDriver) GetMetadataUrl() string {
}
func init() {
providers.Register(&SQcloudSAMLDriver{})
models.Register(&SQcloudSAMLDriver{})
}
+3
View File
@@ -65,6 +65,9 @@ func InitHandlers(app *appsrv.Application) {
models.CloudgroupcacheManager,
models.CloudpolicyManager,
models.CloudpolicycacheManager,
models.SAMLProviderManager,
models.CloudroleManager,
models.SamluserManager,
} {
db.RegisterModelManager(manager)
handler := db.NewModelHandler(manager)
+2
View File
@@ -61,8 +61,10 @@ func StartService() {
if !opts.IsSlaveNode {
cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount)
cron.AddJobAtIntervalsWithStartRun("SyncCloudaccounts", time.Duration(opts.CloudaccountSyncIntervalMinutes)*time.Minute, models.CloudaccountManager.SyncCloudaccounts, true)
cron.AddJobAtIntervalsWithStartRun("SyncSAMLProviders", time.Duration(opts.SAMLProviderSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncSAMLProviders, true)
cron.AddJobAtIntervalsWithStartRun("SyncSystemCloudpolicies", time.Duration(opts.SystemPoliciesSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncCloudidSystemPolicies, true)
cron.AddJobAtIntervalsWithStartRun("SyncCloudIdResources", time.Duration(opts.CloudIdResourceSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncCloudidResources, true)
cron.AddJobAtIntervalsWithStartRun("SyncCloudroles", time.Duration(opts.CloudroleSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncCloudroles, true)
cron.Start()
defer cron.Stop()
}
@@ -0,0 +1,82 @@
// 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"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/cloudid"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type CloudroleDeleteTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(CloudroleDeleteTask{})
}
func (self *CloudroleDeleteTask) taskFailed(ctx context.Context, role *models.SCloudrole, err error) {
role.SetStatus(self.GetUserCred(), api.CLOUD_ROLE_STATUS_DELETE_FAILED, err.Error())
logclient.AddActionLogWithStartable(self, role, logclient.ACT_DELETE, err, self.UserCred, false)
self.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
}
func (self *CloudroleDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
role := obj.(*models.SCloudrole)
isPurge := jsonutils.QueryBoolean(self.Params, "purge", false)
if len(role.ExternalId) == 0 || isPurge {
role.RealDelete(ctx, self.GetUserCred())
self.SetStageComplete(ctx, nil)
return
}
account, err := role.GetCloudaccount()
if err != nil {
self.taskFailed(ctx, role, errors.Wrapf(err, "GetCloudaccount"))
return
}
provider, err := account.GetProvider()
if err != nil {
self.taskFailed(ctx, role, errors.Wrapf(err, "GetProvider"))
return
}
iRole, err := provider.GetICloudroleById(role.ExternalId)
if err != nil {
if errors.Cause(err) == cloudprovider.ErrNotFound {
role.RealDelete(ctx, self.GetUserCred())
self.SetStageComplete(ctx, nil)
return
}
self.taskFailed(ctx, role, errors.Wrapf(err, "GetICloudroleById(%s)", role.ExternalId))
return
}
err = iRole.Delete()
if err != nil {
self.taskFailed(ctx, role, errors.Wrapf(err, "iRole.Delete"))
return
}
self.SetStageComplete(ctx, nil)
}
@@ -0,0 +1,92 @@
// 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"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/cloudid"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type SAMLProviderCreateTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(SAMLProviderCreateTask{})
}
func (self *SAMLProviderCreateTask) taskFailed(ctx context.Context, saml *models.SSAMLProvider, err error) {
saml.SetStatus(self.GetUserCred(), api.SAML_PROVIDER_STATUS_CREATE_FAILED, err.Error())
logclient.AddActionLogWithStartable(self, saml, logclient.ACT_CREATE, err, self.UserCred, false)
self.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
}
func (self *SAMLProviderCreateTask) taskComplete(ctx context.Context, saml *models.SSAMLProvider) {
self.SetStageComplete(ctx, nil)
}
func (self *SAMLProviderCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
saml := obj.(*models.SSAMLProvider)
metadata, err := saml.GetMetadataDocument()
if err != nil {
self.taskFailed(ctx, saml, errors.Wrapf(err, "GetMetadataDocument"))
return
}
account, err := saml.GetCloudaccount()
if err != nil {
self.taskFailed(ctx, saml, errors.Wrapf(err, "GetCloudaccount"))
return
}
provider, err := account.GetProvider()
if err != nil {
self.taskFailed(ctx, saml, errors.Wrap(err, "GetProvider"))
return
}
opts := cloudprovider.SAMLProviderCreateOptions{
Name: saml.Name,
Metadata: metadata,
}
lockman.LockRawObject(ctx, saml.CloudaccountId, "saml-provider")
defer lockman.ReleaseRawObject(ctx, saml.CloudaccountId, "saml-provider")
iSAMLProvider, err := provider.CreateICloudSAMLProvider(&opts)
if err != nil {
self.taskFailed(ctx, saml, errors.Wrapf(err, "CreateICloudSAMLProvider"))
return
}
err = saml.SyncWithCloudSAMLProvider(ctx, self.GetUserCred(), iSAMLProvider)
if err != nil {
self.taskFailed(ctx, saml, errors.Wrap(err, "SyncWithCloudSAMLProvider"))
return
}
self.SetStageComplete(ctx, nil)
}
@@ -0,0 +1,85 @@
// 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"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/cloudid"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type SAMLProviderDeleteTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(SAMLProviderDeleteTask{})
}
func (self *SAMLProviderDeleteTask) taskFailed(ctx context.Context, saml *models.SSAMLProvider, err error) {
saml.SetStatus(self.GetUserCred(), api.SAML_PROVIDER_STATUS_DELETE_FAILED, err.Error())
logclient.AddActionLogWithStartable(self, saml, logclient.ACT_DELETE, err, self.UserCred, false)
self.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
}
func (self *SAMLProviderDeleteTask) taskComplete(ctx context.Context, saml *models.SSAMLProvider) {
saml.RealDelete(ctx, self.GetUserCred())
self.SetStageComplete(ctx, nil)
}
func (self *SAMLProviderDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
saml := obj.(*models.SSAMLProvider)
if len(saml.ExternalId) == 0 {
self.taskComplete(ctx, saml)
return
}
account, err := saml.GetCloudaccount()
if err != nil {
self.taskFailed(ctx, saml, errors.Wrapf(err, "GetCloudaccount"))
return
}
provider, err := account.GetProvider()
if err != nil {
self.taskFailed(ctx, saml, errors.Wrapf(err, "GetProvider"))
return
}
samls, err := provider.GetICloudSAMLProviders()
if err != nil {
self.taskFailed(ctx, saml, errors.Wrapf(err, "GetICloudSAMLProviders"))
return
}
for i := range samls {
if samls[i].GetGlobalId() == saml.ExternalId {
err = samls[i].Delete()
if err != nil {
self.taskFailed(ctx, saml, errors.Wrapf(err, "Delete"))
return
}
}
}
self.taskComplete(ctx, saml)
}
+59
View File
@@ -0,0 +1,59 @@
// 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"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudid/models"
)
type SyncCloudrolesTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(SyncCloudrolesTask{})
}
func (self *SyncCloudrolesTask) taskFailed(ctx context.Context, account *models.SCloudaccount, err error) {
self.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
}
func (self *SyncCloudrolesTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
account := obj.(*models.SCloudaccount)
provider, err := account.GetProvider()
if err != nil {
self.taskFailed(ctx, account, errors.Wrapf(err, "GetProvider"))
return
}
roles, err := provider.GetICloudroles()
if err != nil {
self.taskFailed(ctx, account, errors.Wrapf(err, "GetICloudroles"))
return
}
result := account.SyncCloudroles(ctx, self.GetUserCred(), roles)
log.Infof("SyncCloudroles for account %s(%s) result: %s", account.Name, account.Provider, result.Result())
self.SetStageComplete(ctx, nil)
}
@@ -0,0 +1,64 @@
// 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"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudid/models"
)
type SyncSAMLProvidersTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(SyncSAMLProvidersTask{})
}
func (self *SyncSAMLProvidersTask) taskFailed(ctx context.Context, account *models.SCloudaccount, err error) {
log.Errorf("failed sync saml provider for account %s error: %v", account.Name, err)
self.SetStageFailed(ctx, nil)
}
func (self *SyncSAMLProvidersTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
account := obj.(*models.SCloudaccount)
provider, err := account.GetProvider()
if err != nil {
self.taskFailed(ctx, account, errors.Wrapf(err, "GetProvider"))
return
}
lockman.LockRawObject(ctx, account.Id, "saml-provider")
defer lockman.ReleaseRawObject(ctx, account.Id, "saml-provider")
samls, err := provider.GetICloudSAMLProviders()
if err != nil {
self.taskFailed(ctx, account, errors.Wrapf(err, "GetICloudSAMLProviders"))
return
}
result := account.SyncSAMLProviders(ctx, self.GetUserCred(), samls)
log.Infof("Sync SAMLProviders for account %s(%s) result: %s", account.Name, account.Provider, result.Result())
self.SetStageComplete(ctx, nil)
}