rails_api_auth 0.0.6 → 0.0.7

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: 584f55bb7244f72b626eeb8f43820e254f59ba68
4
- data.tar.gz: c4ef92cac5e776a2c7ca94e08c9162b744fb97f0
3
+ metadata.gz: 643e8f86cbe69072c9b057c1dae39e24fd49982d
4
+ data.tar.gz: ad2a2f90465fbeed37877dde1f758d5bf4ff7f77
5
5
  SHA512:
6
- metadata.gz: 4f5cc6e933c27e602bb355697477228c18a64072b056211168c0152ae1150491a35d4a19337d296acc68625e0a97465dab141125d26b04982ce8cf4facda64cb
7
- data.tar.gz: d9a311da8ce1eaf373620cad07f6dd161fedf98df6f174b30aa1d95e09584c03a0e689e1d29e5c4a91e9b64376b2a9c1159bc590faa0d23c4829e3d37c1c31b4
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 = '<your 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 = '<your app 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
- render nothing: true, status: 502
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
- render nothing: true, status: 502
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  module RailsApiAuth
2
2
 
3
- VERSION = '0.0.6'.freeze
3
+ VERSION = '0.0.7'.freeze
4
4
 
5
5
  end
@@ -2,10 +2,18 @@ class AccessOnceController < ApplicationController
2
2
 
3
3
  include RailsApiAuth::Authentication
4
4
 
5
- before_filter :consume_single_use_oauth2_token!
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
- render text: 'zuper content', status: 200
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
- before_filter :authenticate!
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
- render text: 'zuper content', status: 200
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
- before_filter :authenticate_with_account!
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
- render text: 'zuper content', status: 200
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
@@ -2,6 +2,7 @@ require File.expand_path('../boot', __FILE__)
2
2
 
3
3
  require 'active_record/railtie'
4
4
  require 'action_controller/railtie'
5
+ require 'sprockets/railtie'
5
6
 
6
7
  Bundler.require(*Rails.groups)
7
8
 
@@ -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
- config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
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
- config.serve_static_files = true
17
- config.static_cache_control = 'public, max-age=3600'
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
@@ -7,8 +7,8 @@ describe Login do
7
7
 
8
8
  describe 'validations' do
9
9
  before do
10
- Login.any_instance.stub(:ensure_oauth2_token).and_return(true)
11
- Login.any_instance.stub(:assign_single_use_oauth2_token).and_return(true)
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
- subject { get '/access-once', {}, headers }
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
- get '/access-once', {}, headers
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
- subject { get '/authenticated', {}, headers }
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
- subject { get '/custom-authenticated', {}, headers }
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
- subject { post '/token', params, 'HTTPS' => ssl }
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
- subject { post '/revoke', params, 'HTTPS' => ssl }
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