plgd-dev/hub

[cloud2cloud-connector] Account Linking API cannot exchange origin cloud authorization code caused by oauth 403 forbidden error.

Trishia opened this issue · 9 comments

Description

@ondrejtomcik @jkralik @Danielius1922
[cloud2cloud-connector] Account Linking API cannot exchange origin cloud authorization code caused by oauth 403 forbidden error. This is because handleOAuth() function of account linking API (addLinkedAccount.go) was not handle properly. The logic exchanges to origin cloud oauth provider with auth code of target cloud now. So you should change the logic in handleOAuth() in the addLinkedAccount.go to fixed codes below :

Error Logs, error output, etc.

cannot process oauth callback: cannot exchange origin cloud authorization code for access token: oauth2: cannot fetch token: 403 Forbidden
Response: {"error":"invalid_grant","error_description":"Invalid authorization code"}

Fixed Codes for proper OAuth exchange for both origin/target cloud account linking

func (rh *RequestHandler) handleOAuth(w http.ResponseWriter, r *http.Request, linkedAccount store.LinkedAccount, linkedCloud store.LinkedCloud) (int, error) {
	linkedCloud, ok := rh.store.LoadCloud(linkedAccount.LinkedCloudID)
	if !ok {
		return http.StatusBadRequest, fmt.Errorf("cannot find linked cloud with ID %v: not found", linkedAccount.LinkedCloudID)
	}
	t, err := generateRandomString(32)
	if err != nil {
		return http.StatusInternalServerError, fmt.Errorf("cannot generate token")
	}
	err = rh.provisionCache.Add(t, provisionCacheData{
		linkedAccount: linkedAccount,
		linkedCloud:   linkedCloud,
	}, cache.DefaultExpiration)
	if err != nil {
		return http.StatusInternalServerError, fmt.Errorf("cannot store key - collision")
	}

	if !linkedAccount.Data.HasOrigin() {
		url := rh.provider.OAuth2.AuthCodeURL(t)
		http.Redirect(w, r, url, http.StatusTemporaryRedirect)
	}

	if !linkedAccount.Data.HasTarget() {
		oauthCfg := linkedCloud.OAuth
		if oauthCfg.RedirectURL == "" {
			oauthCfg.RedirectURL = rh.provider.Config.RedirectURL
		}
		url := oauthCfg.AuthCodeURL(t)
		http.Redirect(w, r, url, http.StatusTemporaryRedirect)
	}
	return http.StatusOK, nil
}

Environment

  • plgd source: commit ba6d959
  • Deployment option: own deployment for each service

Additional context

@Danielius1922 @jkralik

I have tried to test account linking via {{ORIGIN_CLOUD_HOST}}/api/v1/clouds/{{cloud_id}}/accounts API in the web browser with ModHeader but origin cloud did not pull devices from target cloud. I also tried to test account linking with config option apis.http.pullDevices.disabled: false and apis.http.pullDevices.interval: 10s but there was pulled device in origin cloud even if the target cloud has one device.

  • Have you ever tested cloud2cloud account linking in kistler v2.0.0 ?
  • Is there any other way to do account linking except web browser with ModHeader ?

@Danielius1922 @jkralik

I witnessed weird access token in origin cloud data for account linking. The access token of origin cloud is too short. Could you look into below result data of account linking ? What is problem in oauthCallback.go of cloud2cloud-connector ?

{
    "4d4f6b26-9d9d-4deb-8e28-c19467fb1374": {
        "accounts": {
            "55338e38-037a-4f58-9654-1fce4a5db8eb": {
                "account": {
                    "Data": {
                        "OriginCloud": {
                            "AccessToken": "IIMT-9YZZyN9SD7QA1Az-BbgDaLwCbK8",
                            "Expiry": "2021-11-04T14:24:24.063Z",
                            "RefreshToken": "97eM7MeTawQPVf90lRgkhkEhoZ0mRnG7CxqEosScfJutP"
                        },
                        "State": 3,
                        "TargetCloud": {
                            "AccessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImxfcUtMNlE3djFRNTNyZ3dSSFN1aiJ9.eyJpc3MiOiJodHRwczovL3R0YW9jZi51cy5hdXRoMC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDM1MTc1MjAzMTQ4ODUwNjEyMzMiLCJhdWQiOiJodHRwczovL3d3dy50dGFvY2ZjbG91ZC5vcmciLCJpYXQiOjE2MzU5NDk0NjQsImV4cCI6MTYzNjgxMzQ2NCwiYXpwIjoieXhGOXJQQ3B0OFRrcVJEbmpYZ1NPa0YxR3dFUVVaYVoiLCJzY29wZSI6InI6ZGV2aWNlaW5mb3JtYXRpb246KiByOnJlc291cmNlczoqIHc6cmVzb3VyY2VzOiogdzpzdWJzY3JpcHRpb25zOiogb2ZmbGluZV9hY2Nlc3MifQ.dFsm7GF15AcGF_RO2COtA7V56xuc8RX4i4MyAui5u8RCIEr86ql03e7mCpWdRQG0cupPIYCYik2wI9BoHcD6fRMjA137fd0Al-irV5lP1Chl1tozIQtB7rrKQd68dvtXiyLqeh4H75yGxTI1Z2Os5E4ELtOaNGUMkfLzI9hUvnml5b0S5tZF2JmQ0S0wlfgTzNGOgMdpRHWghTXJkdabgDdqzoMm-i5B0ZFQu0QMm25kBEqJ9J6UgPTn6GXM2hAZhGn9IuQjnLxvmSx4WzkmlHhxDmKk7CbJKG4qiCHGxrrf2FOlUrBwivNu7_1VBv-MCg0gLyoPuAJ8hfYiD4vzJw",
                            "Expiry": "2021-11-13T14:24:25.032Z",
                            "RefreshToken": "UaocsJ5x4xLUXp-ivpQqrsSNcSboJssZMctM83lstQRpz"
                        }
                    },
                    "Id": "55338e38-037a-4f58-9654-1fce4a5db8eb",
                    "LinkedCloudID": "4d4f6b26-9d9d-4deb-8e28-c19467fb1374",
                    "UserID": "google-oauth2|103517520314885061233"
                },
                "subscription": {
                    "CorrelationID": "7158f94c-24ce-4185-ac63-2421aee91568",
                    "DeviceID": "",
                    "Href": "",
                    "ID": "de7c3018-6f2d-4d1c-aba7-f1837a2f9072",
                    "LinkedAccountID": "55338e38-037a-4f58-9654-1fce4a5db8eb",
                    "LinkedCloudID": "4d4f6b26-9d9d-4deb-8e28-c19467fb1374",
                    "SigningSecret": "Ogj--XBcQ1myN9vxDRRevIPKtApWDLi71_GeoHalaG0=",
                    "Type": "devices"
                }
            }
        },

It's an opaque access token, not JWT. To get a JWT token from auth0, you need to pass audience to your /authorize request.

I found wrong source I used in addLinkedAccount.go and then added extra code in plgd.go like below:

Refer to if !linkedAccount.Data.HasOrigin() { ... } in handleOAuth() of addLinkedAccount.go

func (rh *RequestHandler) handleOAuth(w http.ResponseWriter, r *http.Request, linkedAccount store.LinkedAccount, linkedCloud store.LinkedCloud) (int, error) {
	linkedCloud, ok := rh.store.LoadCloud(linkedAccount.LinkedCloudID)
	if !ok {
		return http.StatusBadRequest, fmt.Errorf("cannot find linked cloud with ID %v: not found", linkedAccount.LinkedCloudID)
	}
	t, err := generateRandomString(32)
	if err != nil {
		return http.StatusInternalServerError, fmt.Errorf("cannot generate token")
	}
	err = rh.provisionCache.Add(t, provisionCacheData{
		linkedAccount: linkedAccount,
		linkedCloud:   linkedCloud,
	}, cache.DefaultExpiration)
	if err != nil {
		return http.StatusInternalServerError, fmt.Errorf("cannot store key - collision")
	}

	if !linkedAccount.Data.HasOrigin() {
		url := rh.provider.Config.AuthCodeURL(t)
		fmt.Printf("linked orign cloud auth url: %s \n", url)
		http.Redirect(w, r, url, http.StatusTemporaryRedirect)
		return http.StatusOK, nil
	}

	if !linkedAccount.Data.HasTarget() {
		oauthCfg := linkedCloud.OAuth
		if oauthCfg.RedirectURL == "" {
			oauthCfg.RedirectURL = rh.provider.Config.RedirectURL
		}
		url := oauthCfg.AuthCodeURL(t)
		fmt.Printf("linked target cloud auth url: %s \n", url)
		http.Redirect(w, r, url, http.StatusTemporaryRedirect)
		return http.StatusOK, nil
	}

	return http.StatusOK, nil
}

Refer to config.AuthURL = oidcfg.AuthURL in NewPlgdProvider() of plgd.go

func NewPlgdProvider(ctx context.Context, config Config, logger log.Logger) (*PlgdProvider, error) {
	if config.ResponseMode == "" {
		config.ResponseMode = "query"
	}
	if config.AccessType == "" {
		config.AccessType = "offline"
	}
	if config.ResponseType == "" {
		config.ResponseType = "code"
	}

	if config.ClientSecret == "" {
		clientSecret, err := file.Load(config.ClientSecretFile, make([]byte, 4096))
		if err != nil {
			return nil, err
		}
		config.ClientSecret = string(clientSecret)
		fmt.Printf("load clientSecret from file: %s \n", string(clientSecret))
	}
	fmt.Printf("load clientSecret from config: %s \n", config.ClientSecret)

	httpClient, err := client.New(config.HTTP, logger)
	if err != nil {
		return nil, err
	}
	oidcfg, err := openid.GetConfiguration(ctx, httpClient.HTTP(), config.Authority)
	if err != nil {
		return nil, err
	}

	config.AuthURL = oidcfg.AuthURL
	config.TokenURL = oidcfg.TokenURL
	oauth2 := config.ToOAuth2(config.AuthURL, config.TokenURL, config.ClientSecret)

	return &PlgdProvider{
		Config:     config,
		OAuth2:     &oauth2,
		HTTPClient: httpClient,
		OpenID:     oidcfg,
	}, nil
}

With above changes, I could finish account linking completely via web browser with ModHeader.
I hope so you could change your codes in main branch.

Trishia please create a merge request. We will review your proposal.

@ondrejtomcik
I created merge request #559 for this issue. Review plz.

Hi @Trishia, I've merged the PR. Can this be closed or do you want to do some verification first?

@Danielius1922
No, I don't want to do verification. I will close this issue.
Thank you for your job.

Supes, thanks for your help as well.