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 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