zaikio-jwt_auth 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10365dcb96c417de5f8226e97fc835334e5b6ed92868fead27d443565013c7d3
4
- data.tar.gz: 5c66b0a5359ab1db156861650ccb142ec1e5572074000fb8c0c4c1740ccc833c
3
+ metadata.gz: 69257f2c9dcf661babfc6b5600c0b4b724c32b4e92ffb6743e47a560e020a81c
4
+ data.tar.gz: 0b61afe2f4587ba84ac3e922bcb67024b8ec9f53b69cdbf155c17b4c556388d6
5
5
  SHA512:
6
- metadata.gz: b52f0baddf61c6d418b0b82a1e003a82fde64484182485662111ec52a8d37275886db7c282ab9ab7d211888eb64c760d2541662905200e80f016c13dcb04566a
7
- data.tar.gz: 815b388a900b8b3f6d10a46f9a0968f6c51c7227fe232ef44ecb2509f4c23d9dbea34482de4f9229af6de455e98bf3d31529ab86618d30685dcfcae2e4cc6881
6
+ metadata.gz: 7717ef3559a647592cca0568e09531fdd8854c61d2dcdfb64077faec8bf0bd31e3dc87336b62dd0fa6b6673cbc6de4dfc5fb94d3371e2c9598e295d18e4d8578
7
+ data.tar.gz: 82094718a024d29a023220560517c77d59bc29b7d4dc293419b23ee9204bd936f6d14e76c4b2698c95a71ca0f72bddbb6bc2387d70b801f85da86b2ad2b86b5f
@@ -15,17 +15,29 @@ module Zaikio
15
15
  BadResponseError = Class.new(StandardError)
16
16
 
17
17
  class << self
18
+ # Retrieve some data from the Hub, reachable at `directory_path`. Attempts to
19
+ # retrieve data from a cache first (usually Redis, if configured). Caching can be
20
+ # skipped by setting an `:invalidate` option, or if the cached data is stale it
21
+ # will be refetched from the Hub anyway. Please note that this method can return
22
+ # `nil` if there is no cache available and the Hub is giving error responses or
23
+ # failures.
24
+ #
25
+ # @example Fetching revoked access token information
26
+ #
27
+ # DirectoryCache.fetch("api/v1/revoked_access_tokens.json")
28
+ #
29
+ # @returns Hash (in the happy path)
30
+ # @returns nil (if the cache is unavailable and the API is down)
18
31
  def fetch(directory_path, options = {})
19
32
  cache = Zaikio::JWTAuth.configuration.cache.read("zaikio::jwt_auth::#{directory_path}")
20
33
 
21
- json = Oj.load(cache) if cache
34
+ return reload_or_enqueue(directory_path) unless cache
22
35
 
23
- if !cache || options[:invalidate] || cache_expired?(json, options[:expires_after])
24
- new_values = reload_or_enqueue(directory_path)
25
- return new_values || json["data"]
26
- end
36
+ json = Oj.load(cache)
27
37
 
28
- json["data"]
38
+ if options[:invalidate] || cache_expired?(json, options[:expires_after])
39
+ reload_or_enqueue(directory_path)
40
+ end || json["data"]
29
41
  end
30
42
 
31
43
  def update(directory_path, options = {})
@@ -30,7 +30,7 @@ module Zaikio
30
30
  def keys
31
31
  return Zaikio::JWTAuth.configuration.keys if Zaikio::JWTAuth.configuration.keys
32
32
 
33
- fetch_from_cache["keys"]
33
+ fetch_from_cache.fetch("keys")
34
34
  end
35
35
 
36
36
  def fetch_from_cache(options = {})
@@ -47,36 +47,15 @@ module Zaikio
47
47
 
48
48
  # scope_options is an array of objects with:
49
49
  # scope, app_name (optional), except/only (array, optional), type (read, write, readwrite)
50
- def scope_by_configurations?(scope_configurations, action_name, context) # rubocop:disable Metrics/AbcSize
51
- configuration = scope_configurations.find do |scope_configuration|
52
- action_matches = action_matches_config?(scope_configuration, action_name)
53
-
54
- if action_matches && scope_configuration[:if] && !context.instance_exec(&scope_configuration[:if])
55
- false
56
- elsif action_matches && scope_configuration[:unless] && context.instance_exec(&scope_configuration[:unless])
57
- false
58
- else
59
- action_matches
60
- end
61
- end
62
-
50
+ def scope_by_configurations?(configuration, action_name)
63
51
  return true unless configuration
64
52
 
65
53
  scope?(configuration[:scopes], action_name, app_name: configuration[:app_name], type: configuration[:type])
66
54
  end
67
55
 
68
- def action_matches_config?(scope_configuration, action_name)
69
- if scope_configuration[:only]
70
- Array(scope_configuration[:only]).any? { |a| a.to_s == action_name }
71
- elsif scope_configuration[:except]
72
- Array(scope_configuration[:except]).none? { |a| a.to_s == action_name }
73
- else
74
- true
75
- end
76
- end
77
-
78
- def scope?(allowed_scopes, action_name, app_name: nil, type: nil)
56
+ def scope?(allowed_scopes, action_name, app_name: nil, type: nil, scope: nil) # rubocop:disable Metrics/AbcSize
79
57
  app_name ||= Zaikio::JWTAuth.configuration.app_name
58
+ scope ||= self.scope
80
59
  Array(allowed_scopes).map(&:to_s).any? do |allowed_scope|
81
60
  scope.any? do |s|
82
61
  parts = s.split(".")
@@ -1,5 +1,5 @@
1
1
  module Zaikio
2
2
  module JWTAuth
3
- VERSION = "2.2.0".freeze
3
+ VERSION = "2.3.0".freeze
4
4
  end
5
5
  end
@@ -12,6 +12,8 @@ require "zaikio/jwt_auth/test_helper"
12
12
 
13
13
  module Zaikio
14
14
  module JWTAuth
15
+ DOCS_LINK = "For more information check our docs: https://docs.zaikio.com/guide/oauth/scopes.html".freeze
16
+
15
17
  class << self
16
18
  attr_accessor :configuration
17
19
  end
@@ -34,10 +36,14 @@ module Zaikio
34
36
  def self.revoked_token_ids
35
37
  return [] if mocked_jwt_payload
36
38
 
37
- configuration.revoked_token_ids || DirectoryCache.fetch(
39
+ return configuration.revoked_token_ids if configuration.revoked_token_ids
40
+
41
+ result = DirectoryCache.fetch(
38
42
  "api/v1/revoked_access_tokens.json",
39
43
  expires_after: 60.minutes
40
- )["revoked_token_ids"]
44
+ ) || {}
45
+
46
+ result.fetch("revoked_token_ids", [])
41
47
  end
42
48
 
43
49
  def self.included(base)
@@ -125,14 +131,61 @@ module Zaikio
125
131
 
126
132
  private
127
133
 
134
+ def find_scope_configuration(scope_configurations)
135
+ scope_configurations.find do |scope_configuration|
136
+ action_matches = action_matches_config?(scope_configuration)
137
+
138
+ if action_matches && scope_configuration[:if] && !instance_exec(&scope_configuration[:if])
139
+ false
140
+ elsif action_matches && scope_configuration[:unless] && instance_exec(&scope_configuration[:unless])
141
+ false
142
+ else
143
+ action_matches
144
+ end
145
+ end
146
+ end
147
+
148
+ def action_matches_config?(scope_configuration)
149
+ if scope_configuration[:only]
150
+ Array(scope_configuration[:only]).any? { |a| a.to_s == action_name }
151
+ elsif scope_configuration[:except]
152
+ Array(scope_configuration[:except]).none? { |a| a.to_s == action_name }
153
+ else
154
+ true
155
+ end
156
+ end
157
+
158
+ def required_scopes(token_data, configuration)
159
+ Array(configuration[:scopes]).flat_map do |allowed_scope|
160
+ %i[r w rw].filter_map do |type|
161
+ app_name = configuration[:app_name] || Zaikio::JWTAuth.configuration.app_name
162
+ full_scope = "#{app_name}.#{allowed_scope}.#{type}"
163
+ if token_data.scope?([allowed_scope], action_name, app_name: app_name, type: configuration[:type],
164
+ scope: [full_scope])
165
+ full_scope
166
+ end
167
+ end
168
+ end
169
+ end
170
+
128
171
  def show_error_if_authorize_by_jwt_scopes_fails(token_data)
172
+ configuration = find_scope_configuration(self.class.authorize_by_jwt_scopes)
173
+
129
174
  return if token_data.scope_by_configurations?(
130
- self.class.authorize_by_jwt_scopes,
131
- action_name,
132
- self
175
+ configuration,
176
+ action_name
133
177
  )
134
178
 
135
- render_error("unpermitted_scope")
179
+ details = nil
180
+
181
+ if configuration
182
+ required_scopes = required_scopes(token_data, configuration)
183
+
184
+ details = "This endpoint requires one of the following scopes: #{required_scopes.join(', ')} but your " \
185
+ "access token only includes the following scopes: #{token_data.scope.join(', ')} - #{DOCS_LINK}"
186
+ end
187
+
188
+ render_error(["unpermitted_scope", details])
136
189
  end
137
190
 
138
191
  def show_error_if_authorize_by_jwt_subject_type_fails(token_data)
@@ -141,7 +194,8 @@ module Zaikio
141
194
  return
142
195
  end
143
196
 
144
- render_error("unpermitted_subject")
197
+ render_error(["unpermitted_subject", "Expected Subject Type: #{self.class.authorize_by_jwt_subject_type} | "\
198
+ "Subject type from Access Token: #{token_data.subject_type} - #{DOCS_LINK}"])
145
199
  end
146
200
 
147
201
  def show_error_if_token_is_revoked(token_data)
@@ -151,7 +205,7 @@ module Zaikio
151
205
  end
152
206
 
153
207
  def render_error(error, status: :forbidden)
154
- render(status: status, json: { "errors" => [error] })
208
+ render(status: status, json: { "errors" => Array(error).compact })
155
209
  end
156
210
 
157
211
  def jwt_options
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zaikio-jwt_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - crispymtn
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-09-28 00:00:00.000000000 Z
13
+ date: 2023-02-09 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activejob