Skip to content

Commit

Permalink
Merge pull request #115 from UiPath/feature/identity-uri
Browse files Browse the repository at this point in the history
Add global parameter identity-uri
  • Loading branch information
thschmitt authored May 9, 2024
2 parents 4978601 + 9ad8512 commit d2379a7
Show file tree
Hide file tree
Showing 14 changed files with 123 additions and 139 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ You can either pass global arguments as CLI parameters, set an env variable or s
| `--uri` | `UIPATH_URI` | `uri` | `https://cloud.uipath.com` | URL override |
| `--organization` | `UIPATH_ORGANIZATION` | `string` | | Organization name |
| `--tenant` | `UIPATH_TENANT` | `string` | | Tenant name |
| `--identity-uri` | `UIPATH_IDENTITY_URI` | `uri` | `https://cloud.uipath.com/identity_` | URL override for identity calls |
| | `UIPATH_CLIENT_ID` | `string` | | Client Id |
| | `UIPATH_CLIENT_SECRET` | `string` | | Client Secret |
| | `UIPATH_PAT` | `string` | | Personal Access Token |
Expand Down
16 changes: 10 additions & 6 deletions auth/authenticator_context.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
package auth

import "net/url"

// AuthenticatorContext provides information required for authenticating requests.
type AuthenticatorContext struct {
Type string `json:"type"`
Config map[string]interface{} `json:"config"`
Debug bool `json:"debug"`
Insecure bool `json:"insecure"`
Request AuthenticatorRequest `json:"request"`
Type string `json:"type"`
Config map[string]interface{} `json:"config"`
IdentityUri url.URL `json:"identityUri"`
Debug bool `json:"debug"`
Insecure bool `json:"insecure"`
Request AuthenticatorRequest `json:"request"`
}

func NewAuthenticatorContext(
authType string,
config map[string]interface{},
identityUri url.URL,
debug bool,
insecure bool,
request AuthenticatorRequest) *AuthenticatorContext {
return &AuthenticatorContext{authType, config, debug, insecure, request}
return &AuthenticatorContext{authType, config, identityUri, debug, insecure, request}
}
26 changes: 2 additions & 24 deletions auth/bearer_authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ package auth

import (
"fmt"
"net/url"
"os"

"github.com/UiPath/uipathcli/cache"
)

const ClientIdEnvVarName = "UIPATH_CLIENT_ID"
const ClientSecretEnvVarName = "UIPATH_CLIENT_SECRET" //nolint // This is not a secret but just the env variable name
const IdentityUriEnvVarName = "UIPATH_IDENTITY_URI"

// The BearerAuthenticator calls the identity token-endpoint to retrieve a JWT bearer token.
// It requires clientId and clientSecret.
Expand All @@ -26,21 +24,9 @@ func (a BearerAuthenticator) Auth(ctx AuthenticatorContext) AuthenticatorResult
if err != nil {
return *AuthenticatorError(fmt.Errorf("Invalid bearer authenticator configuration: %w", err))
}
identityBaseUri := config.IdentityUri
if identityBaseUri == nil {
requestUrl, err := url.Parse(ctx.Request.URL)
if err != nil {
return *AuthenticatorError(fmt.Errorf("Invalid request url '%s': %w", ctx.Request.URL, err))
}
identityBaseUri, err = url.Parse(fmt.Sprintf("%s://%s/identity_", requestUrl.Scheme, requestUrl.Host))
if err != nil {
return *AuthenticatorError(fmt.Errorf("Invalid identity url '%s': %w", ctx.Request.URL, err))
}
}

identityClient := newIdentityClient(a.cache)
tokenRequest := newTokenRequest(
*identityBaseUri,
config.IdentityUri,
config.GrantType,
config.Scopes,
config.ClientId,
Expand Down Expand Up @@ -85,15 +71,7 @@ func (a BearerAuthenticator) getConfig(ctx AuthenticatorContext) (*bearerAuthent
if err != nil {
return nil, err
}
var uri *url.URL
uriString, err := a.parseRequiredString(ctx.Config, "uri", os.Getenv(IdentityUriEnvVarName))
if err == nil {
uri, err = url.Parse(uriString)
if err != nil {
return nil, fmt.Errorf("Error parsing identity uri: %w", err)
}
}
return newBearerAuthenticatorConfig(grantType, scopes, clientId, clientSecret, properties, uri), nil
return newBearerAuthenticatorConfig(grantType, scopes, clientId, clientSecret, properties, ctx.IdentityUri), nil
}

func (a BearerAuthenticator) parseProperties(config map[string]interface{}) (map[string]string, error) {
Expand Down
4 changes: 2 additions & 2 deletions auth/bearer_authenticator_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type bearerAuthenticatorConfig struct {
ClientId string
ClientSecret string
Properties map[string]string
IdentityUri *url.URL
IdentityUri url.URL
}

func newBearerAuthenticatorConfig(
Expand All @@ -17,6 +17,6 @@ func newBearerAuthenticatorConfig(
clientId string,
clientSecret string,
properties map[string]string,
identityUri *url.URL) *bearerAuthenticatorConfig {
identityUri url.URL) *bearerAuthenticatorConfig {
return &bearerAuthenticatorConfig{grantType, scopes, clientId, clientSecret, properties, identityUri}
}
23 changes: 2 additions & 21 deletions auth/oauth_authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,7 @@ func (a OAuthAuthenticator) Auth(ctx AuthenticatorContext) AuthenticatorResult {
if err != nil {
return *AuthenticatorError(fmt.Errorf("Invalid oauth authenticator configuration: %w", err))
}
identityBaseUri := config.IdentityUri
if identityBaseUri == nil {
requestUrl, err := url.Parse(ctx.Request.URL)
if err != nil {
return *AuthenticatorError(fmt.Errorf("Invalid request url '%s': %w", ctx.Request.URL, err))
}
identityBaseUri, err = url.Parse(fmt.Sprintf("%s://%s/identity_", requestUrl.Scheme, requestUrl.Host))
if err != nil {
return *AuthenticatorError(fmt.Errorf("Invalid identity url '%s': %w", ctx.Request.URL, err))
}
}
token, err := a.retrieveToken(*identityBaseUri, *config, ctx.Insecure)
token, err := a.retrieveToken(config.IdentityUri, *config, ctx.Insecure)
if err != nil {
return *AuthenticatorError(fmt.Errorf("Error retrieving access token: %w", err))
}
Expand Down Expand Up @@ -176,15 +165,7 @@ func (a OAuthAuthenticator) getConfig(ctx AuthenticatorContext) (*oauthAuthentic
if err != nil {
return nil, err
}
var uri *url.URL
uriString, err := a.parseRequiredString(ctx.Config, "uri")
if err == nil {
uri, err = url.Parse(uriString)
if err != nil {
return nil, fmt.Errorf("Error parsing identity uri: %w", err)
}
}
return newOAuthAuthenticatorConfig(clientId, *parsedRedirectUri, scopes, uri), nil
return newOAuthAuthenticatorConfig(clientId, *parsedRedirectUri, scopes, ctx.IdentityUri), nil
}

func (a OAuthAuthenticator) parseRequiredString(config map[string]interface{}, name string) (string, error) {
Expand Down
4 changes: 2 additions & 2 deletions auth/oauth_authenticator_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ type oauthAuthenticatorConfig struct {
ClientId string
RedirectUrl url.URL
Scopes string
IdentityUri *url.URL
IdentityUri url.URL
}

func newOAuthAuthenticatorConfig(
clientId string,
redirectUrl url.URL,
scopes string,
identityUri *url.URL) *oauthAuthenticatorConfig {
identityUri url.URL) *oauthAuthenticatorConfig {
return &oauthAuthenticatorConfig{clientId, redirectUrl, scopes, identityUri}
}
97 changes: 23 additions & 74 deletions auth/oauth_authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestOAuthAuthenticatorNotEnabled(t *testing.T) {
"scopes": "OR.Users",
}
request := NewAuthenticatorRequest("http:/localhost", map[string]string{})
context := NewAuthenticatorContext("login", config, false, false, *request)
context := NewAuthenticatorContext("login", config, createIdentityUrl(""), false, false, *request)

authenticator := NewOAuthAuthenticator(cache.NewFileCache(), nil)
result := authenticator.Auth(*context)
Expand All @@ -44,7 +44,7 @@ func TestOAuthAuthenticatorPreservesExistingHeaders(t *testing.T) {
"my-header": "my-value",
}
request := NewAuthenticatorRequest("http:/localhost", headers)
context := NewAuthenticatorContext("login", config, false, false, *request)
context := NewAuthenticatorContext("login", config, createIdentityUrl(""), false, false, *request)

authenticator := NewOAuthAuthenticator(cache.NewFileCache(), nil)
result := authenticator.Auth(*context)
Expand All @@ -56,46 +56,14 @@ func TestOAuthAuthenticatorPreservesExistingHeaders(t *testing.T) {
}
}

func TestOAuthAuthenticatorInvalidRequestUrl(t *testing.T) {
config := map[string]interface{}{
"clientId": "my-client-id",
"redirectUri": "http://localhost:0",
"scopes": "OR.Users",
}
request := NewAuthenticatorRequest("://invalid", map[string]string{})
context := NewAuthenticatorContext("login", config, false, false, *request)

authenticator := NewOAuthAuthenticator(cache.NewFileCache(), nil)
result := authenticator.Auth(*context)
if result.Error != `Invalid request url '://invalid': parse "://invalid": missing protocol scheme` {
t.Errorf("Expected error with invalid request url, but got: %v", result.Error)
}
}

func TestOAuthAuthenticatorInvalidIdentityUrl(t *testing.T) {
config := map[string]interface{}{
"clientId": "my-client-id",
"redirectUri": "http://localhost:0",
"scopes": "OR.Users",
}
request := NewAuthenticatorRequest("INVALID-URL", map[string]string{})
context := NewAuthenticatorContext("login", config, false, false, *request)

authenticator := NewOAuthAuthenticator(cache.NewFileCache(), nil)
result := authenticator.Auth(*context)
if result.Error != `Invalid identity url 'INVALID-URL': parse ":///identity_": missing protocol scheme` {
t.Errorf("Expected error with invalid request url, but got: %v", result.Error)
}
}

func TestOAuthAuthenticatorInvalidConfig(t *testing.T) {
config := map[string]interface{}{
"clientId": 1,
"redirectUri": "http://localhost:0",
"scopes": "OR.Users",
}
request := NewAuthenticatorRequest("http:/localhost", map[string]string{})
context := NewAuthenticatorContext("login", config, false, false, *request)
context := NewAuthenticatorContext("login", config, createIdentityUrl(""), false, false, *request)

authenticator := NewOAuthAuthenticator(cache.NewFileCache(), nil)
result := authenticator.Auth(*context)
Expand All @@ -109,9 +77,9 @@ func TestOAuthFlowIdentityFails(t *testing.T) {
ResponseStatus: 400,
ResponseBody: "Invalid token request",
}
identityUrl := identityServerFake.Start(t)
identityBaseUrl := identityServerFake.Start(t)

context := createAuthContext(identityUrl)
context := createAuthContext(identityBaseUrl)
loginUrl, resultChannel := callAuthenticator(context)
performLogin(loginUrl, t)

Expand All @@ -126,9 +94,9 @@ func TestOAuthFlowSuccessful(t *testing.T) {
ResponseStatus: 200,
ResponseBody: `{"access_token": "my-access-token", "expires_in": 3600, "token_type": "Bearer", "scope": "OR.Users"}`,
}
identityUrl := identityServerFake.Start(t)
identityBaseUrl := identityServerFake.Start(t)

context := createAuthContext(identityUrl)
context := createAuthContext(identityBaseUrl)
loginUrl, resultChannel := callAuthenticator(context)
performLogin(loginUrl, t)

Expand All @@ -142,42 +110,14 @@ func TestOAuthFlowSuccessful(t *testing.T) {
}
}

func TestOAuthFlowWithCustomIdentityUri(t *testing.T) {
identityServerFake := identityServerFake{
ResponseStatus: 200,
ResponseBody: `{"access_token": "my-access-token", "expires_in": 3600, "token_type": "Bearer", "scope": "OR.Users"}`,
}
identityUrl := identityServerFake.Start(t)
config := map[string]interface{}{
"clientId": newClientId(),
"redirectUri": "http://localhost:0",
"scopes": "OR.Users",
"uri": identityUrl.String() + "/identity_",
}
request := NewAuthenticatorRequest("no-url", map[string]string{})
context := NewAuthenticatorContext("login", config, false, false, *request)

loginUrl, resultChannel := callAuthenticator(*context)
performLogin(loginUrl, t)

result := <-resultChannel
if result.Error != "" {
t.Errorf("Expected no error when performing oauth flow, but got: %v", result.Error)
}
authorizationHeader := result.RequestHeader["Authorization"]
if authorizationHeader != "Bearer my-access-token" {
t.Errorf("Expected JWT bearer token in authorization header, but got: %v", authorizationHeader)
}
}

func TestOAuthFlowIsCached(t *testing.T) {
identityServerFake := identityServerFake{
ResponseStatus: 200,
ResponseBody: `{"access_token": "my-access-token", "expires_in": 3600, "token_type": "Bearer", "scope": "OR.Users"}`,
}
identityUrl := identityServerFake.Start(t)
identityBaseUrl := identityServerFake.Start(t)

context := createAuthContext(identityUrl)
context := createAuthContext(identityBaseUrl)
loginUrl, resultChannel := callAuthenticator(context)
performLogin(loginUrl, t)
<-resultChannel
Expand Down Expand Up @@ -217,9 +157,9 @@ func TestShowsSuccessfullyLoggedInPage(t *testing.T) {
ResponseStatus: 200,
ResponseBody: `{"access_token": "my-access-token", "expires_in": 3600, "token_type": "Bearer", "scope": "OR.Users"}`,
}
identityUrl := identityServerFake.Start(t)
identityBaseUrl := identityServerFake.Start(t)

context := createAuthContext(identityUrl)
context := createAuthContext(identityBaseUrl)
loginUrl, _ := callAuthenticator(context)
responseBody := performLogin(loginUrl, t)

Expand Down Expand Up @@ -283,17 +223,26 @@ func callAuthenticator(context AuthenticatorContext) (url.URL, chan Authenticato
return *url, resultChannel
}

func createAuthContext(identityUrl url.URL) AuthenticatorContext {
func createAuthContext(baseUrl url.URL) AuthenticatorContext {
config := map[string]interface{}{
"clientId": newClientId(),
"redirectUri": "http://localhost:0",
"scopes": "OR.Users",
}
request := NewAuthenticatorRequest(fmt.Sprintf("%s://%s", identityUrl.Scheme, identityUrl.Host), map[string]string{})
context := NewAuthenticatorContext("login", config, false, false, *request)
identityUrl := createIdentityUrl(baseUrl.Host)
request := NewAuthenticatorRequest(fmt.Sprintf("%s://%s", baseUrl.Scheme, baseUrl.Host), map[string]string{})
context := NewAuthenticatorContext("login", config, identityUrl, false, false, *request)
return *context
}

func createIdentityUrl(hostName string) url.URL {
if hostName == "" {
hostName = "localhost"
}
identityUrl, _ := url.Parse(fmt.Sprintf("http://%s/identity_", hostName))
return *identityUrl
}

func performLogin(loginUrl url.URL, t *testing.T) string {
redirectUri := loginUrl.Query().Get("redirect_uri")
state := loginUrl.Query().Get("state")
Expand Down
Loading

0 comments on commit d2379a7

Please sign in to comment.