prx_auth-rails 1.0.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/README.md +25 -6
- data/Rakefile +12 -4
- data/app/controllers/prx_auth/rails/sessions_controller.rb +121 -0
- data/app/views/prx_auth/rails/sessions/auth_error.html.erb +15 -0
- data/app/views/prx_auth/rails/sessions/show.html.erb +38 -0
- data/config/routes.rb +7 -0
- data/lib/prx_auth/rails.rb +1 -0
- data/lib/prx_auth/rails/configuration.rb +15 -4
- data/lib/prx_auth/rails/engine.rb +9 -0
- data/lib/prx_auth/rails/ext/controller.rb +81 -4
- data/lib/prx_auth/rails/token.rb +5 -1
- data/lib/prx_auth/rails/version.rb +1 -1
- data/prx_auth-rails.gemspec +4 -2
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/config/manifest.js +2 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/channels/application_cable/channel.rb +4 -0
- data/test/dummy/app/channels/application_cable/connection.rb +4 -0
- data/test/dummy/app/controllers/application_controller.rb +8 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/javascript/packs/application.js +15 -0
- data/test/dummy/app/jobs/application_job.rb +7 -0
- data/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/test/dummy/app/models/application_record.rb +3 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +15 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/bin/rails +5 -0
- data/test/dummy/bin/rake +5 -0
- data/test/dummy/bin/setup +33 -0
- data/test/dummy/bin/spring +10 -0
- data/test/dummy/config.ru +6 -0
- data/test/dummy/config/application.rb +22 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/cable.yml +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +76 -0
- data/test/dummy/config/environments/production.rb +120 -0
- data/test/dummy/config/environments/test.rb +60 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/test/dummy/config/initializers/assets.rb +12 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +8 -0
- data/test/dummy/config/initializers/content_security_policy.rb +28 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/permissions_policy.rb +11 -0
- data/test/dummy/config/initializers/prx_auth.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +33 -0
- data/test/dummy/config/puma.rb +43 -0
- data/test/dummy/config/routes.rb +3 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/config/storage.yml +34 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/test/dummy/public/apple-touch-icon.png +0 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/storage/.keep +0 -0
- data/test/prx_auth/rails/configuration_test.rb +18 -12
- data/test/prx_auth/rails/sessions_controller_test.rb +104 -0
- data/test/prx_auth/rails/token_test.rb +1 -1
- data/test/test_helper.rb +20 -9
- metadata +156 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '081a5943f3b2b9a79035ea23b3c9d1273ba09938ea3e7351025cb4c3a836b108'
|
4
|
+
data.tar.gz: da3cc2f617261d7e22ad031a43fa903114aa537edb89e90b6de3ab8e132ee7b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 801452a31c08d21d7c78ff49048f8bf4247d41a0ed7d491bb78165c558464149c9415017939b14351ade8848d81c143b9a1a1ea23cac6605521e654a651720b7
|
7
|
+
data.tar.gz: d4e3bd24d1c11838c1275db062c9bb9c4494a12cd13f39f3ef36cba20d5e539480920c06fd638ced3c9a372b4413797ab1e625e2ecb6e2d8f01bfccbfcfb0d8d
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# PrxAuth::Rails
|
2
2
|
|
3
|
-
Rails integration for next generation PRX Authorization system.
|
3
|
+
Rails integration for next generation PRX Authorization system. This
|
4
|
+
provides common OpenId authorization patterns used in PRX apps.
|
4
5
|
|
5
6
|
## Installation
|
6
7
|
|
@@ -14,17 +15,32 @@ And then execute:
|
|
14
15
|
|
15
16
|
## Usage
|
16
17
|
|
17
|
-
Installing the gem in a Rails project will automatically add the
|
18
|
+
Installing the gem in a Rails project will automatically add the
|
19
|
+
appropriate Rack middleware to your Rails application and add two
|
20
|
+
methods to your controllers. These methods are:
|
18
21
|
|
19
|
-
* `prx_auth_token`: returns a token (similar to PrxAuth::Token) which
|
22
|
+
* `prx_auth_token`: returns a token (similar to PrxAuth::Token) which
|
23
|
+
automatically namespaces queries. The main methods you will be
|
24
|
+
interested in are `authorized?`, `globally_authorized?` and `resources`.
|
25
|
+
More information can be found in PrxAuth.
|
20
26
|
|
21
|
-
* `prx_authenticated?`: returns whether or not this request includes a
|
27
|
+
* `prx_authenticated?`: returns whether or not this request includes a
|
28
|
+
valid PrxAuth token.
|
29
|
+
|
30
|
+
This will let set up the Rails app to be ready for HTTP requests
|
31
|
+
associated with an OpenId access token.
|
22
32
|
|
23
33
|
### Configuration
|
24
34
|
|
25
|
-
Generally, configuration is not required and the gem aims for great
|
35
|
+
Generally, configuration is not required and the gem aims for great
|
36
|
+
defaults, but you can override some settings if you need to change the
|
37
|
+
default behavior.
|
38
|
+
|
39
|
+
If you're using the Rails server-side session flow, you must supply the
|
40
|
+
client_id via configuration.
|
26
41
|
|
27
|
-
In your rails app, add a file to config/initializers called
|
42
|
+
In your rails app, add a file to config/initializers called
|
43
|
+
`prx_auth.rb`:
|
28
44
|
|
29
45
|
```ruby
|
30
46
|
PrxAuth::Rails.configure do |config|
|
@@ -36,6 +52,9 @@ PrxAuth::Rails.configure do |config|
|
|
36
52
|
# as .authorized?(:my_great_ns, :foo). Has no impact on unscoped queries.
|
37
53
|
config.namespace = :my_great_ns # default: derived from Rails::Application name.
|
38
54
|
# e.g. class Feeder < Rails::Application => :feeder
|
55
|
+
|
56
|
+
# Set up the PRX OpenID client_id if using the backend rails sessions flow.
|
57
|
+
config.client_id = '<some client id>'
|
39
58
|
end
|
40
59
|
```
|
41
60
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,18 @@
|
|
1
|
-
require
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
4
|
+
load "rails/tasks/engine.rake"
|
5
|
+
|
6
|
+
load "rails/tasks/statistics.rake"
|
7
|
+
|
8
|
+
require "bundler/gem_tasks"
|
2
9
|
require 'rake'
|
3
|
-
require
|
10
|
+
require "rake/testtask"
|
4
11
|
|
5
|
-
Rake::TestTask.new do |t|
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
6
13
|
t.libs << 'test'
|
7
|
-
t.pattern = 'test/**/*
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = false
|
8
16
|
end
|
9
17
|
|
10
18
|
task default: :test
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module PrxAuth::Rails
|
2
|
+
class SessionsController < ApplicationController
|
3
|
+
include PrxAuth::Rails::Engine.routes.url_helpers
|
4
|
+
|
5
|
+
skip_before_action :authenticate!
|
6
|
+
|
7
|
+
before_action :set_nonce!, only: :show
|
8
|
+
|
9
|
+
ID_NONCE_SESSION_KEY = 'id_prx_openid_nonce'.freeze
|
10
|
+
|
11
|
+
def new
|
12
|
+
set_nonce! unless fetch_nonce.present?
|
13
|
+
|
14
|
+
config = PrxAuth::Rails.configuration
|
15
|
+
|
16
|
+
id_auth_params = {
|
17
|
+
client_id: config.prx_client_id,
|
18
|
+
nonce: fetch_nonce,
|
19
|
+
response_type: 'id_token token',
|
20
|
+
scope: 'openid apps',
|
21
|
+
prompt: 'necessary'
|
22
|
+
}
|
23
|
+
|
24
|
+
redirect_to '//' + config.id_host + '/authorize?' + id_auth_params.to_query
|
25
|
+
end
|
26
|
+
|
27
|
+
def show
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy
|
31
|
+
sign_out_user
|
32
|
+
redirect_to after_sign_out_path
|
33
|
+
end
|
34
|
+
|
35
|
+
def auth_error
|
36
|
+
@auth_error_message = params.require(:error)
|
37
|
+
end
|
38
|
+
|
39
|
+
def create
|
40
|
+
jwt_id_claims = id_claims
|
41
|
+
jwt_access_claims = access_claims
|
42
|
+
|
43
|
+
jwt_access_claims['id_token'] = jwt_id_claims.as_json
|
44
|
+
|
45
|
+
result_path = if valid_nonce?(jwt_id_claims['nonce']) &&
|
46
|
+
users_match?(jwt_id_claims, jwt_access_claims)
|
47
|
+
sign_in_user(jwt_access_claims)
|
48
|
+
lookup_and_register_accounts_names
|
49
|
+
after_sign_in_path_for(current_user)
|
50
|
+
else
|
51
|
+
auth_error_sessions_path(error: 'verification_failed')
|
52
|
+
end
|
53
|
+
reset_nonce!
|
54
|
+
|
55
|
+
redirect_to result_path
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def after_sign_in_path_for(_)
|
61
|
+
return super if defined?(super)
|
62
|
+
|
63
|
+
"/"
|
64
|
+
end
|
65
|
+
|
66
|
+
def after_sign_out_path
|
67
|
+
return super if defined?(super)
|
68
|
+
|
69
|
+
"https://#{id_host}/session/sign_out"
|
70
|
+
end
|
71
|
+
|
72
|
+
def id_claims
|
73
|
+
id_token = params.require('id_token')
|
74
|
+
validate_token(id_token)
|
75
|
+
end
|
76
|
+
|
77
|
+
def access_claims
|
78
|
+
access_token = params.require('access_token')
|
79
|
+
validate_token(access_token)
|
80
|
+
end
|
81
|
+
|
82
|
+
def reset_nonce!
|
83
|
+
session[ID_NONCE_SESSION_KEY] = nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_nonce!
|
87
|
+
n = session[ID_NONCE_SESSION_KEY]
|
88
|
+
return n if n.present?
|
89
|
+
|
90
|
+
session[ID_NONCE_SESSION_KEY] = SecureRandom.hex
|
91
|
+
end
|
92
|
+
|
93
|
+
def fetch_nonce
|
94
|
+
session[ID_NONCE_SESSION_KEY]
|
95
|
+
end
|
96
|
+
|
97
|
+
def valid_nonce?(nonce)
|
98
|
+
return false if fetch_nonce.nil?
|
99
|
+
|
100
|
+
fetch_nonce == nonce
|
101
|
+
end
|
102
|
+
|
103
|
+
def users_match?(claims1, claims2)
|
104
|
+
return false if claims1['sub'].nil? || claims2['sub'].nil?
|
105
|
+
|
106
|
+
claims1['sub'] == claims2['sub']
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_token(token)
|
110
|
+
prx_auth_cert = Rack::PrxAuth::Certificate.new("https://#{id_host}/api/v1/certs")
|
111
|
+
auth_validator = Rack::PrxAuth::AuthValidator.new(token, prx_auth_cert, id_host)
|
112
|
+
auth_validator.
|
113
|
+
claims.
|
114
|
+
with_indifferent_access
|
115
|
+
end
|
116
|
+
|
117
|
+
def id_host
|
118
|
+
PrxAuth::Rails.configuration.id_host
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<div class='main'>
|
2
|
+
<section>
|
3
|
+
<h3>Not authorized for this application.</h3>
|
4
|
+
|
5
|
+
<p>Message was: <pre><%= @auth_error_message %></pre>
|
6
|
+
<% if @auth_error_message == 'invalid_scope' %>
|
7
|
+
Did you add a row in the account_applications table on id.prx?
|
8
|
+
<% end %>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
<a href="<%= new_sessions_path %>">Try logging in again</a>
|
13
|
+
</p>
|
14
|
+
</section>
|
15
|
+
</div>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<div style="display:none;">
|
2
|
+
<%= form_for(:sessions, :url => PrxAuth::Rails::Engine.routes.url_helpers.sessions_path) do |f| %>
|
3
|
+
<%= hidden_field_tag :access_token, '', id: 'access-token-field' %>
|
4
|
+
<%= hidden_field_tag :id_token, '', id: 'id-token-field' %>
|
5
|
+
<%= f.submit id: 'sessions-form-submit' %>
|
6
|
+
<% end %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<script type='application/javascript'>
|
10
|
+
|
11
|
+
function parseURLFragment() {
|
12
|
+
let hashParams = {};
|
13
|
+
let e,
|
14
|
+
a = /\+/g, // Regex for replacing addition symbol with a space
|
15
|
+
r = /([^&;=]+)=?([^&;]*)/g,
|
16
|
+
d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
|
17
|
+
q = window.location.hash.substring(1);
|
18
|
+
|
19
|
+
while (e = r.exec(q))
|
20
|
+
hashParams[d(e[1])] = d(e[2]);
|
21
|
+
|
22
|
+
return hashParams;
|
23
|
+
}
|
24
|
+
|
25
|
+
window.addEventListener("load", () => {
|
26
|
+
var idToken = document.querySelector("#id-token-field");
|
27
|
+
var accessToken = document.querySelector("#access-token-field");
|
28
|
+
var submit = document.querySelector("input#sessions-form-submit[type=submit]");
|
29
|
+
|
30
|
+
var hashParams = parseURLFragment();
|
31
|
+
|
32
|
+
accessToken.value = hashParams['access_token'];
|
33
|
+
idToken.value = hashParams['id_token'];
|
34
|
+
|
35
|
+
submit.click();
|
36
|
+
});
|
37
|
+
|
38
|
+
</script>
|
data/config/routes.rb
ADDED
data/lib/prx_auth/rails.rb
CHANGED
@@ -1,17 +1,28 @@
|
|
1
1
|
class PrxAuth::Rails::Configuration
|
2
|
-
attr_accessor :install_middleware,
|
2
|
+
attr_accessor :install_middleware,
|
3
|
+
:namespace,
|
4
|
+
:prx_client_id,
|
5
|
+
:id_host
|
6
|
+
|
3
7
|
|
4
8
|
def initialize
|
5
9
|
@install_middleware = true
|
6
10
|
if defined?(::Rails)
|
7
11
|
klass = ::Rails.application.class
|
8
|
-
|
9
|
-
|
12
|
+
parent_name = if ::Rails::VERSION::MAJOR >= 6
|
13
|
+
klass.module_parent_name
|
14
|
+
else
|
15
|
+
klass.parent_name
|
16
|
+
end
|
17
|
+
klass_name = if parent_name.present?
|
18
|
+
parent_name
|
10
19
|
else
|
11
20
|
klass.name
|
12
21
|
end
|
13
22
|
|
14
23
|
@namespace = klass_name.underscore.intern
|
24
|
+
@prx_client_id = nil
|
25
|
+
@id_host = nil
|
15
26
|
end
|
16
27
|
end
|
17
|
-
end
|
28
|
+
end
|
@@ -1,19 +1,96 @@
|
|
1
1
|
require 'prx_auth/rails/token'
|
2
|
+
require 'open-uri'
|
2
3
|
|
3
4
|
module PrxAuth
|
4
5
|
module Rails
|
5
6
|
module Controller
|
7
|
+
|
8
|
+
PRX_ACCOUNT_NAME_MAPPING_KEY = 'prx.account.name.mapping'.freeze
|
9
|
+
PRX_TOKEN_SESSION_KEY = 'prx.auth'.freeze
|
10
|
+
|
6
11
|
def prx_auth_token
|
12
|
+
rack_auth_token = env_prx_auth_token
|
13
|
+
return rack_auth_token if rack_auth_token.present?
|
14
|
+
|
15
|
+
session[PRX_TOKEN_SESSION_KEY] && Rack::PrxAuth::TokenData.new(session[PRX_TOKEN_SESSION_KEY])
|
16
|
+
end
|
17
|
+
|
18
|
+
def prx_authenticated?
|
19
|
+
!!prx_auth_token
|
20
|
+
end
|
21
|
+
|
22
|
+
def authenticate!
|
23
|
+
return true if current_user.present?
|
24
|
+
|
25
|
+
redirect_to PrxAuth::Rails::Engine.routes.url_helpers.new_sessions_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def current_user
|
29
|
+
return if prx_auth_token.nil?
|
30
|
+
|
31
|
+
PrxAuth::Rails::Token.new(prx_auth_token)
|
32
|
+
end
|
33
|
+
|
34
|
+
def lookup_and_register_accounts_names
|
35
|
+
session[PRX_ACCOUNT_NAME_MAPPING_KEY] =
|
36
|
+
lookup_account_names_mapping
|
37
|
+
end
|
38
|
+
|
39
|
+
def account_name_for(id)
|
40
|
+
id = id.to_i
|
41
|
+
|
42
|
+
session[PRX_ACCOUNT_NAME_MAPPING_KEY] ||= {}
|
43
|
+
|
44
|
+
name =
|
45
|
+
if session[PRX_ACCOUNT_NAME_MAPPING_KEY].has_key?(id)
|
46
|
+
session[PRX_ACCOUNT_NAME_MAPPING_KEY][id]
|
47
|
+
else
|
48
|
+
session[PRX_ACCOUNT_NAME_MAPPING_KEY][id] = lookup_account_name_for(id)
|
49
|
+
end
|
50
|
+
|
51
|
+
name = "[#{id}] Unknown Account Name" unless name.present?
|
52
|
+
|
53
|
+
name
|
54
|
+
end
|
55
|
+
|
56
|
+
def sign_in_user(token)
|
57
|
+
session[PRX_TOKEN_SESSION_KEY] = token
|
58
|
+
end
|
59
|
+
|
60
|
+
def sign_out_user
|
61
|
+
session.delete(PRX_TOKEN_SESSION_KEY)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def lookup_account_name_for(id)
|
67
|
+
id = id.to_i
|
68
|
+
|
69
|
+
res = lookup_account_names_mapping([id])
|
70
|
+
res[id]
|
71
|
+
end
|
72
|
+
|
73
|
+
def lookup_account_names_mapping(ids=current_user.resources)
|
74
|
+
id_host = PrxAuth::Rails.configuration.id_host
|
75
|
+
ids_param = ids.map(&:to_s).join(',')
|
76
|
+
|
77
|
+
options = {}
|
78
|
+
options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if ::Rails.env.development?
|
79
|
+
|
80
|
+
accounts = URI.open("https://#{id_host}/api/v1/accounts?account_ids=#{ids_param}", options).read
|
81
|
+
|
82
|
+
mapping = JSON.parse(accounts)['accounts'].map { |acct| [acct['id'], acct['display_name']] }.to_h
|
83
|
+
|
84
|
+
mapping
|
85
|
+
end
|
86
|
+
|
87
|
+
def env_prx_auth_token
|
7
88
|
if !defined? @_prx_auth_token
|
8
89
|
@_prx_auth_token = request.env['prx.auth'] && PrxAuth::Rails::Token.new(request.env['prx.auth'])
|
9
90
|
else
|
10
91
|
@_prx_auth_token
|
11
92
|
end
|
12
93
|
end
|
13
|
-
|
14
|
-
def prx_authenticated?
|
15
|
-
!!prx_auth_token
|
16
|
-
end
|
17
94
|
end
|
18
95
|
end
|
19
96
|
end
|