Skip to content

Commit f40e370

Browse files
authored
[STYTCH-2][AUTH-5999] Local authorization manual methods for custom org roles. (#280)
* Adds local authorization methods for handling Custom Org Roles. * Version bump.
1 parent ded2a70 commit f40e370

File tree

6 files changed

+252
-98
lines changed

6 files changed

+252
-98
lines changed

stytch/b2b/rbac.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/stytchauth/stytch-go/v17/stytch"
1414
"github.com/stytchauth/stytch-go/v17/stytch/b2b/rbac"
15+
"github.com/stytchauth/stytch-go/v17/stytch/b2b/rbac/organizations"
1516
)
1617

1718
type RBACClient struct {
@@ -63,17 +64,28 @@ func (c *RBACClient) Policy(
6364
}
6465

6566
// MANUAL(PolicyCache)(TYPES)
67+
// ADDIMPORT: "github.com/stytchauth/stytch-go/v17/stytch/b2b/rbac/organizations"
6668

6769
type PolicyCache struct {
6870
rbacClient *RBACClient
6971
policy *rbac.Policy
7072
lastUpdatedAt time.Time
73+
// Maps an Organization's ID to its policy cache.
74+
orgPolicies map[string]CachedOrgPolicy
75+
}
76+
77+
type CachedOrgPolicy struct {
78+
orgPolicy *rbac.OrgPolicy
79+
updatedAt time.Time
7180
}
7281

7382
const refreshCadence = 5 * time.Minute
7483

7584
func NewPolicyCache(rbacClient *RBACClient) *PolicyCache {
76-
return &PolicyCache{rbacClient: rbacClient}
85+
return &PolicyCache{
86+
rbacClient: rbacClient,
87+
orgPolicies: make(map[string]CachedOrgPolicy),
88+
}
7789
}
7890

7991
func (pc *PolicyCache) Get(ctx context.Context) (*rbac.Policy, error) {
@@ -89,4 +101,24 @@ func (pc *PolicyCache) Get(ctx context.Context) (*rbac.Policy, error) {
89101
return pc.policy, nil
90102
}
91103

104+
func (pc *PolicyCache) GetOrgPolicy(ctx context.Context, organizationID string) (*rbac.OrgPolicy, error) {
105+
cache, ok := pc.orgPolicies[organizationID]
106+
if !ok || time.Since(cache.updatedAt) > refreshCadence {
107+
orgPolicyResp, err := pc.rbacClient.Organizations.GetOrgPolicy(ctx, &organizations.GetOrgPolicyParams{
108+
OrganizationID: organizationID,
109+
})
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
cache = CachedOrgPolicy{
115+
orgPolicy: &orgPolicyResp.OrgPolicy,
116+
updatedAt: time.Now(),
117+
}
118+
pc.orgPolicies[organizationID] = cache
119+
}
120+
121+
return cache.orgPolicy, nil
122+
}
123+
92124
// ENDMANUAL(PolicyCache)

stytch/b2b/sessions.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,18 @@ func (c *SessionsClient) AuthenticateJWTLocal(
571571
return nil, fmt.Errorf("failed to get cached policy: %w", err)
572572
}
573573

574-
err = shared.PerformB2BAuthorizationCheck(policy, staticClaims.Session.Roles, memberSession.OrganizationID, authorizationCheck)
574+
orgPolicy, err := c.PolicyCache.GetOrgPolicy(ctx, memberSession.OrganizationID)
575+
if err != nil {
576+
return nil, fmt.Errorf("failed to get cached org policy: %w", err)
577+
}
578+
579+
err = shared.PerformB2BAuthorizationCheck(shared.PerformB2BAuthorizationCheckIn{
580+
ProjectPolicy: policy,
581+
OrgPolicy: orgPolicy,
582+
SubjectRoles: staticClaims.Session.Roles,
583+
SubjectOrgID: memberSession.OrganizationID,
584+
AuthorizationCheck: authorizationCheck,
585+
})
575586
if err != nil {
576587
return nil, err
577588
}

stytch/config/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package config
22

3-
const APIVersion = "17.1.0"
3+
const APIVersion = "17.2.0"

stytch/shared/rbac_local.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,32 @@ import (
88
"github.com/stytchauth/stytch-go/v17/stytch/stytcherror"
99
)
1010

11-
func PerformB2BAuthorizationCheck(
12-
policy *b2brbac.Policy,
13-
subjectRoles []string,
14-
subjectOrgID string,
15-
authorizationCheck *b2bsessions.AuthorizationCheck,
16-
) error {
17-
if authorizationCheck == nil {
11+
type PerformB2BAuthorizationCheckIn struct {
12+
ProjectPolicy *b2brbac.Policy
13+
OrgPolicy *b2brbac.OrgPolicy
14+
SubjectRoles []string
15+
SubjectOrgID string
16+
AuthorizationCheck *b2bsessions.AuthorizationCheck
17+
}
18+
19+
func PerformB2BAuthorizationCheck(in PerformB2BAuthorizationCheckIn) error {
20+
if in.AuthorizationCheck == nil {
1821
return nil
1922
}
2023

21-
if subjectOrgID != authorizationCheck.OrganizationID {
22-
return stytcherror.NewSessionAuthorizationTenancyError(subjectOrgID, authorizationCheck.OrganizationID)
24+
if in.SubjectOrgID != in.AuthorizationCheck.OrganizationID {
25+
return stytcherror.NewSessionAuthorizationTenancyError(in.SubjectOrgID, in.AuthorizationCheck.OrganizationID)
2326
}
2427

25-
return performRoleAuthorizationCheck(policyFromB2B(policy), authorizationCheckFromB2B(authorizationCheck), subjectRoles)
28+
return performRoleAuthorizationCheck(
29+
policyFromB2B(in.ProjectPolicy, in.OrgPolicy),
30+
authorizationCheckFromB2B(in.AuthorizationCheck),
31+
in.SubjectRoles,
32+
)
2633
}
2734

2835
func PerformB2BScopeAuthorizationCheck(
29-
policy *b2brbac.Policy,
36+
policy *b2brbac.Policy, // NOTE: org policy check not needed here.
3037
tokenScopes []string,
3138
subjectOrgID string,
3239
authorizationCheck *b2bsessions.AuthorizationCheck,
@@ -38,8 +45,8 @@ func PerformB2BScopeAuthorizationCheck(
3845
if subjectOrgID != authorizationCheck.OrganizationID {
3946
return stytcherror.NewSessionAuthorizationTenancyError(subjectOrgID, authorizationCheck.OrganizationID)
4047
}
41-
42-
return performScopeAuthorizationCheck(policyFromB2B(policy), authorizationCheckFromB2B(authorizationCheck), tokenScopes)
48+
// Custom Org Roles don't interact with scopes.
49+
return performScopeAuthorizationCheck(policyFromB2B(policy, nil), authorizationCheckFromB2B(authorizationCheck), tokenScopes)
4350
}
4451

4552
func PerformConsumerAuthorizationCheck(

stytch/shared/rbac_local_helpers.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,26 +45,41 @@ type policyResource struct {
4545
Actions []string
4646
}
4747

48-
func policyFromB2B(p *b2brbac.Policy) *policy {
48+
func policyFromB2B(projectPolicy *b2brbac.Policy, orgPolicy *b2brbac.OrgPolicy) *policy {
4949
var roles []policyRole
50-
for _, role := range p.Roles {
50+
for _, role := range projectPolicy.Roles {
5151
var permissions []policyRolePermission
5252
for _, permission := range role.Permissions {
5353
permissions = append(permissions, policyRolePermission{
5454
ResourceID: permission.ResourceID,
5555
Actions: permission.Actions,
5656
})
5757
}
58-
5958
roles = append(roles, policyRole{
6059
RoleID: role.RoleID,
6160
Description: role.Description,
6261
Permissions: permissions,
6362
})
6463
}
64+
if orgPolicy != nil {
65+
for _, role := range orgPolicy.Roles {
66+
var permissions []policyRolePermission
67+
for _, permission := range role.Permissions {
68+
permissions = append(permissions, policyRolePermission{
69+
ResourceID: permission.ResourceID,
70+
Actions: permission.Actions,
71+
})
72+
}
73+
roles = append(roles, policyRole{
74+
RoleID: role.RoleID,
75+
Description: role.Description,
76+
Permissions: permissions,
77+
})
78+
}
79+
}
6580

6681
var resources []policyResource
67-
for _, resource := range p.Resources {
82+
for _, resource := range projectPolicy.Resources {
6883
resources = append(resources, policyResource{
6984
ResourceID: resource.ResourceID,
7085
Description: resource.Description,
@@ -73,7 +88,7 @@ func policyFromB2B(p *b2brbac.Policy) *policy {
7388
}
7489

7590
var scopes []policyScope
76-
for _, scope := range p.Scopes {
91+
for _, scope := range projectPolicy.Scopes {
7792
var permissions []policyScopePermission
7893
for _, permission := range scope.Permissions {
7994
permissions = append(permissions, policyScopePermission{

0 commit comments

Comments
 (0)