oauth_im 0.8.1 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8208d820c7e100554ecce30e7059fd4d082679a90875a6345a19cb072c010f2c
4
- data.tar.gz: 4210b53980b4d73a75bd4a48ec5318d2369e22e7d3db15253b5d706dd4c46821
3
+ metadata.gz: 4c516706747b55e09365f8803351f8a282fa15fc47c7c5dfb0eb8fbd20cea0b5
4
+ data.tar.gz: dfe17e0f5b654cc518d813ec9c645edcb951b9b8a8111f8360dba5c078639934
5
5
  SHA512:
6
- metadata.gz: 38d9767641f6b8b691cfc1ceaabac18db7bfdade8f04d4110d669c3c5b48f9e0a501931c859650d7976f458225233e97eb475f92b8dcb2ca9b2b66488c596cfc
7
- data.tar.gz: 2ed159a29e9164d3aec610e20559a65fe6386e065945b942f7b1dded21a4aac0bc7f38ac2333b2a0a529134273286700dff636a65ba75518ee1586cd677ff0cb
6
+ metadata.gz: 19bbc2692b590850e2b73de9d8c082c200da8b667083715156bbcf80004d43ada5876038c79f2fee12dc0bd2c40b1cae49c5df80074abd75f01fb1d6f1f075e0
7
+ data.tar.gz: da2dd4a76af69919b00a57e2db4f75ef1ba2b6bf18c4631b71472c3d88eb1729affd4e8d6d7d5facb397cabbde6b395a7093b6b3f59c3cbb739cf130dfd924d2
data/README.md CHANGED
@@ -17,51 +17,13 @@ $ bundle
17
17
  ```
18
18
 
19
19
  ## Configuration
20
- Once the gem is installed, add an initializer. Here is an example:
20
+ Once the gem is installed, add an initializer. The [iiab
21
+ app](https://github.com/illustrativemathematics/iiab/blob/master/config/initializers/oauth_im.rb)
22
+ provides an example.
21
23
 
22
- ```ruby
23
- # config/initializers/oauth_im.rb
24
- module OauthIm
25
- configure do |config|
26
- #####################################
27
- # these routes are local to the app #
28
- #####################################
29
- config.authorize_url = ENV.fetch 'FUSION_AUTH_AUTHORIZE_URL', DEFAULT_AUTHORIZE_URL
30
- config.callback_route = ENV.fetch 'FUSION_CALLBACK_ROUTE', DEFAULT_CALLBACK_ROUTE
31
- config.token_url = ENV.fetch 'FUSION_AUTH_TOKEN_URL', DEFAULT_TOKEN_URL
32
-
33
- ##############################################
34
- # identity provider url (e.g., fusion auth): #
35
- ##############################################
36
- config.idp_url = ENV.fetch 'FUSION_AUTH_IDP_URL', DEFAULT_IDP_URL
37
-
38
- ################################################
39
- # Issuer domain: find on FA tenant General tab #
40
- ################################################
41
- config.iss_domain = ENV.fetch 'FUSION_AUTH_ISS_DOMAIN', DEFAULT_ISS_DOMAIN
42
-
43
- ####################################
44
- # find on FA application OAuth tab #
45
- ####################################
46
- config.client_id = ENV['FUSION_AUTH_CLIENT_ID']
47
- config.client_secret = ENV['FUSION_AUTH_CLIENT_SECRET']
48
-
49
- #################################################################################
50
- # 1. Find signing key name on the app details name. #
51
- # 2. Look up the key (by name) under Key Master tab under Settings: #
52
- # https://illustrativemath-dev.fusionauth.io/admin/key/ #
53
- # 3. The key should be either HMAC or RSA. #
54
- # - If HMAC, view the Secret under Details. You will need to click to reveal. #
55
- # - If RSA, copy the PEM encoded public key as-is. #
56
- # Note: You don't need both keys --- TokenDecoder will use the one available. #
57
- #################################################################################
58
- config.hmac = ENV['FUSION_AUTH_HMAC']
59
- config.rsa_public = ENV['FUSION_AUTH_RSA_PUBLIC]
60
- end
61
- end
62
- ```
24
+ ### Environment
63
25
 
64
- * The `ENV` variable values can be obtained from the OAuth provider.
26
+ The `ENV` variable values can be obtained from the OAuth provider.
65
27
  * Here is [an article at FusionAuth](https://fusionauth.io/blog/2020/12/14/how-to-securely-implement-oauth-rails) describing many of these settings.
66
28
  * The `callback_route` setting is used in two related ways:
67
29
  * It [defines a route](https://github.com/illustrativemathematics/oauth_im/blob/main/config/routes.rb#L4) to the [`OAuthIm::ClientController#callback`
@@ -75,7 +37,30 @@ end
75
37
  must be entered in the OAuth provider's list of authorized
76
38
  redirect URLs.
77
39
 
40
+ ### RSA v. HMAC
41
+
42
+ To determine the access token signing key, find the name of the key and then look it up
43
+ on the Settings|Key Master pane. (See screenshots.)
44
+
45
+ * Inspect your app settings. The screenshot shows this being done for the app
46
+ `Kendall Hunt - Terraform`.
47
+
48
+ ![app settings](./docs/images/fa-app-settings.png?raw=true)
49
+
50
+ * Find the name of the token. The screenshot shows this being done for the app
51
+ `Kendall Hunt - Terraform`. You will need to scroll down the page to the `JWT` section.
52
+
53
+ ![token name](./docs/images/fa-signing-key-name.png?raw=true)
54
+
55
+ * Look up this signing token under Home|Settings|Key Master. The screenshot shows this being done
56
+ for the signing token `KendallHunt-Terraform (12)`.
57
+ * For RSA tokens like this one, use the PEM encoded public key as-is.
58
+ * For HMAC tokens, view the secret under Details (click to reveal).
59
+
60
+ ![token name](./docs/images/fa-access-token.png?raw=true)
61
+
78
62
  ## Usage
63
+
79
64
  ### Helpers for Logging in and Out
80
65
  The engine provides [two endpoints](https://github.com/illustrativemathematics/oauth_im/blob/main/config/routes.rb#L5-L6) for logging in and out, and exposes
81
66
  corresponding view helpers. These are accessible from the main app as:
@@ -154,6 +139,20 @@ After many false starts, this repo includes two (seemingly functional) github wo
154
139
 
155
140
  ## Version History
156
141
 
142
+ ### 0.9.1
143
+ * Facilitate specs for privileged users by providing spec_user_data on AppContext
144
+
145
+ ### 0.9.0
146
+ * Consolidate all OAuth (FusionAuth) functionality
147
+ * the register app can now include this gem for OAuth to create users
148
+ * IIAB can include this gem to authenticate users
149
+ * IIAB can include this gem to determine user privileges
150
+ * Relax Rails version constraint in Gemfile
151
+ * necessary in order for register app to use gem
152
+
153
+ ### 0.8.2
154
+ * README
155
+
157
156
  ### 0.8.1
158
157
  * Tightened up test environment helpers.
159
158
 
@@ -11,25 +11,23 @@ module OauthIm
11
11
 
12
12
  private
13
13
 
14
+ delegate :email, :email_verified?,
15
+ :user_privileges,
16
+ to: :user_client,
17
+ allow_nil: true
18
+
14
19
  def authenticated?
15
20
  AppContext.authenticated_for_specs? ||
16
21
  (AppContext.provide_authentication? && logged_in?)
17
22
  end
18
23
 
19
- def email
20
- @email ||= jwt_token['email']
21
- end
22
-
23
24
  def user_jwt
24
- @user_jwt ||= session[:user_jwt] || {}
25
- end
26
-
27
- def jwt_token
28
- @jwt_token ||= user_jwt['value']&.first || {}
25
+ @user_jwt ||= session[:user_jwt]
29
26
  end
30
27
 
31
- def email_verified?
32
- jwt_token.present? && jwt_token['email_verified'].present?
28
+ def user_client
29
+ @user_client ||= OauthIm::Client.for(user_jwt: user_jwt) if
30
+ user_jwt.present?
33
31
  end
34
32
 
35
33
  def current_user
@@ -15,7 +15,7 @@ module OauthIm
15
15
 
16
16
  def logout
17
17
  reset_session
18
- redirect_to logout_url
18
+ redirect_to oauth_client.logout_url
19
19
  end
20
20
 
21
21
  def local_login
@@ -34,11 +34,10 @@ module OauthIm
34
34
 
35
35
  private
36
36
 
37
- delegate :logout_url, :user_jwt,
38
- to: :oauth_client
37
+ delegate :user_jwt, to: :oauth_client
39
38
 
40
39
  def oauth_client
41
- @oauth_client ||= OauthIm::Client.new request: request
40
+ @oauth_client ||= OauthIm::Client.for request: request
42
41
  end
43
42
 
44
43
  def local_login_userinfo
@@ -2,75 +2,16 @@
2
2
 
3
3
  module OauthIm
4
4
  class Client
5
- attr_reader :request
6
-
7
- def initialize(request:)
8
- @request = request
9
- end
10
-
11
- def login_url
12
- @login_url ||= auth_code.authorize_url
13
- end
14
-
15
- def logout_url
16
- @logout_url ||= "#{idp_url}/oauth2/logout" \
17
- "?post_logout_redirect_uri=#{return_to_url}" \
18
- "&client_id=#{client_id}"
19
- end
20
-
21
- def user_jwt
22
- @user_jwt ||= { value: decoded_token, httponly: httponly? }
23
- end
24
-
25
- private
26
-
27
- delegate :host_with_port, :params, to: :request
28
- delegate :configuration, to: OauthIm
29
- delegate :authorize_url, :token_url, :idp_url, :client_id, :client_secret,
30
- to: :configuration
31
- delegate :auth_code, to: :oauth_client
32
-
33
- def httponly?
34
- true
35
- end
36
-
37
- def return_to_url
38
- @return_to_url ||= params.fetch :return_to
39
- end
40
-
41
- def redirect_url
42
- @redirect_url ||=
43
- Engine.routes.url_helpers.callback_url callback_url_params
44
- end
45
-
46
- def callback_url_params
47
- @callback_url_params ||= { host: host_with_port,
48
- protocol: protocol }.freeze
49
- end
50
-
51
- def protocol
52
- @protocol ||= Rails.env.production? ? :https : :http
53
- end
54
-
55
- def decoded_token
56
- @decoded_token ||= TokenDecoder.new(access_token, oauth_client.id).decode
57
- end
58
-
59
- def access_token
60
- @access_token ||= response_hash[:access_token]
61
- end
62
-
63
- def oauth_client
64
- @oauth_client ||= ::OAuth2::Client.new client_id,
65
- client_secret,
66
- authorize_url: authorize_url,
67
- site: idp_url,
68
- token_url: token_url,
69
- redirect_uri: redirect_url
70
- end
71
-
72
- def response_hash
73
- @response_hash ||= auth_code.get_token(params[:code]).to_hash
5
+ def self.for(request: nil, user_jwt: nil, secure_id: nil)
6
+ if request.present?
7
+ RequestClient.new request: request
8
+ elsif user_jwt.present?
9
+ UserClient.new user_jwt: user_jwt
10
+ elsif secure_id.present?
11
+ RegistrationClient.new secure_id: secure_id
12
+ else
13
+ raise ArgumentError
14
+ end
74
15
  end
75
16
  end
76
17
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fusionauth/fusionauth_client'
4
+
5
+ module OauthIm
6
+ class IdpClient
7
+ private
8
+
9
+ delegate :configuration, to: OauthIm
10
+ delegate :api_key, :idp_url, to: :configuration
11
+
12
+ def client
13
+ @client ||= ::FusionAuth::FusionAuthClient.new api_key, idp_url
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OauthIm
4
+ class RegistrationClient < IdpClient
5
+ attr_reader :secure_id
6
+
7
+ def initialize(secure_id:)
8
+ @secure_id = secure_id
9
+ super()
10
+ end
11
+
12
+ def register(submission_params:)
13
+ client.register secure_id, submission_params
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OauthIm
4
+ class RequestClient
5
+ attr_reader :request
6
+
7
+ def initialize(request:)
8
+ @request = request
9
+ end
10
+
11
+ def login_url
12
+ @login_url ||= auth_code.authorize_url
13
+ end
14
+
15
+ def logout_url
16
+ @logout_url ||= "#{idp_url}/oauth2/logout" \
17
+ "?post_logout_redirect_uri=#{return_to_url}" \
18
+ "&client_id=#{client_id}"
19
+ end
20
+
21
+ def user_jwt
22
+ @user_jwt ||= { value: decoded_token, httponly: httponly? }
23
+ end
24
+
25
+ private
26
+
27
+ delegate :host_with_port, :params, to: :request
28
+ delegate :configuration, to: OauthIm
29
+ delegate :authorize_url, :token_url, :idp_url, :client_id, :client_secret,
30
+ to: :configuration
31
+ delegate :auth_code, to: :oauth_client
32
+
33
+ def httponly?
34
+ true
35
+ end
36
+
37
+ def return_to_url
38
+ @return_to_url ||= params.fetch :return_to
39
+ end
40
+
41
+ def redirect_url
42
+ @redirect_url ||=
43
+ Engine.routes.url_helpers.callback_url callback_url_params
44
+ end
45
+
46
+ def callback_url_params
47
+ @callback_url_params ||= { host: host_with_port,
48
+ protocol: protocol }.freeze
49
+ end
50
+
51
+ def protocol
52
+ @protocol ||= Rails.env.production? ? :https : :http
53
+ end
54
+
55
+ def decoded_token
56
+ @decoded_token ||= TokenDecoder.new(access_token, oauth_client.id).decode
57
+ end
58
+
59
+ def access_token
60
+ @access_token ||= response_hash[:access_token]
61
+ end
62
+
63
+ def oauth_client
64
+ @oauth_client ||= ::OAuth2::Client.new client_id,
65
+ client_secret,
66
+ authorize_url: authorize_url,
67
+ site: idp_url,
68
+ token_url: token_url,
69
+ redirect_uri: redirect_url
70
+ end
71
+
72
+ def response_hash
73
+ @response_hash ||= auth_code.get_token(params[:code]).to_hash
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fusionauth/fusionauth_client'
4
+
5
+ module OauthIm
6
+ class UserClient < IdpClient
7
+ attr_reader :user_jwt
8
+
9
+ def initialize(user_jwt:)
10
+ @user_jwt = user_jwt&.with_indifferent_access
11
+ super()
12
+ end
13
+
14
+ def email
15
+ @email ||= jwt_token[:email]
16
+ end
17
+
18
+ def email_verified?
19
+ email.present?
20
+ end
21
+
22
+ def user_privileges
23
+ @user_privileges ||= data[:privileges] || []
24
+ end
25
+
26
+ private
27
+
28
+ def user_data
29
+ @user_data ||= user[:registrations]&.first || {}
30
+ end
31
+
32
+ def data
33
+ @data ||= user_data[:data] || {}
34
+ end
35
+
36
+ def success_response
37
+ @success_response ||= client_response&.success_response || {}
38
+ end
39
+
40
+ def user
41
+ @user ||= success_response[:user] || {}
42
+ end
43
+
44
+ # https://www.rubydoc.info/gems/fusionauth_client/1.32.1/FusionAuth/FusionAuthClient#retrieve_user-instance_method
45
+ def client_response
46
+ @client_response ||= client.retrieve_user user_id
47
+ end
48
+
49
+ def jwt_token
50
+ @jwt_token ||= user_jwt[:value]&.first&.with_indifferent_access || {}
51
+ end
52
+
53
+ def user_id
54
+ @user_id ||= jwt_token[:sub]
55
+ end
56
+ end
57
+ end
@@ -5,28 +5,32 @@ module AppContext
5
5
  true
6
6
  end
7
7
 
8
- def self.privileged?
9
- @privileged if provide_authentication?
10
- end
11
-
12
8
  def self.spec_user
13
- @spec_user if Rails.env.test? && provide_authentication?
9
+ @spec_user if override_for_specs?
14
10
  end
15
11
 
16
12
  def self.authenticated_for_specs?
17
- @authenticated_for_specs if Rails.env.test? && provide_authentication?
13
+ @authenticated_for_specs if override_for_specs?
14
+ end
15
+
16
+ def self.spec_user_data
17
+ override_for_specs? ? (@spec_user_data.presence || {}) : {}
18
+ end
19
+
20
+ def self.override_for_specs?
21
+ Rails.env.test? && provide_authentication?
18
22
  end
19
23
 
20
- def self.authenticate_for_specs(spec_user: nil, privileged: false)
24
+ def self.authenticate_for_specs(spec_user: nil, spec_user_data: {})
21
25
  return unless provide_authentication?
22
26
  raise 'Use only in test environment!!' unless Rails.env.test?
23
27
 
24
28
  @authenticated_for_specs = true
25
29
  @spec_user = spec_user
26
- @privileged = privileged
30
+ @spec_user_data = spec_user_data
27
31
  yield
28
- @privileged = false
29
- @spec_user = nil
32
+ @privileged_for_specs = false
33
+ @spec_user_data = {}
30
34
  @authenticated_for_specs = false
31
35
  end
32
36
  end
@@ -7,6 +7,7 @@
7
7
  module OauthIm
8
8
  CONFIGURABLE_FIELDS =
9
9
  %i[
10
+ api_key
10
11
  authorize_url
11
12
  callback_route
12
13
  token_url
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OauthIm
4
- VERSION = '0.8.1'
4
+ VERSION = '0.9.1'
5
5
  end
data/lib/oauth_im.rb CHANGED
@@ -9,11 +9,16 @@ require 'oauth_im/engine'
9
9
  require 'oauth_im/configuration'
10
10
 
11
11
  module OauthIm
12
- DEFAULT_AUTHORIZE_URL = '/oauth2/authorize'
13
- DEFAULT_CALLBACK_ROUTE = 'callback'
14
- DEFAULT_TOKEN_URL = '/oauth2/token'
15
- DEFAULT_IDP_URL = 'https://illustrativemath-dev.fusionauth.io'
16
- DEFAULT_ISS_DOMAIN = 'illustrativemathematics.org'
12
+ DEFAULT_AUTH_AUTHORIZE_URL = '/oauth2/authorize'
13
+ DEFAULT_AUTH_CALLBACK_ROUTE = 'callback'
14
+ DEFAULT_AUTH_TOKEN_URL = '/oauth2/token'
15
+ DEFAULT_AUTH_IDP_URL = 'https://illustrativemath-dev.fusionauth.io'
16
+ DEFAULT_AUTH_ISS_DOMAIN = 'illustrativemathematics.org'
17
+ DEFAULT_AUTH_API_KEY = nil
18
+ DEFAULT_AUTH_CLIENT_ID = nil
19
+ DEFAULT_AUTH_CLIENT_SECRET = nil
20
+ DEFAULT_AUTH_HMAC = nil
21
+ DEFAULT_AUTH_RSA_PUBLIC = nil
17
22
 
18
23
  class << self
19
24
  attr_reader :configuration
@@ -29,6 +34,6 @@ module OauthIm
29
34
  end
30
35
 
31
36
  def self.callback_route
32
- configuration&.callback_route || DEFAULT_CALLBACK_ROUTE
37
+ configuration&.callback_route || DEFAULT_AUTH_CALLBACK_ROUTE
33
38
  end
34
39
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oauth_im
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Connally
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-26 00:00:00.000000000 Z
11
+ date: 2022-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fusionauth_client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.32.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.32.1
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: jwt
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -42,14 +56,14 @@ dependencies:
42
56
  name: rails
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - "~>"
59
+ - - ">="
46
60
  - !ruby/object:Gem::Version
47
61
  version: 5.pre.2.pre.stable
48
62
  type: :runtime
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - "~>"
66
+ - - ">="
53
67
  - !ruby/object:Gem::Version
54
68
  version: 5.pre.2.pre.stable
55
69
  - !ruby/object:Gem::Dependency
@@ -126,7 +140,11 @@ files:
126
140
  - app/controllers/oauth_im/client_controller.rb
127
141
  - app/helpers/oauth_im/application_helper.rb
128
142
  - app/services/oauth_im/client.rb
143
+ - app/services/oauth_im/idp_client.rb
144
+ - app/services/oauth_im/registration_client.rb
145
+ - app/services/oauth_im/request_client.rb
129
146
  - app/services/oauth_im/token_decoder.rb
147
+ - app/services/oauth_im/user_client.rb
130
148
  - app/views/layouts/oauth_im/application.html.erb
131
149
  - config/initializers/app_context.rb
132
150
  - config/routes.rb