mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-21 06:09:39 +08:00
203 lines
6.2 KiB
Go
203 lines
6.2 KiB
Go
// 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 oidc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
|
|
api "yunion.io/x/onecloud/pkg/apis/identity"
|
|
"yunion.io/x/onecloud/pkg/keystone/driver"
|
|
"yunion.io/x/onecloud/pkg/keystone/models"
|
|
"yunion.io/x/onecloud/pkg/keystone/options"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
"yunion.io/x/onecloud/pkg/util/oidcutils"
|
|
"yunion.io/x/onecloud/pkg/util/oidcutils/client"
|
|
)
|
|
|
|
// OpenID Connect client driver
|
|
// https://openid.net/specs/openid-connect-basic-1_0.html
|
|
// https://tools.ietf.org/html/rfc6749
|
|
type SOIDCDriver struct {
|
|
driver.SBaseIdentityDriver
|
|
|
|
oidcConfig *api.SOIDCIdpConfigOptions
|
|
|
|
isDebug bool
|
|
}
|
|
|
|
func NewOIDCDriver(idpId, idpName, template, targetDomainId string, conf api.TConfigs) (driver.IIdentityBackend, error) {
|
|
base, err := driver.NewBaseIdentityDriver(idpId, idpName, template, targetDomainId, conf)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "NewBaseIdentityDriver")
|
|
}
|
|
isDebug := false
|
|
if options.Options.LogLevel == "debug" {
|
|
isDebug = true
|
|
}
|
|
drv := SOIDCDriver{
|
|
SBaseIdentityDriver: base,
|
|
isDebug: isDebug,
|
|
}
|
|
drv.SetVirtualObject(&drv)
|
|
err = drv.prepareConfig()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "prepareConfig")
|
|
}
|
|
return &drv, nil
|
|
}
|
|
|
|
func (self *SOIDCDriver) prepareConfig() error {
|
|
if self.oidcConfig == nil {
|
|
confJson := jsonutils.Marshal(self.Config[api.IdentityDriverOIDC])
|
|
conf := api.SOIDCIdpConfigOptions{}
|
|
switch self.Template {
|
|
case api.IdpTemplateDex:
|
|
conf = DexOIDCTemplate
|
|
case api.IdpTemplateGithub:
|
|
conf = GithubOIDCTemplate
|
|
case api.IdpTemplateGoogle:
|
|
conf = GoogleOIDCTemplate
|
|
case api.IdpTemplateAzureOAuth2:
|
|
conf = AzureADTemplate
|
|
tenantId, _ := confJson.GetString("tenant_id")
|
|
if len(tenantId) == 0 {
|
|
tenantId = "common"
|
|
}
|
|
cloudEnv, _ := confJson.GetString("cloud_env")
|
|
loginUrl := "https://login.microsoftonline.com"
|
|
graphUrl := "https://graph.microsoft.com"
|
|
switch cloudEnv {
|
|
case api.AZURE_CLOUD_ENV_CHINA:
|
|
loginUrl = "https://login.partner.microsoftonline.cn"
|
|
graphUrl = "https://microsoftgraph.chinacloudapi.cn"
|
|
}
|
|
conf.AuthUrl = fmt.Sprintf("%s/%s/oauth2/v2.0/authorize", loginUrl, tenantId)
|
|
conf.TokenUrl = fmt.Sprintf("%s/%s/oauth2/v2.0/token", loginUrl, tenantId)
|
|
conf.UserinfoUrl = fmt.Sprintf("%s/oidc/userinfo", graphUrl)
|
|
}
|
|
err := confJson.Unmarshal(&conf)
|
|
if err != nil {
|
|
return errors.Wrap(err, "json.Unmarshal")
|
|
}
|
|
log.Debugf("%s %s %#v", self.Config, confJson, self.oidcConfig)
|
|
self.oidcConfig = &conf
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self *SOIDCDriver) getOIDCClient(ctx context.Context) (*client.SOIDCClient, error) {
|
|
timeout := self.oidcConfig.TimeoutSecs
|
|
if timeout <= 0 {
|
|
timeout = 30
|
|
}
|
|
cli := client.NewOIDCClient(self.oidcConfig.ClientId, self.oidcConfig.ClientSecret, timeout, self.isDebug)
|
|
if len(self.oidcConfig.Endpoint) > 0 {
|
|
err := cli.FetchConfiguration(ctx, self.oidcConfig.Endpoint)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "FetchConfiguration")
|
|
}
|
|
} else {
|
|
cli.SetConfig(self.oidcConfig.AuthUrl, self.oidcConfig.TokenUrl, self.oidcConfig.UserinfoUrl, self.oidcConfig.Scopes)
|
|
}
|
|
log.Debugf("Userinfo url: %s", cli.GetConfig().UserinfoEndpoint)
|
|
return cli, nil
|
|
}
|
|
|
|
func (oidc *SOIDCDriver) GetSsoRedirectUri(ctx context.Context, callbackUrl, state string) (string, error) {
|
|
cli, err := oidc.getOIDCClient(ctx)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "getOIDCClient")
|
|
}
|
|
conf := cli.GetConfig()
|
|
qs := oidcutils.SOIDCAuthRequest{
|
|
ResponseType: oidcutils.OIDC_RESPONSE_TYPE_CODE,
|
|
ClientId: oidc.oidcConfig.ClientId,
|
|
RedirectUri: callbackUrl,
|
|
State: state,
|
|
Scope: strings.Join(conf.ScopesSupported, " "),
|
|
}
|
|
urlstr := fmt.Sprintf("%s?%s", conf.AuthorizationEndpoint, jsonutils.Marshal(qs).QueryString())
|
|
return urlstr, nil
|
|
}
|
|
|
|
func (self *SOIDCDriver) Authenticate(ctx context.Context, ident mcclient.SAuthenticationIdentity) (*api.SUserExtended, error) {
|
|
cli, err := self.getOIDCClient(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getOIDCClient")
|
|
}
|
|
token, err := cli.FetchToken(ctx, ident.OIDCAuth.Code, ident.OIDCAuth.RedirectUri)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "OIDCClient.FetchToken %s", self.oidcConfig.TokenUrl)
|
|
}
|
|
userAttrs, err := cli.FetchUserInfo(ctx, token.AccessToken)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "OIDCClient.FetchUserInfo")
|
|
}
|
|
|
|
log.Debugf("attrs: %s", userAttrs)
|
|
|
|
attrs := make(map[string][]string)
|
|
for k, v := range userAttrs {
|
|
attrs[k] = []string{v}
|
|
}
|
|
|
|
var domainId, domainName, usrId, usrName string
|
|
if v, ok := attrs[self.oidcConfig.DomainIdAttribute]; ok && len(v) > 0 {
|
|
domainId = v[0]
|
|
}
|
|
if v, ok := attrs[self.oidcConfig.DomainNameAttribute]; ok && len(v) > 0 {
|
|
domainName = v[0]
|
|
}
|
|
if v, ok := attrs[self.oidcConfig.UserIdAttribute]; ok && len(v) > 0 {
|
|
usrId = v[0]
|
|
}
|
|
if v, ok := attrs[self.oidcConfig.UserNameAttribute]; ok && len(v) > 0 {
|
|
usrName = v[0]
|
|
}
|
|
|
|
idp, err := models.IdentityProviderManager.FetchIdentityProviderById(self.IdpId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "self.GetIdentityProvider")
|
|
}
|
|
domain, usr, err := idp.SyncOrCreateDomainAndUser(ctx, domainId, domainName, usrId, usrName)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "idp.SyncOrCreateDomainAndUser")
|
|
}
|
|
extUser, err := models.UserManager.FetchUserExtended(usr.Id, "", "", "")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "models.UserManager.FetchUserExtended")
|
|
}
|
|
|
|
idp.TryUserJoinProject(self.oidcConfig.SIdpAttributeOptions, ctx, usr, domain.Id, attrs)
|
|
|
|
extUser.AuditIds = []string{ident.OIDCAuth.Code}
|
|
|
|
return extUser, nil
|
|
}
|
|
|
|
func (self *SOIDCDriver) Sync(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (self *SOIDCDriver) Probe(ctx context.Context) error {
|
|
return nil
|
|
}
|