yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcso/saml_provider.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package hcso
    16  
    17  import (
    18  	"fmt"
    19  	"time"
    20  	"unicode"
    21  
    22  	"yunion.io/x/jsonutils"
    23  	"yunion.io/x/pkg/errors"
    24  	"yunion.io/x/pkg/util/stringutils"
    25  
    26  	api "yunion.io/x/cloudmux/pkg/apis/cloudid"
    27  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    28  	"yunion.io/x/cloudmux/pkg/multicloud"
    29  	"yunion.io/x/cloudmux/pkg/multicloud/hcso/client/modules"
    30  	"yunion.io/x/cloudmux/pkg/multicloud/huawei"
    31  	"yunion.io/x/onecloud/pkg/util/samlutils"
    32  )
    33  
    34  type SAMLProviderLinks struct {
    35  	Self      string
    36  	Protocols string
    37  }
    38  
    39  type SAMLProvider struct {
    40  	multicloud.SResourceBase
    41  	huawei.HuaweiTags
    42  	client *SHuaweiClient
    43  
    44  	Id          string
    45  	Links       SAMLProviderLinks
    46  	Description string
    47  }
    48  
    49  func (self *SAMLProvider) GetId() string {
    50  	return self.Id
    51  }
    52  
    53  func (self *SAMLProvider) GetGlobalId() string {
    54  	return self.Id
    55  }
    56  
    57  func (self *SAMLProvider) GetName() string {
    58  	return self.Id
    59  }
    60  
    61  func (self *SAMLProvider) GetStatus() string {
    62  	mapping, _ := self.client.findMapping()
    63  	if mapping != nil {
    64  		return api.SAML_PROVIDER_STATUS_AVAILABLE
    65  	}
    66  	return api.SAML_PROVIDER_STATUS_UNVALIABLE
    67  }
    68  
    69  func (self *SAMLProvider) GetAuthUrl() string {
    70  	return fmt.Sprintf("https://auth.%s/authui/federation/websso?domain_id=%s&idp=%s&protocol=saml", self.client.endpoints.EndpointDomain, self.client.ownerId, self.Id)
    71  }
    72  
    73  func (self *SAMLProvider) Delete() error {
    74  	return self.client.DeleteSAMLProvider(self.Id)
    75  }
    76  
    77  func (self *SAMLProvider) GetMetadataDocument() (*samlutils.EntityDescriptor, error) {
    78  	info, err := self.client.GetSAMLProviderMetadata(self.Id)
    79  	if err != nil {
    80  		return nil, errors.Wrapf(err, "GetSAMLProviderMetadata(%s)", self.Id)
    81  	}
    82  	metadata, err := samlutils.ParseMetadata([]byte(info.Data))
    83  	if err != nil {
    84  		return nil, errors.Wrapf(err, "ParseMetadata")
    85  	}
    86  	return &metadata, nil
    87  }
    88  
    89  func (self *SAMLProvider) UpdateMetadata(metadata samlutils.EntityDescriptor) error {
    90  	return self.client.UpdateSAMLProviderMetadata(self.Id, metadata.String())
    91  }
    92  
    93  func (self *SHuaweiClient) ListSAMLProviders() ([]SAMLProvider, error) {
    94  	client, err := self.newGeneralAPIClient()
    95  	if err != nil {
    96  		return nil, errors.Wrapf(err, "newGeneralAPIClient")
    97  	}
    98  	samls := []SAMLProvider{}
    99  	err = doListAllWithNextLink(client.SAMLProviders.List, nil, &samls)
   100  	if err != nil {
   101  		return nil, errors.Wrapf(err, "doListAll")
   102  	}
   103  	return samls, nil
   104  }
   105  
   106  type SAMLProviderProtocol struct {
   107  	MappingId string
   108  	Id        string
   109  }
   110  
   111  func (self *SHuaweiClient) GetSAMLProviderProtocols(id string) ([]SAMLProviderProtocol, error) {
   112  	client, err := self.newGeneralAPIClient()
   113  	if err != nil {
   114  		return nil, errors.Wrap(err, "newGeneralAPIClient")
   115  	}
   116  	resp, err := client.SAMLProviders.ListInContextWithSpec(nil, fmt.Sprintf("%s/protocols", id), nil, "protocols")
   117  	if err != nil {
   118  		return nil, errors.Wrapf(err, "ListInContextWithSpec")
   119  	}
   120  	protocols := []SAMLProviderProtocol{}
   121  	return protocols, jsonutils.Update(&protocols, resp.Data)
   122  }
   123  
   124  func (self *SHuaweiClient) DeleteSAMLProviderProtocol(spId, id string) error {
   125  	client, err := self.newGeneralAPIClient()
   126  	if err != nil {
   127  		return errors.Wrap(err, "newGeneralAPIClient")
   128  	}
   129  	_, err = client.SAMLProviders.DeleteInContextWithSpec(nil, spId, fmt.Sprintf("protocols/%s", id), nil, nil, "")
   130  	return err
   131  }
   132  
   133  type SAMLProviderMetadata struct {
   134  	DomainId     string
   135  	UpdateTime   time.Time
   136  	Data         string
   137  	IdpId        string
   138  	ProtocolId   string
   139  	Id           string
   140  	EntityId     string
   141  	XaccountType string
   142  }
   143  
   144  func (self *SHuaweiClient) GetSAMLProviderMetadata(id string) (*SAMLProviderMetadata, error) {
   145  	client, err := self.newGeneralAPIClient()
   146  	if err != nil {
   147  		return nil, errors.Wrap(err, "newGeneralAPIClient")
   148  	}
   149  	client.SAMLProviders.SetVersion("v3-ext/OS-FEDERATION")
   150  	resp, err := client.SAMLProviders.GetInContextWithSpec(nil, id, fmt.Sprintf("protocols/saml/metadata"), nil, "")
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	metadata := &SAMLProviderMetadata{}
   156  	err = resp.Unmarshal(metadata)
   157  	if err != nil {
   158  		return nil, errors.Wrap(err, "resp.Unmarshal")
   159  	}
   160  	return metadata, nil
   161  }
   162  
   163  func (self *SHuaweiClient) UpdateSAMLProviderMetadata(id, metadata string) error {
   164  	params := map[string]string{
   165  		"domain_id":     self.ownerId,
   166  		"xaccount_type": "",
   167  		"metadata":      metadata,
   168  	}
   169  	client, err := self.newGeneralAPIClient()
   170  	if err != nil {
   171  		return errors.Wrap(err, "newGeneralAPIClient")
   172  	}
   173  	client.SAMLProviders.SetVersion("v3-ext/OS-FEDERATION")
   174  	_, err = client.SAMLProviders.PerformAction2("protocols/saml/metadata", id, jsonutils.Marshal(params), "")
   175  	if err != nil {
   176  		return errors.Wrapf(err, "SAMLProvider.PerformAction")
   177  	}
   178  	return nil
   179  }
   180  
   181  func (self *SHuaweiClient) GetICloudSAMLProviders() ([]cloudprovider.ICloudSAMLProvider, error) {
   182  	samls, err := self.ListSAMLProviders()
   183  	if err != nil {
   184  		return nil, errors.Wrapf(err, "ListSAMLProviders")
   185  	}
   186  	ret := []cloudprovider.ICloudSAMLProvider{}
   187  	for i := range samls {
   188  		samls[i].client = self
   189  		ret = append(ret, &samls[i])
   190  	}
   191  	return ret, nil
   192  }
   193  
   194  func (self *SHuaweiClient) DeleteSAMLProvider(id string) error {
   195  	client, err := self.newGeneralAPIClient()
   196  	if err != nil {
   197  		return errors.Wrap(err, "newGeneralAPIClient")
   198  	}
   199  	_, err = client.SAMLProviders.Delete(id, nil)
   200  	return err
   201  }
   202  
   203  func (self *SHuaweiClient) CreateSAMLProvider(opts *cloudprovider.SAMLProviderCreateOptions) (*SAMLProvider, error) {
   204  	client, err := self.newGeneralAPIClient()
   205  	if err != nil {
   206  		return nil, errors.Wrap(err, "newGeneralAPIClient")
   207  	}
   208  	params := jsonutils.Marshal(map[string]interface{}{
   209  		"identity_provider": map[string]interface{}{
   210  			"description": opts.Name,
   211  			"enabled":     true,
   212  		},
   213  	})
   214  	opts.Name = fmt.Sprintf("%s-%s", self.ownerName, opts.Name)
   215  	name := []byte{}
   216  	for _, c := range opts.Name {
   217  		if unicode.IsLetter(c) || unicode.IsNumber(c) || c == '-' || c == '_' {
   218  			name = append(name, byte(c))
   219  		} else {
   220  			name = append(name, '-')
   221  		}
   222  	}
   223  	opts.Name = string(name)
   224  	_, err = client.SAMLProviders.Update(opts.Name, params)
   225  	if err != nil {
   226  		if he, ok := err.(*modules.HuaweiClientError); ok && he.Code != 409 {
   227  			return nil, errors.Wrapf(err, "SAMLProviders.Update")
   228  		}
   229  	}
   230  	ret := SAMLProvider{client: self, Id: opts.Name}
   231  	err = self.UpdateSAMLProviderMetadata(opts.Name, opts.Metadata.String())
   232  	if err != nil {
   233  		return nil, errors.Wrapf(err, "resp.Unmarshal")
   234  	}
   235  	err = self.InitSAMLProviderMapping(opts.Name)
   236  	if err != nil {
   237  		return nil, errors.Wrapf(err, "InitSAMLProviderMapping")
   238  	}
   239  	return &ret, nil
   240  }
   241  
   242  type SAMLProviderMapping struct {
   243  	Id    string
   244  	Rules jsonutils.JSONObject
   245  }
   246  
   247  var (
   248  	onecloudMappingRules = jsonutils.Marshal(map[string]interface{}{
   249  		"rules": []map[string]interface{}{
   250  			{
   251  				"remote": []map[string]interface{}{
   252  					{
   253  						"type": "User",
   254  					},
   255  					{
   256  						"type": "Groups",
   257  					},
   258  				},
   259  				"local": []map[string]interface{}{
   260  					{
   261  						"groups": "{1}",
   262  						"user":   map[string]string{"name": "{0}"},
   263  					},
   264  				},
   265  			},
   266  		},
   267  	})
   268  )
   269  
   270  func (self *SHuaweiClient) ListSAMLProviderMappings() ([]SAMLProviderMapping, error) {
   271  	client, err := self.newGeneralAPIClient()
   272  	if err != nil {
   273  		return nil, errors.Wrap(err, "newGeneralAPIClient")
   274  	}
   275  	mappings := []SAMLProviderMapping{}
   276  	err = doListAllWithNextLink(client.SAMLProviderMappings.List, nil, &mappings)
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  	return mappings, nil
   281  }
   282  
   283  func (self *SHuaweiClient) findMapping() (*SAMLProviderMapping, error) {
   284  	mappings, err := self.ListSAMLProviderMappings()
   285  	if err != nil {
   286  		return nil, errors.Wrapf(err, "ListSAMLProviderMappings")
   287  	}
   288  	for i := range mappings {
   289  		if jsonutils.Marshal(map[string]interface{}{"rules": mappings[i].Rules}).Equals(jsonutils.Marshal(onecloudMappingRules)) {
   290  			return &mappings[i], nil
   291  		}
   292  	}
   293  	return nil, cloudprovider.ErrNotFound
   294  }
   295  
   296  func (self *SHuaweiClient) InitSAMLProviderMapping(spId string) error {
   297  	client, err := self.newGeneralAPIClient()
   298  	if err != nil {
   299  		return errors.Wrap(err, "newGeneralAPIClient")
   300  	}
   301  
   302  	mapping, err := self.findMapping()
   303  	if err != nil {
   304  		if errors.Cause(err) != cloudprovider.ErrNotFound {
   305  			return errors.Wrapf(err, "findMapping")
   306  		}
   307  		mappingId := stringutils.UUID4()
   308  		params := map[string]interface{}{
   309  			"mapping": onecloudMappingRules,
   310  		}
   311  		_, err = client.SAMLProviderMappings.Update(mappingId, jsonutils.Marshal(params))
   312  		if err != nil {
   313  			return errors.Wrapf(err, "create mapping")
   314  		}
   315  		mapping = &SAMLProviderMapping{
   316  			Id:    mappingId,
   317  			Rules: onecloudMappingRules,
   318  		}
   319  	}
   320  	protocols, err := self.GetSAMLProviderProtocols(spId)
   321  	if err != nil {
   322  		return errors.Wrapf(err, "GetSAMLProviderProtocols")
   323  	}
   324  	params := map[string]interface{}{
   325  		"protocol": map[string]string{
   326  			"mapping_id": mapping.Id,
   327  		},
   328  	}
   329  	for i := range protocols {
   330  		if protocols[i].Id == "saml" {
   331  			if protocols[i].MappingId == mapping.Id {
   332  				return nil
   333  			}
   334  			_, err = client.SAMLProviders.PatchInContextWithSpec(nil, spId, "protocols/saml", jsonutils.Marshal(params), "")
   335  			return err
   336  		}
   337  	}
   338  	_, err = client.SAMLProviders.UpdateInContextWithSpec(nil, spId, "protocols/saml", jsonutils.Marshal(params), "")
   339  	return err
   340  }
   341  
   342  func (self *SHuaweiClient) DeleteSAMLProviderMapping(id string) error {
   343  	client, err := self.newGeneralAPIClient()
   344  	if err != nil {
   345  		return errors.Wrap(err, "newGeneralAPIClient")
   346  	}
   347  
   348  	_, err = client.SAMLProviderMappings.Delete(id, nil)
   349  	return err
   350  }