gamora 0.8.0 → 0.10.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: d898db3b0a39bb00099a8912953f6dcdfda11cddbb38eccfe72a798986a4dbb9
4
- data.tar.gz: 943a549def5cc2b014fe7200cb72cfdfd00acef025b2db74731cce0eecfa5927
3
+ metadata.gz: 62fcee90396875db0256e297c90dda80546c3268cfecdd4f0e53b7f3c4e68ba8
4
+ data.tar.gz: '093571bfd8706530b474c1bf9d046db56cd5a0a53820546afc88f274f528a6a6'
5
5
  SHA512:
6
- metadata.gz: a71915324e16d094db423133defdfa83f21a4451eb32ac468d4d2be35eb0768493e6cba8ed48c2efea746f4219e406751159c3d5eac500fe3e52f1e7c94c99d9
7
- data.tar.gz: 86a1b5282d7bfc407264f971bc3f5ba2c6f756028d5afdb857e069ede7cc9787c9ad4b291b9e4fcf9365bc9a4f4e26e977a3b13f4a870b1c32c0b24bd6ba95e4
6
+ metadata.gz: '0888b96495e6e55f0cc49512a9ec0c8d73d67e839a2dae0207bdb81728032e46436b8a3ce0cdf1daa1352d5ae849a4a723a28fe29d9c6c615b5efbf6dd33cc8c'
7
+ data.tar.gz: 3c00a26cce8ad257170853ffc0c6c9fd3a355372f401881b470cb9cdb86d0a94e43b401e5ac94024979faa3636969fc33ec7429f033287d006a6ff5bfa8360aa
data/README.md CHANGED
@@ -115,6 +115,38 @@ Optionally, if you want to do something different when authentication
115
115
  fails, you just need to override the `user_authentication_failed!`
116
116
  method in you controller and customize it as you wish.
117
117
 
118
+ ## Cross-Client Identity
119
+
120
+ By default, gamora will accept only access tokens that were generating
121
+ with the `client_id` in the configuration. If access tokens coming from
122
+ other clients have to be accepted, make sure to add their client ids to
123
+ the `whitelisted_clients` config option.
124
+
125
+ ```ruby
126
+ Gamora.setup do |config|
127
+ ...
128
+
129
+ config.whitelisted_clients = ["OTHER_CLIENT_ID"]
130
+ end
131
+ ```
132
+
133
+ ## Caching
134
+
135
+ In order to avoid performing requests to the IDP on each request in the
136
+ application, it is possible to set a caching time for introspection and
137
+ userinfo endpoints. Make sure to not have a too long expiration time for
138
+ `introspect_cache_expires_in` but not too short to impact the application
139
+ performance, it is a balance.
140
+
141
+ ```ruby
142
+ Gamora.setup do |config|
143
+ ...
144
+
145
+ config.userinfo_cache_expires_in = 10.minute
146
+ config.introspect_cache_expires_in = 5.seconds
147
+ end
148
+ ```
149
+
118
150
  ## Development
119
151
 
120
152
  After checking out the repo, run `bin/setup` to install dependencies. Then,
@@ -7,6 +7,7 @@ module Gamora
7
7
  sub: :id,
8
8
  roles: :roles,
9
9
  email: :email,
10
+ username: :username,
10
11
  given_name: :first_name,
11
12
  family_name: :last_name,
12
13
  phone_number: :phone_number,
@@ -15,9 +16,15 @@ module Gamora
15
16
  }.freeze
16
17
 
17
18
  def authenticate_user!
19
+ return authentication_failed! unless access_token.present?
20
+
21
+ token_data = introspect_access_token(access_token)
22
+ return authentication_failed! unless valid_token_data?(token_data)
23
+
18
24
  claims = resource_owner_claims(access_token)
19
- assign_current_user_from_claims(claims) if claims.present?
20
- validate_authentication!
25
+ return authentication_failed! unless claims.present?
26
+
27
+ assign_current_user_from_claims(claims)
21
28
  end
22
29
 
23
30
  def current_user
@@ -26,21 +33,29 @@ module Gamora
26
33
 
27
34
  private
28
35
 
29
- def validate_authentication!
36
+ def access_token
30
37
  raise NotImplementedError
31
38
  end
32
39
 
33
- def access_token
40
+ def authentication_failed!
34
41
  raise NotImplementedError
35
42
  end
36
43
 
37
- def user_authentication_failed!
38
- raise NotImplementedError
44
+ def valid_token_data?(token_data)
45
+ token_data[:active] && whitelisted_client?(token_data[:client_id])
46
+ end
47
+
48
+ def whitelisted_client?(client_id)
49
+ whitelisted_clients.include?(client_id)
50
+ end
51
+
52
+ def whitelisted_clients
53
+ Configuration.whitelisted_clients | [Configuration.client_id]
39
54
  end
40
55
 
41
56
  def assign_current_user_from_claims(claims)
42
- attrs = user_attributes_from_claims(claims)
43
- @current_user = User.new(attrs)
57
+ attributes = user_attributes_from_claims(claims)
58
+ @current_user = User.new(attributes)
44
59
  end
45
60
 
46
61
  def user_attributes_from_claims(claims)
@@ -48,27 +63,29 @@ module Gamora
48
63
  end
49
64
 
50
65
  def resource_owner_claims(access_token)
51
- return {} if access_token.blank?
52
-
53
- resource_owner_claims!(access_token)
54
- end
66
+ cache_key = cache_key(:userinfo, access_token)
67
+ expires_in = Configuration.userinfo_cache_expires_in
55
68
 
56
- def resource_owner_claims!(access_token)
57
- Rails.cache.fetch(cache_key(access_token), cache_options) do
69
+ Rails.cache.fetch(cache_key, { expires_in: expires_in }) do
58
70
  oauth_client.userinfo(access_token)
59
71
  end
60
72
  end
61
73
 
62
- def oauth_client
63
- Client.from_config
74
+ def introspect_access_token(access_token)
75
+ cache_key = cache_key(:introspect, access_token)
76
+ expires_in = Configuration.introspect_cache_expires_in
77
+
78
+ Rails.cache.fetch(cache_key, { expires_in: expires_in }) do
79
+ oauth_client.introspect(access_token)
80
+ end
64
81
  end
65
82
 
66
- def cache_options
67
- { expires_in: Configuration.userinfo_cache_expires_in }
83
+ def cache_key(context, access_token)
84
+ "gamora:#{context}:#{Digest::SHA256.hexdigest(access_token)}"
68
85
  end
69
86
 
70
- def cache_key(access_token)
71
- "userinfo:#{Digest::SHA256.hexdigest(access_token)}"
87
+ def oauth_client
88
+ Client.from_config
72
89
  end
73
90
  end
74
91
  end
@@ -7,12 +7,6 @@ module Gamora
7
7
 
8
8
  private
9
9
 
10
- def validate_authentication!
11
- return if current_user.present?
12
-
13
- user_authentication_failed!
14
- end
15
-
16
10
  def access_token
17
11
  pattern = /^Bearer /
18
12
  header = request.headers["Authorization"]
@@ -21,7 +15,7 @@ module Gamora
21
15
  header.gsub(pattern, "")
22
16
  end
23
17
 
24
- def user_authentication_failed!
18
+ def authentication_failed!
25
19
  render json: { error: "Access token invalid" }, status: :unauthorized
26
20
  end
27
21
  end
@@ -11,18 +11,12 @@ module Gamora
11
11
 
12
12
  private
13
13
 
14
- def validate_authentication!
15
- return if current_user.present?
16
-
17
- session["gamora.origin"] = request.original_url
18
- user_authentication_failed!
19
- end
20
-
21
14
  def access_token
22
15
  session[:access_token]
23
16
  end
24
17
 
25
- def user_authentication_failed!
18
+ def authentication_failed!
19
+ session["gamora.origin"] = request.original_url
26
20
  redirect_to gamora.authentication_path
27
21
  end
28
22
  end
data/lib/gamora/client.rb CHANGED
@@ -20,13 +20,25 @@ module Gamora
20
20
  token_method: Configuration.token_method,
21
21
  redirect_uri: Configuration.redirect_uri,
22
22
  userinfo_url: Configuration.userinfo_url,
23
- authorize_url: Configuration.authorize_url
23
+ authorize_url: Configuration.authorize_url,
24
+ introspect_url: Configuration.introspect_url
24
25
  }
25
26
  end
26
27
  end
27
28
 
28
29
  def userinfo(access_token)
29
- response = userinfo_request(access_token)
30
+ params = userinfo_params(access_token)
31
+ opts = request_options(params)
32
+ response = request(:post, options[:userinfo_url], opts)
33
+ JSON.parse(response.body).symbolize_keys
34
+ rescue OAuth2::Error
35
+ {}
36
+ end
37
+
38
+ def introspect(access_token)
39
+ params = introspect_params(access_token)
40
+ opts = request_options(params)
41
+ response = request(:post, options[:introspect_url], opts)
30
42
  JSON.parse(response.body).symbolize_keys
31
43
  rescue OAuth2::Error
32
44
  {}
@@ -34,15 +46,22 @@ module Gamora
34
46
 
35
47
  private
36
48
 
37
- def userinfo_request(access_token)
38
- opts = userinfo_request_options(access_token)
39
- request(:post, options[:userinfo_url], opts)
49
+ def request_options(params)
50
+ {
51
+ body: params.to_json,
52
+ headers: { "Content-Type": "application/json" }
53
+ }
40
54
  end
41
55
 
42
- def userinfo_request_options(access_token)
56
+ def userinfo_params(access_token)
57
+ { access_token: access_token }
58
+ end
59
+
60
+ def introspect_params(access_token)
43
61
  {
44
- body: { access_token: access_token }.to_json,
45
- headers: { "Content-Type": "application/json" }
62
+ token: access_token,
63
+ client_id: Configuration.client_id,
64
+ client_secret: Configuration.client_secret
46
65
  }
47
66
  end
48
67
  end
@@ -6,8 +6,9 @@ module Gamora
6
6
  mattr_accessor :client_secret, default: nil
7
7
  mattr_accessor :site, default: nil
8
8
  mattr_accessor :token_url, default: "/oauth2/token"
9
- mattr_accessor :authorize_url, default: "/oauth2/authorize"
10
9
  mattr_accessor :userinfo_url, default: "/oauth2/userinfo"
10
+ mattr_accessor :authorize_url, default: "/oauth2/authorize"
11
+ mattr_accessor :introspect_url, default: "/oauth2/introspect"
11
12
  mattr_accessor :token_method, default: :post
12
13
  mattr_accessor :redirect_uri, default: nil
13
14
  mattr_accessor :default_scope, default: "openid profile email"
@@ -16,7 +17,9 @@ module Gamora
16
17
  mattr_accessor :default_branding, default: "amco"
17
18
  mattr_accessor :default_theme, default: "default"
18
19
  mattr_accessor :ui_locales, default: -> { I18n.locale }
19
- mattr_accessor :userinfo_cache_expires_in, default: 0.seconds
20
+ mattr_accessor :userinfo_cache_expires_in, default: 1.minute
21
+ mattr_accessor :introspect_cache_expires_in, default: 0.seconds
22
+ mattr_accessor :whitelisted_clients, default: []
20
23
 
21
24
  def setup
22
25
  yield(self) if block_given?
data/lib/gamora/user.rb CHANGED
@@ -7,6 +7,7 @@ module Gamora
7
7
  attr_accessor :id,
8
8
  :roles,
9
9
  :email,
10
+ :username,
10
11
  :last_name,
11
12
  :first_name,
12
13
  :phone_number,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamora
4
- VERSION = "0.8.0"
4
+ VERSION = "0.10.0"
5
5
  end
@@ -8,8 +8,9 @@ Gamora.setup do |config|
8
8
 
9
9
  # ===> Optional OAuth2 configuration options and its defaults.
10
10
  # config.token_url = "/oauth2/token"
11
- # config.authorize_url = "/oauth2/authorize"
12
11
  # config.userinfo_url = "/oauth2/userinfo"
12
+ # config.authorize_url = "/oauth2/authorize"
13
+ # config.introspect_url = "/oauth2/introspect"
13
14
  # config.token_method = :post
14
15
  # config.redirect_uri = nil
15
16
  # config.default_scope = "openid profile email"
@@ -18,5 +19,7 @@ Gamora.setup do |config|
18
19
  # config.default_branding = "amco"
19
20
  # config.default_theme = "default"
20
21
  # config.ui_locales = -> { I18n.locale }
21
- # config.userinfo_cache_expires_in = 0.seconds
22
+ # config.userinfo_cache_expires_in = 1.minute
23
+ # config.introspect_cache_expires_in = 0.seconds
24
+ # config.whitelisted_clients = []
22
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gamora
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alejandro Gutiérrez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-21 00:00:00.000000000 Z
11
+ date: 2023-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oauth2