summaryrefslogtreecommitdiff
path: root/users
diff options
context:
space:
mode:
authorAzul <azul@leap.se>2012-12-07 08:28:23 +0100
committerAzul <azul@leap.se>2012-12-07 08:28:23 +0100
commit1ec55c4f562a4fdd57c50077ff286ef08e9978a1 (patch)
tree16203d2ca4f32e24d38fef6062aa9534cecb3bfe /users
parenteffa6b0f84cfe954cc9dd73f592663b743b0d857 (diff)
parenta3dce077881c7e97090e5e560b1fb004952d5b23 (diff)
Merge branch 'develop'
Diffstat (limited to 'users')
m---------users/app/assets/javascripts/srp0
-rw-r--r--users/app/assets/javascripts/users.js.coffee62
-rw-r--r--users/app/controllers/controller_extension/authentication.rb38
-rw-r--r--users/app/controllers/sessions_controller.rb27
-rw-r--r--users/app/controllers/users_controller.rb42
-rw-r--r--users/app/models/session.rb34
-rw-r--r--users/app/models/user.rb41
-rw-r--r--users/app/views/sessions/_admin_nav.html.haml6
-rw-r--r--users/app/views/sessions/_nav.html.haml13
-rw-r--r--users/app/views/sessions/new.html.haml15
-rw-r--r--users/app/views/sessions/new.json.erb3
-rw-r--r--users/app/views/users/_cancel_account.html.haml6
-rw-r--r--users/app/views/users/_form.html.haml15
-rw-r--r--users/app/views/users/_user.html.haml10
-rw-r--r--users/app/views/users/edit.html.haml5
-rw-r--r--users/app/views/users/index.html.haml17
-rw-r--r--users/app/views/users/new.html.haml13
-rw-r--r--users/config/initializers/add_controller_methods.rb3
-rw-r--r--users/config/initializers/warden.rb7
-rw-r--r--users/config/locales/en.yml14
-rw-r--r--users/config/routes.rb8
-rw-r--r--users/leap_web_users.gemspec3
-rw-r--r--users/lib/leap_web_users/engine.rb4
-rw-r--r--users/lib/warden/session_serializer.rb13
-rw-r--r--users/lib/warden/strategies/secure_remote_password.rb58
-rw-r--r--users/test/functional/application_controller_test.rb28
-rw-r--r--users/test/functional/helper_methods_test.rb39
-rw-r--r--users/test/functional/sessions_controller_test.rb95
-rw-r--r--users/test/functional/users_controller_test.rb121
-rw-r--r--users/test/integration/api/account_flow_test.rb52
-rwxr-xr-xusers/test/integration/api/python/flow_with_srp.py2
-rw-r--r--users/test/support/auth_test_helper.rb35
-rw-r--r--users/test/support/stub_record_helper.rb41
-rw-r--r--users/test/test_helper.rb3
-rw-r--r--users/test/unit/user_test.rb11
-rw-r--r--users/test/unit/warden_strategy_secure_remote_password_test.rb63
36 files changed, 764 insertions, 183 deletions
diff --git a/users/app/assets/javascripts/srp b/users/app/assets/javascripts/srp
-Subproject d6a78049f3356d9d645143362eca74434410bf6
+Subproject fff770a866b44abce6fe0fc5d5ffde034225436
diff --git a/users/app/assets/javascripts/users.js.coffee b/users/app/assets/javascripts/users.js.coffee
index 160a7f0..76a6d79 100644
--- a/users/app/assets/javascripts/users.js.coffee
+++ b/users/app/assets/javascripts/users.js.coffee
@@ -1,41 +1,35 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
-#
+preventDefault = (event) ->
+ event.preventDefault()
-validate_password = (event) ->
+srp.session = new srp.Session()
+srp.signedUp = ->
+ srp.login
- password = $('#srp_password').val()
- confirmation = $('#srp_password_confirmation').val()
- login = $('#srp_username').val()
+srp.loggedIn = ->
+ window.location = '/'
- if password != confirmation
- alert "Password and Confirmation do not match!"
- $('#srp_password').focus()
- return false
- if password == login
- alert "Password and Login may not match!"
- $('#srp_password').focus()
- return false
- if password.length < 8
- alert "Password needs to be at least 8 characters long!"
- $('#srp_password').focus()
- return false
-
- return true
-
+#// TODO: not sure this is what we want.
+srp.updated = ->
+ window.location = '/'
-insert_verifier = (event) ->
- # TODO: verify password confimation
- srp = new SRP
- salt = srp.session.getSalt()
- $('#srp_salt').val(salt)
- $('#srp_password_verifier').val(srp.session.getV().toString(16))
- # clear the password so we do not submit it
- $('#srp_password').val('cleared out - use verifier instead')
- $('#srp_password_confirmation').val('using srp - store verifier')
+srp.error = (message) ->
+ if $.isPlainObject(message) && message.errors
+ for field, error of message.errors
+ element = $('form input[name$="['+field+']"]')
+ next unless element
+ element.trigger('element:validate:fail.ClientSideValidations', error).data('valid', false)
+ else
+ alert(message)
+
+pollUsers = (query, process) ->
+ $.get( "/users.json", query: query).done(process)
$(document).ready ->
- $('#new_user').submit validate_password
- $('#new_user').submit insert_verifier
+ $('#new_user').submit preventDefault
+ $('#new_user').submit srp.signup
+ $('#new_session').submit preventDefault
+ $('#new_session').submit srp.login
+ $('.user.form.edit').submit srp.update
+ $('.user.form.edit').submit preventDefault
+ $('.user.typeahead').typeahead({source: pollUsers});
diff --git a/users/app/controllers/controller_extension/authentication.rb b/users/app/controllers/controller_extension/authentication.rb
new file mode 100644
index 0000000..6ac7a5b
--- /dev/null
+++ b/users/app/controllers/controller_extension/authentication.rb
@@ -0,0 +1,38 @@
+module ControllerExtension::Authentication
+ extend ActiveSupport::Concern
+
+ private
+
+ included do
+ helper_method :current_user, :logged_in?, :admin?
+ end
+
+ def authentication_errors
+ return unless errors = warden.winning_strategy.try(:message)
+ errors.inject({}) do |translated,err|
+ translated[err.first] = I18n.t(err.last)
+ translated
+ end
+ end
+
+ def logged_in?
+ !!current_user
+ end
+
+ def authorize
+ access_denied unless logged_in?
+ end
+
+ def access_denied
+ redirect_to login_url, :alert => "Not authorized"
+ end
+
+ def admin?
+ current_user && current_user.is_admin?
+ end
+
+ def authorize_admin
+ access_denied unless admin?
+ end
+
+end
diff --git a/users/app/controllers/sessions_controller.rb b/users/app/controllers/sessions_controller.rb
index 284c0e2..bc910b5 100644
--- a/users/app/controllers/sessions_controller.rb
+++ b/users/app/controllers/sessions_controller.rb
@@ -3,33 +3,24 @@ class SessionsController < ApplicationController
skip_before_filter :verify_authenticity_token
def new
+ @session = Session.new
+ if authentication_errors
+ @errors = authentication_errors
+ render :status => 422
+ end
end
def create
- @user = User.find_by_param(params[:login])
- session[:handshake] = @user.initialize_auth(params['A'].hex)
- User.current = @user #?
- render :json => session[:handshake]
- rescue RECORD_NOT_FOUND
- render :json => {:errors => {:login => ["unknown user"]}}
+ authenticate!
end
def update
- # TODO: validate the id belongs to the session
- @user = User.find_by_param(params[:id])
- @srp_session = session.delete(:handshake)
- @srp_session.authenticate!(params[:client_auth].hex)
- session[:user_id] = @user.id
- User.current = @user #?
- render :json => @srp_session
- rescue WRONG_PASSWORD
- session[:handshake] = nil
- render :json => {:errors => {"password" => ["wrong password"]}}
+ authenticate!
+ render :json => session.delete(:handshake)
end
def destroy
- session[:user_id] = nil
- User.current = nil #?
+ logout
redirect_to root_path
end
end
diff --git a/users/app/controllers/users_controller.rb b/users/app/controllers/users_controller.rb
index 82d2eac..cffc8c6 100644
--- a/users/app/controllers/users_controller.rb
+++ b/users/app/controllers/users_controller.rb
@@ -1,18 +1,48 @@
class UsersController < ApplicationController
- skip_before_filter :verify_authenticity_token
+ skip_before_filter :verify_authenticity_token, :only => [:create]
+
+ before_filter :fetch_user, :only => [:edit, :update, :destroy]
+ before_filter :authorize_admin, :only => [:index]
respond_to :json, :html
+ def index
+ if params[:query]
+ @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ)
+ else
+ @users = User.by_created_at.descending
+ end
+ @users = @users.limit(10)
+ respond_with @users.map(&:login).sort
+ end
+
def new
@user = User.new
end
def create
- @user = User.create!(params[:user])
- respond_with(@user, :location => root_url, :notice => "Signed up!")
- rescue VALIDATION_FAILED => e
- @user = e.document
- respond_with(@user, :location => new_user_path)
+ @user = User.create(params[:user])
+ respond_with @user
+ end
+
+ def edit
+ end
+
+ def update
+ @user.update_attributes(params[:user])
+ respond_with @user
+ end
+
+ def destroy
+ @user.destroy
+ redirect_to admin? ? users_path : login_path
+ end
+
+ protected
+
+ def fetch_user
+ @user = User.find_by_param(params[:id])
+ access_denied unless admin? or (@user == current_user)
end
end
diff --git a/users/app/models/session.rb b/users/app/models/session.rb
new file mode 100644
index 0000000..a9fdb1b
--- /dev/null
+++ b/users/app/models/session.rb
@@ -0,0 +1,34 @@
+class Session < SRP::Session
+ include ActiveModel::Validations
+
+ attr_accessor :login
+
+ validates :login,
+ :presence => true,
+ :format => { :with => /\A[A-Za-z\d_]+\z/,
+ :message => "Only letters, digits and _ allowed" }
+
+ def initialize(user = nil, aa = nil)
+ super(user, aa) if user
+ end
+
+ def persisted?
+ false
+ end
+
+ def new_record?
+ true
+ end
+
+ def to_model
+ self
+ end
+
+ def to_key
+ [object_id]
+ end
+
+ def to_param
+ nil
+ end
+end
diff --git a/users/app/models/user.rb b/users/app/models/user.rb
index 1afb9db..325c981 100644
--- a/users/app/models/user.rb
+++ b/users/app/models/user.rb
@@ -9,42 +9,46 @@ class User < CouchRest::Model::Base
:presence => true
validates :login,
- :uniqueness => true
+ :uniqueness => true,
+ :if => :serverside?
validates :login,
:format => { :with => /\A[A-Za-z\d_]+\z/,
:message => "Only letters, digits and _ allowed" }
validates :password_salt, :password_verifier,
- :format => { :with => /\A[\dA-Fa-f]+\z/,
- :message => "Only hex numbers allowed" }
+ :format => { :with => /\A[\dA-Fa-f]+\z/, :message => "Only hex numbers allowed" }
+
+ validates :password, :presence => true,
+ :confirmation => true,
+ :format => { :with => /.{8}.*/, :message => "needs to be at least 8 characters long" }
timestamps!
design do
view :by_login
+ view :by_created_at
end
class << self
- def find_by_param(login)
- return find_by_login(login) || raise(RECORD_NOT_FOUND)
- end
+ alias_method :find_by_param, :find
# valid set of attributes for testing
def valid_attributes_hash
{ :login => "me",
- :password_verifier => "1234ABC",
+ :password_verifier => "1234ABCD",
:password_salt => "4321AB" }
end
end
- def to_param
- self.login
- end
+ alias_method :to_param, :id
def to_json(options={})
- super(options.merge(:only => ['login', 'password_salt']))
+ {
+ :login => login,
+ :ok => valid?
+ }.to_json(options)
end
def initialize_auth(aa)
@@ -63,11 +67,18 @@ class User < CouchRest::Model::Base
login
end
- def self.current
- Thread.current[:user]
+ # Since we are storing admins by login, we cannot allow admins to change their login.
+ def is_admin?
+ APP_CONFIG['admins'].include? self.login
end
- def self.current=(user)
- Thread.current[:user] = user
+
+ protected
+ def password
+ password_verifier
end
+ # used as a condition for validations that are server side only
+ def serverside?
+ true
+ end
end
diff --git a/users/app/views/sessions/_admin_nav.html.haml b/users/app/views/sessions/_admin_nav.html.haml
new file mode 100644
index 0000000..14dfbdc
--- /dev/null
+++ b/users/app/views/sessions/_admin_nav.html.haml
@@ -0,0 +1,6 @@
+%a#admin-menu{"data-toggle" => "dropdown", :role => :button}
+ Admin
+%ul.dropdown-menu{:role => "menu", "aria-labelledby" => "admin-menu"}
+ %li
+ = link_to Ticket.model_name.human(:count => ""), tickets_path, {:tabindex => -1}
+ = link_to User.model_name.human(:count => ""), users_path, {:tabindex => -1}
diff --git a/users/app/views/sessions/_nav.html.haml b/users/app/views/sessions/_nav.html.haml
new file mode 100644
index 0000000..5306d0e
--- /dev/null
+++ b/users/app/views/sessions/_nav.html.haml
@@ -0,0 +1,13 @@
+- if logged_in?
+ - if admin?
+ %li.dropdown
+ = render 'sessions/admin_nav'
+ %li
+ = link_to current_user.login, edit_user_path(current_user)
+ %li
+ = link_to t(:logout), logout_path
+- else
+ %li
+ = link_to t(:login), login_path
+ %li
+ = link_to t(:signup), signup_path
diff --git a/users/app/views/sessions/new.html.haml b/users/app/views/sessions/new.html.haml
index 39ee7bf..a04f584 100644
--- a/users/app/views/sessions/new.html.haml
+++ b/users/app/views/sessions/new.html.haml
@@ -1,7 +1,8 @@
-%h2=t :login
-= simple_form_for :session, :url => sessions_path, :html => { :id => :new_session } do |f|
- %legend=t :login_message
- = f.input :login, :input_html => { :id => :srp_username }
- = f.input :password, :required => true, :input_html => { :id => :srp_password }
- = f.button :submit, :value => t(:login), :class => 'btn-primary'
- = link_to t(:cancel), root_url, :class => :btn
+.span8.offset2
+ %h2=t :login
+ = simple_form_for @session, :validate => true, :html => { :id => :new_session, :class => 'form-horizontal' } do |f|
+ %legend=t :login_message
+ = f.input :login, :input_html => { :id => :srp_username }
+ = f.input :password, :required => true, :input_html => { :id => :srp_password }
+ = f.button :submit, :value => t(:login), :class => 'btn-primary'
+ = link_to t(:cancel), root_url, :class => :btn
diff --git a/users/app/views/sessions/new.json.erb b/users/app/views/sessions/new.json.erb
new file mode 100644
index 0000000..36154b8
--- /dev/null
+++ b/users/app/views/sessions/new.json.erb
@@ -0,0 +1,3 @@
+{
+"errors": <%= raw @errors.to_json %>
+}
diff --git a/users/app/views/users/_cancel_account.html.haml b/users/app/views/users/_cancel_account.html.haml
new file mode 100644
index 0000000..41580b0
--- /dev/null
+++ b/users/app/views/users/_cancel_account.html.haml
@@ -0,0 +1,6 @@
+%legend
+ =t :cancel_account
+ %small You will not be able to login anymore.
+= link_to user_path(@user), :method => :delete, :class => "btn btn-danger" do
+ %i.icon-remove.icon-white
+ Remove my Account
diff --git a/users/app/views/users/_form.html.haml b/users/app/views/users/_form.html.haml
new file mode 100644
index 0000000..39e26a6
--- /dev/null
+++ b/users/app/views/users/_form.html.haml
@@ -0,0 +1,15 @@
+- only = local_assigns[:only]
+- html = {:class => 'form-horizontal user form ' + (@user.new_record? ? 'new' : 'edit')}
+= simple_form_for @user, :validate => true, :format => :json, :html => html do |f|
+ %legend
+ = t(only || :signup_message)
+ - if !only || only == :change_login
+ = f.input :login, :input_html => { :id => :srp_username }
+ - if !only || only == :change_password
+ = f.input :password, :required => true, :validate => true, :input_html => { :id => :srp_password }
+ = f.input :password_confirmation, :required => true, :input_html => { :id => :srp_password_confirmation }
+ .pull-right
+ = f.button :submit, :class => 'btn-primary'
+ - unless only
+ = link_to t(:cancel), root_url, :class => :btn
+ .clearfix
diff --git a/users/app/views/users/_user.html.haml b/users/app/views/users/_user.html.haml
new file mode 100644
index 0000000..7db0041
--- /dev/null
+++ b/users/app/views/users/_user.html.haml
@@ -0,0 +1,10 @@
+%tr
+ %td= user.login
+ %td= time_ago_in_words(user.created_at) + " ago"
+ %td
+ = link_to edit_user_path(user), :class => "btn btn-mini btn-primary" do
+ %i.icon-edit.icon-white
+ Edit
+ = link_to user_path(user), :method => :delete, :class => "btn btn-danger btn-mini" do
+ %i.icon-remove.icon-white
+ Remove
diff --git a/users/app/views/users/edit.html.haml b/users/app/views/users/edit.html.haml
new file mode 100644
index 0000000..25da71a
--- /dev/null
+++ b/users/app/views/users/edit.html.haml
@@ -0,0 +1,5 @@
+.span8.offset2
+ %h2=t :settings
+ = render :partial => 'form', :locals => {:only => :change_login}
+ = render :partial => 'form', :locals => {:only => :change_password}
+ = render 'cancel_account' if @user == current_user
diff --git a/users/app/views/users/index.html.haml b/users/app/views/users/index.html.haml
new file mode 100644
index 0000000..9e6a179
--- /dev/null
+++ b/users/app/views/users/index.html.haml
@@ -0,0 +1,17 @@
+.page-header
+ %h1= User.model_name.human(:count =>User.count)
+.row
+ .span8
+ %h2= params[:query] ? "Users starting with '#{params[:query]}'" : "Last users who signed up"
+ %table.table.table-hover
+ %tr
+ %th Login
+ %th Created
+ %th Action
+ = render @users.all
+ .span4
+ %h4 Find user
+ = form_tag users_path, :method => :get, :class => "form-search" do
+ .input-append
+ = text_field_tag :query, "", :class => "user typeahead span2 search-query", :autocomplete => :off
+ %button.btn{:type => :submit} Search
diff --git a/users/app/views/users/new.html.haml b/users/app/views/users/new.html.haml
index f6ece3a..c1c4208 100644
--- a/users/app/views/users/new.html.haml
+++ b/users/app/views/users/new.html.haml
@@ -1,10 +1,3 @@
-%h2=t :signup
-= simple_form_for @user do |f|
- %legend=t :signup_message
- = f.input :login, :input_html => { :id => :srp_username }
- = f.input :password, :required => true, :input_html => { :id => :srp_password }
- = f.input :password_confirmation, :required => true, :input_html => { :id => :srp_password_confirmation }
- = f.input :password_verifier, :as => :hidden, :input_html => { :id => :srp_password_verifier }
- = f.input :password_salt, :as => :hidden, :input_html => { :id => :srp_salt }
- = f.button :submit, :value => t(:signup), :class => 'btn-primary'
- = link_to t(:cancel), root_url, :class => :btn
+.span8.offset2
+ %h2=t :signup
+ = render 'form'
diff --git a/users/config/initializers/add_controller_methods.rb b/users/config/initializers/add_controller_methods.rb
new file mode 100644
index 0000000..2579176
--- /dev/null
+++ b/users/config/initializers/add_controller_methods.rb
@@ -0,0 +1,3 @@
+ActiveSupport.on_load(:application_controller) do
+ include ControllerExtension::Authentication
+end
diff --git a/users/config/initializers/warden.rb b/users/config/initializers/warden.rb
new file mode 100644
index 0000000..45feb6c
--- /dev/null
+++ b/users/config/initializers/warden.rb
@@ -0,0 +1,7 @@
+Rails.configuration.middleware.use RailsWarden::Manager do |config|
+ config.default_strategies :secure_remote_password
+ config.failure_app = SessionsController
+end
+
+RailsWarden.unauthenticated_action = :new
+
diff --git a/users/config/locales/en.yml b/users/config/locales/en.yml
new file mode 100644
index 0000000..1260494
--- /dev/null
+++ b/users/config/locales/en.yml
@@ -0,0 +1,14 @@
+en:
+ signup: "Sign up"
+ signup_message: "Please create an account."
+ cancel: "Cancel"
+ login: "Login"
+ login_message: "Please login with your account."
+ wrong_password: "wrong password"
+ user_not_found: "could not be found"
+
+ activemodel:
+ models:
+ user:
+ one: User
+ other: "%{count} Users"
diff --git a/users/config/routes.rb b/users/config/routes.rb
index cfc0407..1d144b4 100644
--- a/users/config/routes.rb
+++ b/users/config/routes.rb
@@ -1,10 +1,10 @@
Rails.application.routes.draw do
- get "log_in" => "sessions#new", :as => "log_in"
- get "log_out" => "sessions#destroy", :as => "log_out"
+ get "login" => "sessions#new", :as => "login"
+ get "logout" => "sessions#destroy", :as => "logout"
resources :sessions, :only => [:new, :create, :update, :destroy]
- get "sign_up" => "users#new", :as => "sign_up"
- resources :users, :only => [:new, :create]
+ get "signup" => "users#new", :as => "signup"
+ resources :users
end
diff --git a/users/leap_web_users.gemspec b/users/leap_web_users.gemspec
index f64a76a..0682a99 100644
--- a/users/leap_web_users.gemspec
+++ b/users/leap_web_users.gemspec
@@ -17,5 +17,6 @@ Gem::Specification.new do |s|
s.add_dependency "leap_web_core", LeapWeb::VERSION
- s.add_dependency "ruby-srp", "~> 0.1.1"
+ s.add_dependency "ruby-srp", "~> 0.1.4"
+ s.add_dependency "rails_warden"
end
diff --git a/users/lib/leap_web_users/engine.rb b/users/lib/leap_web_users/engine.rb
index 9b7545e..7033576 100644
--- a/users/lib/leap_web_users/engine.rb
+++ b/users/lib/leap_web_users/engine.rb
@@ -1,8 +1,12 @@
# thou shall require all your dependencies in an engine.
require "leap_web_core"
require "leap_web_core/ui_dependencies"
+require "rails_warden"
require "ruby-srp"
+require "warden/session_serializer"
+require "warden/strategies/secure_remote_password"
+
module LeapWebUsers
class Engine < ::Rails::Engine
diff --git a/users/lib/warden/session_serializer.rb b/users/lib/warden/session_serializer.rb
new file mode 100644
index 0000000..81d7076
--- /dev/null
+++ b/users/lib/warden/session_serializer.rb
@@ -0,0 +1,13 @@
+module Warden
+ # Setup Session Serialization
+ class SessionSerializer
+ def serialize(record)
+ [record.class.name, record.id]
+ end
+
+ def deserialize(keys)
+ klass, id = keys
+ klass.constantize.find(id)
+ end
+ end
+end
diff --git a/users/lib/warden/strategies/secure_remote_password.rb b/users/lib/warden/strategies/secure_remote_password.rb
new file mode 100644
index 0000000..594e27e
--- /dev/null
+++ b/users/lib/warden/strategies/secure_remote_password.rb
@@ -0,0 +1,58 @@
+module Warden
+ module Strategies
+ class SecureRemotePassword < Warden::Strategies::Base
+
+ def valid?
+ handshake? || authentication?
+ end
+
+ def authenticate!
+ if authentication?
+ validate!
+ else # handshake
+ initialize!
+ end
+ end
+
+ protected
+
+ def handshake?
+ params['A'] && params['login']
+ end
+
+ def authentication?
+ params['client_auth'] && session[:handshake]
+ end
+
+ def validate!
+ user = session[:handshake].authenticate(params['client_auth'].hex)
+ user ? success!(user) : fail!(:password => "wrong_password")
+ end
+
+ def initialize!
+ if user = User.find_by_login(id)
+ session[:handshake] = user.initialize_auth(params['A'].hex)
+ custom! json_response(session[:handshake])
+ else
+ fail! :login => "user_not_found"
+ end
+ end
+
+ def json_response(object)
+ [ 200,
+ {"Content-Type" => "application/json; charset=utf-8"},
+ [object.to_json]
+ ]
+ end
+
+ def id
+ params["id"] || params["login"]
+ end
+ end
+ end
+ Warden::Strategies.add :secure_remote_password,
+ Warden::Strategies::SecureRemotePassword
+
+end
+
+
diff --git a/users/test/functional/application_controller_test.rb b/users/test/functional/application_controller_test.rb
new file mode 100644
index 0000000..857bae5
--- /dev/null
+++ b/users/test/functional/application_controller_test.rb
@@ -0,0 +1,28 @@
+require 'test_helper'
+
+class ApplicationControllerTest < ActionController::TestCase
+
+ def setup
+ # so we can test the effect on the response
+ @controller.response = @response
+ end
+
+ def test_authorize_redirect
+ @controller.send(:authorize)
+ assert_access_denied
+ end
+
+ def test_authorized
+ login
+ @controller.send(:authorize)
+ assert_access_denied(false)
+ end
+
+ def test_authorize_admin
+ login
+ @current_user.expects(:is_admin?).returns(false)
+ @controller.send(:authorize_admin)
+ assert_access_denied
+ end
+
+end
diff --git a/users/test/functional/helper_methods_test.rb b/users/test/functional/helper_methods_test.rb
new file mode 100644
index 0000000..2b2375c
--- /dev/null
+++ b/users/test/functional/helper_methods_test.rb
@@ -0,0 +1,39 @@
+#
+# Testing and documenting the helper methods available from
+# ApplicationController
+#
+
+require 'test_helper'
+
+class HelperMethodsTest < ActionController::TestCase
+ tests ApplicationController
+
+ # we test them right in here...
+ include ApplicationController._helpers
+
+ # they all reference the controller.
+ def controller
+ @controller
+ end
+
+ def test_current_user
+ login
+ assert_equal @current_user, current_user
+ end
+
+ def test_logged_in
+ login
+ assert logged_in?
+ end
+
+ def test_logged_out
+ assert !logged_in?
+ end
+
+ def test_admin
+ login
+ @current_user.expects(:is_admin?).returns(bool = stub)
+ assert_equal bool, admin?
+ end
+
+end
diff --git a/users/test/functional/sessions_controller_test.rb b/users/test/functional/sessions_controller_test.rb
index b6e56a7..9df4455 100644
--- a/users/test/functional/sessions_controller_test.rb
+++ b/users/test/functional/sessions_controller_test.rb
@@ -1,79 +1,72 @@
require 'test_helper'
+# This is a simple controller unit test.
+# We're stubbing out both warden and srp.
+# There's an integration test testing the full rack stack and srp
class SessionsControllerTest < ActionController::TestCase
- def setup
+ setup do
+ @user = stub :login => "me", :id => 123
@client_hex = 'a123'
- @client_rnd = @client_hex.hex
- @server_hex = 'b123'
- @server_rnd = @server_hex.hex
- @server_rnd_exp = 'e123'.hex
- @salt = 'stub user salt'
- @server_handshake = stub :aa => @client_rnd, :bb => @server_rnd, :b => @server_rnd_exp
- @server_auth = 'adfe'
end
test "should get login screen" do
+ request.env['warden'].expects(:winning_strategy)
get :new
assert_response :success
+ assert_equal "text/html", response.content_type
+ assert_template "sessions/new"
end
- test "should perform handshake" do
- user = stub :login => "me", :id => 123
- user.expects(:initialize_auth).
- with(@client_rnd).
- returns(@server_handshake)
- @server_handshake.expects(:to_json).
- returns({'B' => @server_hex, 'salt' => @salt}.to_json)
- User.expects(:find_by_param).with(user.login).returns(user)
- post :create, :login => user.login, 'A' => @client_hex
- assert_equal @server_handshake, session[:handshake]
+ test "renders json" do
+ request.env['warden'].expects(:winning_strategy)
+ get :new, :format => :json
assert_response :success
- assert_json_response :B => @server_hex, :salt => @salt
+ assert_json_error nil
end
- test "should report user not found" do
- unknown = "login_that_does_not_exist"
- User.expects(:find_by_param).with(unknown).raises(RECORD_NOT_FOUND)
- post :create, :login => unknown
- assert_response :success
- assert_json_response :errors => {"login" => ["unknown user"]}
+ test "renders warden errors" do
+ strategy = stub :message => {:field => :translate_me}
+ request.env['warden'].stubs(:winning_strategy).returns(strategy)
+ I18n.expects(:t).with(:translate_me).at_least_once.returns("translation stub")
+ get :new, :format => :json
+ assert_response 422
+ assert_json_error :field => "translation stub"
end
- test "should authorize" do
- session[:handshake] = @server_handshake
- user = stub :login => "me", :id => 123
- @server_handshake.expects(:authenticate!).
- with(@client_rnd).
- returns(@server_auth)
- @server_handshake.expects(:to_json).
- returns({:M2 => @server_auth}.to_json)
- User.expects(:find_by_param).with(user.login).returns(user)
- post :update, :id => user.login, :client_auth => @client_hex
- assert_nil session[:handshake]
- assert_json_response :M2 => @server_auth
- assert_equal user.id, session[:user_id]
+ # Warden takes care of parsing the params and
+ # rendering the response. So not much to test here.
+ test "should perform handshake" do
+ request.env['warden'].expects(:authenticate!)
+ # make sure we don't get a template missing error:
+ @controller.stubs(:render)
+ post :create, :login => @user.login, 'A' => @client_hex
end
- test "should report wrong password" do
- session[:handshake] = @server_handshake
- user = stub :login => "me", :id => 123
- @server_handshake.expects(:authenticate!).
- with(@client_rnd).
- raises(WRONG_PASSWORD)
- User.expects(:find_by_param).with(user.login).returns(user)
- post :update, :id => user.login, :client_auth => @client_hex
+ test "should authorize" do
+ request.env['warden'].expects(:authenticate!)
+ handshake = stub(:to_json => "JSON")
+ session[:handshake] = handshake
+ post :update, :id => @user.login, :client_auth => @client_hex
assert_nil session[:handshake]
- assert_nil session[:user_id]
- assert_json_response :errors => {"password" => ["wrong password"]}
+ assert_response :success
+ assert_equal handshake.to_json, @response.body
end
- test "logout should reset sessions user_id" do
- session[:user_id] = "set"
+ test "logout should reset warden user" do
+ expect_warden_logout
delete :destroy
- assert_nil session[:user_id]
assert_response :redirect
assert_redirected_to root_url
end
+ def expect_warden_logout
+ raw = mock('raw session') do
+ expects(:inspect)
+ end
+ request.env['warden'].expects(:raw_session).returns(raw)
+ request.env['warden'].expects(:logout)
+ end
+
+
end
diff --git a/users/test/functional/users_controller_test.rb b/users/test/functional/users_controller_test.rb
index 1cb28a6..939d105 100644
--- a/users/test/functional/users_controller_test.rb
+++ b/users/test/functional/users_controller_test.rb
@@ -1,33 +1,132 @@
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
+ include StubRecordHelper
+
test "should get new" do
get :new
+
assert_equal User, assigns(:user).class
assert_response :success
end
test "should create new user" do
- params = User.valid_attributes_hash
- user = stub params.merge(:id => 123)
- params.stringify_keys!
- User.expects(:create!).with(params).returns(user)
- post :create, :user => params
+ user = stub_record User
+ User.expects(:create).with(user.params).returns(user)
+
+ post :create, :user => user.params, :format => :json
+
assert_nil session[:user_id]
- assert_response :redirect
- assert_redirected_to root_url
+ assert_json_response user
+ assert_response :success
end
test "should redirect to signup form on failed attempt" do
params = User.valid_attributes_hash.slice(:login)
user = User.new(params)
params.stringify_keys!
- User.expects(:create!).with(params).raises(VALIDATION_FAILED.new(user))
- post :create, :user => params
- assert_nil session[:user_id]
+ assert !user.valid?
+ User.expects(:create).with(params).returns(user)
+
+ post :create, :user => params, :format => :json
+
+ assert_json_error user.errors.messages
+ assert_response 422
+ end
+
+ test "should get edit view" do
+ user = find_record User
+
+ login user
+ get :edit, :id => user.id
+
assert_equal user, assigns[:user]
+ end
+
+ test "should process updated params" do
+ user = find_record User
+ user.expects(:update_attributes).with(user.params).returns(true)
+
+ login user
+ put :update, :user => user.params, :id => user.id, :format => :json
+
+ assert_equal user, assigns[:user]
+ assert_response 204
+ assert_equal " ", @response.body
+ end
+
+ test "admin can update user" do
+ user = find_record User
+ user.expects(:update_attributes).with(user.params).returns(true)
+
+ login :is_admin? => true
+ put :update, :user => user.params, :id => user.id, :format => :json
+
+ assert_equal user, assigns[:user]
+ assert_response 204
+ assert_equal " ", @response.body
+ end
+
+ test "admin can destroy user" do
+ user = find_record User
+ user.expects(:destroy)
+
+ login :is_admin? => true
+ delete :destroy, :id => user.id
+
+ assert_response :redirect
+ assert_redirected_to users_path
+ end
+
+ test "user can cancel account" do
+ user = find_record User
+ user.expects(:destroy)
+
+ login user
+ delete :destroy, :id => @current_user.id
+
assert_response :redirect
- assert_redirected_to new_user_path
+ assert_redirected_to login_path
+ end
+
+ test "non-admin can't destroy user" do
+ user = stub_record User
+
+ login
+ delete :destroy, :id => user.id
+
+ assert_access_denied
+ end
+
+ test "admin can list users" do
+ login :is_admin? => true
+ get :index
+
+ assert_response :success
+ assert assigns(:users)
+ end
+
+ test "non-admin can't list users" do
+ login
+ get :index
+
+ assert_access_denied
+ end
+
+ test "admin can autocomplete users" do
+ login :is_admin? => true
+ get :index, :format => :json
+
+ assert_response :success
+ assert assigns(:users)
+ end
+
+ test "admin can search users" do
+ login :is_admin? => true
+ get :index, :query => "a"
+
+ assert_response :success
+ assert assigns(:users)
end
end
diff --git a/users/test/integration/api/account_flow_test.rb b/users/test/integration/api/account_flow_test.rb
index 66de1e5..add12fe 100644
--- a/users/test/integration/api/account_flow_test.rb
+++ b/users/test/integration/api/account_flow_test.rb
@@ -1,23 +1,19 @@
require 'test_helper'
-class AccountFlowTest < ActionDispatch::IntegrationTest
+CONFIG_RU = (Rails.root + 'config.ru').to_s
+OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first
- # this test wraps the api and implements the interface the ruby-srp client.
- def handshake(login, aa)
- post "sessions", :login => login, 'A' => aa.to_s(16)
- assert_response :success
- response = JSON.parse(@response.body)
- if response['errors']
- raise RECORD_NOT_FOUND.new(response['errors'])
- else
- return response['B'].hex
- end
+class AccountFlowTest < ActiveSupport::TestCase
+ include Rack::Test::Methods
+ include Warden::Test::Helpers
+ include LeapWebCore::AssertResponses
+
+ def app
+ OUTER_APP
end
- def validate(m)
- put "sessions/" + @login, :client_auth => m.to_s(16)
- assert_response :success
- return JSON.parse(@response.body)
+ def teardown
+ Warden.test_reset!
end
def setup
@@ -38,13 +34,30 @@ class AccountFlowTest < ActionDispatch::IntegrationTest
@user.destroy if @user # make sure we can run this test again
end
+ # this test wraps the api and implements the interface the ruby-srp client.
+ def handshake(login, aa)
+ post "/sessions.json", :login => login, 'A' => aa.to_s(16), :format => :json
+ response = JSON.parse(last_response.body)
+ if response['errors']
+ raise RECORD_NOT_FOUND.new(response['errors'])
+ else
+ return response['B'].hex
+ end
+ end
+
+ def validate(m)
+ put "/sessions/" + @login + '.json', :client_auth => m.to_s(16), :format => :json
+ return JSON.parse(last_response.body)
+ end
+
test "signup response" do
- assert_json_response @user_params.slice(:login, :password_salt)
- assert_response :success
+ assert_json_response :login => @login, :ok => true
+ assert last_response.successful?
end
test "signup and login with srp via api" do
server_auth = @srp.authenticate(self)
+ assert last_response.successful?
assert_nil server_auth["errors"]
assert server_auth["M2"]
end
@@ -52,7 +65,8 @@ class AccountFlowTest < ActionDispatch::IntegrationTest
test "signup and wrong password login attempt" do
srp = SRP::Client.new(@login, "wrong password")
server_auth = srp.authenticate(self)
- assert_equal ["wrong password"], server_auth["errors"]['password']
+ assert_json_error :password => "wrong password"
+ assert !last_response.successful?
assert_nil server_auth["M2"]
end
@@ -62,6 +76,8 @@ class AccountFlowTest < ActionDispatch::IntegrationTest
assert_raises RECORD_NOT_FOUND do
server_auth = srp.authenticate(self)
end
+ assert_json_error :login => "could not be found"
+ assert !last_response.successful?
assert_nil server_auth
end
diff --git a/users/test/integration/api/python/flow_with_srp.py b/users/test/integration/api/python/flow_with_srp.py
index 0a11aec..b599252 100755
--- a/users/test/integration/api/python/flow_with_srp.py
+++ b/users/test/integration/api/python/flow_with_srp.py
@@ -16,7 +16,7 @@ def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
# using globals for a start
-server = 'http://springbok/1/'
+server = 'http://springbok.leap.se/1/'
login = id_generator()
password = id_generator() + id_generator()
diff --git a/users/test/support/auth_test_helper.rb b/users/test/support/auth_test_helper.rb
new file mode 100644
index 0000000..f3506ae
--- /dev/null
+++ b/users/test/support/auth_test_helper.rb
@@ -0,0 +1,35 @@
+module AuthTestHelper
+ include StubRecordHelper
+ extend ActiveSupport::Concern
+
+ # Controller will fetch current user from warden.
+ # Make it pick up our current_user
+ included do
+ setup do
+ request.env['warden'] ||= stub :user => nil
+ end
+ end
+
+ def login(user_or_method_hash = {})
+ @current_user = stub_record(User, user_or_method_hash)
+ unless @current_user.respond_to? :is_admin?
+ @current_user.stubs(:is_admin?).returns(false)
+ end
+ request.env['warden'] = stub :user => @current_user
+ return @current_user
+ end
+
+ def assert_access_denied(denied = true)
+ if denied
+ assert_equal({:alert => "Not authorized"}, flash.to_hash)
+ assert_redirected_to login_path
+ else
+ assert flash[:alert].blank?
+ end
+ end
+
+end
+
+class ActionController::TestCase
+ include AuthTestHelper
+end
diff --git a/users/test/support/stub_record_helper.rb b/users/test/support/stub_record_helper.rb
new file mode 100644
index 0000000..2e1a533
--- /dev/null
+++ b/users/test/support/stub_record_helper.rb
@@ -0,0 +1,41 @@
+module StubRecordHelper
+
+ # Will expect find_by_param or find_by_id to be called on klass and
+ # return the record given.
+ # 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(klass, record_or_method_hash = {})
+ record = stub_record(klass, record_or_method_hash)
+ finder = klass.respond_to?(:find_by_param) ? :find_by_param : :find_by_id
+ klass.expects(finder).with(record.to_param).returns(record)
+ return record
+ end
+
+ # Create a stub that has the usual functions of a database record.
+ # It won't fail on rendering a form for example.
+ #
+ # If the second parameter is a record we return the record itself.
+ # This way you can build functions that either take a record or a
+ # method hash to stub from. See find_record for an example.
+ def stub_record(klass, record_or_method_hash = {}, persisted = true)
+ if record_or_method_hash && !record_or_method_hash.is_a?(Hash)
+ return record_or_method_hash
+ end
+ stub record_params_for(klass, record_or_method_hash, persisted)
+ end
+
+ def record_params_for(klass, params = {}, persisted = true)
+ if klass.respond_to?(:valid_attributes_hash)
+ params.reverse_merge!(klass.valid_attributes_hash)
+ end
+ params[:params] = params.stringify_keys
+ params.reverse_merge! :id => "A123",
+ :to_param => "A123",
+ :class => klass,
+ :to_key => ['123'],
+ :to_json => %Q({"stub":"#{klass.name}"}),
+ :new_record? => !persisted,
+ :persisted? => persisted
+ end
+
+end
diff --git a/users/test/test_helper.rb b/users/test/test_helper.rb
index 08d4d41..52dff53 100644
--- a/users/test/test_helper.rb
+++ b/users/test/test_helper.rb
@@ -1,10 +1,9 @@
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../../test/dummy/config/environment', __FILE__)
require 'rails/test_help'
-require 'mocha'
+require 'mocha/setup'
Rails.backtrace_cleaner.remove_silencers!
# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
-
diff --git a/users/test/unit/user_test.rb b/users/test/unit/user_test.rb
index 822ef33..cce11c2 100644
--- a/users/test/unit/user_test.rb
+++ b/users/test/unit/user_test.rb
@@ -5,6 +5,7 @@ class UserTest < ActiveSupport::TestCase
include SRP::Util
setup do
@attribs = User.valid_attributes_hash
+ User.find_by_login(@attribs[:login]).try(:destroy)
@user = User.new(@attribs)
end
@@ -19,18 +20,18 @@ class UserTest < ActiveSupport::TestCase
end
test "test require alphanumerical for login" do
- @user.login = "qwär"
+ @user.login = "qw#r"
assert !@user.valid?
end
- test "find_by_param gets User by login" do
+ test "find_by_param gets User by id" do
@user.save
- assert_equal @user, User.find_by_param(@user.login)
+ assert_equal @user, User.find_by_param(@user.id)
@user.destroy
end
- test "to_param gives user login" do
- assert_equal @user.login, @user.to_param
+ test "to_param gives user id" do
+ assert_equal @user.id, @user.to_param
end
test "verifier returns number for the hex in password_verifier" do
diff --git a/users/test/unit/warden_strategy_secure_remote_password_test.rb b/users/test/unit/warden_strategy_secure_remote_password_test.rb
new file mode 100644
index 0000000..319809a
--- /dev/null
+++ b/users/test/unit/warden_strategy_secure_remote_password_test.rb
@@ -0,0 +1,63 @@
+class WardenStrategySecureRemotePasswordTest < ActiveSupport::TestCase
+
+# TODO : turn this into sth. real
+=begin
+ setup do
+ @user = stub :login => "me", :id => 123
+ @client_hex = 'a123'
+ @client_rnd = @client_hex.hex
+ @server_hex = 'b123'
+ @server_rnd = @server_hex.hex
+ @server_rnd_exp = 'e123'.hex
+ @salt = 'stub user salt'
+ @server_handshake = stub :aa => @client_rnd, :bb => @server_rnd, :b => @server_rnd_exp
+ @server_auth = 'adfe'
+ end
+
+
+ test "should perform handshake" do
+ @user.expects(:initialize_auth).
+ with(@client_rnd).
+ returns(@server_handshake)
+ @server_handshake.expects(:to_json).
+ returns({'B' => @server_hex, 'salt' => @salt}.to_json)
+ User.expects(:find_by_param).with(@user.login).returns(@user)
+ assert_equal @server_handshake, session[:handshake]
+ assert_response :success
+ assert_json_response :B => @server_hex, :salt => @salt
+ end
+
+ test "should report user not found" do
+ unknown = "login_that_does_not_exist"
+ User.expects(:find_by_param).with(unknown).raises(RECORD_NOT_FOUND)
+ post :create, :login => unknown
+ assert_response :success
+ assert_json_error "login" => ["unknown user"]
+ end
+
+ test "should authorize" do
+ session[:handshake] = @server_handshake
+ @server_handshake.expects(:authenticate!).
+ with(@client_rnd).
+ returns(@user)
+ @server_handshake.expects(:to_json).
+ returns({:M2 => @server_auth}.to_json)
+ post :update, :id => @user.login, :client_auth => @client_hex
+ assert_nil session[:handshake]
+ assert_json_response :M2 => @server_auth
+ assert_equal @user.id, session[:user_id]
+ end
+
+ test "should report wrong password" do
+ session[:handshake] = @server_handshake
+ @server_handshake.expects(:authenticate!).
+ with(@client_rnd).
+ raises(WRONG_PASSWORD)
+ post :update, :id => @user.login, :client_auth => @client_hex
+ assert_nil session[:handshake]
+ assert_nil session[:user_id]
+ assert_json_error "password" => ["wrong password"]
+ end
+
+=end
+end