oauth_im 0.7.0 → 0.7.3

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: 49173e12b6a9dd53124b3ecb56a5f71411a819daacd7a1d2e914dd911e4157a1
4
- data.tar.gz: 1c7e47395be3b93a95604aa880cacc2f84edf6d45cd516e0e036e86068d67feb
3
+ metadata.gz: f4b28f51bcd7b2893dd52780a267678fab317eb70feefa6d3ab9d714cad45834
4
+ data.tar.gz: c605bb66ede19ea12e1e124f12164d3cee63358d24a0bd0971693732e96b380f
5
5
  SHA512:
6
- metadata.gz: b376f8f3b03157df81d18c56d34dd9c9f58d4f5577a308ad75fb709e02ed0b6dbec3d51f9733ad2897402b9bfe2d0c5fed639e861c25a80975dc89db1a3abd4a
7
- data.tar.gz: c81f6b6aad610aa99f800f00023c0478e25b39afb56abcf8dbedd9affe08a2fdd16cd4ee5101d2c896d5243b8d2996d6311b3d9accd69626c2df354a8c6d1d9a
6
+ metadata.gz: a61461548d89152f21df67bdafc810ff7e38826e070a61185a5b557c28c42d3477fc92acf1de2e256d0a6349a1a8c6923248bda1586c7303fe5f77aa5d1a2c4f
7
+ data.tar.gz: 3c4db160ded76f36e56ed22791f404741b43cc1b204c8221f09852c0ab3ca71b7c9cbd0ed28a5a0d298b7d855becc841efb768e5578359057db8d8cc062208d3
data/README.md CHANGED
@@ -23,29 +23,47 @@ Once the gem is installed, add an initializer. Here is an example:
23
23
  # config/initializers/oauth_im.rb
24
24
  module OauthIm
25
25
  configure do |config|
26
- config.api_key = ENV['FUSION_AUTH_API_KEY']
27
- config.callback_path = ENV['FUSION_CALLBACK_PATH'] || DEFAULT_CALLBACK_PATH
28
- config.client_id = ENV['FUSION_AUTH_CLIENT_ID']
29
- config.client_secret = ENV['FUSION_AUTH_CLIENT_SECRET']
30
- config.domain = ENV['FUSION_AUTH_DOMAIN']
31
- config.hmac = ENV['FUSION_AUTH_HMAC']
32
- config.iss_domain = ENV['FUSION_AUTH_ISS_DOMAIN']
33
- config.tenant_id = ENV['FUSION_AUTH_TENANT_ID']
34
- config.authorize_url = ENV['FUSION_AUTH_AUTHORIZE_URL'] || DEFAULT_AUTHORIZE_URL
35
- config.token_url = ENV['FUSION_AUTH_TOKEN_URL'] || DEFAULT_TOKEN_URL
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
+ # 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
+ # View default signing key: https://illustrativemath-dev.fusionauth.io/admin/key/ #
51
+ ###################################################################################
52
+ config.hmac = ENV['FUSION_AUTH_HMAC']
36
53
  end
37
54
  end
38
55
  ```
39
56
 
40
57
  * The `ENV` variable values can be obtained from the OAuth provider.
41
- * The `callback_path` setting is used in two related ways:
58
+ * Here is [an article at FusionAuth](https://fusionauth.io/blog/2020/12/14/how-to-securely-implement-oauth-rails) describing many of these settings.
59
+ * The `callback_route` setting is used in two related ways:
42
60
  * It [defines a route](https://github.com/illustrativemathematics/oauth_im/blob/main/config/routes.rb#L4) to the [`OAuthIm::ClientController#callback`
43
61
  action](https://github.com/illustrativemathematics/oauth_im/blob/main/app/controllers/oauth_im/client_controller.rb#L7-L12).
44
62
  * It defines a [callback URL](https://github.com/illustrativemathematics/oauth_im/blob/main/app/controllers/oauth_im/client_controller.rb#L69) used by the OAuth provider.
45
63
  * Note that this callback URL must be whitelisted at the provider.
46
64
  At FusionAuth, this is done under the `Applications|OAuth` tab.
47
65
  * For instance, for the app `staging-kh-iiab.herokuapp.com`, if
48
- `config.callback_path` is set to `callback` (the default), then
66
+ `config.callback_route` is set to `callback` (the default), then
49
67
  the URL `https://staging-kh-iiab.herokuapp.com/oauth_im/callback`
50
68
  must be entered in the OAuth provider's list of authorized
51
69
  redirect URLs.
@@ -55,9 +73,9 @@ end
55
73
  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
56
74
  corresponding view helpers. These are accessible from the main app as:
57
75
 
58
- | path | url |
59
- |------|-----|
60
- | `oauth_im.login_path` | `oauth_im.login_url` |
76
+ | path | url |
77
+ |------------------------|-----------------------|
78
+ | `oauth_im.login_path` | `oauth_im.login_url` |
61
79
  | `oauth_im.logout_path` | `oauth_im.logout_url` |
62
80
 
63
81
  * Note that the helpers are namespaced to the engine.
@@ -125,6 +143,15 @@ After many false starts, this repo includes two (seemingly functional) github wo
125
143
 
126
144
  ## Version History
127
145
 
146
+ ### 0.7.3
147
+ * Cleaned up configuration
148
+
149
+ ### 0.7.2
150
+ * Using :http protocol in tests (not https)
151
+
152
+ ### 0.7.1
153
+ * Improving separation of concerns by way of a separate service object to manage oauth client.
154
+
128
155
  ### 0.6.0
129
156
  * Remove coupling between gem and `iiab` app via the `AppContext`
130
157
  module. Added default `AppContext` settings to be overridden in
@@ -5,72 +5,46 @@ require 'oauth2'
5
5
  module OauthIm
6
6
  class ClientController < OauthIm::ApplicationController
7
7
  def callback
8
- session[:user_jwt] = { value: decoded_token, httponly: true }
8
+ session[:user_jwt] = user_jwt
9
9
  redirect_to main_app.root_path
10
10
  rescue StandardError
11
11
  head :forbidden
12
12
  end
13
13
 
14
14
  def login
15
- redirect_to oauth_client.auth_code.authorize_url
15
+ redirect_to oauth_client.login_url
16
16
  end
17
17
 
18
18
  def logout
19
19
  reset_session
20
- redirect_to logout_uri
20
+ redirect_to logout_url
21
21
  end
22
22
 
23
23
  def local_login
24
- raise 'Disallowed' if Rails.env.production?
24
+ head :forbidden if Rails.env.production?
25
25
 
26
- session[:userinfo] = { info: { email: 'local_login@example.com' } }
27
- redirect_back(fallback_location: main_app.root_path)
26
+ session[:userinfo] = local_login_userinfo
27
+ redirect_back fallback_location: main_app.root_path
28
28
  end
29
29
 
30
30
  def local_logout
31
+ head :forbidden if Rails.env.production?
32
+
31
33
  reset_session
32
- redirect_back(fallback_location: main_app.root_path)
34
+ redirect_back fallback_location: main_app.root_path
33
35
  end
34
36
 
35
37
  private
36
38
 
37
- def logout_uri
38
- @logout_uri ||= "#{configuration.domain}/oauth2/logout" \
39
- "?post_logout_redirect_uri=#{post_logout_redirect_uri}" \
40
- "&client_id=#{configuration.client_id}"
41
- end
42
-
43
- def post_logout_redirect_uri
44
- @post_logout_redirect_uri ||= params[:return_to]
45
- end
46
-
47
- def decoded_token
48
- @decoded_token ||= TokenDecoder.new(access_token, oauth_client.id).decode
49
- end
50
-
51
- def access_token
52
- @access_token ||= response_hash[:access_token]
53
- end
54
-
55
- def response_hash
56
- @response_hash ||= auth_code.get_token(params[:code]).to_hash
57
- end
58
-
59
- def auth_code
60
- @auth_code ||= oauth_client.auth_code
61
- end
39
+ delegate :logout_url, :user_jwt,
40
+ to: :oauth_client
62
41
 
63
42
  def oauth_client
64
- @oauth_client = ::OAuth2::Client.new configuration.client_id,
65
- configuration.client_secret,
66
- authorize_url: configuration.authorize_url,
67
- site: configuration.domain,
68
- token_url: configuration.token_url,
69
- redirect_uri: callback_url
43
+ @oauth_client ||= OauthIm::Client.new request: request
70
44
  end
71
45
 
72
- def configuration
73
- OauthIm.configuration
46
+ def local_login_userinfo
47
+ { info: { email: 'local_login@example.com' } }.freeze
74
48
  end
75
49
  end
76
50
  end
@@ -1,4 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OauthIm
2
- module ApplicationHelper
3
- end
4
+ module ApplicationHelper; end
4
5
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OauthIm
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.test? ? :http : :https
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
@@ -6,7 +6,7 @@ module OauthIm
6
6
  class TokenDecoder
7
7
  attr_reader :token, :aud
8
8
 
9
- DECODE_ALGORITHM = 'HS256'
9
+ DEFAULT_DECODE_ALGORITHM = 'HS256'
10
10
 
11
11
  def initialize(token, aud)
12
12
  @token = token
@@ -19,20 +19,31 @@ module OauthIm
19
19
 
20
20
  private
21
21
 
22
- def configuration
23
- @configuration ||= OauthIm.configuration
24
- end
22
+ delegate :configuration, to: OauthIm
23
+ delegate :hmac, :iss_domain, to: :configuration
25
24
 
26
25
  def decoded_token
27
- @decoded_token ||= JWT.decode token, configuration.hmac, true, decode_params
26
+ @decoded_token ||= JWT.decode token, hmac, true, decode_params
27
+ end
28
+
29
+ def decode_algorithm
30
+ DEFAULT_DECODE_ALGORITHM
31
+ end
32
+
33
+ def verify_iss?
34
+ true
35
+ end
36
+
37
+ def verify_aud?
38
+ true
28
39
  end
29
40
 
30
41
  def decode_params
31
- @decode_params ||= { verify_iss: true,
32
- iss: configuration.iss_domain,
33
- verify_aud: true,
42
+ @decode_params ||= { verify_iss: verify_iss?,
43
+ iss: iss_domain,
44
+ verify_aud: verify_aud?,
34
45
  aud: aud,
35
- algorithm: DECODE_ALGORITHM }.freeze
46
+ algorithm: decode_algorithm }.freeze
36
47
  end
37
48
  end
38
49
  end
@@ -9,15 +9,21 @@ module AppContext
9
9
  @authenticated_for_specs
10
10
  end
11
11
 
12
- def self.authenticate_for_specs(current_user: nil)
13
- @current_user = current_user
14
- @authenticated_for_specs = true
15
- yield
16
- @authenticated_for_specs = false
17
- @current_user = nil
12
+ def self.privileged?
13
+ @privileged
18
14
  end
19
15
 
20
16
  def self.current_user
21
17
  @current_user
22
18
  end
19
+
20
+ def self.authenticate_for_specs(current_user: nil, privileged: false)
21
+ @authenticated_for_specs = true
22
+ @current_user = current_user
23
+ @privileged = privileged
24
+ yield
25
+ @privileged = false
26
+ @current_user = nil
27
+ @authenticated_for_specs = false
28
+ end
23
29
  end
data/config/routes.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  OauthIm::Engine.routes.draw do
4
- get OauthIm.callback_path, to: 'client#callback'
4
+ get OauthIm.callback_route, to: 'client#callback'
5
5
  get 'login', to: 'client#login'
6
6
  get 'logout', to: 'client#logout'
7
7
 
@@ -1,17 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ########################################################################################
4
+ # edc: see https://fusionauth.io/blog/2020/12/14/how-to-securely-implement-oauth-rails #
5
+ ########################################################################################
6
+
3
7
  module OauthIm
4
8
  CONFIGURABLE_FIELDS =
5
- %i[api_key
6
- authorize_url
7
- callback_path
8
- client_id
9
- client_secret
10
- domain
11
- hmac
12
- iss_domain
13
- tenant_id
14
- token_url].freeze
9
+ %i[
10
+ authorize_url
11
+ callback_route
12
+ token_url
13
+ idp_url
14
+ iss_domain
15
+ client_id
16
+ client_secret
17
+ hmac
18
+ ].freeze
15
19
 
16
20
  class Configuration
17
21
  attr_reader(* CONFIGURABLE_FIELDS)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OauthIm
4
- VERSION = '0.7.0'
4
+ VERSION = '0.7.3'
5
5
  end
data/lib/oauth_im.rb CHANGED
@@ -1,13 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ########################################################################################
4
+ # edc: see https://fusionauth.io/blog/2020/12/14/how-to-securely-implement-oauth-rails #
5
+ ########################################################################################
6
+
3
7
  require 'oauth_im/version'
4
8
  require 'oauth_im/engine'
5
9
  require 'oauth_im/configuration'
6
10
 
7
11
  module OauthIm
8
- DEFAULT_AUTHORIZE_URL = '/oauth2/authorize'
9
- DEFAULT_TOKEN_URL = '/oauth2/token'
10
- DEFAULT_CALLBACK_PATH = 'callback'
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'
11
17
 
12
18
  class << self
13
19
  attr_reader :configuration
@@ -22,7 +28,7 @@ module OauthIm
22
28
  end
23
29
  end
24
30
 
25
- def self.callback_path
26
- configuration&.callback_path || DEFAULT_CALLBACK_PATH
31
+ def self.callback_route
32
+ configuration&.callback_route || DEFAULT_CALLBACK_ROUTE
27
33
  end
28
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oauth_im
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.3
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-04 00:00:00.000000000 Z
11
+ date: 2022-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -125,6 +125,7 @@ files:
125
125
  - app/controllers/oauth_im/application_controller.rb
126
126
  - app/controllers/oauth_im/client_controller.rb
127
127
  - app/helpers/oauth_im/application_helper.rb
128
+ - app/services/oauth_im/client.rb
128
129
  - app/services/oauth_im/token_decoder.rb
129
130
  - app/views/layouts/oauth_im/application.html.erb
130
131
  - config/initializers/app_context.rb