oidc.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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. package oidc
  15. import (
  16. "context"
  17. "fmt"
  18. "strings"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/log"
  21. "yunion.io/x/pkg/errors"
  22. api "yunion.io/x/onecloud/pkg/apis/identity"
  23. "yunion.io/x/onecloud/pkg/keystone/driver"
  24. "yunion.io/x/onecloud/pkg/keystone/models"
  25. "yunion.io/x/onecloud/pkg/keystone/options"
  26. "yunion.io/x/onecloud/pkg/mcclient"
  27. "yunion.io/x/onecloud/pkg/util/oidcutils"
  28. "yunion.io/x/onecloud/pkg/util/oidcutils/client"
  29. )
  30. // OpenID Connect client driver
  31. // https://openid.net/specs/openid-connect-basic-1_0.html
  32. // https://tools.ietf.org/html/rfc6749
  33. type SOIDCDriver struct {
  34. driver.SBaseIdentityDriver
  35. oidcConfig *api.SOIDCIdpConfigOptions
  36. isDebug bool
  37. }
  38. func NewOIDCDriver(idpId, idpName, template, targetDomainId string, conf api.TConfigs) (driver.IIdentityBackend, error) {
  39. base, err := driver.NewBaseIdentityDriver(idpId, idpName, template, targetDomainId, conf)
  40. if err != nil {
  41. return nil, errors.Wrap(err, "NewBaseIdentityDriver")
  42. }
  43. isDebug := false
  44. if options.Options.LogLevel == "debug" {
  45. isDebug = true
  46. }
  47. drv := SOIDCDriver{
  48. SBaseIdentityDriver: base,
  49. isDebug: isDebug,
  50. }
  51. drv.SetVirtualObject(&drv)
  52. err = drv.prepareConfig()
  53. if err != nil {
  54. return nil, errors.Wrap(err, "prepareConfig")
  55. }
  56. return &drv, nil
  57. }
  58. func (self *SOIDCDriver) prepareConfig() error {
  59. if self.oidcConfig == nil {
  60. confJson := jsonutils.Marshal(self.Config[api.IdentityDriverOIDC])
  61. conf := api.SOIDCIdpConfigOptions{}
  62. switch self.Template {
  63. case api.IdpTemplateDex:
  64. conf = DexOIDCTemplate
  65. case api.IdpTemplateGithub:
  66. conf = GithubOIDCTemplate
  67. case api.IdpTemplateGoogle:
  68. conf = GoogleOIDCTemplate
  69. case api.IdpTemplateAzureOAuth2:
  70. conf = AzureADTemplate
  71. tenantId, _ := confJson.GetString("tenant_id")
  72. if len(tenantId) == 0 {
  73. tenantId = "common"
  74. }
  75. cloudEnv, _ := confJson.GetString("cloud_env")
  76. loginUrl := "https://login.microsoftonline.com"
  77. graphUrl := "https://graph.microsoft.com"
  78. switch cloudEnv {
  79. case api.AZURE_CLOUD_ENV_CHINA:
  80. loginUrl = "https://login.partner.microsoftonline.cn"
  81. graphUrl = "https://microsoftgraph.chinacloudapi.cn"
  82. }
  83. conf.AuthUrl = fmt.Sprintf("%s/%s/oauth2/v2.0/authorize", loginUrl, tenantId)
  84. conf.TokenUrl = fmt.Sprintf("%s/%s/oauth2/v2.0/token", loginUrl, tenantId)
  85. conf.UserinfoUrl = fmt.Sprintf("%s/oidc/userinfo", graphUrl)
  86. }
  87. err := confJson.Unmarshal(&conf)
  88. if err != nil {
  89. return errors.Wrap(err, "json.Unmarshal")
  90. }
  91. log.Debugf("%s %s %#v", self.Config, confJson, self.oidcConfig)
  92. self.oidcConfig = &conf
  93. }
  94. return nil
  95. }
  96. func (self *SOIDCDriver) getOIDCClient(ctx context.Context) (*client.SOIDCClient, error) {
  97. timeout := self.oidcConfig.TimeoutSecs
  98. if timeout <= 0 {
  99. timeout = 30
  100. }
  101. cli := client.NewOIDCClient(self.oidcConfig.ClientId, self.oidcConfig.ClientSecret, timeout, self.isDebug)
  102. if len(self.oidcConfig.Endpoint) > 0 {
  103. err := cli.FetchConfiguration(ctx, self.oidcConfig.Endpoint)
  104. if err != nil {
  105. return nil, errors.Wrap(err, "FetchConfiguration")
  106. }
  107. } else {
  108. cli.SetConfig(self.oidcConfig.AuthUrl, self.oidcConfig.TokenUrl, self.oidcConfig.UserinfoUrl, self.oidcConfig.Scopes)
  109. }
  110. log.Debugf("Userinfo url: %s", cli.GetConfig().UserinfoEndpoint)
  111. return cli, nil
  112. }
  113. func (oidc *SOIDCDriver) GetSsoRedirectUri(ctx context.Context, callbackUrl, state string) (string, error) {
  114. cli, err := oidc.getOIDCClient(ctx)
  115. if err != nil {
  116. return "", errors.Wrap(err, "getOIDCClient")
  117. }
  118. conf := cli.GetConfig()
  119. qs := oidcutils.SOIDCAuthRequest{
  120. ResponseType: oidcutils.OIDC_RESPONSE_TYPE_CODE,
  121. ClientId: oidc.oidcConfig.ClientId,
  122. RedirectUri: callbackUrl,
  123. State: state,
  124. Scope: strings.Join(conf.ScopesSupported, " "),
  125. }
  126. urlstr := fmt.Sprintf("%s?%s", conf.AuthorizationEndpoint, jsonutils.Marshal(qs).QueryString())
  127. return urlstr, nil
  128. }
  129. func (self *SOIDCDriver) Authenticate(ctx context.Context, ident mcclient.SAuthenticationIdentity) (*api.SUserExtended, error) {
  130. cli, err := self.getOIDCClient(ctx)
  131. if err != nil {
  132. return nil, errors.Wrap(err, "getOIDCClient")
  133. }
  134. token, err := cli.FetchToken(ctx, ident.OIDCAuth.Code, ident.OIDCAuth.RedirectUri)
  135. if err != nil {
  136. return nil, errors.Wrapf(err, "OIDCClient.FetchToken %s", self.oidcConfig.TokenUrl)
  137. }
  138. userAttrs, err := cli.FetchUserInfo(ctx, token.AccessToken)
  139. if err != nil {
  140. return nil, errors.Wrap(err, "OIDCClient.FetchUserInfo")
  141. }
  142. log.Debugf("attrs: %s", userAttrs)
  143. attrs := make(map[string][]string)
  144. for k, v := range userAttrs {
  145. attrs[k] = []string{v}
  146. }
  147. var domainId, domainName, usrId, usrName string
  148. if v, ok := attrs[self.oidcConfig.DomainIdAttribute]; ok && len(v) > 0 {
  149. domainId = v[0]
  150. }
  151. if v, ok := attrs[self.oidcConfig.DomainNameAttribute]; ok && len(v) > 0 {
  152. domainName = v[0]
  153. }
  154. if v, ok := attrs[self.oidcConfig.UserIdAttribute]; ok && len(v) > 0 {
  155. usrId = v[0]
  156. }
  157. if v, ok := attrs[self.oidcConfig.UserNameAttribute]; ok && len(v) > 0 {
  158. usrName = v[0]
  159. }
  160. idp, err := models.IdentityProviderManager.FetchIdentityProviderById(self.IdpId)
  161. if err != nil {
  162. return nil, errors.Wrap(err, "self.GetIdentityProvider")
  163. }
  164. domain, usr, err := idp.SyncOrCreateDomainAndUser(ctx, domainId, domainName, usrId, usrName)
  165. if err != nil {
  166. return nil, errors.Wrap(err, "idp.SyncOrCreateDomainAndUser")
  167. }
  168. extUser, err := models.UserManager.FetchUserExtended(usr.Id, "", "", "")
  169. if err != nil {
  170. return nil, errors.Wrap(err, "models.UserManager.FetchUserExtended")
  171. }
  172. idp.TryUserJoinProject(self.oidcConfig.SIdpAttributeOptions, ctx, usr, domain.Id, attrs)
  173. extUser.AuditIds = []string{ident.OIDCAuth.Code}
  174. return extUser, nil
  175. }
  176. func (self *SOIDCDriver) Sync(ctx context.Context) error {
  177. return nil
  178. }
  179. func (self *SOIDCDriver) Probe(ctx context.Context) error {
  180. return nil
  181. }