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 +4 -4
- data/app/controllers/rails_identity/sessions_controller.rb +9 -4
- data/app/controllers/rails_identity/users_controller.rb +54 -30
- data/app/helpers/rails_identity/application_helper.rb +101 -3
- data/app/models/rails_identity/session.rb +7 -0
- data/app/models/rails_identity/user.rb +1 -0
- data/db/migrate/20160507014709_add_api_key_to_users.rb +10 -0
- data/lib/rails_identity/version.rb +1 -1
- data/test/controllers/rails_identity/application_controller_test.rb +124 -0
- data/test/controllers/rails_identity/sessions_controller_test.rb +77 -10
- data/test/controllers/rails_identity/users_controller_test.rb +13 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/schema.rb +3 -1
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +120 -0
- data/test/dummy/log/test.log +148863 -0
- data/test/fixtures/rails_identity/users.yml +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 069d960770fb7c3e3d60a2a7a7de61217a3d864c
|
4
|
+
data.tar.gz: f5367277a5aa44adc273009b11bedbf140258eac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 :
|
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
|
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
|
-
#
|
85
|
+
# A Repia::Errors::NotFound is raised if the session does not
|
84
86
|
# exist (or deleted due to expiration).
|
85
87
|
#
|
86
|
-
#
|
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 :
|
13
|
-
prepend_before_action :
|
14
|
-
prepend_before_action :
|
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,
|
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
|
-
|
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
|
58
|
-
# notable ways to update a user.
|
59
|
+
# Patches the user object. There are four notable operations:
|
59
60
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
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
|
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
|
-
|
155
|
+
user_mailer.password_reset(@user).deliver_later
|
132
156
|
else
|
133
|
-
|
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("
|
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("
|
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("
|
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
|
@@ -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
|
@@ -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
|