rails-identity 0.2.3 → 0.3.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: 13d1f01c0a93ffb9ea2548258bf1b63a261085bf
4
- data.tar.gz: 4edfcaca8bc9d151a62799c54c1451fd51592196
3
+ metadata.gz: 069d960770fb7c3e3d60a2a7a7de61217a3d864c
4
+ data.tar.gz: f5367277a5aa44adc273009b11bedbf140258eac
5
5
  SHA512:
6
- metadata.gz: 2acceb4c9f742fd0ea740639f4b316e72d061e09dc56f46a6fe88bcadbfaedea0a9a9ddfbf12fcee5ed9f75d06b146c9c8d2a0ebdab429ec1ad07affd42f1ce5
7
- data.tar.gz: e981b7d80ad98a39af5a0ed1d69b2f0cfd488f8914dbaa5e398f70a9438b5bef5a88828fc40cfcb7bf16eb57d3449d7e9ae89029fd2a1c1e76b32e95b4544157
6
+ metadata.gz: 2c31e81c81d366e0f1b8b59e0e023715779f288899946d7c3ed283a317b66f27f4453270977f8e77e95e54d3f53bbdc08f6f354f021f6e9985ffce2e2353d7cb
7
+ data.tar.gz: ffca70e53b8d9e4e53ab383c3cfc9752774485b2f4f208a83916bd907e8037c23f8110fcc643685bda025d27be7605ce3a222d67ff51fc50aef15c8a47913c4c
@@ -9,7 +9,7 @@ module RailsIdentity
9
9
  #
10
10
  class SessionsController < ApplicationController
11
11
 
12
- prepend_before_action :require_token, except: [:create, :options]
12
+ prepend_before_action :require_auth, except: [:create, :options]
13
13
  before_action :get_session, only: [:show, :destroy]
14
14
  before_action :get_user, only: [:index]
15
15
 
@@ -38,6 +38,8 @@ module RailsIdentity
38
38
  # token based authentication. If the latter doesn't make sense, just use
39
39
  # the username and password approach.
40
40
  #
41
+ # A Repia::Errors::Unauthorized is thrown if user is not verified.
42
+ #
41
43
  def create
42
44
  @user = User.find_by_username(session_params[:username])
43
45
  if (@user && @user.authenticate(session_params[:password])) || get_user()
@@ -70,7 +72,7 @@ module RailsIdentity
70
72
  render body: "", status: 204
71
73
  else
72
74
  # :nocov:
73
- render_error 500, "Something went wrong. Oops!"
75
+ render_error 400, @session.errors.full_messages
74
76
  # :nocov:
75
77
  end
76
78
  end
@@ -80,15 +82,18 @@ module RailsIdentity
80
82
  ##
81
83
  # Get the specified or current session.
82
84
  #
83
- # An Repia::Errors::NotFound is raised if the session does not
85
+ # A Repia::Errors::NotFound is raised if the session does not
84
86
  # exist (or deleted due to expiration).
85
87
  #
86
- # An Repia::Errors::Unauthorized is raised if the authenticated user
88
+ # A Repia::Errors::Unauthorized is raised if the authenticated user
87
89
  # does not have authorization for the specified session.
88
90
  #
89
91
  def get_session
90
92
  session_id = params[:id]
91
93
  if session_id == "current"
94
+ if @auth_session.nil?
95
+ raise Repia::Errors::NotFound
96
+ end
92
97
  session_id = @auth_session.id
93
98
  end
94
99
  @session = find_object(Session, session_id)
@@ -9,9 +9,9 @@ module RailsIdentity
9
9
 
10
10
  # All except user creation requires a session token. Note that reset
11
11
  # token is also a legit session token, so :require_token will suffice.
12
- prepend_before_action :require_token, only: [:show, :destroy]
13
- prepend_before_action :accept_token, only: [:update, :create]
14
- prepend_before_action :require_admin_token, only: [:index]
12
+ prepend_before_action :require_auth, only: [:show, :destroy]
13
+ prepend_before_action :accept_auth, only: [:update, :create]
14
+ prepend_before_action :require_admin_auth, only: [:index]
15
15
 
16
16
  # Some actions must have a user specified.
17
17
  before_action :get_user, only: [:show, :destroy]
@@ -34,13 +34,15 @@ module RailsIdentity
34
34
  if @user.save
35
35
 
36
36
  # Save succeeded. Render the response based on the created user.
37
- render json: @user, except: [:verification_token, :reset_token, :password_digest], status: 201
37
+ render json: @user,
38
+ except: [:verification_token, :reset_token, :password_digest],
39
+ status: 201
38
40
 
39
41
  # Then, issue the verification token and send the email for
40
42
  # verification.
41
43
  @user.issue_token(:verification_token)
42
44
  @user.save
43
- UserMailer.email_verification(@user).deliver_later
45
+ user_mailer.email_verification(@user).deliver_later
44
46
  else
45
47
  render_errors 400, @user.errors.full_messages
46
48
  end
@@ -54,28 +56,29 @@ module RailsIdentity
54
56
  end
55
57
 
56
58
  ##
57
- # Patches the user. Some overloading operations here. There are five
58
- # notable ways to update a user.
59
+ # Patches the user object. There are four notable operations:
59
60
  #
60
- # - Issue a reset token
61
- # If params has :issue_reset_token set to true, the action will
62
- # issue a reset token for the user and returns 204. Yes, 204 No
63
- # Content.
64
- # - Reset the password
65
- # Two ways to reset password:
66
- # - Provide the old password along with the new password and
67
- # confirmation.
68
- # - Provide the reset token as the auth token.
69
- # - Issue a verification token
70
- # - Change other data
61
+ # - issue reset token
62
+ # - issue verification token
63
+ # - change password
64
+ # - others
65
+ #
66
+ # Issuing either reset token or verification token requires NO
67
+ # authentication. However, for that reason, the request does not get any
68
+ # meaningful response. Instead, an email is sent out for either request.
69
+ #
70
+ # For changing password, there are two ways. One is to use old password
71
+ # and the other is to use reset token.
72
+ #
73
+ # Otherwise, it's a normal update operation.
71
74
  #
72
75
  def update
73
76
  if params[:issue_reset_token] || params[:issue_verification_token]
74
77
  # For issuing a reset token, one does not need an auth token. so do
75
- # not authorize the request.
78
+ # not authorize the request. For consistency, we require the id to
79
+ # be "current".
76
80
  raise Repia::Errors::Unauthorized unless params[:id] == "current"
77
81
  get_user_for_token()
78
- raise Repia::Errors::Unauthorized unless params[:username] == @user.username
79
82
  if params[:issue_reset_token]
80
83
  update_token(:reset_token)
81
84
  else
@@ -83,13 +86,7 @@ module RailsIdentity
83
86
  end
84
87
  else
85
88
  get_user()
86
- if params[:password]
87
- if params[:old_password]
88
- raise Repia::Errors::Unauthorized unless @user.authenticate(params[:old_password])
89
- else
90
- raise Repia::Errors::Unauthorized unless @token == @user.reset_token
91
- end
92
- end
89
+ allow_password_change? if params[:password]
93
90
  update_user(user_params)
94
91
  end
95
92
  end
@@ -102,13 +99,40 @@ module RailsIdentity
102
99
  render body: '', status: 204
103
100
  else
104
101
  # :nocov:
105
- render_error 500, "Something went wrong!"
102
+ render_error 400, @user.errors.full_messages
106
103
  # :nocov:
107
104
  end
108
105
  end
109
106
 
110
107
  protected
111
108
 
109
+ ##
110
+ # Override this method to app specific mailer.
111
+ #
112
+ def user_mailer
113
+ return UserMailer
114
+ end
115
+
116
+ ##
117
+ # Check if password change should be allowed. Two ways to do this: one
118
+ # is to use old password or to use a valid reset token.
119
+ #
120
+ # A Repia::Errors::Unauthorized is thrown for invalid old password or
121
+ # invalid reset token
122
+ #
123
+ def allow_password_change?
124
+ if params[:old_password]
125
+ unless @user.authenticate(params[:old_password])
126
+ raise Repia::Errors::Unauthorized
127
+ end
128
+ else
129
+ unless @token == @user.reset_token
130
+ raise Repia::Errors::Unauthorized
131
+ end
132
+ end
133
+ return true
134
+ end
135
+
112
136
  ##
113
137
  # This method normally updates the user using permitted params.
114
138
  #
@@ -128,9 +152,9 @@ module RailsIdentity
128
152
  @user.issue_token(kind)
129
153
  @user.save
130
154
  if kind == :reset_token
131
- UserMailer.password_reset(@user).deliver_later
155
+ user_mailer.password_reset(@user).deliver_later
132
156
  else
133
- UserMailer.email_verification(@user).deliver_later
157
+ user_mailer.email_verification(@user).deliver_later
134
158
  end
135
159
  render body: '', status: 204
136
160
  end
@@ -31,11 +31,69 @@ module RailsIdentity
31
31
  end
32
32
  end
33
33
 
34
+ ##
35
+ # Requires authentication. Either token or api key must be present.
36
+ #
37
+ def require_auth
38
+ logger.debug("Requiring any authentication")
39
+ get_auth
40
+ end
41
+
42
+ ##
43
+ # Requires admin authentication. Either token or api key must be present
44
+ # and it should be an admin's.
45
+ #
46
+ def require_admin_auth
47
+ logger.debug("Requiring any admin authentication")
48
+ get_auth(required_role: Roles::ADMIN)
49
+ end
50
+
51
+ ##
52
+ # Accepts authentication if present. Either token or api key is
53
+ # accepted.
54
+ #
55
+ def accept_auth
56
+ logger.debug("Accepting any authentication")
57
+ begin
58
+ get_auth
59
+ rescue StandardError => e
60
+ logger.error("Suppressing error: #{e.message}")
61
+ end
62
+ end
63
+
64
+ ##
65
+ # Requires API key for authentication.
66
+ #
67
+ def require_api_key
68
+ logger.debug("Requiring api key")
69
+ get_api_key
70
+ end
71
+
72
+ ##
73
+ # Requires admin API key for authentication.
74
+ #
75
+ def require_admin_api_key
76
+ logger.debug("Requiring admin api key")
77
+ get_api_key(required_role: Roles::ADMIN)
78
+ end
79
+
80
+ ##
81
+ # Accepts API key for authentication if one is present.
82
+ #
83
+ def accept_api_key
84
+ logger.debug("Accepting api key")
85
+ begin
86
+ get_api_key
87
+ rescue StandardError => e
88
+ logger.error("Suppressing error: #{e.message}")
89
+ end
90
+ end
91
+
34
92
  ##
35
93
  # Requires a token.
36
94
  #
37
95
  def require_token
38
- logger.debug("Requires a token")
96
+ logger.debug("Requiring token")
39
97
  get_token
40
98
  end
41
99
 
@@ -44,7 +102,7 @@ module RailsIdentity
44
102
  # suppressed.
45
103
  #
46
104
  def accept_token()
47
- logger.debug("Accepts a token")
105
+ logger.debug("Accepting token")
48
106
  begin
49
107
  get_token()
50
108
  rescue StandardError => e
@@ -57,7 +115,7 @@ module RailsIdentity
57
115
  # issued for an admin user (role == 1000).
58
116
  #
59
117
  def require_admin_token
60
- logger.debug("Requires an admin token")
118
+ logger.debug("Requiring admin token")
61
119
  get_token(required_role: Roles::ADMIN)
62
120
  end
63
121
 
@@ -151,6 +209,9 @@ module RailsIdentity
151
209
  # Attempt to get a token for the session. Token must be specified in
152
210
  # query string or part of the JSON object.
153
211
  #
212
+ # Raises a Repia::Errors::Unauthorized if cached session has less role
213
+ # than what's required.
214
+ #
154
215
  def get_token(required_role: Roles::PUBLIC)
155
216
  token = params[:token]
156
217
  payload = get_token_payload(token)
@@ -160,11 +221,48 @@ module RailsIdentity
160
221
  if @auth_session.nil?
161
222
  @auth_session = verify_token_payload(token, payload,
162
223
  required_role: required_role)
224
+ @auth_session.role # NOTE: no-op
163
225
  Rails.cache.write("#{CACHE_PREFIX}-session-#{token}", @auth_session)
226
+ elsif @auth_session.role < required_role
227
+ raise Repia::Errors::Unauthorized
164
228
  end
165
229
  @auth_user = @auth_session.user
166
230
  @token = @auth_session.token
167
231
  end
168
232
 
233
+ ##
234
+ # Get API key from the request.
235
+ #
236
+ # Raises a Repia::Errors::Unauthorized if API key is not valid (or not
237
+ # provided).
238
+ #
239
+ def get_api_key(required_role: Roles::PUBLIC)
240
+ api_key = params[:api_key]
241
+ if api_key.nil?
242
+ # This case is not likely, but as a safeguard in case migration
243
+ # has not gone well.
244
+ # :nocov:
245
+ raise Repia::Errors::Unauthorized, "Invalid api key"
246
+ # :nocov:
247
+ end
248
+ auth_user = User.find_by_api_key(api_key)
249
+ if auth_user.nil? || auth_user.role < required_role
250
+ raise Repia::Errors::Unauthorized, "Invalid api key"
251
+ end
252
+ @auth_user = auth_user
253
+ @auth_session = nil
254
+ @token = nil
255
+ end
256
+
257
+ ##
258
+ # Get auth data from the request. The token takes the precedence.
259
+ #
260
+ def get_auth(required_role: Roles::USER)
261
+ if params[:token]
262
+ get_token(required_role: required_role)
263
+ else
264
+ get_api_key(required_role: required_role)
265
+ end
266
+ end
169
267
  end
170
268
  end
@@ -40,5 +40,12 @@ module RailsIdentity
40
40
  return false
41
41
  end
42
42
 
43
+ def role
44
+ if @role.nil?
45
+ @role = user.role
46
+ end
47
+ return @role
48
+ end
49
+
43
50
  end
44
51
  end
@@ -18,6 +18,7 @@ module RailsIdentity
18
18
  # re-issue the verification token.
19
19
  #
20
20
  def initialize(attributes = {})
21
+ attributes[:api_key] = SecureRandom.hex(32)
21
22
  super
22
23
  end
23
24
 
@@ -0,0 +1,10 @@
1
+ class AddApiKeyToUsers < ActiveRecord::Migration
2
+ def change
3
+ add_column :rails_identity_users, :api_key, :string
4
+ add_index :rails_identity_users, :api_key
5
+
6
+ RailsIdentity::User.find_each do |user|
7
+ user.update(api_key: SecureRandom.hex(32))
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsIdentity
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,124 @@
1
+ require 'test_helper'
2
+
3
+ module RailsIdentity
4
+
5
+ class TestsController < ApplicationController
6
+ def index
7
+ render json: {}, status: 200
8
+ end
9
+ end
10
+
11
+ class TestsControllerTest < ActionController::TestCase
12
+ setup do
13
+ Rails.cache.clear
14
+ @session = rails_identity_sessions(:one)
15
+ @token = @session.token
16
+ @admin_session = rails_identity_sessions(:admin_one)
17
+ @admin_token = @admin_session.token
18
+ @api_key = rails_identity_users(:one).api_key
19
+ @admin_api_key = rails_identity_users(:admin_one).api_key
20
+ # Rails.application.routes.draw do
21
+ RailsIdentity::Engine.routes.draw do
22
+ match "tests" => "tests#index", via: [:get]
23
+ end
24
+ @routes = Engine.routes
25
+ end
26
+
27
+ teardown do
28
+ RailsIdentity::Engine.routes.draw do
29
+ resources :sessions
30
+ match 'sessions(/:id)' => 'sessions#options', via: [:options]
31
+
32
+ resources :users
33
+ match 'users(/:id)' => 'users#options', via: [:options]
34
+ end
35
+ end
36
+
37
+ test "require only token" do
38
+ class ::RailsIdentity::TestsController < ApplicationController
39
+ reset_callbacks :process_action
40
+ before_action :require_token, only: [:index]
41
+ def index
42
+ render json: {}, status: 200
43
+ end
44
+ end
45
+ get :index, token: @token
46
+ assert_response :success
47
+ get :index
48
+ assert_response 401
49
+ end
50
+
51
+ test "require only admin token" do
52
+ class ::RailsIdentity::TestsController < ApplicationController
53
+ reset_callbacks :process_action
54
+ before_action :require_admin_token, only: [:index]
55
+ def index
56
+ render json: {}, status: 200
57
+ end
58
+ end
59
+ get :index, token: @admin_token
60
+ assert_response :success
61
+ Rails.cache.write("#{CACHE_PREFIX}-session-#{@token}", @session)
62
+ get :index, token: @token
63
+ assert_response 401
64
+ end
65
+
66
+ test "accept only token" do
67
+ class ::RailsIdentity::TestsController < ApplicationController
68
+ reset_callbacks :process_action
69
+ before_action :accept_token, only: [:index]
70
+ def index
71
+ render json: {}, status: 200
72
+ end
73
+ end
74
+ get :index, token: @token
75
+ assert_response :success
76
+ get :index
77
+ assert_response :success
78
+ end
79
+
80
+ test "require only api key" do
81
+ class ::RailsIdentity::TestsController < ApplicationController
82
+ reset_callbacks :process_action
83
+ before_action :require_api_key, only: [:index]
84
+ def index
85
+ render json: {}, status: 200
86
+ end
87
+ end
88
+ get :index, api_key: @api_key
89
+ assert_response :success
90
+ get :index, token: @token
91
+ assert_response 401
92
+ get :index
93
+ assert_response 401
94
+ end
95
+
96
+ test "require only admin api key" do
97
+ class ::RailsIdentity::TestsController < ApplicationController
98
+ reset_callbacks :process_action
99
+ before_action :require_admin_api_key, only: [:index]
100
+ def index
101
+ render json: {}, status: 200
102
+ end
103
+ end
104
+ get :index, api_key: @admin_api_key
105
+ assert_response :success
106
+ get :index, api_key: @api_key
107
+ assert_response 401
108
+ end
109
+
110
+ test "accept only api key" do
111
+ class ::RailsIdentity::TestsController < ApplicationController
112
+ reset_callbacks :process_action
113
+ before_action :accept_api_key, only: [:index]
114
+ def index
115
+ render json: {}, status: 200
116
+ end
117
+ end
118
+ get :index, api_key: @api_key
119
+ assert_response :success
120
+ get :index
121
+ assert_response :success
122
+ end
123
+ end
124
+ end