From 51e822716182e15ea08449712b7eeb25391cf7f1 Mon Sep 17 00:00:00 2001 From: Asaf Shen Date: Wed, 3 Jul 2024 11:36:28 +0300 Subject: [PATCH] fix get teannts with descope tenants claim (#439) --- descope/internal/auth/auth.go | 10 +++-- descope/internal/auth/auth_test.go | 60 +++++++++++++++++++++++------- descope/internal/auth/utils.go | 7 ++-- descope/types.go | 7 ++++ descope/types_test.go | 16 +++++++- 5 files changed, 77 insertions(+), 23 deletions(-) diff --git a/descope/internal/auth/auth.go b/descope/internal/auth/auth.go index e3c1c3c9..5d927707 100644 --- a/descope/internal/auth/auth.go +++ b/descope/internal/auth/auth.go @@ -827,13 +827,15 @@ func getAuthorizationClaimItems(token *descope.Token, tenant string, claim strin } } else { var claimValue []interface{} - if token.Claims[claimDescopeCurrentTenant] == tenant && len(token.GetTenants()) == 0 { + if v, ok := token.GetTenantValue(tenant, claim).([]interface{}); ok { + claimValue = v + } else if token.Claims[descope.ClaimDescopeCurrentTenant] == tenant && token.Claims[descope.ClaimAuthorizedTenants] == nil { // The token may have the current tenant in the "dct" claim and without the "tenants" claim + // Note: We also must ensure that the tenants claim is not present because in the if "tenants" claim exists, + // the top level claim represents for the project level roles/permissions if v, ok := token.Claims[claim].([]interface{}); ok { claimValue = v } - } else if v, ok := token.GetTenantValue(tenant, claim).([]interface{}); ok { - claimValue = v } for i := range claimValue { @@ -852,7 +854,7 @@ func getAuthorizationClaimItems(token *descope.Token, tenant string, claim strin } func isAssociatedWithTenant(token *descope.Token, tenant string) bool { - return slices.Contains(token.GetTenants(), tenant) || (token.Claims != nil && token.Claims[claimDescopeCurrentTenant] == tenant) + return slices.Contains(token.GetTenants(), tenant) || (token.Claims != nil && token.Claims[descope.ClaimDescopeCurrentTenant] == tenant) } func getPendingRefFromResponse(httpResponse *api.HTTPResponse) (*descope.EnchantedLinkResponse, error) { diff --git a/descope/internal/auth/auth_test.go b/descope/internal/auth/auth_test.go index b2f3ce11..f7e18097 100644 --- a/descope/internal/auth/auth_test.go +++ b/descope/internal/auth/auth_test.go @@ -77,11 +77,24 @@ var ( }, }, } - mockAuthorizationCurrentTenantToken = &descope.Token{ + mockAuthorizationCurrentTenantTokenNoTenants = &descope.Token{ Claims: map[string]any{ - claimPermissions: permissions, - claimRoles: roles, - claimDescopeCurrentTenant: "t1", + claimPermissions: permissions, + claimRoles: roles, + descope.ClaimDescopeCurrentTenant: "t1", + }, + } + mockAuthorizationCurrentTenantTokenWithTenants = &descope.Token{ + Claims: map[string]any{ + claimPermissions: permissions, + claimRoles: roles, + descope.ClaimAuthorizedTenants: map[string]any{ + "t1": map[string]any{ + claimPermissions: []interface{}{"t1-perm1", "t1-perm2"}, + claimRoles: []interface{}{"t1-role1", "t1-role2"}, + }, + "t2": map[string]any{}, + }, }, } ) @@ -1030,9 +1043,12 @@ func TestValidatePermissions(t *testing.T) { require.True(t, a.ValidateTenantPermissions(context.Background(), mockAuthorizationTenantToken, "t1", []string{})) require.False(t, a.ValidateTenantPermissions(context.Background(), mockAuthorizationTenantToken, "t2", []string{})) - require.True(t, a.ValidateTenantPermissions(context.Background(), mockAuthorizationCurrentTenantToken, "t1", []string{"foo"})) - require.False(t, a.ValidateTenantPermissions(context.Background(), mockAuthorizationCurrentTenantToken, "t1", []string{"qux"})) - require.False(t, a.ValidateTenantPermissions(context.Background(), mockAuthorizationCurrentTenantToken, "t2", []string{"foo"})) + require.True(t, a.ValidateTenantPermissions(context.Background(), mockAuthorizationCurrentTenantTokenNoTenants, "t1", []string{"foo"})) + require.False(t, a.ValidateTenantPermissions(context.Background(), mockAuthorizationCurrentTenantTokenNoTenants, "t1", []string{"qux"})) + require.False(t, a.ValidateTenantPermissions(context.Background(), mockAuthorizationCurrentTenantTokenNoTenants, "t2", []string{"foo"})) + + require.True(t, a.ValidateTenantPermissions(context.Background(), mockAuthorizationCurrentTenantTokenWithTenants, "t1", []string{"t1-perm1"})) + require.False(t, a.ValidateTenantPermissions(context.Background(), mockAuthorizationCurrentTenantTokenWithTenants, "t1", []string{"foo"})) // check when the value of the claim is not a map require.False(t, a.ValidateTenantPermissions( @@ -1062,8 +1078,11 @@ func TestGetMatchedPermissions(t *testing.T) { require.Equal(t, []string{"foo", "bar"}, a.GetMatchedTenantPermissions(context.Background(), mockAuthorizationTenantToken, "kuku", []string{"foo", "bar"})) require.Equal(t, []string{"foo", "bar"}, a.GetMatchedTenantPermissions(context.Background(), mockAuthorizationTenantToken, "kuku", []string{"foo", "bar", "qux"})) - require.Equal(t, []string{"foo", "bar"}, a.GetMatchedTenantPermissions(context.Background(), mockAuthorizationCurrentTenantToken, "t1", []string{"foo", "bar", "qux"})) - require.Equal(t, []string{}, a.GetMatchedTenantPermissions(context.Background(), mockAuthorizationCurrentTenantToken, "t2", []string{"foo", "bar", "qux"})) + require.Equal(t, []string{"foo", "bar"}, a.GetMatchedTenantPermissions(context.Background(), mockAuthorizationCurrentTenantTokenNoTenants, "t1", []string{"foo", "bar", "qux"})) + require.Equal(t, []string{}, a.GetMatchedTenantPermissions(context.Background(), mockAuthorizationCurrentTenantTokenNoTenants, "t2", []string{"foo", "bar", "qux"})) + + require.Equal(t, []string{"t1-perm1"}, a.GetMatchedTenantPermissions(context.Background(), mockAuthorizationCurrentTenantTokenWithTenants, "t1", []string{"t1-perm1"})) + require.Equal(t, []string{}, a.GetMatchedTenantPermissions(context.Background(), mockAuthorizationCurrentTenantTokenWithTenants, "t1", []string{"foo"})) } func TestValidateRoles(t *testing.T) { @@ -1096,9 +1115,12 @@ func TestValidateRoles(t *testing.T) { require.True(t, a.ValidateTenantRoles(context.Background(), mockAuthorizationTenantToken, "t1", []string{})) require.False(t, a.ValidateTenantRoles(context.Background(), mockAuthorizationTenantToken, "t2", []string{})) - require.True(t, a.ValidateTenantRoles(context.Background(), mockAuthorizationCurrentTenantToken, "t1", []string{"abc"})) - require.False(t, a.ValidateTenantRoles(context.Background(), mockAuthorizationCurrentTenantToken, "t1", []string{"tuv"})) - require.False(t, a.ValidateTenantRoles(context.Background(), mockAuthorizationCurrentTenantToken, "t2", []string{"abc"})) + require.True(t, a.ValidateTenantRoles(context.Background(), mockAuthorizationCurrentTenantTokenNoTenants, "t1", []string{"abc"})) + require.False(t, a.ValidateTenantRoles(context.Background(), mockAuthorizationCurrentTenantTokenNoTenants, "t1", []string{"tuv"})) + require.False(t, a.ValidateTenantRoles(context.Background(), mockAuthorizationCurrentTenantTokenNoTenants, "t2", []string{"abc"})) + + require.True(t, a.ValidateTenantRoles(context.Background(), mockAuthorizationCurrentTenantTokenWithTenants, "t1", []string{"t1-role1"})) + require.False(t, a.ValidateTenantRoles(context.Background(), mockAuthorizationCurrentTenantTokenWithTenants, "t1", []string{"abc"})) } func TestGetMatchedRoles(t *testing.T) { @@ -1118,8 +1140,18 @@ func TestGetMatchedRoles(t *testing.T) { require.Equal(t, []string{"abc", "xyz"}, a.GetMatchedTenantRoles(context.Background(), mockAuthorizationTenantToken, "kuku", []string{"abc", "xyz"})) require.Equal(t, []string{"abc", "xyz"}, a.GetMatchedTenantRoles(context.Background(), mockAuthorizationTenantToken, "kuku", []string{"abc", "xyz", "tuv"})) - require.Equal(t, []string{"abc", "xyz"}, a.GetMatchedTenantRoles(context.Background(), mockAuthorizationCurrentTenantToken, "t1", []string{"abc", "xyz", "tuv"})) - require.Equal(t, []string{}, a.GetMatchedTenantRoles(context.Background(), mockAuthorizationCurrentTenantToken, "t2", []string{"abc", "xyz", "tuv"})) + require.Equal(t, []string{"abc", "xyz"}, a.GetMatchedTenantRoles(context.Background(), mockAuthorizationCurrentTenantTokenNoTenants, "t1", []string{"abc", "xyz", "tuv"})) + require.Equal(t, []string{}, a.GetMatchedTenantRoles(context.Background(), mockAuthorizationCurrentTenantTokenNoTenants, "t2", []string{"abc", "xyz", "tuv"})) + + require.Equal(t, []string{"t1-role1"}, a.GetMatchedTenantRoles(context.Background(), mockAuthorizationCurrentTenantTokenWithTenants, "t1", []string{"t1-role1"})) + require.Equal(t, []string{}, a.GetMatchedTenantRoles(context.Background(), mockAuthorizationCurrentTenantTokenWithTenants, "t1", []string{"abc"})) +} + +func TestGetTenants(t *testing.T) { + require.Equal(t, []string{}, mockAuthorizationToken.GetTenants()) + require.Equal(t, []string{"t1"}, mockAuthorizationCurrentTenantTokenNoTenants.GetTenants()) + require.ElementsMatch(t, []string{"kuku", "t1"}, mockAuthorizationTenantToken.GetTenants()) + require.ElementsMatch(t, []string{"t1", "t2"}, mockAuthorizationCurrentTenantTokenWithTenants.GetTenants()) } func TestMe(t *testing.T) { diff --git a/descope/internal/auth/utils.go b/descope/internal/auth/utils.go index 32cb9587..a945fed7 100644 --- a/descope/internal/auth/utils.go +++ b/descope/internal/auth/utils.go @@ -254,10 +254,9 @@ func newExchangeAccessKeyBody(loginOptions *descope.AccessKeyLoginOptions) *exch } const ( - claimAttributeName = "drn" - claimPermissions = "permissions" - claimRoles = "roles" - claimDescopeCurrentTenant = "dct" + claimAttributeName = "drn" + claimPermissions = "permissions" + claimRoles = "roles" ) var ( diff --git a/descope/types.go b/descope/types.go index 29e2ae17..0e946af9 100644 --- a/descope/types.go +++ b/descope/types.go @@ -193,6 +193,9 @@ type Token struct { func (to *Token) GetTenants() []string { tenants := to.getTenants() + if len(tenants) == 0 && to.Claims != nil && to.Claims[ClaimDescopeCurrentTenant] != nil { + return []string{to.Claims[ClaimDescopeCurrentTenant].(string)} + } return maps.Keys(tenants) } @@ -224,6 +227,9 @@ func (to *Token) IsPermitted(permission string) bool { func (to *Token) IsPermittedPerTenant(tenant string, permission string) bool { permitted := false tenants := to.getTenants() + if to.Claims[ClaimDescopeCurrentTenant] == tenant && len(tenants) == 0 { + return to.IsPermitted(permission) + } tPermissions, ok := tenants[tenant] if ok { if tPermissionsMap, ok := tPermissions.(map[string]any); ok { @@ -930,6 +936,7 @@ const ( ContextUserIDPropertyKey ContextKey = ContextUserIDProperty ClaimAuthorizedTenants = "tenants" ClaimAuthorizedGlobalPermissions = "permissions" + ClaimDescopeCurrentTenant = "dct" EnvironmentVariableProjectID = "DESCOPE_PROJECT_ID" EnvironmentVariablePublicKey = "DESCOPE_PUBLIC_KEY" diff --git a/descope/types_test.go b/descope/types_test.go index 1bbfb509..2cc38b80 100644 --- a/descope/types_test.go +++ b/descope/types_test.go @@ -92,7 +92,7 @@ func TestGetCreatedTime(t *testing.T) { assert.True(t, r.GetCreatedTime().Equal(now)) } -func TestIsPermittedPerTenant(t *testing.T) { +func TestIsPermittedPerTenantFromTenantsClaim(t *testing.T) { tenantID := "somestring" dt := &Token{ Claims: map[string]any{ @@ -111,6 +111,20 @@ func TestIsPermittedPerTenant(t *testing.T) { assert.False(t, p) } +func TestIsPermittedPerTenantWithCurrentTenant(t *testing.T) { + tenantID := "t1" + dt := &Token{ + Claims: map[string]any{ + ClaimDescopeCurrentTenant: tenantID, + ClaimAuthorizedGlobalPermissions: []any{"a", "b", "c"}, + }, + } + p := dt.IsPermittedPerTenant(tenantID, "a") + assert.True(t, p) + p = dt.IsPermittedPerTenant(tenantID, "d") + assert.False(t, p) +} + func TestIsPermitted(t *testing.T) { dt := &Token{ Claims: map[string]any{