oauth_im 0.8.1 → 0.9.1

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: 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