Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AuthManagementKey to config and client #507

Merged
merged 1 commit into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions descope/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,7 @@ const (
type ClientParams struct {
ProjectID string
BaseURL string
AuthManagementKey string
DefaultClient IHttpClient
CustomDefaultHeaders map[string]string
CertificateVerify CertificateVerifyMode
Expand Down Expand Up @@ -1399,6 +1400,11 @@ func (c *Client) DoRequest(ctx context.Context, method, uriPath string, body io.
if len(pswd) > 0 {
bearer = fmt.Sprintf("%s:%s", bearer, pswd)
}
// append auth management key if available
authKey := c.conf.AuthManagementKey
if len(authKey) > 0 {
bearer = fmt.Sprintf("%s:%s", bearer, authKey)
}
req.Header.Set(AuthorizationHeaderName, BearerAuthorizationPrefix+bearer)
c.addDescopeHeaders(req)

Expand Down
33 changes: 23 additions & 10 deletions descope/api/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,25 @@ import (
"github.com/stretchr/testify/require"
)

func getProjectAndJwt(r *http.Request) (string, string) {
var projectID, jwt string
func parseAuthorizationHeader(r *http.Request) (projectID, jwt, accessKey string) {
reqToken := r.Header.Get(AuthorizationHeaderName)
if splitToken := strings.Split(reqToken, BearerAuthorizationPrefix); len(splitToken) == 2 {
bearer := splitToken[1]
bearers := strings.Split(bearer, ":")
projectID = bearers[0]
if len(bearers) > 1 {
if len(bearers) == 2 {
if strings.Contains(bearers[1], ".") {
jwt = bearers[1]
} else {
accessKey = bearers[1]
}
}
if len(bearers) > 2 {
jwt = bearers[1]
accessKey = bearers[2]
}
}
return projectID, jwt
return
}

func TestClient(t *testing.T) {
Expand Down Expand Up @@ -57,7 +64,7 @@ func TestGetRequest(t *testing.T) {
assert.Nil(t, r.Body)
assert.EqualValues(t, "/path", r.URL.Path)
assert.EqualValues(t, "test=1", r.URL.RawQuery)
actualProject, _ := getProjectAndJwt(r)
actualProject, _, _ := parseAuthorizationHeader(r)
assert.EqualValues(t, projectID, actualProject)
return &http.Response{Body: io.NopCloser(strings.NewReader(expectedResponse)), StatusCode: http.StatusOK}, nil
})})
Expand All @@ -74,13 +81,15 @@ func TestPutRequest(t *testing.T) {
expectedOutput := &dummy{Test: "test"}
expectedHeaders := map[string]string{"header1": "value1"}
projectID := "test"
accesskey := "accessKey"
outputBytes, err := utils.Marshal(expectedOutput)
require.NoError(t, err)
c := NewClient(ClientParams{ProjectID: projectID, DefaultClient: mocks.NewTestClient(func(r *http.Request) (*http.Response, error) {
c := NewClient(ClientParams{ProjectID: projectID, AuthManagementKey: accesskey, DefaultClient: mocks.NewTestClient(func(r *http.Request) (*http.Response, error) {
assert.NotNil(t, r.Body)
actualProject, _ := getProjectAndJwt(r)
actualProject, _, actualAccessKey := parseAuthorizationHeader(r)
assert.EqualValues(t, http.MethodPut, r.Method)
assert.EqualValues(t, projectID, actualProject)
assert.EqualValues(t, accesskey, actualAccessKey)
assert.EqualValues(t, expectedHeaders["header1"], r.Header.Get("header1"))
return &http.Response{Body: io.NopCloser(bytes.NewReader(outputBytes)), StatusCode: http.StatusOK}, nil
})})
Expand All @@ -100,19 +109,23 @@ func TestPostRequest(t *testing.T) {
expectedOutput := &dummy{Test: "test"}
expectedHeaders := map[string]string{"header1": "value1"}
projectID := "test"
accesskey := "accessKey"
jwtStr := "eyJhbGciOiJFUzM4NCIsImtpZCI6IjI4eVRTeDZRMGNpSzU4QWRDU3ZLZkNKcEJJTiIsInR5cCI6IkpXVCJ9.eyJleHAiOi01Njk3NzcxNjg2LCJpc3MiOiIyOHlUU3g2UTBjaUs1OEFkQ1N2S2ZDSnBCSU4iLCJzdWIiOiIyOHlldzQ3NTVLdElSNnhmMk1rV2lITDRYSnEifQ.fm5h2AlyOzUCVMIezSQf8wddE6xhcfqnSAzpG4SoOy6HK387T8hxcpbmCc7qbFOQfaPDdhVhqS7JkX7wessaTznbiK_xiDac6CkENgzrl_V8eMXEHt1HcyCW1s6IQd5D"
outputBytes, err := utils.Marshal(expectedOutput)
require.NoError(t, err)
c := NewClient(ClientParams{ProjectID: projectID, DefaultClient: mocks.NewTestClient(func(r *http.Request) (*http.Response, error) {
c := NewClient(ClientParams{ProjectID: projectID, AuthManagementKey: accesskey, DefaultClient: mocks.NewTestClient(func(r *http.Request) (*http.Response, error) {
assert.NotNil(t, r.Body)
actualProject, _ := getProjectAndJwt(r)
actualProject, actualJwt, actualAccessKey := parseAuthorizationHeader(r)
assert.EqualValues(t, http.MethodPost, r.Method)
assert.EqualValues(t, projectID, actualProject)
assert.EqualValues(t, accesskey, actualAccessKey)
assert.EqualValues(t, jwtStr, actualJwt)
assert.EqualValues(t, expectedHeaders["header1"], r.Header.Get("header1"))
return &http.Response{Body: io.NopCloser(bytes.NewReader(outputBytes)), StatusCode: http.StatusOK}, nil
})})

actualOutput := &dummy{}
res, err := c.DoPostRequest(context.Background(), "path", strings.NewReader("test"), &HTTPRequest{ResBodyObj: actualOutput, Headers: expectedHeaders}, "")
res, err := c.DoPostRequest(context.Background(), "path", strings.NewReader("test"), &HTTPRequest{ResBodyObj: actualOutput, Headers: expectedHeaders}, jwtStr)
require.NoError(t, err)
assert.EqualValues(t, string(outputBytes), res.BodyStr)
assert.EqualValues(t, expectedOutput, actualOutput)
Expand Down
2 changes: 2 additions & 0 deletions descope/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ func NewWithConfig(config *Config) (*DescopeClient, error) {
logger.LogInfo("Provided public key is set, forcing only provided public key validation")
}
config.setManagementKey()
config.setAuthManagementKey()

c := api.NewClient(api.ClientParams{
ProjectID: config.ProjectID,
BaseURL: config.DescopeBaseURL,
AuthManagementKey: config.AuthManagementKey,
DefaultClient: config.DefaultClient,
CustomDefaultHeaders: config.CustomDefaultHeaders,
CertificateVerify: config.CertificateVerify,
Expand Down
15 changes: 15 additions & 0 deletions descope/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ func TestEnvVariableManagementKey(t *testing.T) {
assert.NotNil(t, a.Management)
}

func TestEnvVariableAuthManagementKey(t *testing.T) {
expectedManagementKey := "test"
err := os.Setenv(descope.EnvironmentVariableAuthManagementKey, expectedManagementKey)
defer func() {
err = os.Setenv(descope.EnvironmentVariableAuthManagementKey, "")
require.NoError(t, err)
}()
require.NoError(t, err)
a, err := NewWithConfig(&Config{ProjectID: "a"})
require.NoError(t, err)
assert.EqualValues(t, expectedManagementKey, a.config.AuthManagementKey)
assert.NotNil(t, a.Auth)
assert.NotNil(t, a.Management)
}

func TestConcurrentClients(t *testing.T) {
// This test should be run with the 'race' flag, to ensure that
// creating two client in a concurrent manner is safe
Expand Down
18 changes: 17 additions & 1 deletion descope/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ type Config struct {
ProjectID string
// ManagementKey (optional, "") - used to provide a management key that's required
// for using any of the Management APIs. If empty, this value is retrieved
// from the DESCOPE_MANAGEMENT_KEY environement variable instead. If neither
// from the DESCOPE_MANAGEMENT_KEY environment variable instead. If neither
// values are set then any Management API call with fail.
ManagementKey string
// AuthManagementKey (optional, "") - used to provide a management key to use
// with Authentication APIs whose public access has been disabled.
// If empty, this value is retrieved from the DESCOPE_AUTH_MANAGEMENT_KEY environment variable instead.
// If neither values are set then any disabled authentication methods API calls with fail.
AuthManagementKey string
// PublicKey (optional, "") - used to override or implicitly use a dedicated public key in order to decrypt and validate the JWT tokens
// during ValidateSessionRequest(). If empty, will attempt to fetch all public keys from the specified project id.
PublicKey string
Expand Down Expand Up @@ -79,3 +84,14 @@ func (c *Config) setManagementKey() string {
}
return c.ManagementKey
}

func (c *Config) setAuthManagementKey() string {
if c.AuthManagementKey == "" {
if authKey := utils.GetAuthManagementKeyEnvVariable(); authKey != "" {
c.AuthManagementKey = authKey
} else {
return ""
}
}
return c.AuthManagementKey
}
1 change: 1 addition & 0 deletions descope/internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const SKEW = time.Second * 5
type AuthParams struct {
ProjectID string
PublicKey string
AuthManagementKey string
SessionJWTViaCookie bool
CookieDomain string
CookieSameSite http.SameSite
Expand Down
4 changes: 4 additions & 0 deletions descope/internal/utils/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func GetManagementKeyEnvVariable() string {
return os.Getenv(descope.EnvironmentVariableManagementKey)
}

func GetAuthManagementKeyEnvVariable() string {
return os.Getenv(descope.EnvironmentVariableAuthManagementKey)
}

func GetPublicKeyEnvVariable() string {
return os.Getenv(descope.EnvironmentVariablePublicKey)
}
9 changes: 5 additions & 4 deletions descope/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -985,10 +985,11 @@ const (
ClaimAuthorizedGlobalPermissions = "permissions"
ClaimDescopeCurrentTenant = "dct"

EnvironmentVariableProjectID = "DESCOPE_PROJECT_ID"
EnvironmentVariablePublicKey = "DESCOPE_PUBLIC_KEY"
EnvironmentVariableManagementKey = "DESCOPE_MANAGEMENT_KEY"
EnvironmentVariableBaseURL = "DESCOPE_BASE_URL"
EnvironmentVariableProjectID = "DESCOPE_PROJECT_ID"
EnvironmentVariablePublicKey = "DESCOPE_PUBLIC_KEY"
EnvironmentVariableManagementKey = "DESCOPE_MANAGEMENT_KEY"
EnvironmentVariableAuthManagementKey = "DESCOPE_AUTH_MANAGEMENT_KEY"
EnvironmentVariableBaseURL = "DESCOPE_BASE_URL"
)

type ThirdPartyApplicationScope struct {
Expand Down