Class: Stytch::PolicyCache

Inherits:
Object
  • Object
show all
Defined in:
lib/stytch/rbac_local.rb

Instance Method Summary collapse

Constructor Details

#initialize(rbac_client:) ⇒ PolicyCache

Returns a new instance of PolicyCache.



8
9
10
11
12
13
14
15
16
# File 'lib/stytch/rbac_local.rb', line 8

def initialize(rbac_client:)
  @rbac_client = rbac_client
  @policy_last_update = 0
  @cached_policy = nil
  @cached_org_policies = {}
  # TTL, in seconds, before a cached policy is considered stale
  # and should be refreshed. Amounts to 5 minutes.
  @cache_ttl = 300
end

Instance Method Details

#get_org_policy(organization_id:, invalidate: false) ⇒ Object



34
35
36
37
38
39
40
41
42
# File 'lib/stytch/rbac_local.rb', line 34

def get_org_policy(organization_id:, invalidate: false)
  is_missing = @cached_org_policies[organization_id].nil?
  is_stale = !is_missing && @cached_org_policies[organization_id].last_update < Time.now.to_i - @cache_ttl
  reload_org_policy(organization_id: organization_id) if invalidate || is_missing || is_stale

  return { 'roles' => [] } if @cached_org_policies[organization_id].nil?

  @cached_org_policies[organization_id].org_policy
end

#get_policy(invalidate: false) ⇒ Object



29
30
31
32
# File 'lib/stytch/rbac_local.rb', line 29

def get_policy(invalidate: false)
  reload_policy if invalidate || @cached_policy.nil? || @policy_last_update < Time.now.to_i - @cache_ttl
  @cached_policy
end

#perform_authorization_check(subject_roles:, subject_org_id:, authorization_check:) ⇒ Object

Performs an authorization check against the project’s policy and a set of roles. If the check succeeds, this method will return. If the check fails, a PermissionError will be raised. It’s also possible for a TenancyError to be raised if the subject_org_id does not match the authZ request organization_id. authorization_check is an object with keys ‘action’, ‘resource_id’, and ‘organization_id’



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/stytch/rbac_local.rb', line 49

def perform_authorization_check(
  subject_roles:,
  subject_org_id:,
  authorization_check:
)
  raise Stytch::TenancyError.new(subject_org_id, authorization_check['organization_id']) if subject_org_id != authorization_check['organization_id']

  policy = get_policy
  org_policy = get_org_policy(organization_id: subject_org_id)
  all_roles = policy['roles'].concat(org_policy['roles'])

  return if all_roles.any? do |role|
    next unless subject_roles.include?(role['role_id'])

    role['permissions'].any? do |permission|
      actions = permission['actions']
      resource = permission['resource_id']
      has_matching_action = actions.include?('*') || actions.include?(authorization_check['action'])
      has_matching_resource = resource == authorization_check['resource_id']
      has_matching_action && has_matching_resource
    end
  end

  # If we get here, we didn't find a matching permission
  raise Stytch::PermissionError, authorization_check
end

#perform_consumer_authorization_check(subject_roles:, authorization_check:) ⇒ Object

Performs an authorization check against the project’s policy and a set of roles. If the check succeeds, this method will return. If the check fails, a PermissionError will be raised. This is used for role based authorization.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/stytch/rbac_local.rb', line 79

def perform_consumer_authorization_check(
  subject_roles:,
  authorization_check:
)
  policy = get_policy

  # For consumer authorization, we check roles without tenancy validation
  return if policy['roles'].any? do |role|
    next unless subject_roles.include?(role['role_id'])

    role['permissions'].any? do |permission|
      actions = permission['actions']
      resource = permission['resource_id']
      has_matching_action = actions.include?('*') || actions.include?(authorization_check['action'])
      has_matching_resource = resource == authorization_check['resource_id']
      has_matching_action && has_matching_resource
    end
  end

  # If we get here, we didn't find a matching permission
  raise Stytch::PermissionError, authorization_check
end

#perform_scope_authorization_check(token_scopes:, authorization_check:) ⇒ Object

Performs an authorization check against the project’s policy and a set of scopes. If the check succeeds, this method will return. If the check fails, a PermissionError will be raised. This is used for OAuth-style scope-based authorization. authorization_check is an object with keys ‘action’ and ‘resource_id’



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/stytch/rbac_local.rb', line 106

def perform_scope_authorization_check(
  token_scopes:,
  authorization_check:
)
  policy = get_policy

  # For scope-based authorization, we check if any of the token scopes match policy scopes
  # and if those scopes grant permission for the requested action/resource
  action = authorization_check['action']
  resource_id = authorization_check['resource_id']

  # Check if any of the token scopes grant permission for this action/resource
  return if policy['scopes'].any? do |scope_obj|
    scope_name = scope_obj['scope']
    next unless token_scopes.include?(scope_name)

    # Check if this scope grants permission for the requested action/resource
    scope_obj['permissions'].any? do |permission|
      actions = permission['actions']
      resource = permission['resource_id']
      has_matching_action = actions.include?('*') || actions.include?(action)
      has_matching_resource = resource == resource_id
      has_matching_action && has_matching_resource
    end
  end

  # If we get here, we didn't find a matching permission
  raise Stytch::PermissionError, authorization_check
end

#reload_org_policy(organization_id:) ⇒ Object



23
24
25
26
27
# File 'lib/stytch/rbac_local.rb', line 23

def reload_org_policy(organization_id:)
  @cached_org_policies[organization_id] = CachedOrgPolicy.new(
    org_policy: @rbac_client.organizations.get_org_policy(organization_id: organization_id)
  )
end

#reload_policyObject



18
19
20
21
# File 'lib/stytch/rbac_local.rb', line 18

def reload_policy
  @cached_policy = @rbac_client.policy['policy']
  @policy_last_update = Time.now.to_i
end