prx_auth-rails 0.2.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/Guardfile +8 -0
- data/README.md +44 -2
- data/Rakefile +17 -0
- data/app/controllers/prx_auth/rails/sessions_controller.rb +107 -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 +10 -2
- data/lib/prx_auth/rails/configuration.rb +28 -0
- data/lib/prx_auth/rails/engine.rb +9 -0
- data/lib/prx_auth/rails/ext/controller.rb +77 -1
- data/lib/prx_auth/rails/railtie.rb +1 -1
- data/lib/prx_auth/rails/token.rb +35 -0
- data/lib/prx_auth/rails/version.rb +1 -1
- data/prx_auth-rails.gemspec +13 -3
- 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/log/development.log +0 -0
- data/test/prx_auth/rails/configuration_test.rb +36 -0
- data/test/prx_auth/rails/sessions_controller_test.rb +94 -0
- data/test/prx_auth/rails/token_test.rb +45 -0
- data/test/test_helper.rb +35 -0
- metadata +240 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16f02bc649e6a16709b4a504649a925d3ead05209f8b3bd5c5e4aa28416bc57e
|
4
|
+
data.tar.gz: fbc0d03cd674fa514541f23d9c6da6ac46db9031c110ddc3f604e66e073c9a8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d16efe0713bb453b5a4238f0ff6983898b5b5872d07396be5c66cf88ca7f4e2f23834a1abc8462c9a2cf0e8f40f1416d7e8cf82ec48611492deddcd52b8812c
|
7
|
+
data.tar.gz: d3eacf2afcc2b9af1a741988bdb8e70c7c664672c487c6515ca336e65ac5f3db42adf63fc05b6fe89b39ef2ff6c45ad1e8dccaa0e5204b3bfba50d2388e6a07f
|
data/.gitignore
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
guard :minitest, all_after_pass: true do
|
2
|
+
watch(%r{^test/(.*)\/?test_(.*)\.rb})
|
3
|
+
watch(%r{^lib/(.*/)?([^/]+)\.rb}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
4
|
+
watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
|
5
|
+
watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
|
6
|
+
watch(%r{^test/.+_test\.rb})
|
7
|
+
watch(%r{^test/test_helper\.rb}) { 'test' }
|
8
|
+
end
|
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,7 +15,48 @@ And then execute:
|
|
14
15
|
|
15
16
|
## Usage
|
16
17
|
|
17
|
-
|
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:
|
21
|
+
|
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.
|
26
|
+
|
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.
|
32
|
+
|
33
|
+
### Configuration
|
34
|
+
|
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.
|
41
|
+
|
42
|
+
In your rails app, add a file to config/initializers called
|
43
|
+
`prx_auth.rb`:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
PrxAuth::Rails.configure do |config|
|
47
|
+
|
48
|
+
# enables automatic installation of token parser middleware
|
49
|
+
config.install_middleware = false # default: true
|
50
|
+
|
51
|
+
# automatically adds namespace to all scoped queries, e.g. .authorized?(:foo) will be treated
|
52
|
+
# as .authorized?(:my_great_ns, :foo). Has no impact on unscoped queries.
|
53
|
+
config.namespace = :my_great_ns # default: derived from Rails::Application name.
|
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>'
|
58
|
+
end
|
59
|
+
```
|
18
60
|
|
19
61
|
## Contributing
|
20
62
|
|
data/Rakefile
CHANGED
@@ -1 +1,18 @@
|
|
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
|
+
|
1
8
|
require "bundler/gem_tasks"
|
9
|
+
require 'rake'
|
10
|
+
require "rake/testtask"
|
11
|
+
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = false
|
16
|
+
end
|
17
|
+
|
18
|
+
task default: :test
|
@@ -0,0 +1,107 @@
|
|
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 auth_error
|
31
|
+
@auth_error_message = params.require(:error)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create
|
35
|
+
jwt_id_claims = id_claims
|
36
|
+
jwt_access_claims = access_claims
|
37
|
+
|
38
|
+
jwt_access_claims['id_token'] = jwt_id_claims.as_json
|
39
|
+
|
40
|
+
result_path = if valid_nonce?(jwt_id_claims['nonce']) &&
|
41
|
+
users_match?(jwt_id_claims, jwt_access_claims)
|
42
|
+
sign_in_user(jwt_access_claims)
|
43
|
+
lookup_and_register_accounts_names
|
44
|
+
after_sign_in_path_for(current_user)
|
45
|
+
else
|
46
|
+
auth_error_sessions_path(error: 'verification_failed')
|
47
|
+
end
|
48
|
+
reset_nonce!
|
49
|
+
|
50
|
+
redirect_to result_path
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def after_sign_in_path_for(_)
|
56
|
+
return super if defined?(super)
|
57
|
+
|
58
|
+
"/"
|
59
|
+
end
|
60
|
+
|
61
|
+
def id_claims
|
62
|
+
id_token = params.require('id_token')
|
63
|
+
validate_token(id_token)
|
64
|
+
end
|
65
|
+
|
66
|
+
def access_claims
|
67
|
+
access_token = params.require('access_token')
|
68
|
+
validate_token(access_token)
|
69
|
+
end
|
70
|
+
|
71
|
+
def reset_nonce!
|
72
|
+
session[ID_NONCE_SESSION_KEY] = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_nonce!
|
76
|
+
n = session[ID_NONCE_SESSION_KEY]
|
77
|
+
return n if n.present?
|
78
|
+
|
79
|
+
session[ID_NONCE_SESSION_KEY] = SecureRandom.hex
|
80
|
+
end
|
81
|
+
|
82
|
+
def fetch_nonce
|
83
|
+
session[ID_NONCE_SESSION_KEY]
|
84
|
+
end
|
85
|
+
|
86
|
+
def valid_nonce?(nonce)
|
87
|
+
return false if fetch_nonce.nil?
|
88
|
+
|
89
|
+
fetch_nonce == nonce
|
90
|
+
end
|
91
|
+
|
92
|
+
def users_match?(claims1, claims2)
|
93
|
+
return false if claims1['sub'].nil? || claims2['sub'].nil?
|
94
|
+
|
95
|
+
claims1['sub'] == claims2['sub']
|
96
|
+
end
|
97
|
+
|
98
|
+
def validate_token(token)
|
99
|
+
id_host = PrxAuth::Rails.configuration.id_host
|
100
|
+
prx_auth_cert = Rack::PrxAuth::Certificate.new("https://#{id_host}/api/v1/certs")
|
101
|
+
auth_validator = Rack::PrxAuth::AuthValidator.new(token, prx_auth_cert, id_host)
|
102
|
+
auth_validator.
|
103
|
+
claims.
|
104
|
+
with_indifferent_access
|
105
|
+
end
|
106
|
+
end
|
107
|
+
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,10 +1,18 @@
|
|
1
1
|
require "prx_auth/rails/version"
|
2
|
+
require "prx_auth/rails/configuration"
|
2
3
|
require "prx_auth/rails/railtie" if defined?(Rails)
|
4
|
+
require "prx_auth/rails/engine" if defined?(Rails)
|
5
|
+
|
3
6
|
module PrxAuth
|
4
7
|
module Rails
|
5
8
|
class << self
|
6
|
-
attr_accessor :
|
9
|
+
attr_accessor :configuration
|
10
|
+
|
11
|
+
def configure
|
12
|
+
yield configuration
|
13
|
+
end
|
7
14
|
end
|
8
|
-
|
15
|
+
|
16
|
+
self.configuration = Configuration.new
|
9
17
|
end
|
10
18
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class PrxAuth::Rails::Configuration
|
2
|
+
attr_accessor :install_middleware,
|
3
|
+
:namespace,
|
4
|
+
:prx_client_id,
|
5
|
+
:id_host
|
6
|
+
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@install_middleware = true
|
10
|
+
if defined?(::Rails)
|
11
|
+
klass = ::Rails.application.class
|
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
|
19
|
+
else
|
20
|
+
klass.name
|
21
|
+
end
|
22
|
+
|
23
|
+
@namespace = klass_name.underscore.intern
|
24
|
+
@prx_client_id = nil
|
25
|
+
@id_host = nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,13 +1,89 @@
|
|
1
|
+
require 'prx_auth/rails/token'
|
2
|
+
require 'open-uri'
|
3
|
+
|
1
4
|
module PrxAuth
|
2
5
|
module Rails
|
3
6
|
module Controller
|
7
|
+
|
8
|
+
PRX_ACCOUNT_NAME_MAPPING_KEY = 'prx.account.name.mapping'.freeze
|
9
|
+
|
4
10
|
def prx_auth_token
|
5
|
-
|
11
|
+
rack_auth_token = env_prx_auth_token
|
12
|
+
return rack_auth_token if rack_auth_token.present?
|
13
|
+
|
14
|
+
session['prx.auth'] && Rack::PrxAuth::TokenData.new(session['prx.auth'])
|
6
15
|
end
|
7
16
|
|
8
17
|
def prx_authenticated?
|
9
18
|
!!prx_auth_token
|
10
19
|
end
|
20
|
+
|
21
|
+
def authenticate!
|
22
|
+
return true if current_user.present?
|
23
|
+
|
24
|
+
redirect_to PrxAuth::Rails::Engine.routes.url_helpers.new_sessions_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def current_user
|
28
|
+
return if prx_auth_token.nil?
|
29
|
+
|
30
|
+
PrxAuth::Rails::Token.new(prx_auth_token)
|
31
|
+
end
|
32
|
+
|
33
|
+
def lookup_and_register_accounts_names
|
34
|
+
session[PRX_ACCOUNT_NAME_MAPPING_KEY] =
|
35
|
+
lookup_account_names_mapping
|
36
|
+
end
|
37
|
+
|
38
|
+
def account_name_for(id)
|
39
|
+
id = id.to_i
|
40
|
+
|
41
|
+
name =
|
42
|
+
if session[PRX_ACCOUNT_NAME_MAPPING_KEY].has_key?(id)
|
43
|
+
session[PRX_ACCOUNT_NAME_MAPPING_KEY][id]
|
44
|
+
else
|
45
|
+
session[PRX_ACCOUNT_NAME_MAPPING_KEY][id] = lookup_account_name_for(id)
|
46
|
+
end
|
47
|
+
|
48
|
+
name = "[#{id}] Unknown Account Name" unless name.present?
|
49
|
+
|
50
|
+
name
|
51
|
+
end
|
52
|
+
|
53
|
+
def sign_in_user(token)
|
54
|
+
session['prx.auth'] = token
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def lookup_account_name_for(id)
|
60
|
+
id = id.to_i
|
61
|
+
|
62
|
+
res = lookup_account_names_mapping([id])
|
63
|
+
res[id]
|
64
|
+
end
|
65
|
+
|
66
|
+
def lookup_account_names_mapping(ids=current_user.resources)
|
67
|
+
id_host = PrxAuth::Rails.configuration.id_host
|
68
|
+
ids_param = ids.map(&:to_s).join(',')
|
69
|
+
|
70
|
+
options = {}
|
71
|
+
options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if ::Rails.env.development?
|
72
|
+
|
73
|
+
accounts = URI.open("https://#{id_host}/api/v1/accounts?account_ids=#{ids_param}", options).read
|
74
|
+
|
75
|
+
mapping = JSON.parse(accounts)['accounts'].map { |acct| [acct['id'], acct['display_name']] }.to_h
|
76
|
+
|
77
|
+
mapping
|
78
|
+
end
|
79
|
+
|
80
|
+
def env_prx_auth_token
|
81
|
+
if !defined? @_prx_auth_token
|
82
|
+
@_prx_auth_token = request.env['prx.auth'] && PrxAuth::Rails::Token.new(request.env['prx.auth'])
|
83
|
+
else
|
84
|
+
@_prx_auth_token
|
85
|
+
end
|
86
|
+
end
|
11
87
|
end
|
12
88
|
end
|
13
89
|
end
|