rails_api_auth 0.0.6 → 0.0.7
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 +4 -4
- data/README.md +26 -3
- data/app/controllers/oauth2_controller.rb +17 -2
- data/app/services/edx_authenticator.rb +72 -0
- data/lib/rails_api_auth.rb +16 -0
- data/lib/rails_api_auth/version.rb +1 -1
- data/spec/dummy/app/controllers/access_once_controller.rb +10 -2
- data/spec/dummy/app/controllers/authenticated_controller.rb +10 -2
- data/spec/dummy/app/controllers/custom_authenticated_controller.rb +10 -2
- data/spec/dummy/config/application.rb +1 -0
- data/spec/dummy/config/environments/production.rb +5 -1
- data/spec/dummy/config/environments/test.rb +7 -2
- data/spec/dummy/config/initializers/rails_api_auth.rb +5 -0
- data/spec/models/login_spec.rb +2 -2
- data/spec/requests/access_once_spec.rb +14 -2
- data/spec/requests/authenticated_spec.rb +7 -1
- data/spec/requests/custom_authenticated_spec.rb +13 -4
- data/spec/requests/oauth2_spec.rb +32 -2
- data/spec/services/edx_authenticator_spec.rb +12 -0
- data/spec/support/shared_contexts/stubbed_edx_requests.rb +16 -0
- data/spec/support/shared_examples/authenticator_shared_requests.rb +5 -1
- data/spec/support/shared_examples/oauth2_edx_shared_requests.rb +93 -0
- data/spec/support/shared_examples/oauth2_shared_requests.rb +1 -3
- metadata +12 -14
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +0 -29
- data/spec/dummy/log/test.log +0 -20111
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 643e8f86cbe69072c9b057c1dae39e24fd49982d
|
4
|
+
data.tar.gz: ad2a2f90465fbeed37877dde1f758d5bf4ff7f77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0589164744d500bf57b6cd115e617bab91b1929e036b2fc0889330dd9f2548fa12341b701119ead95eec6c274f5edb23cc75aa1b00e79bb0035ebf6efa1195b9'
|
7
|
+
data.tar.gz: a0d1c552c7081eb31516b95b719938563f55f4688bec551878a66d78db096075d9df5c30d1576be4a82df68116392fca076a44d8f9864bdfd27d77ec6099c9b3
|
data/README.md
CHANGED
@@ -156,9 +156,15 @@ RailsApiAuth.tap do |raa|
|
|
156
156
|
raa.facebook_redirect_uri = '<your Facebook app redirect uri>'
|
157
157
|
|
158
158
|
# Google configurations
|
159
|
-
raa.google_client_id
|
159
|
+
raa.google_client_id = '<your Google client id>'
|
160
160
|
raa.google_client_secret = '<your Google client secret>'
|
161
|
-
raa.google_redirect_uri
|
161
|
+
raa.google_redirect_uri = '<your app redirect uri>'
|
162
|
+
|
163
|
+
# Edx configurations
|
164
|
+
raa.edx_client_id = '<your Edx client id>'
|
165
|
+
raa.edx_client_secret = '<your Edx client secret>'
|
166
|
+
raa.edx_domain = '<your Edx app domain>'
|
167
|
+
raa.edx_redirect_uri = 'your Edx app redirect uri'
|
162
168
|
|
163
169
|
# Force SSL for Oauth2Controller; defaults to `false` for the development environment, otherwise `true`
|
164
170
|
raa.force_ssl = false
|
@@ -166,9 +172,26 @@ end
|
|
166
172
|
|
167
173
|
```
|
168
174
|
|
175
|
+
### A note on Edx Oauth2 code flows
|
176
|
+
|
177
|
+
It is nesescary to include the Edx username in the request when making a call
|
178
|
+
rails_api_auth call /token. When rails_api_auth interfaces with Edx's
|
179
|
+
user api, the username is need to retrieve user data, not just a valid
|
180
|
+
oauth2 token.
|
181
|
+
|
182
|
+
E.g.
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
headers = {
|
186
|
+
username: "alice",
|
187
|
+
auth_code: "alices_authorization_code",
|
188
|
+
grant_type: "edx_auth_code"
|
189
|
+
}
|
190
|
+
```
|
191
|
+
|
169
192
|
## Contribution
|
170
193
|
|
171
|
-
See [CONTRIBUTING](https://github.com/simplabs/rails_api_auth/blob/master/CONTRIBUTING).
|
194
|
+
See [CONTRIBUTING](https://github.com/simplabs/rails_api_auth/blob/master/CONTRIBUTING.md).
|
172
195
|
|
173
196
|
## License
|
174
197
|
|
@@ -7,6 +7,7 @@ class Oauth2Controller < ApplicationController
|
|
7
7
|
|
8
8
|
force_ssl if: -> { RailsApiAuth.force_ssl }
|
9
9
|
|
10
|
+
# rubocop:disable MethodLength
|
10
11
|
def create
|
11
12
|
case params[:grant_type]
|
12
13
|
when 'password'
|
@@ -15,11 +16,14 @@ class Oauth2Controller < ApplicationController
|
|
15
16
|
authenticate_with_facebook(params[:auth_code])
|
16
17
|
when 'google_auth_code'
|
17
18
|
authenticate_with_google(params[:auth_code])
|
19
|
+
when 'edx_auth_code'
|
20
|
+
authenticate_with_edx(params[:username], params[:auth_code])
|
18
21
|
else
|
19
22
|
oauth2_error('unsupported_grant_type')
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
26
|
+
# rubocop:enable MethodLength
|
23
27
|
def destroy
|
24
28
|
oauth2_error('unsupported_token_type') && return unless params[:token_type_hint] == 'access_token'
|
25
29
|
|
@@ -48,7 +52,7 @@ class Oauth2Controller < ApplicationController
|
|
48
52
|
|
49
53
|
render json: { access_token: login.oauth2_token }
|
50
54
|
rescue FacebookAuthenticator::ApiError
|
51
|
-
|
55
|
+
head 502
|
52
56
|
end
|
53
57
|
|
54
58
|
def authenticate_with_google(auth_code)
|
@@ -58,7 +62,18 @@ class Oauth2Controller < ApplicationController
|
|
58
62
|
|
59
63
|
render json: { access_token: login.oauth2_token }
|
60
64
|
rescue GoogleAuthenticator::ApiError
|
61
|
-
|
65
|
+
head 502
|
66
|
+
end
|
67
|
+
|
68
|
+
def authenticate_with_edx(username, auth_code)
|
69
|
+
oauth2_error('no_authorization_code') && return unless auth_code.present?
|
70
|
+
oauth2_error('no_username') && return unless username.present?
|
71
|
+
|
72
|
+
login = EdxAuthenticator.new(username, auth_code).authenticate!
|
73
|
+
|
74
|
+
render json: { access_token: login.oauth2_token }
|
75
|
+
rescue EdxAuthenticator::ApiError
|
76
|
+
head 502
|
62
77
|
end
|
63
78
|
|
64
79
|
def oauth2_error(error)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
# Handles Edx authentication
|
4
|
+
#
|
5
|
+
# @!visibility private
|
6
|
+
class EdxAuthenticator < BaseAuthenticator
|
7
|
+
|
8
|
+
PROVIDER = 'edx'.freeze
|
9
|
+
DOMAIN = 'http://' + RailsApiAuth.edx_domain
|
10
|
+
TOKEN_URL = (DOMAIN + '/oauth2/access_token').freeze
|
11
|
+
PROFILE_URL = (DOMAIN + '/api/user/v1/accounts/%{username}').freeze
|
12
|
+
|
13
|
+
def initialize(username, auth_code)
|
14
|
+
@auth_code = auth_code
|
15
|
+
@username = username
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def connect_login_to_account(login, user)
|
21
|
+
login.update_attributes!(uid: user[:username], provider: PROVIDER)
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_login_from_account(user)
|
25
|
+
login_attributes = {
|
26
|
+
identification: user[:email],
|
27
|
+
uid: user[:username],
|
28
|
+
provider: PROVIDER
|
29
|
+
}
|
30
|
+
Login.create!(login_attributes)
|
31
|
+
end
|
32
|
+
|
33
|
+
def access_token
|
34
|
+
response = HTTParty.post(TOKEN_URL, token_options)
|
35
|
+
response.parsed_response['access_token']
|
36
|
+
end
|
37
|
+
|
38
|
+
# Override base authenticator
|
39
|
+
def get_request(url, headers)
|
40
|
+
response = HTTParty.get(url, headers: headers)
|
41
|
+
unless response.code == 200
|
42
|
+
Rails.logger.warn "#{self.class::PROVIDER} API request failed with status #{response.code}."
|
43
|
+
Rails.logger.debug "#{self.class::PROVIDER} API error response was:\n#{response.body}"
|
44
|
+
raise ApiError.new
|
45
|
+
end
|
46
|
+
response
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_user(access_token)
|
50
|
+
headers = { 'Authorization' => "Bearer #{access_token}" }
|
51
|
+
@edx_user ||= begin
|
52
|
+
get_request(user_url, headers).parsed_response.symbolize_keys
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def user_url
|
57
|
+
PROFILE_URL % { username: @username }
|
58
|
+
end
|
59
|
+
|
60
|
+
def token_options
|
61
|
+
@token_options ||= {
|
62
|
+
body: {
|
63
|
+
code: @auth_code,
|
64
|
+
client_id: RailsApiAuth.edx_client_id,
|
65
|
+
client_secret: RailsApiAuth.edx_client_secret,
|
66
|
+
redirect_uri: RailsApiAuth.edx_redirect_uri,
|
67
|
+
grant_type: 'authorization_code'
|
68
|
+
}
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
data/lib/rails_api_auth.rb
CHANGED
@@ -36,6 +36,22 @@ module RailsApiAuth
|
|
36
36
|
# The Google App's redirect URI.
|
37
37
|
mattr_accessor :google_redirect_uri
|
38
38
|
|
39
|
+
# @!attribute [rw] edx_client_id
|
40
|
+
# The Edx client ID.
|
41
|
+
mattr_accessor :edx_client_id
|
42
|
+
|
43
|
+
# @!attribute [rw] edx_client_secret
|
44
|
+
# The Edx client secret.
|
45
|
+
mattr_accessor :edx_client_secret
|
46
|
+
|
47
|
+
# @!attribute [rw] edx_redirect_uri
|
48
|
+
# The Edx App's redirect URI.
|
49
|
+
mattr_accessor :edx_redirect_uri
|
50
|
+
|
51
|
+
# @!attribute [rw] edx_domain
|
52
|
+
# The domain used for the Edx oauth2 provider
|
53
|
+
mattr_accessor :edx_domain
|
54
|
+
|
39
55
|
# @!attribute [rw] primary_key_type
|
40
56
|
# Configures database column type used for primary keys,
|
41
57
|
# currently only accepts :uuid
|
@@ -2,10 +2,18 @@ class AccessOnceController < ApplicationController
|
|
2
2
|
|
3
3
|
include RailsApiAuth::Authentication
|
4
4
|
|
5
|
-
|
5
|
+
if Rails::VERSION::MAJOR < 4
|
6
|
+
before_filter :consume_single_use_oauth2_token!
|
7
|
+
else
|
8
|
+
before_action :consume_single_use_oauth2_token!
|
9
|
+
end
|
6
10
|
|
7
11
|
def index
|
8
|
-
|
12
|
+
if Rails::VERSION::MAJOR < 4
|
13
|
+
render text: 'zuper content', status: 200
|
14
|
+
else
|
15
|
+
render plain: 'zuper content', status: 200
|
16
|
+
end
|
9
17
|
end
|
10
18
|
|
11
19
|
end
|
@@ -2,10 +2,18 @@ class AuthenticatedController < ApplicationController
|
|
2
2
|
|
3
3
|
include RailsApiAuth::Authentication
|
4
4
|
|
5
|
-
|
5
|
+
if Rails::VERSION::MAJOR < 4
|
6
|
+
before_filter :authenticate!
|
7
|
+
else
|
8
|
+
before_action :authenticate!
|
9
|
+
end
|
6
10
|
|
7
11
|
def index
|
8
|
-
|
12
|
+
if Rails::VERSION::MAJOR < 4
|
13
|
+
render text: 'zuper content', status: 200
|
14
|
+
else
|
15
|
+
render plain: 'zuper content', status: 200
|
16
|
+
end
|
9
17
|
end
|
10
18
|
|
11
19
|
end
|
@@ -2,10 +2,18 @@ class CustomAuthenticatedController < ApplicationController
|
|
2
2
|
|
3
3
|
include RailsApiAuth::Authentication
|
4
4
|
|
5
|
-
|
5
|
+
if Rails::VERSION::MAJOR < 4
|
6
|
+
before_filter :authenticate_with_account!
|
7
|
+
else
|
8
|
+
before_action :authenticate_with_account!
|
9
|
+
end
|
6
10
|
|
7
11
|
def index
|
8
|
-
|
12
|
+
if Rails::VERSION::MAJOR < 4
|
13
|
+
render text: 'zuper content', status: 200
|
14
|
+
else
|
15
|
+
render plain: 'zuper content', status: 200
|
16
|
+
end
|
9
17
|
end
|
10
18
|
|
11
19
|
private
|
@@ -22,7 +22,11 @@ Dummy::Application.configure do
|
|
22
22
|
|
23
23
|
# Disable serving static files from the `/public` folder by default since
|
24
24
|
# Apache or NGINX already handles this.
|
25
|
-
|
25
|
+
if Rails::VERSION::MAJOR < 5
|
26
|
+
config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
|
27
|
+
else
|
28
|
+
config.public_file_server.enabled = false
|
29
|
+
end
|
26
30
|
|
27
31
|
# Compress JavaScripts and CSS.
|
28
32
|
config.assets.js_compressor = :uglifier
|
@@ -13,8 +13,13 @@ Dummy::Application.configure do
|
|
13
13
|
config.eager_load = false
|
14
14
|
|
15
15
|
# Configure static file server for tests with Cache-Control for performance.
|
16
|
-
|
17
|
-
|
16
|
+
if Rails::VERSION::MAJOR < 5
|
17
|
+
config.serve_static_files = true
|
18
|
+
config.static_cache_control = 'public, max-age=3600'
|
19
|
+
else
|
20
|
+
config.public_file_server.enabled = true
|
21
|
+
config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }
|
22
|
+
end
|
18
23
|
|
19
24
|
# Show full error reports and disable caching.
|
20
25
|
config.consider_all_requests_local = true
|
@@ -8,4 +8,9 @@ RailsApiAuth.tap do |raa|
|
|
8
8
|
raa.google_client_id = 'google_client_id'
|
9
9
|
raa.google_client_secret = 'google_client_secret'
|
10
10
|
raa.google_redirect_uri = 'google_redirect_uri'
|
11
|
+
|
12
|
+
raa.edx_client_id = 'edx_client_id'
|
13
|
+
raa.edx_client_secret = 'edx_client_secret'
|
14
|
+
raa.edx_domain = 'edxdomain.org'
|
15
|
+
raa.edx_redirect_uri = 'edx_redirect_uri'
|
11
16
|
end
|
data/spec/models/login_spec.rb
CHANGED
@@ -7,8 +7,8 @@ describe Login do
|
|
7
7
|
|
8
8
|
describe 'validations' do
|
9
9
|
before do
|
10
|
-
Login.
|
11
|
-
Login.
|
10
|
+
allow_any_instance_of(Login).to receive(:ensure_oauth2_token).and_return(true)
|
11
|
+
allow_any_instance_of(Login).to receive(:assign_single_use_oauth2_token).and_return(true)
|
12
12
|
end
|
13
13
|
|
14
14
|
it { is_expected.to validate_presence_of(:identification) }
|
@@ -1,5 +1,11 @@
|
|
1
1
|
describe 'an access-once route' do
|
2
|
-
|
2
|
+
if Rails::VERSION::MAJOR < 5
|
3
|
+
# rubocop:disable Rails/HttpPositionalArguments
|
4
|
+
subject { get '/access-once', {}, headers }
|
5
|
+
# rubocop:enable Rails/HttpPositionalArguments
|
6
|
+
else
|
7
|
+
subject { get '/access-once', params: {}, headers: headers }
|
8
|
+
end
|
3
9
|
|
4
10
|
let(:login) { create(:login) }
|
5
11
|
let(:headers) do
|
@@ -52,7 +58,13 @@ describe 'an access-once route' do
|
|
52
58
|
|
53
59
|
context 'when accessed a second time with the same token' do
|
54
60
|
before do
|
55
|
-
|
61
|
+
if Rails::VERSION::MAJOR < 5
|
62
|
+
# rubocop:disable Rails/HttpPositionalArguments
|
63
|
+
get '/access-once', {}, headers
|
64
|
+
# rubocop:enable Rails/HttpPositionalArguments
|
65
|
+
else
|
66
|
+
get '/access-once', params: {}, headers: headers
|
67
|
+
end
|
56
68
|
end
|
57
69
|
|
58
70
|
it_behaves_like 'when access is not allowed'
|
@@ -1,5 +1,11 @@
|
|
1
1
|
describe 'an authenticated route' do
|
2
|
-
|
2
|
+
if Rails::VERSION::MAJOR < 5
|
3
|
+
# rubocop:disable Rails/HttpPositionalArguments
|
4
|
+
subject { get '/authenticated', {}, headers }
|
5
|
+
# rubocop:enable Rails/HttpPositionalArguments
|
6
|
+
else
|
7
|
+
subject { get '/authenticated', params: {}, headers: headers }
|
8
|
+
end
|
3
9
|
|
4
10
|
let(:headers) { {} }
|
5
11
|
|
@@ -1,11 +1,20 @@
|
|
1
1
|
describe 'a custom authenticated route' do
|
2
|
-
|
2
|
+
if Rails::VERSION::MAJOR < 5
|
3
|
+
# rubocop:disable Rails/HttpPositionalArguments
|
4
|
+
subject { get '/custom-authenticated', {}, headers }
|
5
|
+
# rubocop:enable Rails/HttpPositionalArguments
|
6
|
+
let(:headers) do
|
7
|
+
{ 'Authorization' => "Bearer #{login.oauth2_token}" }
|
8
|
+
end
|
9
|
+
else
|
10
|
+
subject { get '/custom-authenticated', params: {}, headers: headers }
|
11
|
+
let(:headers) do
|
12
|
+
{ HTTP_AUTHORIZATION: "Bearer #{login.oauth2_token}" }
|
13
|
+
end
|
14
|
+
end
|
3
15
|
|
4
16
|
let(:account) { create(:account) }
|
5
17
|
let(:login) { create(:login, account: account) }
|
6
|
-
let(:headers) do
|
7
|
-
{ 'Authorization' => "Bearer #{login.oauth2_token}" }
|
8
|
-
end
|
9
18
|
|
10
19
|
context 'when the block returns true' do
|
11
20
|
let(:account) { create(:account, first_name: 'user x') }
|
@@ -3,7 +3,13 @@ describe 'Oauth2 API' do
|
|
3
3
|
|
4
4
|
describe 'POST /token' do
|
5
5
|
let(:params) { { grant_type: 'password', username: login.identification, password: login.password } }
|
6
|
-
|
6
|
+
if Rails::VERSION::MAJOR < 5
|
7
|
+
# rubocop:disable Rails/HttpPositionalArguments
|
8
|
+
subject { post '/token', params, 'HTTPS' => ssl }
|
9
|
+
# rubocop:enable Rails/HttpPositionalArguments
|
10
|
+
else
|
11
|
+
subject { post '/token', params: params, headers: { 'HTTPS' => ssl } }
|
12
|
+
end
|
7
13
|
|
8
14
|
shared_examples 'when the request gets through' do
|
9
15
|
context 'for grant_type "password"' do
|
@@ -68,6 +74,21 @@ describe 'Oauth2 API' do
|
|
68
74
|
include_examples 'oauth2 shared contexts'
|
69
75
|
end
|
70
76
|
|
77
|
+
context 'for grant_type "edx_auth_code"' do
|
78
|
+
let(:authenticated_user_data) do
|
79
|
+
{
|
80
|
+
username: 'user',
|
81
|
+
email: email
|
82
|
+
}
|
83
|
+
end
|
84
|
+
let(:uid_mapped_field) { 'username' }
|
85
|
+
let(:grant_type) { 'edx_auth_code' }
|
86
|
+
let(:profile_url) { EdxAuthenticator::PROFILE_URL }
|
87
|
+
|
88
|
+
include_context 'stubbed edx requests'
|
89
|
+
include_examples 'oauth2 edx shared contexts'
|
90
|
+
end
|
91
|
+
|
71
92
|
context 'for an unknown grant type' do
|
72
93
|
let(:params) { { grant_type: 'UNKNOWN' } }
|
73
94
|
|
@@ -126,7 +147,16 @@ describe 'Oauth2 API' do
|
|
126
147
|
|
127
148
|
describe 'POST #destroy' do
|
128
149
|
let(:params) { { token_type_hint: 'access_token', token: login.oauth2_token } }
|
129
|
-
|
150
|
+
|
151
|
+
if Rails::VERSION::MAJOR < 5
|
152
|
+
# rubocop:disable Rails/HttpPositionalArguments
|
153
|
+
subject { get '/access-once', {}, headers }
|
154
|
+
subject { post '/revoke', params, 'HTTPS' => ssl }
|
155
|
+
# rubocop:enable Rails/HttpPositionalArguments
|
156
|
+
else
|
157
|
+
subject { get '/access-once', params: {}, headers: headers }
|
158
|
+
subject { post '/revoke', params: params, headers: { 'HTTPS' => ssl } }
|
159
|
+
end
|
130
160
|
|
131
161
|
shared_examples 'when the request gets through' do
|
132
162
|
it 'responds with status 200' do
|