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 +4 -4
- data/README.md +42 -15
- data/app/controllers/oauth_im/client_controller.rb +14 -40
- data/app/helpers/oauth_im/application_helper.rb +3 -2
- data/app/services/oauth_im/client.rb +76 -0
- data/app/services/oauth_im/token_decoder.rb +20 -9
- data/config/initializers/app_context.rb +12 -6
- data/config/routes.rb +1 -1
- data/lib/oauth_im/configuration.rb +14 -10
- data/lib/oauth_im/version.rb +1 -1
- data/lib/oauth_im.rb +11 -5
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4b28f51bcd7b2893dd52780a267678fab317eb70feefa6d3ab9d714cad45834
|
4
|
+
data.tar.gz: c605bb66ede19ea12e1e124f12164d3cee63358d24a0bd0971693732e96b380f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
config.
|
30
|
-
config.
|
31
|
-
config.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
*
|
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.
|
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
|
59
|
-
|
60
|
-
| `oauth_im.login_path`
|
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] =
|
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.
|
15
|
+
redirect_to oauth_client.login_url
|
16
16
|
end
|
17
17
|
|
18
18
|
def logout
|
19
19
|
reset_session
|
20
|
-
redirect_to
|
20
|
+
redirect_to logout_url
|
21
21
|
end
|
22
22
|
|
23
23
|
def local_login
|
24
|
-
|
24
|
+
head :forbidden if Rails.env.production?
|
25
25
|
|
26
|
-
session[:userinfo] =
|
27
|
-
redirect_back
|
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
|
34
|
+
redirect_back fallback_location: main_app.root_path
|
33
35
|
end
|
34
36
|
|
35
37
|
private
|
36
38
|
|
37
|
-
|
38
|
-
|
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
|
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
|
73
|
-
|
46
|
+
def local_login_userinfo
|
47
|
+
{ info: { email: 'local_login@example.com' } }.freeze
|
74
48
|
end
|
75
49
|
end
|
76
50
|
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
|
-
|
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
|
-
|
23
|
-
|
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,
|
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:
|
32
|
-
iss:
|
33
|
-
verify_aud:
|
42
|
+
@decode_params ||= { verify_iss: verify_iss?,
|
43
|
+
iss: iss_domain,
|
44
|
+
verify_aud: verify_aud?,
|
34
45
|
aud: aud,
|
35
|
-
algorithm:
|
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.
|
13
|
-
@
|
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,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[
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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)
|
data/lib/oauth_im/version.rb
CHANGED
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
|
9
|
-
|
10
|
-
|
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.
|
26
|
-
configuration&.
|
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.
|
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-
|
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
|