g5_authenticatable_api 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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