// 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 bingoiam import ( "context" "encoding/base64" "fmt" "net/http" "strings" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/httputils" "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/keystone/driver/oauth2" "yunion.io/x/onecloud/pkg/keystone/models" ) type SBingoIAMOAuth2Driver struct { oauth2.SOAuth2BaseDriver idp *models.SIdentityProvider accessToken string domains map[string]*models.SDomain projects map[string]*models.SProject } func NewBingoIAMOAuth2Driver(appId string, secret string) oauth2.IOAuth2Driver { drv := &SBingoIAMOAuth2Driver{ domains: map[string]*models.SDomain{}, projects: map[string]*models.SProject{}, SOAuth2BaseDriver: oauth2.SOAuth2BaseDriver{ AppId: appId, Secret: secret, }, } return drv } func (drv *SBingoIAMOAuth2Driver) getIAMEndpoint(ctx context.Context) string { endpoint := "" if config, isOk := ctx.Value("config").(identity.TConfigs); isOk { endpoint = strings.Trim(config["oauth2"]["iam_endpoint"].String(), `"`) } return endpoint } func (drv *SBingoIAMOAuth2Driver) getIAMApiEndpoint(ctx context.Context) string { endpoint := "" if config, isOk := ctx.Value("config").(identity.TConfigs); isOk { endpoint = strings.Trim(config["oauth2"]["iam_api_endpoint"].String(), `"`) } return endpoint } func (drv *SBingoIAMOAuth2Driver) GetSsoRedirectUri(ctx context.Context, callbackUrl, state string) (string, error) { req := map[string]string{ "client_id": drv.AppId, "redirect_uri": callbackUrl, "response_type": "code id_token", "scope": "openid", "state": state, } authUrl := drv.getIAMEndpoint(ctx) urlStr := fmt.Sprintf("%s/oauth2/authorize?%s", authUrl, jsonutils.Marshal(req).QueryString()) return urlStr, nil } func (drv *SBingoIAMOAuth2Driver) Authenticate(ctx context.Context, code string) (map[string][]string, error) { accessToken, err := drv.fetchAccessToken(ctx, code) if err != nil { return nil, errors.Wrap(err, "fetch bingo iam accessToken") } userInfo, err := drv.fetchUserInfo(ctx, accessToken) if err != nil { return nil, errors.Wrap(err, "fetch bingo iam userInfo") } attrs := make(map[string][]string) attrs["name"] = []string{fmt.Sprintf("%s", userInfo["preferred_username"])} attrs["display_name"] = []string{fmt.Sprintf("%s", userInfo["name"])} attrs["user_id"] = []string{fmt.Sprintf("%s", userInfo["sub"])} attrs["name_en"] = []string{fmt.Sprintf("%s", userInfo["preferred_username"])} attrs["email"] = []string{fmt.Sprintf("%s", userInfo["email"])} attrs["org_id"] = []string{fmt.Sprintf("%s", userInfo["org_id"])} attrs["mobile"] = []string{fmt.Sprintf("%s", userInfo["phone_number"])} attrs["tenant_id"] = []string{fmt.Sprintf("%s", userInfo["tenant_id"])} attrs["tenant_name"] = []string{fmt.Sprintf("%s", userInfo["tenant_name"])} return attrs, nil } func (drv *SBingoIAMOAuth2Driver) Sync(ctx context.Context, idpId string) error { var err error drv.idp, err = models.IdentityProviderManager.FetchIdentityProviderById(idpId) if err != nil { return err } //err = drv.syncTenants(ctx, drv.idp) //if err != nil { // return err //} //err = drv.syncProjects(ctx, drv.idp) //if err != nil { // return err //} //err = drv.syncUsers(ctx, drv.idp) //if err != nil { // return err //} return nil } func (drv *SBingoIAMOAuth2Driver) getAccessToken(ctx context.Context) (string, error) { authUrl := drv.getIAMEndpoint(ctx) authUrl = fmt.Sprintf("%s/oauth2/token?grant_type=client_credentials", authUrl) headers := http.Header{} headers.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%v:%v", drv.AppId, drv.Secret)))) httpclient := httputils.GetDefaultClient() _, resp, err := httputils.JSONRequest(httpclient, ctx, httputils.POST, authUrl, headers, nil, true) if err != nil { return "", err } var data map[string]interface{} err = resp.Unmarshal(&data) if err != nil { return "", err } drv.accessToken = data["access_token"].(string) return drv.accessToken, nil } func (drv *SBingoIAMOAuth2Driver) fetchAccessToken(ctx context.Context, code string) (string, error) { authUrl := drv.getIAMEndpoint(ctx) authUrl = fmt.Sprintf("%s/oauth2/token?grant_type=authorization_code&code=%s", authUrl, code) headers := http.Header{} headers.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%v:%v", drv.AppId, drv.Secret)))) httpclient := httputils.GetDefaultClient() _, resp, err := httputils.JSONRequest(httpclient, ctx, httputils.POST, authUrl, headers, nil, true) if err != nil { return "", err } var data map[string]interface{} err = resp.Unmarshal(&data) if err != nil { return "", err } return data["access_token"].(string), nil } func (drv *SBingoIAMOAuth2Driver) fetchUserInfo(ctx context.Context, accessToken string) (map[string]interface{}, error) { authUrl := drv.getIAMEndpoint(ctx) authUrl = fmt.Sprintf("%s/oauth2/userinfo?access_token=%s", authUrl, accessToken) headers := http.Header{} headers.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%v:%v", drv.AppId, drv.Secret)))) httpclient := httputils.GetDefaultClient() _, resp, err := httputils.JSONRequest(httpclient, ctx, httputils.GET, authUrl, headers, nil, true) if err != nil { return nil, err } var data map[string]interface{} err = resp.Unmarshal(&data) if err != nil { return nil, err } return data, nil }