yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/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 huawei
    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/huawei/client/modules"
    30  	"yunion.io/x/onecloud/pkg/util/samlutils"
    31  )
    32  
    33  type SAMLProviderLinks struct {
    34  	Self      string
    35  	Protocols string
    36  }
    37  
    38  type SAMLProvider struct {
    39  	multicloud.SResourceBase
    40  	HuaweiTags
    41  	client *SHuaweiClient
    42  
    43  	Id          string
    44  	Links       SAMLProviderLinks
    45  	Description string
    46  }
    47  
    48  func (self *SAMLProvider) GetId() string {
    49  	return self.Id
    50  }
    51  
    52  func (self *SAMLProvider) GetGlobalId() string {
    53  	return self.Id
    54  }
    55  
    56  func (self *SAMLProvider) GetName() string {
    57  	return self.Id
    58  }
    59  
    60  func (self *SAMLProvider) GetStatus() string {
    61  	mapping, _ := self.client.findMapping()
    62  	if mapping != nil {
    63  		return api.SAML_PROVIDER_STATUS_AVAILABLE
    64  	}
    65  	return api.SAML_PROVIDER_STATUS_UNVALIABLE
    66  }
    67  
    68  func (self *SAMLProvider) GetAuthUrl() string {
    69  	return fmt.Sprintf("https://auth.huaweicloud.com/authui/federation/websso?domain_id=%s&idp=%s&protocol=saml", self.client.ownerId, self.Id)
    70  }
    71  
    72  func (self *SAMLProvider) Delete() error {
    73  	return self.client.DeleteSAMLProvider(self.Id)
    74  }
    75  
    76  func (self *SAMLProvider) GetMetadataDocument() (*samlutils.EntityDescriptor, error) {
    77  	info, err := self.client.GetSAMLProviderMetadata(self.Id)
    78  	if err != nil {
    79  		return nil, errors.Wrapf(err, "GetSAMLProviderMetadata(%s)", self.Id)
    80  	}
    81  	metadata, err := samlutils.ParseMetadata([]byte(info.Data))
    82  	if err != nil {
    83  		return nil, errors.Wrapf(err, "ParseMetadata")
    84  	}
    85  	return &metadata, nil
    86  }
    87  
    88  func (self *SAMLProvider) UpdateMetadata(metadata samlutils.EntityDescriptor) error {
    89  	return self.client.UpdateSAMLProviderMetadata(self.Id, metadata.String())
    90  }
    91  
    92  func (self *SHuaweiClient) ListSAMLProviders() ([]SAMLProvider, error) {
    93  	client, err := self.newGeneralAPIClient()
    94  	if err != nil {
    95  		return nil, errors.Wrapf(err, "newGeneralAPIClient")
    96  	}
    97  	samls := []SAMLProvider{}
    98  	err = doListAllWithNextLink(client.SAMLProviders.List, nil, &samls)
    99  	if err != nil {
   100  		return nil, errors.Wrapf(err, "doListAll")
   101  	}
   102  	return samls, nil
   103  }
   104  
   105  type SAMLProviderProtocol struct {
   106  	MappingId string
   107  	Id        string
   108  }
   109  
   110  func (self *SHuaweiClient) GetSAMLProviderProtocols(id string) ([]SAMLProviderProtocol, error) {
   111  	client, err := self.newGeneralAPIClient()
   112  	if err != nil {
   113  		return nil, errors.Wrap(err, "newGeneralAPIClient")
   114  	}
   115  	resp, err := client.SAMLProviders.ListInContextWithSpec(nil, fmt.Sprintf("%s/protocols", id), nil, "protocols")
   116  	if err != nil {
   117  		return nil, errors.Wrapf(err, "ListInContextWithSpec")
   118  	}
   119  	protocols := []SAMLProviderProtocol{}
   120  	return protocols, jsonutils.Update(&protocols, resp.Data)
   121  }
   122  
   123  func (self *SHuaweiClient) DeleteSAMLProviderProtocol(spId, id string) error {
   124  	client, err := self.newGeneralAPIClient()
   125  	if err != nil {
   126  		return errors.Wrap(err, "newGeneralAPIClient")
   127  	}
   128  	_, err = client.SAMLProviders.DeleteInContextWithSpec(nil, spId, fmt.Sprintf("protocols/%s", id), nil, nil, "")
   129  	return err
   130  }
   131  
   132  type SAMLProviderMetadata struct {
   133  	DomainId     string
   134  	UpdateTime   time.Time
   135  	Data         string
   136  	IdpId        string
   137  	ProtocolId   string
   138  	Id           string
   139  	EntityId     string
   140  	XaccountType string
   141  }
   142  
   143  func (self *SHuaweiClient) GetSAMLProviderMetadata(id string) (*SAMLProviderMetadata, error) {
   144  	client, err := self.newGeneralAPIClient()
   145  	if err != nil {
   146  		return nil, errors.Wrap(err, "newGeneralAPIClient")
   147  	}
   148  	client.SAMLProviders.SetVersion("v3-ext/OS-FEDERATION")
   149  	resp, err := client.SAMLProviders.GetInContextWithSpec(nil, id, fmt.Sprintf("protocols/saml/metadata"), nil, "")
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	metadata := &SAMLProviderMetadata{}
   155  	err = resp.Unmarshal(metadata)
   156  	if err != nil {
   157  		return nil, errors.Wrap(err, "resp.Unmarshal")
   158  	}
   159  	return metadata, nil
   160  }
   161  
   162  func (self *SHuaweiClient) UpdateSAMLProviderMetadata(id, metadata string) error {
   163  	params := map[string]string{
   164  		"domain_id":     self.ownerId,
   165  		"xaccount_type": "",
   166  		"metadata":      metadata,
   167  	}
   168  	client, err := self.newGeneralAPIClient()
   169  	if err != nil {
   170  		return errors.Wrap(err, "newGeneralAPIClient")
   171  	}
   172  	client.SAMLProviders.SetVersion("v3-ext/OS-FEDERATION")
   173  	_, err = client.SAMLProviders.PerformAction2("protocols/saml/metadata", id, jsonutils.Marshal(params), "")
   174  	if err != nil {
   175  		return errors.Wrapf(err, "SAMLProvider.PerformAction")
   176  	}
   177  	return nil
   178  }
   179  
   180  func (self *SHuaweiClient) GetICloudSAMLProviders() ([]cloudprovider.ICloudSAMLProvider, error) {
   181  	samls, err := self.ListSAMLProviders()
   182  	if err != nil {
   183  		return nil, errors.Wrapf(err, "ListSAMLProviders")
   184  	}
   185  	ret := []cloudprovider.ICloudSAMLProvider{}
   186  	for i := range samls {
   187  		samls[i].client = self
   188  		ret = append(ret, &samls[i])
   189  	}
   190  	return ret, nil
   191  }
   192  
   193  func (self *SHuaweiClient) DeleteSAMLProvider(id string) error {
   194  	client, err := self.newGeneralAPIClient()
   195  	if err != nil {
   196  		return errors.Wrap(err, "newGeneralAPIClient")
   197  	}
   198  	_, err = client.SAMLProviders.Delete(id, nil)
   199  	return err
   200  }
   201  
   202  func (self *SHuaweiClient) CreateSAMLProvider(opts *cloudprovider.SAMLProviderCreateOptions) (*SAMLProvider, error) {
   203  	client, err := self.newGeneralAPIClient()
   204  	if err != nil {
   205  		return nil, errors.Wrap(err, "newGeneralAPIClient")
   206  	}
   207  	params := jsonutils.Marshal(map[string]interface{}{
   208  		"identity_provider": map[string]interface{}{
   209  			"description": opts.Name,
   210  			"enabled":     true,
   211  		},
   212  	})
   213  	name := []byte{}
   214  	for _, c := range opts.Name {
   215  		if unicode.IsLetter(c) || unicode.IsNumber(c) || c == '-' || c == '_' {
   216  			name = append(name, byte(c))
   217  		} else {
   218  			name = append(name, '-')
   219  		}
   220  	}
   221  	samlName := string(name)
   222  	err = func() error {
   223  		idx := 1
   224  		for {
   225  			_, err = client.SAMLProviders.Update(samlName, params)
   226  			if err == nil {
   227  				return nil
   228  			}
   229  			he, ok := err.(*modules.HuaweiClientError)
   230  			if !ok {
   231  				return errors.Wrapf(err, "SAMLProviders.Update")
   232  			}
   233  			if he.Code != 409 {
   234  				return errors.Wrapf(err, "SAMLProviders.Update")
   235  			}
   236  			samlName = fmt.Sprintf("%s-%d", string(name), idx)
   237  			idx++
   238  			if idx >= 40 {
   239  				break
   240  			}
   241  		}
   242  		return err
   243  	}()
   244  	if err != nil {
   245  		return nil, errors.Wrapf(err, "saml provider create")
   246  	}
   247  	ret := SAMLProvider{client: self, Id: samlName}
   248  	err = self.UpdateSAMLProviderMetadata(samlName, opts.Metadata.String())
   249  	if err != nil {
   250  		return nil, errors.Wrapf(err, "resp.Unmarshal")
   251  	}
   252  	err = self.InitSAMLProviderMapping(samlName)
   253  	if err != nil {
   254  		return nil, errors.Wrapf(err, "InitSAMLProviderMapping")
   255  	}
   256  	return &ret, nil
   257  }
   258  
   259  type SAMLProviderMapping struct {
   260  	Id    string
   261  	Rules jsonutils.JSONObject
   262  }
   263  
   264  var (
   265  	onecloudMappingRules = jsonutils.Marshal(map[string]interface{}{
   266  		"rules": []map[string]interface{}{
   267  			{
   268  				"remote": []map[string]interface{}{
   269  					{
   270  						"type": "User",
   271  					},
   272  					{
   273  						"type": "Groups",
   274  					},
   275  				},
   276  				"local": []map[string]interface{}{
   277  					{
   278  						"groups": "{1}",
   279  						"user":   map[string]string{"name": "{0}"},
   280  					},
   281  				},
   282  			},
   283  		},
   284  	})
   285  )
   286  
   287  func (self *SHuaweiClient) ListSAMLProviderMappings() ([]SAMLProviderMapping, error) {
   288  	client, err := self.newGeneralAPIClient()
   289  	if err != nil {
   290  		return nil, errors.Wrap(err, "newGeneralAPIClient")
   291  	}
   292  	mappings := []SAMLProviderMapping{}
   293  	err = doListAllWithNextLink(client.SAMLProviderMappings.List, nil, &mappings)
   294  	if err != nil {
   295  		return nil, err
   296  	}
   297  	return mappings, nil
   298  }
   299  
   300  func (self *SHuaweiClient) findMapping() (*SAMLProviderMapping, error) {
   301  	mappings, err := self.ListSAMLProviderMappings()
   302  	if err != nil {
   303  		return nil, errors.Wrapf(err, "ListSAMLProviderMappings")
   304  	}
   305  	for i := range mappings {
   306  		if jsonutils.Marshal(map[string]interface{}{"rules": mappings[i].Rules}).Equals(jsonutils.Marshal(onecloudMappingRules)) {
   307  			return &mappings[i], nil
   308  		}
   309  	}
   310  	return nil, cloudprovider.ErrNotFound
   311  }
   312  
   313  func (self *SHuaweiClient) InitSAMLProviderMapping(spId string) error {
   314  	client, err := self.newGeneralAPIClient()
   315  	if err != nil {
   316  		return errors.Wrap(err, "newGeneralAPIClient")
   317  	}
   318  
   319  	mapping, err := self.findMapping()
   320  	if err != nil {
   321  		if errors.Cause(err) != cloudprovider.ErrNotFound {
   322  			return errors.Wrapf(err, "findMapping")
   323  		}
   324  		mappingId := stringutils.UUID4()
   325  		params := map[string]interface{}{
   326  			"mapping": onecloudMappingRules,
   327  		}
   328  		_, err = client.SAMLProviderMappings.Update(mappingId, jsonutils.Marshal(params))
   329  		if err != nil {
   330  			return errors.Wrapf(err, "create mapping")
   331  		}
   332  		mapping = &SAMLProviderMapping{
   333  			Id:    mappingId,
   334  			Rules: onecloudMappingRules,
   335  		}
   336  	}
   337  	protocols, err := self.GetSAMLProviderProtocols(spId)
   338  	if err != nil {
   339  		return errors.Wrapf(err, "GetSAMLProviderProtocols")
   340  	}
   341  	params := map[string]interface{}{
   342  		"protocol": map[string]string{
   343  			"mapping_id": mapping.Id,
   344  		},
   345  	}
   346  	for i := range protocols {
   347  		if protocols[i].Id == "saml" {
   348  			if protocols[i].MappingId == mapping.Id {
   349  				return nil
   350  			}
   351  			_, err = client.SAMLProviders.PatchInContextWithSpec(nil, spId, "protocols/saml", jsonutils.Marshal(params), "")
   352  			return err
   353  		}
   354  	}
   355  	_, err = client.SAMLProviders.UpdateInContextWithSpec(nil, spId, "protocols/saml", jsonutils.Marshal(params), "")
   356  	return err
   357  }
   358  
   359  func (self *SHuaweiClient) DeleteSAMLProviderMapping(id string) error {
   360  	client, err := self.newGeneralAPIClient()
   361  	if err != nil {
   362  		return errors.Wrap(err, "newGeneralAPIClient")
   363  	}
   364  
   365  	_, err = client.SAMLProviderMappings.Delete(id, nil)
   366  	return err
   367  }