summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjessib <jessib@riseup.net>2013-08-27 11:22:52 -0700
committerjessib <jessib@riseup.net>2013-08-27 11:22:52 -0700
commite481b8cbc05a858674a59ef36d695973622f6b3a (patch)
tree8a20143ce831d71076a8c3913664b3a67742ed6b
parent441db4736e0cd003caf9c8f7b3fbdb1ffa72b969 (diff)
parentfdf9c5f9ea605020ea371de8e221efe8e5d5ba32 (diff)
Merge pull request #72 from azul/feature/token-based-auth
Feature: Token based auth
-rw-r--r--users/app/controllers/controller_extension/authentication.rb47
-rw-r--r--users/app/controllers/controller_extension/token_authentication.rb23
-rw-r--r--users/app/models/token.rb4
-rw-r--r--users/config/initializers/add_controller_methods.rb1
-rw-r--r--users/test/factories.rb3
-rw-r--r--users/test/functional/helper_methods_test.rb2
-rw-r--r--users/test/functional/test_helpers_test.rb38
-rw-r--r--users/test/functional/users_controller_test.rb12
-rw-r--r--users/test/functional/v1/sessions_controller_test.rb18
-rwxr-xr-xusers/test/integration/api/python/flow_with_srp.py59
-rw-r--r--users/test/support/auth_test_helper.rb9
-rw-r--r--users/test/support/stub_record_helper.rb5
12 files changed, 157 insertions, 64 deletions
diff --git a/users/app/controllers/controller_extension/authentication.rb b/users/app/controllers/controller_extension/authentication.rb
index 5fac884..dca3664 100644
--- a/users/app/controllers/controller_extension/authentication.rb
+++ b/users/app/controllers/controller_extension/authentication.rb
@@ -7,28 +7,8 @@ module ControllerExtension::Authentication
helper_method :current_user, :logged_in?, :admin?
end
- def authentication_errors
- return unless attempted_login?
- errors = get_warden_errors
- errors.inject({}) do |translated,err|
- translated[err.first] = I18n.t(err.last)
- translated
- end
- end
-
- def get_warden_errors
- if strategy = warden.winning_strategy
- message = strategy.message
- # in case we get back the default message to fail!
- message.respond_to?(:inject) ? message : { base: message }
- else
- { login: :all_strategies_failed }
- end
- end
-
- def attempted_login?
- request.env['warden.options'] &&
- request.env['warden.options'][:attempted_path]
+ def current_user
+ @current_user ||= token_authenticate || warden.user
end
def logged_in?
@@ -62,4 +42,27 @@ module ControllerExtension::Authentication
access_denied unless admin?
end
+ def authentication_errors
+ return unless attempted_login?
+ errors = get_warden_errors
+ errors.inject({}) do |translated,err|
+ translated[err.first] = I18n.t(err.last)
+ translated
+ end
+ end
+
+ def get_warden_errors
+ if strategy = warden.winning_strategy
+ message = strategy.message
+ # in case we get back the default message to fail!
+ message.respond_to?(:inject) ? message : { base: message }
+ else
+ { login: :all_strategies_failed }
+ end
+ end
+
+ def attempted_login?
+ request.env['warden.options'] &&
+ request.env['warden.options'][:attempted_path]
+ end
end
diff --git a/users/app/controllers/controller_extension/token_authentication.rb b/users/app/controllers/controller_extension/token_authentication.rb
new file mode 100644
index 0000000..3e2816d
--- /dev/null
+++ b/users/app/controllers/controller_extension/token_authentication.rb
@@ -0,0 +1,23 @@
+module ControllerExtension::TokenAuthentication
+ extend ActiveSupport::Concern
+
+ def token_authenticate
+ authenticate_with_http_token do |token_id, options|
+ @token = Token.find(token_id)
+ end
+ @token.user if @token
+ end
+
+ def logout
+ super
+ clear_token
+ end
+
+ def clear_token
+ authenticate_with_http_token do |token_id, options|
+ @token = Token.find(token_id)
+ @token.destroy if @token
+ end
+ end
+end
+
diff --git a/users/app/models/token.rb b/users/app/models/token.rb
index cc62778..514b97f 100644
--- a/users/app/models/token.rb
+++ b/users/app/models/token.rb
@@ -6,6 +6,10 @@ class Token < CouchRest::Model::Base
validates :user_id, presence: true
+ def user
+ User.find(self.user_id)
+ end
+
def initialize(*args)
super
self.id = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '')
diff --git a/users/config/initializers/add_controller_methods.rb b/users/config/initializers/add_controller_methods.rb
index 2579176..f572ecb 100644
--- a/users/config/initializers/add_controller_methods.rb
+++ b/users/config/initializers/add_controller_methods.rb
@@ -1,3 +1,4 @@
ActiveSupport.on_load(:application_controller) do
include ControllerExtension::Authentication
+ include ControllerExtension::TokenAuthentication
end
diff --git a/users/test/factories.rb b/users/test/factories.rb
index 777704b..c87e290 100644
--- a/users/test/factories.rb
+++ b/users/test/factories.rb
@@ -18,4 +18,7 @@ FactoryGirl.define do
end
end
end
+
+ factory :token
+
end
diff --git a/users/test/functional/helper_methods_test.rb b/users/test/functional/helper_methods_test.rb
index 2b2375c..44226ae 100644
--- a/users/test/functional/helper_methods_test.rb
+++ b/users/test/functional/helper_methods_test.rb
@@ -11,7 +11,7 @@ class HelperMethodsTest < ActionController::TestCase
# we test them right in here...
include ApplicationController._helpers
- # they all reference the controller.
+ # the helpers all reference the controller.
def controller
@controller
end
diff --git a/users/test/functional/test_helpers_test.rb b/users/test/functional/test_helpers_test.rb
new file mode 100644
index 0000000..9bd01ad
--- /dev/null
+++ b/users/test/functional/test_helpers_test.rb
@@ -0,0 +1,38 @@
+#
+# There are a few test helpers for dealing with login etc.
+# We test them here and also document their behaviour.
+#
+
+require 'test_helper'
+
+class TestHelpersTest < ActionController::TestCase
+ tests ApplicationController # testing no controller in particular
+
+ def test_login_stubs_warden
+ login
+ assert_equal @current_user, request.env['warden'].user
+ end
+
+ def test_login_token_authenticates
+ login
+ assert_equal @current_user, @controller.send(:token_authenticate)
+ end
+
+ def test_login_stubs_token
+ login
+ assert @token
+ assert_equal @current_user, @token.user
+ end
+
+ def test_login_adds_token_header
+ login
+ token_present = @controller.authenticate_with_http_token do |token, options|
+ assert_equal @token.id, token
+ end
+ # authenticate_with_http_token just returns nil and does not
+ # execute the block if there is no token. So we have to also
+ # ensure it was run:
+ assert token_present
+ end
+end
+
diff --git a/users/test/functional/users_controller_test.rb b/users/test/functional/users_controller_test.rb
index 0ce5cc2..96ae48c 100644
--- a/users/test/functional/users_controller_test.rb
+++ b/users/test/functional/users_controller_test.rb
@@ -59,19 +59,23 @@ class UsersControllerTest < ActionController::TestCase
assert_access_denied
end
- test "show for non-existing user" do
+ test "may not show non-existing user without auth" do
nonid = 'thisisnotanexistinguserid'
- # when unauthenticated:
get :show, :id => nonid
assert_access_denied(true, false)
+ end
- # when authenticated but not admin:
+ test "may not show non-existing user without admin" do
+ nonid = 'thisisnotanexistinguserid'
login
+
get :show, :id => nonid
assert_access_denied
+ end
- # when authenticated as admin:
+ test "redirect admin to user list for non-existing user" do
+ nonid = 'thisisnotanexistinguserid'
login :is_admin? => true
get :show, :id => nonid
assert_response :redirect
diff --git a/users/test/functional/v1/sessions_controller_test.rb b/users/test/functional/v1/sessions_controller_test.rb
index 0c4e325..ff9fca1 100644
--- a/users/test/functional/v1/sessions_controller_test.rb
+++ b/users/test/functional/v1/sessions_controller_test.rb
@@ -7,7 +7,7 @@ class V1::SessionsControllerTest < ActionController::TestCase
setup do
@request.env['HTTP_HOST'] = 'api.lvh.me'
- @user = stub_record :user
+ @user = stub_record :user, {}, true
@client_hex = 'a123'
end
@@ -48,13 +48,22 @@ class V1::SessionsControllerTest < ActionController::TestCase
assert_response :success
assert json_response.keys.include?("id")
assert json_response.keys.include?("token")
+ assert token = Token.find(json_response['token'])
+ assert_equal @user.id, token.user_id
end
- test "logout should reset warden user" do
+ test "logout should reset session" do
expect_warden_logout
delete :destroy
- assert_response :redirect
- assert_redirected_to root_url
+ assert_response 204
+ end
+
+ test "logout should destroy token" do
+ login
+ expect_warden_logout
+ @token.expects(:destroy)
+ delete :destroy
+ assert_response 204
end
def expect_warden_logout
@@ -65,5 +74,4 @@ class V1::SessionsControllerTest < ActionController::TestCase
request.env['warden'].expects(:logout)
end
-
end
diff --git a/users/test/integration/api/python/flow_with_srp.py b/users/test/integration/api/python/flow_with_srp.py
index d37c6af..9fc168b 100755
--- a/users/test/integration/api/python/flow_with_srp.py
+++ b/users/test/integration/api/python/flow_with_srp.py
@@ -11,15 +11,31 @@ import binascii
safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x)
+# using globals for now
+# server = 'https://dev.bitmask.net/1'
+server = 'http://api.lvh.me:3000/1'
+
+def run_tests():
+ login = 'test_' + id_generator()
+ password = id_generator() + id_generator()
+ usr = srp.User( login, password, srp.SHA256, srp.NG_1024 )
+ print_and_parse(signup(login, password))
+
+ auth = print_and_parse(authenticate(usr))
+ verify_or_debug(auth, usr)
+ assert usr.authenticated()
+
+ usr = change_password(auth['id'], login, auth['token'])
+
+ auth = print_and_parse(authenticate(usr))
+ verify_or_debug(auth, usr)
+ # At this point the authentication process is complete.
+ assert usr.authenticated()
+
# let's have some random name
def id_generator(size=6, chars=string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
-# using globals for a start
-server = 'https://dev.bitmask.net/1'
-login = 'test_' + id_generator()
-password = id_generator() + id_generator()
-
# log the server communication
def print_and_parse(response):
request = response.request
@@ -32,28 +48,30 @@ def print_and_parse(response):
except ValueError:
return None
-def signup(session):
+def signup(login, password):
salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 )
user_params = {
'user[login]': login,
'user[password_verifier]': binascii.hexlify(vkey),
'user[password_salt]': binascii.hexlify(salt)
}
- return session.post(server + '/users.json', data = user_params, verify = False)
+ return requests.post(server + '/users.json', data = user_params, verify = False)
-def change_password(session):
+def change_password(user_id, login, token):
password = id_generator() + id_generator()
salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 )
user_params = {
'user[password_verifier]': binascii.hexlify(vkey),
'user[password_salt]': binascii.hexlify(salt)
}
+ auth_headers = { 'Authorization': 'Token token="' + token + '"'}
print user_params
- print_and_parse(session.put(server + '/users/' + auth['id'] + '.json', data = user_params, verify = False))
+ print_and_parse(requests.put(server + '/users/' + user_id + '.json', data = user_params, verify = False, headers = auth_headers))
return srp.User( login, password, srp.SHA256, srp.NG_1024 )
-def authenticate(session, login):
+def authenticate(usr):
+ session = requests.session()
uname, A = usr.start_authentication()
params = {
'login': uname,
@@ -61,10 +79,10 @@ def authenticate(session, login):
}
init = print_and_parse(session.post(server + '/sessions', data = params, verify=False))
M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) )
- return session.put(server + '/sessions/' + login, verify = False,
+ return session.put(server + '/sessions/' + uname, verify = False,
data = {'client_auth': binascii.hexlify(M)})
-def verify_or_debug(auth):
+def verify_or_debug(auth, usr):
if ( 'errors' in auth ):
print ' u = "%x"' % usr.u
print ' x = "%x"' % usr.x
@@ -75,19 +93,4 @@ def verify_or_debug(auth):
else:
usr.verify_session( safe_unhexlify(auth["M2"]) )
-usr = srp.User( login, password, srp.SHA256, srp.NG_1024 )
-session = requests.session()
-user = print_and_parse(signup(session))
-
-# SRP signup would happen here and calculate M hex
-auth = print_and_parse(authenticate(session, user['login']))
-verify_or_debug(auth)
-assert usr.authenticated()
-
-usr = change_password(session)
-
-auth = print_and_parse(authenticate(session, user['login']))
-verify_or_debug(auth)
-# At this point the authentication process is complete.
-assert usr.authenticated()
-
+run_tests()
diff --git a/users/test/support/auth_test_helper.rb b/users/test/support/auth_test_helper.rb
index 555b5db..47147fc 100644
--- a/users/test/support/auth_test_helper.rb
+++ b/users/test/support/auth_test_helper.rb
@@ -13,8 +13,9 @@ module AuthTestHelper
if user_or_method_hash.respond_to?(:reverse_merge)
user_or_method_hash.reverse_merge! :is_admin? => false
end
- @current_user = stub_record(:user, user_or_method_hash, true)
+ @current_user = stub_record(:user, user_or_method_hash)
request.env['warden'] = stub :user => @current_user
+ request.env['HTTP_AUTHORIZATION'] = header_for_token_auth
return @current_user
end
@@ -37,6 +38,12 @@ module AuthTestHelper
end
end
+ protected
+
+ def header_for_token_auth
+ @token = find_record(:token, :user => @current_user)
+ ActionController::HttpAuthentication::Token.encode_credentials @token.id
+ end
end
class ActionController::TestCase
diff --git a/users/test/support/stub_record_helper.rb b/users/test/support/stub_record_helper.rb
index 8aa1973..5bccb66 100644
--- a/users/test/support/stub_record_helper.rb
+++ b/users/test/support/stub_record_helper.rb
@@ -7,9 +7,8 @@ module StubRecordHelper
# If no record is given but a hash or nil will create a stub based on
# that instead and returns the stub.
#
- def find_record(factory, attribs_hash = {})
- attribs_hash = attribs_hash.reverse_merge(:id => Random.rand(10000).to_s)
- record = stub_record factory, attribs_hash
+ def find_record(factory, record_or_attribs_hash = {})
+ record = stub_record factory, record_or_attribs_hash, true
klass = record.class
finder = klass.respond_to?(:find_by_param) ? :find_by_param : :find
klass.stubs(finder).with(record.to_param.to_s).returns(record)