rails-identity 0.2.3 → 0.3.0
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/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
|