diff --git a/external/oidc/callback.go b/external/oidc/callback.go index 6831829..89b5760 100644 --- a/external/oidc/callback.go +++ b/external/oidc/callback.go @@ -12,11 +12,13 @@ import ( ) type CallbackPageInfo struct { - LoginType string - ErrorTitle string - ErrorDescription string - AccountTokenResponse *authentication.AccountManagementAuthenticationTokenResponse - AccountTokenStructBase64 []string + LoginType string + ErrorTitle string + ErrorDescription string + AccountTokenResponse *authentication.AccountManagementAuthenticationTokenResponse + CustomerTokenResponse *authentication.CustomerTokenResponse + AccountTokenStructBase64 []string + CustomerTokenStructBase64 string } func GetCallbackData(ctx context.Context, port uint16, r *http.Request) (*CallbackPageInfo, error) { @@ -45,42 +47,82 @@ func GetCallbackData(ctx context.Context, port uint16, r *http.Request) (*Callba return nil, fmt.Errorf("could not get verifier cookie: %w", err) } - result, err := rest.CreateInternal(context.Background(), &httpclient.HttpParameterOverrides{}, []string{"account-management-authentication-token", - "authentication_mechanism", "oidc", - "oauth_authorization_code", data["code"], - "oauth_redirect_uri", fmt.Sprintf("http://localhost:%d/callback", port), - "oauth_state", state.Value, - "oauth_code_verifier", verifier.Value, - }, false, "", true) + login_type, err := r.Cookie("login_type") if err != nil { - return nil, fmt.Errorf("could not get account tokens: %w", err) + return nil, fmt.Errorf("could not get login_type cookie: %w", err) + } + + if data["state"] != state.Value { + return &CallbackPageInfo{ + ErrorTitle: "State Mismatch", + ErrorDescription: "State mismatch between locally stored value and value from IdP", + LoginType: login_type.Value, + }, nil } cpi := CallbackPageInfo{ - LoginType: "AM", + LoginType: login_type.Value, } - err = gojson.Unmarshal([]byte(result), &cpi.AccountTokenResponse) + if login_type.Value == "AM" { + result, err := rest.CreateInternal(context.Background(), &httpclient.HttpParameterOverrides{}, []string{"account-management-authentication-token", + "authentication_mechanism", "oidc", + "oauth_authorization_code", data["code"], + "oauth_redirect_uri", fmt.Sprintf("http://localhost:%d/callback", port), + "oauth_code_verifier", verifier.Value, + }, false, "", true) - if err != nil { - return nil, fmt.Errorf("could not unmarshal response: %w", err) - } + if err != nil { + return nil, fmt.Errorf("could not get account tokens: %w", err) + } + + err = gojson.Unmarshal([]byte(result), &cpi.AccountTokenResponse) + + if err != nil { + return nil, fmt.Errorf("could not unmarshal response: %w", err) + } + + for _, v := range cpi.AccountTokenResponse.Data { - for _, v := range cpi.AccountTokenResponse.Data { + str, err := gojson.Marshal(v) - str, err := gojson.Marshal(v) + if err != nil { + return nil, fmt.Errorf("could not encode token: %w", err) + } + + cpi.AccountTokenStructBase64 = append(cpi.AccountTokenStructBase64, base64.URLEncoding.EncodeToString(str)) + } + + return &cpi, nil + } else if login_type.Value == "Customers" { + result, err := rest.CreateInternal(context.Background(), &httpclient.HttpParameterOverrides{}, []string{"customer-token", + "authentication_mechanism", "oidc", + "oauth_authorization_code", data["code"], + "oauth_redirect_uri", fmt.Sprintf("http://localhost:%d/callback", port), + "oauth_code_verifier", verifier.Value, + }, false, "", true) if err != nil { - return nil, fmt.Errorf("could not encode token: %w", err) + return nil, fmt.Errorf("could not get customer tokens: %w", err) } - base64.URLEncoding.EncodeToString(str) + err = gojson.Unmarshal([]byte(result), &cpi.CustomerTokenResponse) - cpi.AccountTokenStructBase64 = append(cpi.AccountTokenStructBase64, base64.URLEncoding.EncodeToString(str)) - } + str, err := gojson.Marshal(cpi.CustomerTokenResponse.Data) - return &cpi, nil + if err != nil { + return nil, fmt.Errorf("could not encode token: %w", err) + } + + cpi.CustomerTokenStructBase64 = base64.URLEncoding.EncodeToString(str) + return &cpi, nil + } else { + return &CallbackPageInfo{ + ErrorTitle: "Unknown Login Type", + ErrorDescription: fmt.Sprintf("Unsupported login type used: %v", login_type.Value), + }, nil + } } else if data["error"] == "" { return &CallbackPageInfo{ diff --git a/external/oidc/get_token.go b/external/oidc/get_token.go index db32650..f91a085 100644 --- a/external/oidc/get_token.go +++ b/external/oidc/get_token.go @@ -6,6 +6,9 @@ import ( gojson "encoding/json" "fmt" "github.com/elasticpath/epcc-cli/external/authentication" + "github.com/elasticpath/epcc-cli/external/httpclient" + "github.com/elasticpath/epcc-cli/external/json" + "github.com/elasticpath/epcc-cli/external/rest" log "github.com/sirupsen/logrus" "net/http" "os" @@ -68,6 +71,84 @@ func GetTokenData(ctx context.Context, port uint16, r *http.Request) (*TokenPage Id: amToken.AccountId, }, nil + } else if data["login_type"] == "Customers" { + token := data["token"] + custToken, err := base64.URLEncoding.DecodeString(token) + + if err != nil { + return nil, fmt.Errorf("could not get decode am token: %w", err) + } + + custTokenStruct := authentication.CustomerTokenStruct{} + + err = gojson.Unmarshal(custToken, &custTokenStruct) + + if err != nil { + return nil, fmt.Errorf("could not get unmarshal am token: %w", err) + } + + ctr := authentication.CustomerTokenResponse{ + Data: custTokenStruct, + AdditionalInfo: authentication.CustomerTokenEpccCliAdditionalInfo{}, + } + + authentication.SaveCustomerToken(ctr) + + apiToken := authentication.GetApiToken() + + if apiToken != nil { + if apiToken.Identifier == "client_credentials" { + log.Warnf("You are currently logged in with client_credentials, please switch to implicit with `epcc login implicit` to use the customer token correctly. Mixing client_credentials and the customer token can lead to unintended results.") + } + } + + if authentication.IsAccountManagementAuthenticationTokenSet() { + log.Warnf("Logging out of Account Management") + authentication.ClearAccountManagementAuthenticationToken() + } + + result, err := rest.GetInternal(context.Background(), &httpclient.HttpParameterOverrides{}, []string{"customer", custTokenStruct.CustomerId}, false) + + customerName := "Unknown" + customerEmail := "Unkwown" + + if err == nil { + customerName, err = json.RunJQOnStringAndGetString(".data.name", result) + + if err != nil { + log.Warnf("Could not get customer name from response %s, %v", result, err) + } + + customerEmail, err = json.RunJQOnStringAndGetString(".data.email", result) + + if err != nil { + log.Warnf("Could not get customer email from response %s, %v", result, err) + } + + ctr := authentication.CustomerTokenResponse{ + Data: custTokenStruct, + AdditionalInfo: authentication.CustomerTokenEpccCliAdditionalInfo{ + CustomerName: customerName, + CustomerEmail: customerEmail, + }, + } + + log.Infof("Saving customer token with %s,%s, %v", customerName, customerEmail, result) + authentication.SaveCustomerToken(ctr) + } + + go func() { + time.Sleep(2 * time.Second) + log.Infof("Authentication complete, shutting down") + os.Exit(0) + }() + + return &TokenPageInfo{ + LoginType: "Customers", + Name: customerName, + Id: custTokenStruct.CustomerId, + }, nil + } return nil, fmt.Errorf("invalid login type") diff --git a/external/oidc/site/callback.gohtml b/external/oidc/site/callback.gohtml index aaea694..a698642 100644 --- a/external/oidc/site/callback.gohtml +++ b/external/oidc/site/callback.gohtml @@ -1,10 +1,10 @@ {{ template "header" }} {{ if ne .ErrorTitle "" }} -

An error has occurred

+

errorAn Error has Occurred

Error Type: {{ .ErrorTitle }}

Error Description: {{ .ErrorDescription }}

{{ else if eq .LoginType "AM" }} -

Authentication Successful

+

check_circle Authentication With Identity Provider Successful

To finish the authentication process, you must select an account. If you see no accounts make sure that auto_create_account_for_account_members is enabled or manually create an account. @@ -13,7 +13,7 @@

Please Select An Account

- +
@@ -27,8 +27,21 @@ {{ end }}
Account Name Account ID
+ {{ else if eq .LoginType "Customers" }} +

check_circle Authentication With Identity Provider Successful

+ To continue, press GO next to the Customer. + + + + + - {{ end }} + + + + +
Customer IDAction
{{ .CustomerTokenResponse.Data.Id }}
+ {{ end }}


diff --git a/external/oidc/site/done.gohtml b/external/oidc/site/done.gohtml deleted file mode 100644 index b399795..0000000 --- a/external/oidc/site/done.gohtml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - EPCC CLI OIDC Tester - - - -
-

EPCC CLI OIDC Tester

-
-
- You have successfully logged in as: -
-
- Account Name: {{ .AccountName }}
- Account ID: {{ .AccountId }}
- - -

You may now close this window.

-
- - \ No newline at end of file diff --git a/external/oidc/site/error.gohtml b/external/oidc/site/error.gohtml deleted file mode 100644 index 1c1de49..0000000 --- a/external/oidc/site/error.gohtml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - OIDC Error - - - -
-

OIDC Test Failed

-
-
- -

Error Type: {{ .error }}

-

Error Description: {{ .error_description }}

- Go Back To Start -
- - \ No newline at end of file diff --git a/external/oidc/site/get_token.gohtml b/external/oidc/site/get_token.gohtml index 61f84ea..a4b67b9 100644 --- a/external/oidc/site/get_token.gohtml +++ b/external/oidc/site/get_token.gohtml @@ -1,17 +1,37 @@ {{ template "header" }} {{ if ne .ErrorTitle "" }} -

An error has occurred

+

errorAn Error has Occurred

Error Type: {{ .ErrorTitle }}

Error Description: {{ .ErrorDescription }}

{{ else if eq .LoginType "AM" }} - You have successfully logged in as: -
-
- Account Name: {{ .Name }}
- Account ID: {{ .Id }}
- +

check_circle Authentication Complete

+ You are now authenticated as: + + + + + + + + + +
Account NameAccount ID
{{ .Name }}{{ .Id }}
+

You may now close this window.

+ {{ else if eq .LoginType "Customers" }} +

check_circle Authentication Complete

+ You are now authenticated as: + + + + + + + + + +
Customer NameCustomer ID
{{ .Name }}{{ .Id }}

You may now close this window.

{{ end }} {{ template "footer" }} \ No newline at end of file diff --git a/external/oidc/site/header.gohtml b/external/oidc/site/header.gohtml index 04a1aad..3449908 100644 --- a/external/oidc/site/header.gohtml +++ b/external/oidc/site/header.gohtml @@ -6,7 +6,8 @@ Top Banner - + +
diff --git a/external/oidc/site/index.gohtml b/external/oidc/site/index.gohtml index 7d558f4..c8f8363 100644 --- a/external/oidc/site/index.gohtml +++ b/external/oidc/site/index.gohtml @@ -1,6 +1,12 @@ {{ template "header" }} - -

Welcome

+

Welcome

This utility allows you to test Single Sign-On (SSO) with Elastic Path Commerce Cloud. You can authenticate two ways: @@ -77,9 +82,42 @@

Customers

+ + In order to successfully authenticate with Customers similar to the HOWTO Guide for Account Management, you will need to + make the following changes to the store: +
    +
  1. Add {{ $.RedirectUriUnencoded }} to the Buyer Organization allowed redirect URIs. + + +
    +
    + epcc get customer-authentication-settings
    + epcc get authentication-realm related_authentication-realm_for_customer-authentication-settings_last_read=entity
    +
    +
    + After inspecting the list of redirect_uris, you can add a new one with the following syntax: + +
    +
    + epcc update authentication-realm related_authentication-realm_for_customer-authentication-settings_last_read=entity redirect_uris[0] {{ $.RedirectUriUnencoded }}
    +
    +
    +
  2. +
  3. Add an OpenID Connect Profile that connects to the Identity Provider: + +
    +
    + epcc create oidc-profile related_authentication-realm_for_customer-authentication-settings_last_read=entity=entity name "EPCC CLI Test OIDC Profile" client_id my_client_id client_secret my_client_secret discovery_url my_discovery_url +
    +
    +
    +
  4. Refresh this page, you should then see each OpenID Connect Profile and can authenticate, by clicking the Login button after perhaps editing the URL. The URL will contain all the necessarily arguments.
  5. +
+

OpenID Connect Profiles

- {{ range $i, $item := .CustomerProfiles }} -

{{ $item.Name }}

- - {{ end }} + {{ range $i, $item := .CustomerProfiles }} +

Profile: {{ $item.Name }} ( {{ $item.Idp }} )

+
+
+ {{ end }} {{ template "footer" }} \ No newline at end of file diff --git a/external/oidc/site/static/style.css b/external/oidc/site/static/style.css index 0160a1e..8fca85a 100644 --- a/external/oidc/site/static/style.css +++ b/external/oidc/site/static/style.css @@ -64,6 +64,10 @@ table { border: 2px solid black; /* Adds a bold border around the table */ } +.success { color: mediumseagreen; } + +.error { color: crimson; } + table td, table th { border: 2px solid black; /* Adds borders to each cell */ min-width: 100px; /* Ensures a minimum width for all cells */