g5_authenticatable_api 0.3.2 → 0.4.0

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
  SHA1:
3
- metadata.gz: 12624771940d7daf4727fa08cf7befcc04883458
4
- data.tar.gz: d247b5ed06aa6e2b82c6c99b48916edc0e2962fc
3
+ metadata.gz: b5149507d4b4decc9cea0ecbf91c3ff2ed948188
4
+ data.tar.gz: 6e2cb01e5217b1e7b3ed6991048cd5242e026147
5
5
  SHA512:
6
- metadata.gz: 2f8d08300206e3a3eacbff455df57cbf336199695887fb32fffee241c2856f0b81bd572f412e71fb0c96ec32fe3194df2b8f9c5f09ff6e85e8fc283bd15ee1e0
7
- data.tar.gz: 08d6b7e24da4e71f447c26925265fd60efd8e3efec9145028a78a35989d5a7fe6db7e57909502c8a442a72266989cf8b3ad52c3c162211c9bf505c629b145852
6
+ metadata.gz: 33415eef9d07b90e5dbad1f898691b82b85800810378a25126e6a5f47121c6e76929b5c912ec55df3f5ab45c927901c70b790d6b79148c1205e3fc2739dbfc30
7
+ data.tar.gz: 7f271c4ab903a38f76671f3a0ca7d30b5443d2c4627be86a0413c814135a0116f7ce28f01dde2ebaa592fdb218e1c8f7a03aeb738227848376fa7d81668aafd2
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.1.5
1
+ 2.1.6
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.6
5
+ - 2.2.2
6
+ script:
7
+ - (cd spec/dummy; RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load)
8
+ - bundle exec rspec spec
9
+ before_script:
10
+ - cp spec/dummy/config/database.yml.ci spec/dummy/config/database.yml
11
+ env:
12
+ global:
13
+ - DEVISE_SECRET_KEY=foo
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## v0.4.0 (2015-05-22)
2
+
3
+ * Add helpers for retrieving current API user as well as more detailed token
4
+ information
5
+ ([#8](https://github.com/G5/g5_authenticatable_api/pull/8))
6
+
7
+ ## v0.3.2 (2015-04-20)
8
+
9
+ * Fix for case-insensitive authorization request header
10
+ ([#7](https://github.com/G5/g5_authenticatable_api/pull/7))
11
+
1
12
  ## v0.3.1 (2015-01-20)
2
13
 
3
14
  * Disable strict token validation for session-authenticated users by
data/Gemfile CHANGED
@@ -26,6 +26,7 @@ group :test do
26
26
  gem 'webmock'
27
27
  gem 'shoulda-matchers', '~> 2.6'
28
28
  gem 'rspec-http', require: false
29
+ gem 'rack-test'
29
30
  end
30
31
 
31
32
  # Declare any dependencies that are still in development here instead of in
data/README.md CHANGED
@@ -9,7 +9,7 @@ service using token-based authentication.
9
9
 
10
10
  ## Current Version
11
11
 
12
- 0.3.1
12
+ 0.4.0
13
13
 
14
14
  ## Requirements
15
15
 
@@ -106,6 +106,58 @@ class MyResourceController < ApplicationController
106
106
  end
107
107
  ```
108
108
 
109
+ After authenticating an API user, you can retrieve the current token data as a
110
+ [`G5AuthenticationClient::TokenInfo`](https://github.com/G5/g5_authentication_client/blob/master/lib/g5_authentication_client/token_info.rb)
111
+ using the `token_data` helper:
112
+
113
+ ```ruby
114
+ class MyResourceController < ApplicationController
115
+ before_filter :authenticate_api_user!
116
+
117
+ respond_to :json
118
+
119
+ def index
120
+ token_expiration = token_data.expires_in_seconds
121
+ # ...
122
+ end
123
+ end
124
+ ```
125
+
126
+ You can retrieve the current user data using the `current_api_user` helper,
127
+ which will attempt to retrieve the data from
128
+ [warden](https://github.com/hassox/warden) if possible. Otherwise it will return
129
+ a [`G5AuthenticationClient::User`](https://github.com/G5/g5_authentication_client/blob/master/lib/g5_authentication_client/user.rb):
130
+
131
+ ```ruby
132
+ class MyResourceController < ApplicationController
133
+ before_filter :authenticate_api_user!
134
+
135
+ respond_to :json
136
+
137
+ def index
138
+ user = current_api_user
139
+ # ...
140
+ end
141
+ end
142
+ ```
143
+
144
+
145
+ Finally, you can retrieve the value of the access token in use for this request
146
+ by using the `access_token` helper:
147
+
148
+ ```ruby
149
+ class MyResourceController < ApplicationController
150
+ before_filter :authenticate_api_user!
151
+
152
+ respond_to :json
153
+
154
+ def index
155
+ token = access_token
156
+ # ...
157
+ end
158
+ end
159
+ ```
160
+
109
161
  ### Grape
110
162
 
111
163
  To require authentication for all endpoints exposed by your API:
@@ -138,6 +190,57 @@ class MyApi < Grape::API
138
190
  end
139
191
  ```
140
192
 
193
+ After authenticating an API user, you can retrieve the current token data as a
194
+ [`G5AuthenticationClient::TokenInfo`](https://github.com/G5/g5_authentication_client/blob/master/lib/g5_authentication_client/token_info.rb)
195
+ using the `token_data` helper:
196
+
197
+ ```ruby
198
+ class MyApi < Grape::API
199
+ helpers G5AuthenticatableApi::Helpers::Grape
200
+
201
+ before { authenticate_user! }
202
+
203
+ get :index do
204
+ token_expiration = token_data.expires_in_seconds
205
+ # ...
206
+ end
207
+ end
208
+ ```
209
+
210
+ You can retrieve the current user data using the `current_user` helper,
211
+ which will attempt to retrieve the data from
212
+ [warden](https://github.com/hassox/warden) if possible. Otherwise it will return
213
+ a [`G5AuthenticationClient::User`](https://github.com/G5/g5_authentication_client/blob/master/lib/g5_authentication_client/user.rb):
214
+
215
+ ```ruby
216
+ class MyApi < Grape::API
217
+ helpers G5AuthenticatableApi::Helpers::Grape
218
+
219
+ before { authenticate_user! }
220
+
221
+ get :index do
222
+ user = current_user
223
+ # ...
224
+ end
225
+ end
226
+ ```
227
+
228
+ You can retrieve the value of the access token in use for this request with the
229
+ `access_token` helper:
230
+
231
+ ```ruby
232
+ class MyApi < Grape::API
233
+ helpers G5AuthenticatableApi::Helpers::Grape
234
+
235
+ before { authenticate_user! }
236
+
237
+ get :index do
238
+ token = access_token
239
+ # ...
240
+ end
241
+ end
242
+ ```
243
+
141
244
  ### Submitting a token
142
245
 
143
246
  Authenticated requests follow the requirements described by
@@ -19,6 +19,6 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.add_dependency 'rack'
22
- spec.add_dependency 'g5_authentication_client', '~> 0.2'
22
+ spec.add_dependency 'g5_authentication_client', '~> 0.4'
23
23
  spec.add_dependency 'activesupport', '>= 3.2'
24
24
  end
@@ -1,4 +1,5 @@
1
- require 'g5_authenticatable_api/token_validator'
1
+ require 'g5_authenticatable_api/services/token_validator'
2
+ require 'g5_authenticatable_api/services/user_fetcher'
2
3
 
3
4
  module G5AuthenticatableApi
4
5
  module Helpers
@@ -7,14 +8,37 @@ module G5AuthenticatableApi
7
8
  raise_auth_error if !token_validator.valid?
8
9
  end
9
10
 
11
+ def token_data
12
+ @token_data ||= token_info.token_data
13
+ end
14
+
15
+ def current_user
16
+ @current_user ||= user_fetcher.current_user
17
+ end
18
+
19
+ def access_token
20
+ @access_token ||= token_info.access_token
21
+ end
22
+
10
23
  def warden
11
24
  env['warden']
12
25
  end
13
26
 
14
27
  private
28
+ def request
29
+ Rack::Request.new(env)
30
+ end
31
+
32
+ def token_info
33
+ @token_info ||= Services::TokenInfo.new(request.params, headers, warden)
34
+ end
35
+
15
36
  def token_validator
16
- request = Rack::Request.new(env)
17
- @token_validator ||= TokenValidator.new(request.params, headers, warden)
37
+ @token_validator ||= Services::TokenValidator.new(request.params, headers, warden)
38
+ end
39
+
40
+ def user_fetcher
41
+ @user_fetcher ||= Services::UserFetcher.new(request.params, headers, warden)
18
42
  end
19
43
 
20
44
  def raise_auth_error
@@ -1,4 +1,5 @@
1
- require 'g5_authenticatable_api/token_validator'
1
+ require 'g5_authenticatable_api/services/token_validator'
2
+ require 'g5_authenticatable_api/services/user_fetcher'
2
3
 
3
4
  module G5AuthenticatableApi
4
5
  module Helpers
@@ -7,13 +8,33 @@ module G5AuthenticatableApi
7
8
  raise_auth_error if !token_validator.valid?
8
9
  end
9
10
 
11
+ def token_data
12
+ @token_data ||= token_info.token_data
13
+ end
14
+
15
+ def current_api_user
16
+ @current_api_user ||= user_fetcher.current_user
17
+ end
18
+
19
+ def access_token
20
+ @access_token ||= token_info.access_token
21
+ end
22
+
10
23
  def warden
11
24
  request.env['warden']
12
25
  end
13
26
 
14
27
  private
28
+ def token_info
29
+ @token_info ||= Services::TokenInfo.new(request.params, request.headers, warden)
30
+ end
31
+
15
32
  def token_validator
16
- @token_validator ||= TokenValidator.new(request.params, request.headers, warden)
33
+ @token_validator ||= Services::TokenValidator.new(request.params, request.headers, warden)
34
+ end
35
+
36
+ def user_fetcher
37
+ @user_fetcher ||= Services::UserFetcher.new(request.params, request.headers, warden)
17
38
  end
18
39
 
19
40
  def raise_auth_error
@@ -0,0 +1,40 @@
1
+ module G5AuthenticatableApi
2
+ module Services
3
+ class TokenInfo
4
+ attr_reader :params, :headers, :warden
5
+
6
+ def initialize(params={},headers={},warden=nil)
7
+ @params = params || {}
8
+ @headers = headers || {}
9
+ @warden = warden
10
+ end
11
+
12
+ def access_token
13
+ @access_token ||= (extract_token_from_header ||
14
+ params['access_token'] ||
15
+ warden.try(:user).try(:g5_access_token))
16
+ end
17
+
18
+ def token_data
19
+ auth_client.token_info
20
+ end
21
+
22
+ def auth_client
23
+ @auth_client ||= G5AuthenticationClient::Client.new(allow_password_credentials: 'false',
24
+ access_token: access_token)
25
+ end
26
+
27
+ private
28
+ def extract_token_from_header
29
+ if authorization_header
30
+ parts = authorization_header.match(/Bearer (?<access_token>\S+)/)
31
+ parts['access_token']
32
+ end
33
+ end
34
+
35
+ def authorization_header
36
+ @headers['Authorization'] || @headers['AUTHORIZATION']
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,54 @@
1
+ require 'g5_authenticatable_api/services/token_info'
2
+
3
+ module G5AuthenticatableApi
4
+ module Services
5
+ class TokenValidator < TokenInfo
6
+ attr_reader :error
7
+
8
+ def validate!
9
+ begin
10
+ token_data unless skip_validation?
11
+ rescue StandardError => @error
12
+ raise error
13
+ end
14
+ end
15
+
16
+ def valid?
17
+ begin
18
+ validate!
19
+ true
20
+ rescue StandardError => e
21
+ false
22
+ end
23
+ end
24
+
25
+ def auth_response_header
26
+ if error
27
+ auth_header = "Bearer"
28
+
29
+ if access_token
30
+ auth_header << " error=\"#{error_code}\""
31
+ auth_header << ",error_description=\"#{error_description}\"" if error_description
32
+ end
33
+
34
+ auth_header
35
+ end
36
+ end
37
+
38
+ private
39
+ def error_code
40
+ error_code = error.code if error.respond_to?(:code)
41
+ error_code || 'invalid_request'
42
+ end
43
+
44
+ def error_description
45
+ error_description = error.description if error.respond_to?(:description)
46
+ error_description
47
+ end
48
+
49
+ def skip_validation?
50
+ @warden.try(:user) && !G5AuthenticatableApi.strict_token_validation
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,15 @@
1
+ require 'g5_authenticatable_api/services/token_info'
2
+
3
+ module G5AuthenticatableApi
4
+ module Services
5
+ class UserFetcher < TokenInfo
6
+ def current_user
7
+ if access_token == @warden.try(:user).try(:g5_access_token)
8
+ @warden.user
9
+ else
10
+ auth_client.me
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module G5AuthenticatableApi
2
- VERSION = '0.3.2'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -2,5 +2,4 @@ test:
2
2
  adapter: postgresql
3
3
  encoding: unicode
4
4
  database: g5_authenticatable_api_test
5
- pool: 5
6
- username: ubuntu
5
+ username: postgres
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+
3
+ describe G5AuthenticatableApi::Helpers::Grape do
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ Class.new(Grape::API) do
8
+ helpers G5AuthenticatableApi::Helpers::Grape
9
+
10
+ get :authenticate do
11
+ authenticate_user!
12
+ { hello: 'world' }
13
+ end
14
+
15
+ get :token_data do
16
+ token_data.to_json
17
+ end
18
+
19
+ get :current_user do
20
+ current_user.to_json
21
+ end
22
+
23
+ get :access_token do
24
+ [access_token]
25
+ end
26
+ end
27
+ end
28
+
29
+ let(:env) { {'warden' => warden} }
30
+ let(:warden) { double(:warden) }
31
+
32
+ let(:params) { {'access_token' => token_value} }
33
+ let(:token_value) { 'abc123' }
34
+
35
+ let(:headers) { {'Host'=>'example.org', 'Cookie'=>''} }
36
+
37
+ describe '#authenticate_user!' do
38
+ subject(:authenticate_user!) { get '/authenticate', params, env }
39
+
40
+ let(:token_validator) do
41
+ double(:token_validator, valid?: valid,
42
+ auth_response_header: auth_response_header,
43
+ access_token: token_value)
44
+ end
45
+ before do
46
+ allow(G5AuthenticatableApi::Services::TokenValidator).to receive(:new).
47
+ and_return(token_validator)
48
+ end
49
+
50
+ context 'when token is valid' do
51
+ let(:valid) { true }
52
+ let(:auth_response_header) {}
53
+
54
+ it 'initializes the token validator correctly' do
55
+ authenticate_user!
56
+ expect(G5AuthenticatableApi::Services::TokenValidator).to have_received(:new).
57
+ with(params, headers, warden)
58
+ end
59
+
60
+ it 'is successful' do
61
+ authenticate_user!
62
+ expect(last_response).to be_http_ok
63
+ end
64
+
65
+ it 'does not set the authenticate response header' do
66
+ authenticate_user!
67
+ expect(last_response).to_not have_header('WWW-Authenticate')
68
+ end
69
+ end
70
+
71
+ context 'when token is invalid' do
72
+ let(:valid) { false }
73
+ let(:auth_response_header) { 'whatever' }
74
+
75
+ it 'is unauthorized' do
76
+ authenticate_user!
77
+ expect(last_response).to be_http_unauthorized
78
+ end
79
+
80
+ it 'renders an error message' do
81
+ authenticate_user!
82
+ expect(last_response.body).to eq('Unauthorized')
83
+ end
84
+
85
+ it 'sets the authenticate response header' do
86
+ authenticate_user!
87
+ expect(last_response).to have_header('WWW-Authenticate' => auth_response_header)
88
+ end
89
+ end
90
+ end
91
+
92
+ describe '#token_data' do
93
+ subject(:token_data) { get '/token_data', params, env }
94
+
95
+ before do
96
+ allow(G5AuthenticatableApi::Services::TokenInfo).to receive(:new).
97
+ and_return(token_info)
98
+ end
99
+ let(:token_info) { double(:token_info, token_data: mock_token_data) }
100
+ let(:mock_token_data) { double(:token_data, to_json: '{result: mock_token_data_json}') }
101
+
102
+ it 'initializes the token info service correctly' do
103
+ token_data
104
+ expect(G5AuthenticatableApi::Services::TokenInfo).to have_received(:new).
105
+ with(params, headers, warden)
106
+ end
107
+
108
+ it 'returns the token info from the service' do
109
+ token_data
110
+ expect(last_response.body).to eq(mock_token_data.to_json)
111
+ end
112
+ end
113
+
114
+ describe '#current_user' do
115
+ subject(:current_user) { get '/current_user', params, env }
116
+
117
+ before do
118
+ allow(G5AuthenticatableApi::Services::UserFetcher).to receive(:new).
119
+ and_return(user_fetcher)
120
+ end
121
+ let(:user_fetcher) { double(:user_fetcher, current_user: user) }
122
+ let(:user) { double(:user, to_json: '{result: mock_user_json}') }
123
+
124
+ it 'initializes the user fetcher service correctly' do
125
+ current_user
126
+ expect(G5AuthenticatableApi::Services::UserFetcher).to have_received(:new).
127
+ with(params, headers, warden)
128
+ end
129
+
130
+ it 'returns the user from the service' do
131
+ current_user
132
+ expect(last_response.body).to eq(user.to_json)
133
+ end
134
+ end
135
+
136
+ describe '#access_token' do
137
+ subject(:access_token) { get '/access_token', params, env }
138
+
139
+ before do
140
+ allow(G5AuthenticatableApi::Services::TokenInfo).to receive(:new).
141
+ and_return(token_info)
142
+ end
143
+ let(:token_info) { double(:token_info, access_token: token_value) }
144
+
145
+ it 'initializes the token info service correctly' do
146
+ access_token
147
+ expect(G5AuthenticatableApi::Services::TokenInfo).to have_received(:new).
148
+ with(params, headers, warden)
149
+ end
150
+
151
+ it 'returns the access token from the service' do
152
+ access_token
153
+ expect(last_response.body).to eq([token_value].to_json)
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+
3
+ describe G5AuthenticatableApi::Helpers::Rails, type: :controller do
4
+ controller(ActionController::Base) do
5
+ before_action :authenticate_api_user!, only: :index
6
+
7
+ def index
8
+ render json: [], status: :ok
9
+ end
10
+
11
+ def new
12
+ render json: [], status: :ok
13
+ end
14
+ end
15
+
16
+ let(:warden) { double(:warden) }
17
+ before { request.env['warden'] = warden }
18
+
19
+ describe '#authenticate_api_user!' do
20
+ subject(:authenticate_api_user!) { get :index, access_token: token_value }
21
+
22
+ let(:token_value) { 'abc123' }
23
+
24
+ let(:token_validator) do
25
+ double(:token_validator, valid?: valid,
26
+ auth_response_header: auth_response_header,
27
+ access_token: token_value)
28
+ end
29
+ before do
30
+ allow(G5AuthenticatableApi::Services::TokenValidator).to receive(:new).
31
+ and_return(token_validator)
32
+ end
33
+
34
+ context 'when token is valid' do
35
+ let(:valid) { true }
36
+ let(:auth_response_header) {}
37
+
38
+ it 'initializes the token validator correctly' do
39
+ authenticate_api_user!
40
+ expect(G5AuthenticatableApi::Services::TokenValidator).to have_received(:new).
41
+ with(request.params,
42
+ an_instance_of(ActionDispatch::Http::Headers),
43
+ warden)
44
+ end
45
+
46
+ it 'is successful' do
47
+ authenticate_api_user!
48
+ expect(response).to be_success
49
+ end
50
+
51
+ it 'does not set the authenticate response header' do
52
+ authenticate_api_user!
53
+ expect(response).to_not have_header('WWW-Authenticate')
54
+ end
55
+
56
+ end
57
+
58
+ context 'when token is invalid' do
59
+ let(:valid) { false }
60
+ let(:auth_response_header) { 'whatever' }
61
+
62
+ it 'is unauthorized' do
63
+ authenticate_api_user!
64
+ expect(response).to be_unauthorized
65
+ end
66
+
67
+ it 'renders an error message' do
68
+ authenticate_api_user!
69
+ expect(JSON.parse(response.body)).to eq('error' => 'Unauthorized')
70
+ end
71
+ end
72
+ end
73
+
74
+ describe '#token_data' do
75
+ subject(:token_data) { controller.token_data }
76
+
77
+ before do
78
+ allow(G5AuthenticatableApi::Services::TokenInfo).to receive(:new).
79
+ and_return(token_info)
80
+ end
81
+ let(:token_info) { double(:user_fetcher, token_data: mock_token_data) }
82
+ let(:mock_token_data) { double(:token_info) }
83
+
84
+ before { get :new, access_token: 'abc123' }
85
+
86
+ it 'initializes the token info service correctly' do
87
+ token_data
88
+ expect(G5AuthenticatableApi::Services::TokenInfo).to have_received(:new).
89
+ with(request.params,
90
+ an_instance_of(ActionDispatch::Http::Headers),
91
+ warden)
92
+ end
93
+
94
+ it 'returns the token data from the service' do
95
+ expect(token_data).to eq(mock_token_data)
96
+ end
97
+ end
98
+
99
+ describe '#access_token' do
100
+ subject(:access_token) { controller.access_token }
101
+
102
+ before do
103
+ allow(G5AuthenticatableApi::Services::TokenInfo).to receive(:new).
104
+ and_return(token_info)
105
+ end
106
+ let(:token_info) { double(:token_info, access_token: token_value) }
107
+ let(:token_value) { 'abc123' }
108
+
109
+ before { get :new, access_token: token_value }
110
+
111
+ it 'initializes the token info service correctly' do
112
+ access_token
113
+ expect(G5AuthenticatableApi::Services::TokenInfo).to have_received(:new).
114
+ with(request.params,
115
+ an_instance_of(ActionDispatch::Http::Headers),
116
+ warden)
117
+ end
118
+
119
+ it 'returns the access token from the service' do
120
+ expect(access_token).to eq(token_value)
121
+ end
122
+ end
123
+
124
+ describe '#current_api_user' do
125
+ subject(:current_api_user) { controller.current_api_user }
126
+
127
+ before do
128
+ allow(G5AuthenticatableApi::Services::UserFetcher).to receive(:new).
129
+ and_return(user_fetcher)
130
+ end
131
+ let(:user_fetcher) { double(:user_fetcher, current_user: user) }
132
+ let(:user) { double(:user) }
133
+
134
+ before { get :new, access_token: 'abc123' }
135
+
136
+ it 'initializes the user fetcher service correctly' do
137
+ current_api_user
138
+ expect(G5AuthenticatableApi::Services::UserFetcher).to have_received(:new).
139
+ with(request.params,
140
+ an_instance_of(ActionDispatch::Http::Headers),
141
+ warden)
142
+ end
143
+
144
+ it 'returns the user from the service' do
145
+ expect(current_api_user).to eq(user)
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,158 @@
1
+ require 'spec_helper'
2
+
3
+ describe G5AuthenticatableApi::Services::TokenInfo do
4
+ subject(:token_info) { described_class.new(params, headers, warden) }
5
+ let(:params) { {'access_token' => token_value} }
6
+ let(:headers) { Hash.new }
7
+ let(:warden) {}
8
+
9
+ let(:token_value) { 'abc123' }
10
+
11
+ describe '#initialize' do
12
+ let(:params) { {'foo' => 'bar'} }
13
+ let(:headers) { {'Content-Type' => 'application/json'} }
14
+
15
+ context 'with warden' do
16
+ let(:warden) { double(:warden) }
17
+
18
+ it 'sets the params' do
19
+ expect(token_info.params).to eq(params)
20
+ end
21
+
22
+ it 'sets the headers' do
23
+ expect(token_info.headers).to eq(headers)
24
+ end
25
+
26
+ it 'sets warden' do
27
+ expect(token_info.warden).to eq(warden)
28
+ end
29
+ end
30
+
31
+ context 'without warden' do
32
+ let(:token_info) { described_class.new(params, headers) }
33
+
34
+ it 'sets the params' do
35
+ expect(token_info.params).to eq(params)
36
+ end
37
+
38
+ it 'sets the headers' do
39
+ expect(token_info.headers).to eq(headers)
40
+ end
41
+
42
+ it 'defaults warden to nil' do
43
+ expect(token_info.warden).to be_nil
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '#access_token' do
49
+ subject(:access_token) { token_info.access_token }
50
+
51
+ context 'with auth header' do
52
+ let(:headers) { {'Authorization' => "Bearer #{token_value}"} }
53
+ let(:params) {}
54
+
55
+ it 'should extract the token value from the header' do
56
+ expect(access_token).to eq(token_value)
57
+ end
58
+ end
59
+
60
+ context 'with all caps authorization key' do
61
+ let(:headers) { {'AUTHORIZATION' => "Bearer #{token_value}"} }
62
+ let(:params) {}
63
+
64
+ it 'should extract the token value from the header' do
65
+ expect(access_token).to eq(token_value)
66
+ end
67
+ end
68
+
69
+ context 'with auth param' do
70
+ let(:params) { {'access_token' => token_value} }
71
+ let(:headers) {}
72
+
73
+ it 'should extract the token value from the access_token parameter' do
74
+ expect(access_token).to eq(token_value)
75
+ end
76
+ end
77
+
78
+ context 'with warden user' do
79
+ let(:warden) { double(:warden, user: user) }
80
+ let(:user) { FactoryGirl.build_stubbed(:user) }
81
+
82
+ context 'without token on request' do
83
+ let(:params) {}
84
+ let(:headers) {}
85
+
86
+ it 'should extract the token value from the session user' do
87
+ expect(access_token).to eq(user.g5_access_token)
88
+ end
89
+ end
90
+
91
+ context 'with auth param' do
92
+ let(:params) { {'access_token' => token_value} }
93
+ let(:headers) {}
94
+
95
+ it 'should give precedence to the token on the request' do
96
+ expect(access_token).to eq(token_value)
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ describe '#token_data' do
103
+ subject(:token_data) { token_info.token_data }
104
+
105
+ context 'when token is valid' do
106
+ include_context 'valid access token'
107
+
108
+ it 'includes the resource_owner_id' do
109
+ expect(token_data.resource_owner_id).to eq(raw_token_info['resource_owner_id'])
110
+ end
111
+
112
+ it 'includes the expires_in_seconds' do
113
+ expect(token_data.expires_in_seconds).to eq(raw_token_info['expires_in_seconds'])
114
+ end
115
+
116
+ it 'includes the application_uid' do
117
+ expect(token_data.application_uid).to eq(raw_token_info['application']['uid'])
118
+ end
119
+
120
+ it 'includes the scopes' do
121
+ expect(token_data.scopes). to eq(raw_token_info['scopes'])
122
+ end
123
+ end
124
+
125
+ context 'when token is invalid' do
126
+ include_context 'invalid access token'
127
+
128
+ it 'raises an error' do
129
+ expect { token_data }.to raise_error
130
+ end
131
+ end
132
+ end
133
+
134
+ describe '#auth_client' do
135
+ subject(:auth_client) { token_info.auth_client }
136
+
137
+ let(:client) { double(:auth_client) }
138
+ before do
139
+ allow(G5AuthenticationClient::Client).to receive(:new).and_return(client)
140
+ end
141
+
142
+ it 'returns the initialized client' do
143
+ expect(auth_client).to eq(client)
144
+ end
145
+
146
+ it 'initializes the client with the token' do
147
+ auth_client
148
+ expect(G5AuthenticationClient::Client).to have_received(:new).
149
+ with(hash_including(access_token: token_value))
150
+ end
151
+
152
+ it 'disables password access on the client' do
153
+ auth_client
154
+ expect(G5AuthenticationClient::Client).to have_received(:new).
155
+ with(hash_including(allow_password_credentials: 'false'))
156
+ end
157
+ end
158
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe G5AuthenticatableApi::TokenValidator do
3
+ describe G5AuthenticatableApi::Services::TokenValidator do
4
4
  subject { validator }
5
5
 
6
6
  let(:validator) { described_class.new(params, headers, warden) }
@@ -10,60 +10,6 @@ describe G5AuthenticatableApi::TokenValidator do
10
10
  let(:token_value) { 'abc123' }
11
11
  let(:warden) {}
12
12
 
13
- describe '#access_token' do
14
- subject(:access_token) { validator.access_token }
15
-
16
- context 'with auth header' do
17
- let(:headers) { {'Authorization' => "Bearer #{token_value}"} }
18
- let(:params) {}
19
-
20
- it 'should extract the token value from the header' do
21
- expect(access_token).to eq(token_value)
22
- end
23
- end
24
-
25
- context 'with all caps authorization key' do
26
- let(:headers) { {'AUTHORIZATION' => "Bearer #{token_value}"} }
27
- let(:params) {}
28
-
29
- it 'should extract the token value from the header' do
30
- expect(access_token).to eq(token_value)
31
- end
32
- end
33
-
34
- context 'with auth param' do
35
- let(:params) { {'access_token' => token_value} }
36
- let(:headers) {}
37
-
38
- it 'should extract the token value from the access_token parameter' do
39
- expect(access_token).to eq(token_value)
40
- end
41
- end
42
-
43
- context 'with warden user' do
44
- let(:warden) { double(:warden, user: user) }
45
- let(:user) { FactoryGirl.build_stubbed(:user) }
46
-
47
- context 'without token on request' do
48
- let(:params) {}
49
- let(:headers) {}
50
-
51
- it 'should extract the token value from the session user' do
52
- expect(access_token).to eq(user.g5_access_token)
53
- end
54
- end
55
-
56
- context 'with auth param' do
57
- let(:params) { {'access_token' => token_value} }
58
- let(:headers) {}
59
-
60
- it 'should give precedence to the token on the request' do
61
- expect(access_token).to eq(token_value)
62
- end
63
- end
64
- end
65
- end
66
-
67
13
  describe '#validate!' do
68
14
  subject(:validate!) { validator.validate! }
69
15
 
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe G5AuthenticatableApi::Services::UserFetcher do
4
+ subject(:user_fetcher) { described_class.new(params, headers, warden) }
5
+ let(:params) { {'access_token' => token_value} }
6
+ let(:token_value) { 'abc123' }
7
+ let(:headers) {}
8
+ let(:warden) {}
9
+
10
+ describe '#current_user' do
11
+ include_context 'current auth user'
12
+
13
+ subject(:current_user) { user_fetcher.current_user }
14
+
15
+ context 'when there is no warden user' do
16
+ it_behaves_like 'an auth user' do
17
+ let(:user) { current_user }
18
+ end
19
+ end
20
+
21
+ context 'when there is a warden user' do
22
+ let(:warden) { double(:warden, user: warden_user) }
23
+
24
+ context 'when the access_token is for the warden user' do
25
+ let(:warden_user) { double(:user, g5_access_token: token_value) }
26
+
27
+ it 'returns the warden user' do
28
+ expect(current_user).to eq(warden_user)
29
+ end
30
+ end
31
+
32
+ context 'when the access token is for a different user' do
33
+ let(:warden_user) { double(:user, g5_access_token: "#{token_value}42") }
34
+
35
+ it_behaves_like 'an auth user' do
36
+ let(:user) { current_user }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ shared_context 'current auth user' do
2
+ before do
3
+ stub_request(:get, 'auth.g5search.com/v1/me').
4
+ with(headers: {'Authorization'=>"Bearer #{token_value}"}).
5
+ to_return(status: 200,
6
+ body: raw_user_info.to_json,
7
+ headers: {'Content-Type' => 'application/json'})
8
+ end
9
+
10
+ let(:raw_user_info) do
11
+ {
12
+ 'id' => 42,
13
+ 'email' => 'fred.rogers@thehood.net',
14
+ 'first_name' => 'Fred',
15
+ 'last_name' => 'Rogers',
16
+ 'phone_number' => '(555) 555-1212',
17
+ 'organization_name' => 'The Neighborhood',
18
+ 'title' => 'Head Cardigan Wearer',
19
+ 'roles' => [
20
+ {
21
+ 'name' => 'viewer',
22
+ 'type' => 'GLOBAL',
23
+ 'urn' => nil
24
+ },
25
+ {
26
+ 'name' => 'admin',
27
+ 'type' => 'G5Updatable::Client',
28
+ 'urn' => 'g5-c-some-randomly-generated-string'
29
+ }
30
+ ]
31
+ }
32
+ end
33
+ end
@@ -2,6 +2,17 @@ shared_context 'valid access token' do
2
2
  before do
3
3
  stub_request(:get, 'auth.g5search.com/oauth/token/info').
4
4
  with(headers: {'Authorization'=>"Bearer #{token_value}"}).
5
- to_return(status: 200, body: '', headers: {})
5
+ to_return(status: 200,
6
+ body: raw_token_info.to_json,
7
+ headers: {'Content-Type' => 'application/json'})
8
+ end
9
+
10
+ let(:raw_token_info) do
11
+ {
12
+ 'resource_owner_id' => '42',
13
+ 'scopes' => [],
14
+ 'expires_in_seconds' => 120,
15
+ 'application' => {'uid' => 'abcdefg112358'}
16
+ }
6
17
  end
7
18
  end
@@ -0,0 +1,33 @@
1
+ shared_examples_for 'an auth user' do
2
+ it 'has the correct id' do
3
+ expect(user.id).to eq(raw_user_info['id'])
4
+ end
5
+
6
+ it 'has the correct email' do
7
+ expect(user.email).to eq(raw_user_info['email'])
8
+ end
9
+
10
+ it 'has the correct first_name' do
11
+ expect(user.first_name).to eq(raw_user_info['first_name'])
12
+ end
13
+
14
+ it 'has the correct last_name' do
15
+ expect(user.last_name).to eq(raw_user_info['last_name'])
16
+ end
17
+
18
+ it 'has the correct phone_number' do
19
+ expect(user.phone_number).to eq(raw_user_info['phone_number'])
20
+ end
21
+
22
+ it 'has the correct title' do
23
+ expect(user.title).to eq(raw_user_info['title'])
24
+ end
25
+
26
+ it 'has the correct organization_name' do
27
+ expect(user.organization_name).to eq(raw_user_info['organization_name'])
28
+ end
29
+
30
+ it 'has the correct number of roles' do
31
+ expect(user.roles.count).to eq(raw_user_info['roles'].count)
32
+ end
33
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: g5_authenticatable_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maeve Revels
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-20 00:00:00.000000000 Z
11
+ date: 2015-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.2'
33
+ version: '0.4'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.2'
40
+ version: '0.4'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: activesupport
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -62,18 +62,20 @@ files:
62
62
  - ".gitignore"
63
63
  - ".rspec"
64
64
  - ".ruby-version"
65
+ - ".travis.yml"
65
66
  - CHANGELOG.md
66
67
  - Gemfile
67
68
  - LICENSE.txt
68
69
  - README.md
69
70
  - Rakefile
70
- - circle.yml
71
71
  - g5_authenticatable_api.gemspec
72
72
  - lib/g5_authenticatable_api.rb
73
73
  - lib/g5_authenticatable_api/helpers/grape.rb
74
74
  - lib/g5_authenticatable_api/helpers/rails.rb
75
75
  - lib/g5_authenticatable_api/railtie.rb
76
- - lib/g5_authenticatable_api/token_validator.rb
76
+ - lib/g5_authenticatable_api/services/token_info.rb
77
+ - lib/g5_authenticatable_api/services/token_validator.rb
78
+ - lib/g5_authenticatable_api/services/user_fetcher.rb
77
79
  - lib/g5_authenticatable_api/version.rb
78
80
  - spec/dummy/README.rdoc
79
81
  - spec/dummy/Rakefile
@@ -132,14 +134,20 @@ files:
132
134
  - spec/dummy/vendor/assets/javascripts/.keep
133
135
  - spec/dummy/vendor/assets/stylesheets/.keep
134
136
  - spec/factories/user.rb
135
- - spec/lib/g5_authenticatable_api/token_validator_spec.rb
137
+ - spec/lib/g5_authenticatable_api/helpers/grape_spec.rb
138
+ - spec/lib/g5_authenticatable_api/helpers/rails_spec.rb
139
+ - spec/lib/g5_authenticatable_api/services/token_info_spec.rb
140
+ - spec/lib/g5_authenticatable_api/services/token_validator_spec.rb
141
+ - spec/lib/g5_authenticatable_api/services/user_fetcher_spec.rb
136
142
  - spec/lib/g5_authenticatable_api/version_spec.rb
137
143
  - spec/requests/grape_api_spec.rb
138
144
  - spec/requests/rails_api_spec.rb
139
145
  - spec/spec_helper.rb
140
146
  - spec/support/factory_girl.rb
147
+ - spec/support/shared_contexts/current_auth_user.rb
141
148
  - spec/support/shared_contexts/invalid_access_token.rb
142
149
  - spec/support/shared_contexts/valid_access_token.rb
150
+ - spec/support/shared_examples/auth_user.rb
143
151
  - spec/support/shared_examples/token_authenticatable_api.rb
144
152
  - spec/support/shared_examples/token_validation.rb
145
153
  - spec/support/shared_examples/warden_authenticatable_api.rb
@@ -164,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
172
  version: '0'
165
173
  requirements: []
166
174
  rubyforge_project:
167
- rubygems_version: 2.2.2
175
+ rubygems_version: 2.2.3
168
176
  signing_key:
169
177
  specification_version: 4
170
178
  summary: Helpers for securing APIs with G5
@@ -226,14 +234,20 @@ test_files:
226
234
  - spec/dummy/vendor/assets/javascripts/.keep
227
235
  - spec/dummy/vendor/assets/stylesheets/.keep
228
236
  - spec/factories/user.rb
229
- - spec/lib/g5_authenticatable_api/token_validator_spec.rb
237
+ - spec/lib/g5_authenticatable_api/helpers/grape_spec.rb
238
+ - spec/lib/g5_authenticatable_api/helpers/rails_spec.rb
239
+ - spec/lib/g5_authenticatable_api/services/token_info_spec.rb
240
+ - spec/lib/g5_authenticatable_api/services/token_validator_spec.rb
241
+ - spec/lib/g5_authenticatable_api/services/user_fetcher_spec.rb
230
242
  - spec/lib/g5_authenticatable_api/version_spec.rb
231
243
  - spec/requests/grape_api_spec.rb
232
244
  - spec/requests/rails_api_spec.rb
233
245
  - spec/spec_helper.rb
234
246
  - spec/support/factory_girl.rb
247
+ - spec/support/shared_contexts/current_auth_user.rb
235
248
  - spec/support/shared_contexts/invalid_access_token.rb
236
249
  - spec/support/shared_contexts/valid_access_token.rb
250
+ - spec/support/shared_examples/auth_user.rb
237
251
  - spec/support/shared_examples/token_authenticatable_api.rb
238
252
  - spec/support/shared_examples/token_validation.rb
239
253
  - spec/support/shared_examples/warden_authenticatable_api.rb
data/circle.yml DELETED
@@ -1,4 +0,0 @@
1
- database:
2
- override:
3
- - cp spec/dummy/config/database.yml.ci spec/dummy/config/database.yml
4
- - (cd spec/dummy; RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load)
@@ -1,78 +0,0 @@
1
- module G5AuthenticatableApi
2
- class TokenValidator
3
- attr_reader :error
4
-
5
- def initialize(params={},headers={},warden=nil)
6
- @params = params || {}
7
- @headers = headers || {}
8
- @warden = warden
9
- end
10
-
11
- def validate!
12
- begin
13
- auth_client.token_info unless skip_validation?
14
- rescue StandardError => @error
15
- raise error
16
- end
17
- end
18
-
19
- def valid?
20
- begin
21
- validate!
22
- true
23
- rescue StandardError => e
24
- false
25
- end
26
- end
27
-
28
- def access_token
29
- @access_token ||= (extract_token_from_header ||
30
- @params['access_token'] ||
31
- @warden.try(:user).try(:g5_access_token))
32
- end
33
-
34
- def auth_response_header
35
- if error
36
- auth_header = "Bearer"
37
-
38
- if access_token
39
- auth_header << " error=\"#{error_code}\""
40
- auth_header << ",error_description=\"#{error_description}\"" if error_description
41
- end
42
-
43
- auth_header
44
- end
45
- end
46
-
47
- def auth_client
48
- @auth_client ||= G5AuthenticationClient::Client.new(allow_password_credentials: 'false',
49
- access_token: access_token)
50
- end
51
-
52
- private
53
- def error_code
54
- error_code = error.code if error.respond_to?(:code)
55
- error_code || 'invalid_request'
56
- end
57
-
58
- def error_description
59
- error_description = error.description if error.respond_to?(:description)
60
- error_description
61
- end
62
-
63
- def extract_token_from_header
64
- if authorization_header
65
- parts = authorization_header.match(/Bearer (?<access_token>\S+)/)
66
- parts['access_token']
67
- end
68
- end
69
-
70
- def skip_validation?
71
- @warden.try(:user) && !G5AuthenticatableApi.strict_token_validation
72
- end
73
-
74
- def authorization_header
75
- @headers['Authorization'] || @headers['AUTHORIZATION']
76
- end
77
- end
78
- end