openid-token-proxy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +4 -0
- data/Guardfile +41 -0
- data/LICENSE.md +22 -0
- data/README.md +211 -0
- data/Rakefile +16 -0
- data/app/controllers/openid_token_proxy/application_controller.rb +4 -0
- data/app/controllers/openid_token_proxy/callback_controller.rb +22 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/routes.rb +3 -0
- data/docs/diagrams.sketch +0 -0
- data/docs/openid-token-proxy-flow.png +0 -0
- data/docs/regular-openid-flow.png +0 -0
- data/lib/openid-token-proxy.rb +1 -0
- data/lib/openid_token_proxy/client.rb +48 -0
- data/lib/openid_token_proxy/config.rb +56 -0
- data/lib/openid_token_proxy/engine.rb +5 -0
- data/lib/openid_token_proxy/error.rb +7 -0
- data/lib/openid_token_proxy/token/authentication.rb +54 -0
- data/lib/openid_token_proxy/token/expired.rb +12 -0
- data/lib/openid_token_proxy/token/invalid_application.rb +12 -0
- data/lib/openid_token_proxy/token/invalid_audience.rb +12 -0
- data/lib/openid_token_proxy/token/invalid_issuer.rb +12 -0
- data/lib/openid_token_proxy/token/malformed.rb +12 -0
- data/lib/openid_token_proxy/token/refresh.rb +30 -0
- data/lib/openid_token_proxy/token/required.rb +12 -0
- data/lib/openid_token_proxy/token/unverifiable_signature.rb +12 -0
- data/lib/openid_token_proxy/token.rb +80 -0
- data/lib/openid_token_proxy/version.rb +3 -0
- data/lib/openid_token_proxy.rb +40 -0
- data/openid-token-proxy.gemspec +35 -0
- data/spec/controllers/openid_token_proxy/callback_controller_spec.rb +72 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/accounts_controller.rb +10 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/controllers/home_controller.rb +7 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/home/index.html.erb +25 -0
- data/spec/dummy/app/views/layouts/application.html.erb +54 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +27 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +34 -0
- data/spec/dummy/config/environments/production.rb +75 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/openid.rb +5 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +5 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/fixtures/keys.json +26 -0
- data/spec/fixtures/openid-configuration.json +30 -0
- data/spec/lib/openid_token_proxy/client_spec.rb +150 -0
- data/spec/lib/openid_token_proxy/config_spec.rb +201 -0
- data/spec/lib/openid_token_proxy/error_spec.rb +11 -0
- data/spec/lib/openid_token_proxy/token/authentication_spec.rb +67 -0
- data/spec/lib/openid_token_proxy/token/refresh_spec.rb +71 -0
- data/spec/lib/openid_token_proxy/token_spec.rb +138 -0
- data/spec/lib/openid_token_proxy_spec.rb +38 -0
- data/spec/spec_helper.rb +88 -0
- data/spec/support/env.rb +4 -0
- data/spec/support/fixture.rb +3 -0
- metadata +359 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 036360c10b330eabfbacd91009ab6ffd948caaaf
|
4
|
+
data.tar.gz: 5cc9438acbb74f17d8f4870ffd5899da09a542ae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 82b2be465db4d1e0787dda423e88b434aaa9e4af60c7472d6ae9537fe3aea769275f0f0a8796470925f340b4c8d42f56dfb7fd3b54260cbea8946f6612fcc3fe
|
7
|
+
data.tar.gz: 6e82dd27d7e61e87a75d08663a3d4fb6052cc4f97b2b6774fe2a2359f006b15f3a0f4908dfdac3727e7533794911182922f20e7c944a0aa2876fdc1081dbc218
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
2
|
+
require 'guard/rspec/dsl'
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
|
+
|
5
|
+
# RSpec files
|
6
|
+
rspec = dsl.rspec
|
7
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
8
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
9
|
+
watch(rspec.spec_files)
|
10
|
+
|
11
|
+
# Ruby files
|
12
|
+
ruby = dsl.ruby
|
13
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
14
|
+
|
15
|
+
# Rails files
|
16
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
17
|
+
dsl.watch_spec_files_for(rails.app_files)
|
18
|
+
dsl.watch_spec_files_for(rails.views)
|
19
|
+
|
20
|
+
watch(rails.controllers) do |m|
|
21
|
+
[
|
22
|
+
rspec.spec.("routing/#{m[1]}_routing"),
|
23
|
+
rspec.spec.("controllers/#{m[1]}_controller"),
|
24
|
+
rspec.spec.("acceptance/#{m[1]}")
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Rails config changes
|
29
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
30
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
31
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
32
|
+
|
33
|
+
# Capybara features specs
|
34
|
+
watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
|
35
|
+
|
36
|
+
# Turnip features and steps
|
37
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
38
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
39
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance'
|
40
|
+
end
|
41
|
+
end
|
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Licensed under the **MIT** license
|
2
|
+
|
3
|
+
> Copyright (c) 2015 Hyper Interaktiv AS
|
4
|
+
>
|
5
|
+
> Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
> a copy of this software and associated documentation files (the
|
7
|
+
> "Software"), to deal in the Software without restriction, including
|
8
|
+
> without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
> distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
> permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
> the following conditions:
|
12
|
+
>
|
13
|
+
> The above copyright notice and this permission notice shall be
|
14
|
+
> included in all copies or substantial portions of the Software.
|
15
|
+
>
|
16
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
# OpenID token proxy
|
2
|
+
|
3
|
+
[![Gem Version](https://img.shields.io/gem/v/openid-token-proxy.svg?style=flat)](https://rubygems.org/gems/openid-token-proxy)
|
4
|
+
[![Build Status](https://img.shields.io/travis/hyperoslo/openid-token-proxy.svg?style=flat)](https://travis-ci.org/hyperoslo/openid-token-proxy)
|
5
|
+
[![Dependency Status](https://img.shields.io/gemnasium/hyperoslo/openid-token-proxy.svg?style=flat)](https://gemnasium.com/hyperoslo/openid-token-proxy)
|
6
|
+
[![Code Climate](https://img.shields.io/codeclimate/github/hyperoslo/openid-token-proxy.svg?style=flat)](https://codeclimate.com/github/hyperoslo/openid-token-proxy)
|
7
|
+
[![Coverage Status](https://img.shields.io/coveralls/hyperoslo/openid-token-proxy.svg?style=flat)](https://coveralls.io/r/hyperoslo/openid-token-proxy)
|
8
|
+
|
9
|
+
Retrieves and refreshes OpenID tokens on behalf of a user when dealing with complex
|
10
|
+
authentication schemes, such as client-side certificates.
|
11
|
+
|
12
|
+
**Note: Under development, not for production usage just yet**
|
13
|
+
|
14
|
+
**Supported Ruby versions: 2.0.0 or higher**
|
15
|
+
|
16
|
+
Licensed under the **MIT** license, see LICENSE for more information.
|
17
|
+
|
18
|
+
|
19
|
+
## Background
|
20
|
+
|
21
|
+
When using [OpenID](http://openid.net/specs/openid-connect-core-1_0.html) in
|
22
|
+
native applications, the most common approach is to open the identity provider's
|
23
|
+
authorization page in a web view, let the user authenticate and have the application
|
24
|
+
hold on to access, identity and refresh tokens.
|
25
|
+
|
26
|
+
![Regular OpenID flow](docs/regular-openid-flow.png?raw=1)
|
27
|
+
|
28
|
+
However, the above flow may be unusable if the identity provider provides complex
|
29
|
+
authentication schemes, such as client-side certificates.
|
30
|
+
|
31
|
+
On iOS, client-side certificates stored in the system keychain [cannot be obtained due to application sandboxing](http://stackoverflow.com/questions/7648487/how-to-list-certificates-from-the-iphone-keychain-inside-my-app).
|
32
|
+
|
33
|
+
On Android, one can obtain system certificates but these [can not be used within a web view](http://stackoverflow.com/questions/15588851/android-webview-with-client-certificate).
|
34
|
+
|
35
|
+
![OpenID token proxy flow](docs/openid-token-proxy-flow.png?raw=1)
|
36
|
+
|
37
|
+
When using OpenID token proxy, the application opens a web browser - which has
|
38
|
+
access to client-side certificates regardless of storage location - and lets the
|
39
|
+
user authenticate. The identity provider redirects to the OpenID token proxy,
|
40
|
+
which in turn passes along any obtained tokens to the application.
|
41
|
+
|
42
|
+
|
43
|
+
## Installation
|
44
|
+
|
45
|
+
Add this line to your application's Gemfile:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
gem 'openid-token-proxy'
|
49
|
+
```
|
50
|
+
|
51
|
+
Or install it yourself:
|
52
|
+
|
53
|
+
$ gem install openid-token-proxy
|
54
|
+
|
55
|
+
|
56
|
+
## Usage
|
57
|
+
|
58
|
+
### Configuration
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
OpenIDTokenProxy.configure do |config|
|
62
|
+
config.client_id = 'xxx'
|
63
|
+
config.client_secret = 'xxx'
|
64
|
+
config.issuer = 'https://login.windows.net/common'
|
65
|
+
config.redirect_uri = 'https://example.com/auth/callback'
|
66
|
+
config.resource = 'https://graph.windows.net'
|
67
|
+
|
68
|
+
# Indicates which domain users will presumably be signing in with
|
69
|
+
config.domain_hint = 'example.com'
|
70
|
+
|
71
|
+
# Whether to force authentication in case a session is already established
|
72
|
+
config.prompt = 'login'
|
73
|
+
|
74
|
+
# If these endpoints or public keys are not configured explicitly, they will be
|
75
|
+
# discovered automatically by contacting the issuer (see above)
|
76
|
+
config.authorization_endpoint = 'https://login.windows.net/common/oauth2/authorize'
|
77
|
+
config.token_endpoint = 'https://login.windows.net/common/oauth2/token'
|
78
|
+
config.userinfo_endpoint = 'https://login.windows.net/common/openid/userinfo'
|
79
|
+
config.public_keys = [
|
80
|
+
OpenSSL::PKey::RSA.new("-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9...")
|
81
|
+
]
|
82
|
+
|
83
|
+
# Alternatively, you can override the authorization URI in its entirety:
|
84
|
+
config.authorization_uri = 'https://id.hyper.no/authorize?prompt=login'
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Alternatively, these environment variables will be picked up automatically:
|
89
|
+
|
90
|
+
- `OPENID_AUTHORIZATION_ENDPOINT`
|
91
|
+
- `OPENID_AUTHORIZATION_URI`
|
92
|
+
- `OPENID_CLIENT_ID`
|
93
|
+
- `OPENID_CLIENT_SECRET`
|
94
|
+
- `OPENID_DOMAIN_HINT`
|
95
|
+
- `OPENID_ISSUER`
|
96
|
+
- `OPENID_PROMPT`
|
97
|
+
- `OPENID_REDIRECT_URI`
|
98
|
+
- `OPENID_RESOURCE`
|
99
|
+
- `OPENID_TOKEN_ENDPOINT`
|
100
|
+
- `OPENID_USERINFO_ENDPOINT`
|
101
|
+
|
102
|
+
|
103
|
+
### Token acquirement
|
104
|
+
|
105
|
+
OpenID token proxy's main task is to obtain tokens on behalf of users. To allow it
|
106
|
+
to do so, start by mounting the engine in your Rails application:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
Rails.application.routes.draw do
|
110
|
+
mount OpenIDTokenProxy::Engine, at: '/auth'
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
Next, register the engine's callback - `https://example.com/auth/callback` - as
|
115
|
+
the redirect URL of your OpenID application on the issuer so that any authorization
|
116
|
+
requests are routed back to your application.
|
117
|
+
|
118
|
+
The proxy itself also needs to be configured with a redirect URL in order for it
|
119
|
+
to know what to do with any newly obtained tokens. To boot back into a native
|
120
|
+
applicaton one could use custom URL schemes or intents:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
OpenIDTokenProxy.configure do |config|
|
124
|
+
config.token_acquirement_hook = proc { |token|
|
125
|
+
"my-app://?token=#{token}&refresh_token=#{token.refresh_token}"
|
126
|
+
}
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
|
131
|
+
### Token authentication
|
132
|
+
|
133
|
+
Additionally, OpenID token proxy ships with an authentication module simplifying
|
134
|
+
token validation for use in APIs:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
class AccountsController < ApplicationController
|
138
|
+
include OpenIDTokenProxy::Token::Authentication
|
139
|
+
|
140
|
+
require_valid_token
|
141
|
+
|
142
|
+
...
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
Access tokens may be provided with one of the following:
|
147
|
+
|
148
|
+
- `X-Token` header.
|
149
|
+
- `Authorization: Bearer <token>` header.
|
150
|
+
- Query string parameter `token`.
|
151
|
+
|
152
|
+
|
153
|
+
#### Identity / claims
|
154
|
+
|
155
|
+
A valid token is exposed to a controller as `current_token` and identity information
|
156
|
+
can be extracted by providing a claim name through hash-syntax:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
current_token['email']
|
160
|
+
```
|
161
|
+
|
162
|
+
Identity providers may support additional claims beyond the [standard OpenID ones](http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims).
|
163
|
+
|
164
|
+
|
165
|
+
### Token refreshing
|
166
|
+
|
167
|
+
Most identity providers issue access tokens [with short lifespans](http://openid.net/specs/openid-connect-core-1_0.html#TokenLifetime).
|
168
|
+
To prevent users from having to authenticate often, refresh tokens are used to
|
169
|
+
obtain new access tokens without user intervention.
|
170
|
+
|
171
|
+
OpenID token proxy's token refresh module does just that:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
class AccountsController < ApplicationController
|
175
|
+
include OpenIDTokenProxy::Token::Authentication
|
176
|
+
include OpenIDTokenProxy::Token::Refresh
|
177
|
+
|
178
|
+
require_valid_token
|
179
|
+
|
180
|
+
...
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
Refresh tokens may be provided with one of the following:
|
185
|
+
|
186
|
+
- `X-Refresh-Token` header.
|
187
|
+
- Query string parameter `refresh_token`.
|
188
|
+
|
189
|
+
Whenever an access token has expired and a refresh token is given, the module will
|
190
|
+
attempt to obtain a new token transparently.
|
191
|
+
|
192
|
+
The following headers will be present on the API response if, **and only if**, a new
|
193
|
+
token was obtained:
|
194
|
+
|
195
|
+
- `X-Token` header containing the new access token to be used in future requests.
|
196
|
+
- `X-Refresh-Token` header containing the new refresh token.
|
197
|
+
|
198
|
+
|
199
|
+
## Contributing
|
200
|
+
|
201
|
+
1. Fork it
|
202
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
203
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
204
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
205
|
+
5. Create a pull request
|
206
|
+
|
207
|
+
|
208
|
+
## Credits
|
209
|
+
|
210
|
+
Hyper made this. We're a digital communications agency with a passion for good code,
|
211
|
+
and if you're using this library we probably [want to hire you](http://hyper.no/jobs).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
Bundler::GemHelper.install_tasks
|
8
|
+
|
9
|
+
require 'rspec/core/rake_task'
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
12
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
13
|
+
end
|
14
|
+
|
15
|
+
task test: :spec
|
16
|
+
task default: :spec
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module OpenIDTokenProxy
|
2
|
+
class CallbackController < ApplicationController
|
3
|
+
def handle
|
4
|
+
unless code = params[:code]
|
5
|
+
render text: "Required parameter 'code' missing.", status: :bad_request
|
6
|
+
return
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
token = OpenIDTokenProxy.client.retrieve_token!(auth_code: code)
|
11
|
+
rescue OpenIDTokenProxy::Client::AuthCodeError => error
|
12
|
+
render text: "Could not exchange authorization code: #{error.message}.",
|
13
|
+
status: :bad_request
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
config = OpenIDTokenProxy.config
|
18
|
+
uri = instance_exec token, &config.token_acquirement_hook
|
19
|
+
redirect_to uri || main_app.root_url unless performed?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/config/routes.rb
ADDED
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'openid_token_proxy'
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module OpenIDTokenProxy
|
2
|
+
class Client
|
3
|
+
attr_accessor :config
|
4
|
+
|
5
|
+
def initialize(config = OpenIDTokenProxy.config)
|
6
|
+
@config = config
|
7
|
+
end
|
8
|
+
|
9
|
+
def authorization_uri
|
10
|
+
config.authorization_uri || new_client.authorization_uri(
|
11
|
+
domain_hint: config.domain_hint,
|
12
|
+
prompt: config.prompt,
|
13
|
+
resource: config.resource
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when auth code could not be exchanged
|
18
|
+
class AuthCodeError < Error; end
|
19
|
+
|
20
|
+
# Raised when refresh token could not be exchanged
|
21
|
+
class RefreshTokenError < Error; end
|
22
|
+
|
23
|
+
# Retrieves a token for given authorization code or refresh token
|
24
|
+
def retrieve_token!(params)
|
25
|
+
client = new_client
|
26
|
+
client.authorization_code = params[:auth_code] if params[:auth_code]
|
27
|
+
client.refresh_token = params[:refresh_token] if params[:refresh_token]
|
28
|
+
response = client.access_token!(:query_string)
|
29
|
+
token = Token.decode!(response.access_token)
|
30
|
+
token.refresh_token = response.refresh_token
|
31
|
+
token
|
32
|
+
rescue Rack::OAuth2::Client::Error => e
|
33
|
+
raise AuthCodeError.new(e.message) if params[:auth_code]
|
34
|
+
raise RefreshTokenError.new(e.message) if params[:refresh_token]
|
35
|
+
end
|
36
|
+
|
37
|
+
def new_client
|
38
|
+
OpenIDConnect::Client.new(
|
39
|
+
identifier: config.client_id,
|
40
|
+
secret: config.client_secret,
|
41
|
+
authorization_endpoint: config.authorization_endpoint,
|
42
|
+
token_endpoint: config.token_endpoint,
|
43
|
+
userinfo_endpoint: config.userinfo_endpoint,
|
44
|
+
redirect_uri: config.redirect_uri
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'openid_connect'
|
2
|
+
|
3
|
+
module OpenIDTokenProxy
|
4
|
+
class Config
|
5
|
+
attr_accessor :client_id, :client_secret, :issuer
|
6
|
+
attr_accessor :domain_hint, :prompt, :redirect_uri, :resource
|
7
|
+
attr_accessor :authorization_uri
|
8
|
+
attr_accessor :authorization_endpoint, :token_endpoint, :userinfo_endpoint
|
9
|
+
attr_accessor :token_acquirement_hook
|
10
|
+
attr_accessor :public_keys
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@client_id = ENV['OPENID_CLIENT_ID']
|
14
|
+
@client_secret = ENV['OPENID_CLIENT_SECRET']
|
15
|
+
@issuer = ENV['OPENID_ISSUER']
|
16
|
+
|
17
|
+
@domain_hint = ENV['OPENID_DOMAIN_HINT']
|
18
|
+
@prompt = ENV['OPENID_PROMPT']
|
19
|
+
@redirect_uri = ENV['OPENID_REDIRECT_URI']
|
20
|
+
@resource = ENV['OPENID_RESOURCE']
|
21
|
+
|
22
|
+
@authorization_uri = ENV['OPENID_AUTHORIZATION_URI']
|
23
|
+
|
24
|
+
@authorization_endpoint = ENV['OPENID_AUTHORIZATION_ENDPOINT']
|
25
|
+
@token_endpoint = ENV['OPENID_TOKEN_ENDPOINT']
|
26
|
+
@userinfo_endpoint = ENV['OPENID_USERINFO_ENDPOINT']
|
27
|
+
|
28
|
+
@token_acquirement_hook = proc { }
|
29
|
+
|
30
|
+
yield self if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
def provider_config
|
34
|
+
# TODO: Add support for refreshing provider configuration
|
35
|
+
@provider_config ||= begin
|
36
|
+
OpenIDConnect::Discovery::Provider::Config.discover! issuer
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def authorization_endpoint
|
41
|
+
@authorization_endpoint || provider_config.authorization_endpoint
|
42
|
+
end
|
43
|
+
|
44
|
+
def token_endpoint
|
45
|
+
@token_endpoint || provider_config.token_endpoint
|
46
|
+
end
|
47
|
+
|
48
|
+
def userinfo_endpoint
|
49
|
+
@userinfo_endpoint || provider_config.userinfo_endpoint
|
50
|
+
end
|
51
|
+
|
52
|
+
def public_keys
|
53
|
+
@public_keys ||= provider_config.public_keys
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module OpenIDTokenProxy
|
4
|
+
class Token
|
5
|
+
module Authentication
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
rescue_from OpenIDTokenProxy::Error, with: :require_authorization
|
10
|
+
|
11
|
+
helper_method :current_token, :raw_token
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def require_valid_token(*args)
|
16
|
+
before_action :require_valid_token, *args
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_authentication_url!
|
21
|
+
uri = OpenIDTokenProxy.client.authorization_uri
|
22
|
+
response.headers['X-Authentication-URL'] = uri
|
23
|
+
end
|
24
|
+
|
25
|
+
def require_authorization(exception)
|
26
|
+
set_authentication_url!
|
27
|
+
render json: exception.to_json, status: :unauthorized
|
28
|
+
end
|
29
|
+
|
30
|
+
def require_valid_token
|
31
|
+
config = OpenIDTokenProxy.config
|
32
|
+
current_token.validate! audience: config.resource,
|
33
|
+
client_id: config.client_id
|
34
|
+
end
|
35
|
+
|
36
|
+
def current_token
|
37
|
+
@current_token ||= OpenIDTokenProxy::Token.decode!(raw_token)
|
38
|
+
end
|
39
|
+
|
40
|
+
def raw_token
|
41
|
+
token = params[:token]
|
42
|
+
return token if token
|
43
|
+
|
44
|
+
authorization = request.headers['Authorization']
|
45
|
+
if authorization
|
46
|
+
token = authorization[/Bearer (.+)/, 1]
|
47
|
+
return token if token
|
48
|
+
end
|
49
|
+
|
50
|
+
request.headers['X-Token']
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module OpenIDTokenProxy
|
4
|
+
class Token
|
5
|
+
module Refresh
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include OpenIDTokenProxy::Token::Authentication
|
10
|
+
|
11
|
+
helper_method :raw_refresh_token
|
12
|
+
|
13
|
+
def require_valid_token
|
14
|
+
super
|
15
|
+
rescue OpenIDTokenProxy::Token::Expired
|
16
|
+
raise unless raw_refresh_token.present?
|
17
|
+
@current_token = OpenIDTokenProxy.client.retrieve_token!(
|
18
|
+
refresh_token: raw_refresh_token
|
19
|
+
)
|
20
|
+
response.headers['X-Token'] = current_token.access_token
|
21
|
+
response.headers['X-Refresh-Token'] = current_token.refresh_token
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def raw_refresh_token
|
26
|
+
params[:refresh_token] || request.headers['X-Refresh-Token']
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|