summaryrefslogtreecommitdiff
path: root/users
diff options
context:
space:
mode:
Diffstat (limited to 'users')
-rw-r--r--users/README.rdoc3
-rw-r--r--users/app/assets/javascripts/users.js74
-rw-r--r--users/app/assets/javascripts/users.js.coffee46
-rw-r--r--users/app/controllers/account_settings_controller.rb (renamed from users/Readme.md)0
-rw-r--r--users/app/controllers/controller_extension/authentication.rb15
-rw-r--r--users/app/controllers/email_aliases_controller.rb18
-rw-r--r--users/app/controllers/email_settings_controller.rb41
-rw-r--r--users/app/controllers/overviews_controller.rb9
-rw-r--r--users/app/controllers/sessions_controller.rb11
-rw-r--r--users/app/controllers/users_base_controller.rb18
-rw-r--r--users/app/controllers/users_controller.rb64
-rw-r--r--users/app/controllers/v1/users_controller.rb20
-rw-r--r--users/app/helpers/users_helper.rb36
-rw-r--r--users/app/models/user.rb1
-rw-r--r--users/app/views/email_settings/edit.html.haml38
-rw-r--r--users/app/views/emails/_email.html.haml2
-rw-r--r--users/app/views/overviews/show.html.haml18
-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.haml13
-rw-r--r--users/app/views/users/_cancel_account.html.haml9
-rw-r--r--users/app/views/users/_edit.html.haml37
-rw-r--r--users/app/views/users/_email_aliases.html.haml6
-rw-r--r--users/app/views/users/_email_field.html.haml1
-rw-r--r--users/app/views/users/_email_forward_field.html.haml1
-rw-r--r--users/app/views/users/_form.html.haml11
-rw-r--r--users/app/views/users/_legend_and_submit.html.haml4
-rw-r--r--users/app/views/users/_login_and_password_fields.html.haml2
-rw-r--r--users/app/views/users/_login_field.html.haml1
-rw-r--r--users/app/views/users/_password_fields.html.haml2
-rw-r--r--users/app/views/users/_public_key_field.html.haml1
-rw-r--r--users/app/views/users/_user.html.haml12
-rw-r--r--users/app/views/users/edit.html.haml18
-rw-r--r--users/app/views/users/index.html.haml30
-rw-r--r--users/app/views/users/new.html.haml23
-rw-r--r--users/app/views/users/show.html.haml32
-rw-r--r--users/config/locales/en.yml62
-rw-r--r--users/config/routes.rb6
-rw-r--r--users/leap_web_users.gemspec4
-rw-r--r--users/test/factories.rb4
-rw-r--r--users/test/functional/users_controller_test.rb63
-rw-r--r--users/test/functional/v1/users_controller_test.rb70
-rw-r--r--users/test/integration/api/account_flow_test.rb5
-rw-r--r--users/test/support/auth_test_helper.rb16
-rw-r--r--users/test/support/stub_record_helper.rb9
45 files changed, 479 insertions, 396 deletions
diff --git a/users/README.rdoc b/users/README.rdoc
deleted file mode 100644
index 9fb44c1..0000000
--- a/users/README.rdoc
+++ /dev/null
@@ -1,3 +0,0 @@
-= LeapWebUsers
-
-This project rocks and uses MIT-LICENSE. \ No newline at end of file
diff --git a/users/app/assets/javascripts/users.js b/users/app/assets/javascripts/users.js
new file mode 100644
index 0000000..1d05692
--- /dev/null
+++ b/users/app/assets/javascripts/users.js
@@ -0,0 +1,74 @@
+(function() {
+ //
+ // LOCAL FUNCTIONS
+ //
+
+ var poll_users, prevent_default, form_failed, form_passed;
+
+ prevent_default = function(event) {
+ return event.preventDefault();
+ };
+
+ poll_users = function(query, process) {
+ return $.get("/1/users.json", {
+ query: query
+ }).done(process);
+ };
+
+ //
+ // PUBLIC FUNCTIONS
+ //
+
+ srp.session = new srp.Session();
+
+ srp.signedUp = function() {
+ return srp.login();
+ };
+
+ srp.loggedIn = function() {
+ return window.location = '/';
+ };
+
+ srp.updated = function() {
+ return window.location = '/users/' + srp.session.id();
+ };
+
+ //
+ // if a json request returns an error, this function gets called and
+ // decorates the appropriate fields with the error messages.
+ //
+ srp.error = function(message) {
+ var element, error, field;
+ if ($.isPlainObject(message) && message.errors) {
+ for (field in message.errors) {
+ error = message.errors[field];
+ element = $('form input[name$="[' + field + ']"]');
+ if (!element) {
+ next;
+ }
+ element.trigger('element:validate:fail.ClientSideValidations', error).data('valid', false);
+ }
+ } else if (message.error) {
+ alert_message(message.error);
+ } else {
+ alert_message(JSON.stringify(message));
+ }
+ };
+
+ //
+ // INIT
+ //
+
+ $(document).ready(function() {
+ $('#new_user').submit(prevent_default);
+ $('#new_user').submit(srp.signup);
+ $('#new_session').submit(prevent_default);
+ $('#new_session').submit(srp.login);
+ $('#update_login_and_password').submit(prevent_default);
+ $('#update_login_and_password').submit(srp.update);
+ return $('#user-typeahead').typeahead({
+ source: poll_users
+ });
+ });
+
+}).call(this);
diff --git a/users/app/assets/javascripts/users.js.coffee b/users/app/assets/javascripts/users.js.coffee
deleted file mode 100644
index 955556c..0000000
--- a/users/app/assets/javascripts/users.js.coffee
+++ /dev/null
@@ -1,46 +0,0 @@
-preventDefault = (event) ->
- event.preventDefault()
-
-srp.session = new srp.Session()
-srp.signedUp = ->
- srp.login()
-
-srp.loggedIn = ->
- window.location = '/'
-
-#// TODO: not sure this is what we want.
-srp.updated = ->
- window.location = '/'
-
-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)
-
-followLocationHash = ->
- location = window.location.hash
- if location
- href_select = 'a[href="' + location + '"]'
- link = $(href_select)
- link.tab('show') if link
-
-$(document).ready ->
- followLocationHash()
- $('#new_user').submit preventDefault
- $('#new_user').submit srp.signup
- $('#new_session').submit preventDefault
- $('#new_session').submit srp.login
- $('.user.form.update_login_and_password').submit srp.update
- $('.user.form.update_login_and_password').submit preventDefault
- $('.user.typeahead').typeahead({source: pollUsers});
- $('a[data-toggle="tab"]').on('shown', ->
- $(ClientSideValidations.selectors.forms).validate()
- )
-
diff --git a/users/Readme.md b/users/app/controllers/account_settings_controller.rb
index e69de29..e69de29 100644
--- a/users/Readme.md
+++ b/users/app/controllers/account_settings_controller.rb
diff --git a/users/app/controllers/controller_extension/authentication.rb b/users/app/controllers/controller_extension/authentication.rb
index f0a6564..72df7a7 100644
--- a/users/app/controllers/controller_extension/authentication.rb
+++ b/users/app/controllers/controller_extension/authentication.rb
@@ -38,9 +38,18 @@ module ControllerExtension::Authentication
end
def access_denied
- # TODO: should we redirect to the root_url in either case, and have the root_url include the login screen (and also ability to create unauthenticated tickets) when no user is logged in?
- redirect_to login_url, :alert => "Not authorized" if !logged_in?
- redirect_to root_url, :alert => "Not authorized" if logged_in?
+ respond_to do |format|
+ format.html do
+ if logged_in?
+ redirect_to root_url, :alert => t(:not_authorized)
+ else
+ redirect_to login_url, :alert => t(:not_authorized_login)
+ end
+ end
+ format.json do
+ render :json => {'error' => t(:not_authorized)}, status: :unprocessable_entity
+ end
+ end
end
def admin?
diff --git a/users/app/controllers/email_aliases_controller.rb b/users/app/controllers/email_aliases_controller.rb
index 3b0d5ac..4628a7f 100644
--- a/users/app/controllers/email_aliases_controller.rb
+++ b/users/app/controllers/email_aliases_controller.rb
@@ -1,20 +1,12 @@
-class EmailAliasesController < ApplicationController
-
+class EmailAliasesController < UsersBaseController
before_filter :fetch_user
- respond_to :html
-
def destroy
@alias = @user.email_aliases.delete(params[:id])
- @user.save
- flash[:notice] = t(:email_alias_destroyed_successfully, :alias => @alias)
- redirect_to edit_user_path(@user, :anchor => :email)
+ if @user.save
+ flash[:notice] = t(:email_alias_destroyed_successfully, :alias => bold(@alias))
+ end
+ redirect_to edit_user_email_settings_path(@user)
end
- protected
-
- def fetch_user
- @user = User.find_by_param(params[:user_id])
- access_denied unless admin? or (@user == current_user)
- end
end
diff --git a/users/app/controllers/email_settings_controller.rb b/users/app/controllers/email_settings_controller.rb
new file mode 100644
index 0000000..f7d85be
--- /dev/null
+++ b/users/app/controllers/email_settings_controller.rb
@@ -0,0 +1,41 @@
+class EmailSettingsController < UsersBaseController
+
+ before_filter :authorize
+ before_filter :fetch_user
+
+ def edit
+ @email_alias = LocalEmail.new
+ end
+
+ def update
+ @user.attributes = cleanup_params(params[:user])
+ if @user.changed?
+ if @user.save
+ flash[:notice] = t(:changes_saved)
+ redirect
+ else
+ if @user.email_aliases.last && !@user.email_aliases.last.valid?
+ # display bad alias in text field:
+ @email_alias = @user.email_aliases.pop
+ end
+ render 'email_settings/edit'
+ end
+ else
+ redirect
+ end
+ end
+
+ private
+
+ def redirect
+ redirect_to edit_user_email_settings_url(@user)
+ end
+
+ def cleanup_params(user)
+ if !user['email_forward'].nil? && user['email_forward'].empty?
+ user.delete('email_forward') # don't allow "" as an email forward
+ end
+ user
+ end
+
+end
diff --git a/users/app/controllers/overviews_controller.rb b/users/app/controllers/overviews_controller.rb
new file mode 100644
index 0000000..52ce267
--- /dev/null
+++ b/users/app/controllers/overviews_controller.rb
@@ -0,0 +1,9 @@
+class OverviewsController < UsersBaseController
+
+ before_filter :authorize
+ before_filter :fetch_user
+
+ def show
+ end
+
+end
diff --git a/users/app/controllers/sessions_controller.rb b/users/app/controllers/sessions_controller.rb
index 01ecff6..d6c455b 100644
--- a/users/app/controllers/sessions_controller.rb
+++ b/users/app/controllers/sessions_controller.rb
@@ -22,4 +22,15 @@ class SessionsController < ApplicationController
logout
redirect_to root_path
end
+
+ #
+ # this is a bad hack, but user_overview_url(user) is not available
+ # also, this doesn't work because the redirect happens as a PUT. no idea why.
+ #
+ #Warden::Manager.after_authentication do |user, auth, opts|
+ # response = Rack::Response.new
+ # response.redirect "/users/#{user.id}/overview"
+ # throw :warden, response.finish
+ #end
+
end
diff --git a/users/app/controllers/users_base_controller.rb b/users/app/controllers/users_base_controller.rb
new file mode 100644
index 0000000..dc2fa16
--- /dev/null
+++ b/users/app/controllers/users_base_controller.rb
@@ -0,0 +1,18 @@
+#
+# common base class for all user related controllers
+#
+
+class UsersBaseController < ApplicationController
+
+ protected
+
+ def fetch_user
+ @user = User.find_by_param(params[:user_id] || params[:id])
+ if !@user && admin?
+ redirect_to users_url, :alert => t(:no_such_thing, :thing => 'user')
+ elsif !admin? && @user != current_user
+ access_denied
+ end
+ end
+
+end
diff --git a/users/app/controllers/users_controller.rb b/users/app/controllers/users_controller.rb
index 38a69e3..4ce970b 100644
--- a/users/app/controllers/users_controller.rb
+++ b/users/app/controllers/users_controller.rb
@@ -1,74 +1,42 @@
-class UsersController < ApplicationController
+#
+# This is an HTML-only controller. For the JSON-only controller, see v1/users_controller.rb
+#
- before_filter :authorize, :only => [:show, :edit, :destroy, :update]
+class UsersController < UsersBaseController
+
+ before_filter :authorize, :only => [:show, :edit, :update, :destroy]
before_filter :fetch_user, :only => [:show, :edit, :update, :destroy]
- before_filter :authorize_self, :only => [:update]
- before_filter :set_anchor, :only => [:edit, :update]
before_filter :authorize_admin, :only => [:index]
- respond_to :json, :html
+ respond_to :html
def index
if params[:query]
- @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ)
+ if @user = User.find_by_login(params[:query])
+ redirect_to user_overview_url(@user)
+ return
+ else
+ @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ)
+ end
else
@users = User.by_created_at.descending
end
- @users = @users.limit(10)
- respond_with @users.map(&:login).sort
+ @users = @users.limit(100)
end
def new
@user = User.new
end
- def create
- @user = User.create(params[:user])
- respond_with @user
+ def show
end
def edit
- @email_alias = LocalEmail.new
- end
-
- def update
- @user.attributes = params[:user]
- if @user.changed? and @user.save
- flash[:notice] = t(:user_updated_successfully)
- elsif @user.email_aliases.last and !@user.email_aliases.last.valid?
- @email_alias = @user.email_aliases.pop
- end
- respond_with @user, :location => edit_user_path(@user, :anchor => @anchor)
end
def destroy
@user.destroy
- redirect_to admin? ? users_path : root_path
+ redirect_to admin? ? users_url : root_url
end
- protected
-
- def fetch_user
- # authorize filter has been checked first, so won't get here unless authenticated
- @user = User.find_by_param(params[:id])
- if !@user and admin?
- redirect_to users_path, :alert => t(:no_such_thing, :thing => 'user')
- return
- end
- access_denied unless admin? or (@user == current_user)
- end
-
- def authorize_self
- # have already checked that authorized
- access_denied unless (@user == current_user)
- end
-
- def set_anchor
- @anchor = email_settings? ? :email : :account
- end
-
- def email_settings?
- params[:user] &&
- params[:user].keys.detect{|key| key.index('email')}
- end
end
diff --git a/users/app/controllers/v1/users_controller.rb b/users/app/controllers/v1/users_controller.rb
index 617bd4b..fda56f2 100644
--- a/users/app/controllers/v1/users_controller.rb
+++ b/users/app/controllers/v1/users_controller.rb
@@ -1,20 +1,32 @@
module V1
- class UsersController < ApplicationController
+ class UsersController < UsersBaseController
skip_before_filter :verify_authenticity_token
+ before_filter :fetch_user, :only => [:update]
before_filter :authorize, :only => [:update]
+ before_filter :authorize_admin, :only => [:index]
respond_to :json
+ def index
+ if params[:query]
+ @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ)
+ respond_with @users.map(&:login).sort
+ else
+ render :json => {'error' => 'query required', 'status' => :unprocessable_entity}
+ end
+ end
+
def create
@user = User.create(params[:user])
respond_with @user # return ID instead?
end
def update
- # For now, only allow public key to be updated via the API. Eventually we might want to store in a config what attributes can be updated via the API.
- @user = User.find_by_param(params[:id])
- @user.update_attributes params[:user].slice(:public_key) if params[:user].respond_to?(:slice)
+ @user.update_attributes params[:user]
+ if @user.valid?
+ flash[:notice] = t(:user_updated_successfully)
+ end
respond_with @user
end
diff --git a/users/app/helpers/users_helper.rb b/users/app/helpers/users_helper.rb
index 9feae62..f56faab 100644
--- a/users/app/helpers/users_helper.rb
+++ b/users/app/helpers/users_helper.rb
@@ -1,39 +1,7 @@
module UsersHelper
- def user_form_with(partial, options = {})
- user_form(options) do |f|
- options[:f] = f
- render :partial => partial,
- :layout => 'legend_and_submit',
- :locals => options
- end
- end
-
- def user_form(options = {})
- simple_form_for @user,
- :html => user_form_html_options(options),
- :validate => true do |f|
- yield f
- end
- end
-
- def user_form_html_options(options)
- { :class => user_form_html_classes(options).join(" "),
- :id => dom_id(@user, options[:legend])
- }
- end
-
- def user_form_html_classes(options)
- classes = %W/form-horizontal user form/
- classes << options[:legend]
- classes << (@user.new_record? ? 'new' : 'edit')
- classes.compact
- end
-
- def user_field(field)
- value = @user.send(field)
- value = value.to_s(:long) if field.end_with? '_at'
- value || 'not set'
+ def user_form_class(*classes)
+ (classes + ['user', 'form', (@user.new_record? ? 'new' : 'edit')]).compact.join(' ')
end
def wrapped(item, options = {})
diff --git a/users/app/models/user.rb b/users/app/models/user.rb
index 5c849f0..3459520 100644
--- a/users/app/models/user.rb
+++ b/users/app/models/user.rb
@@ -42,6 +42,7 @@ class User < CouchRest::Model::Base
:format => { :with => /.{8}.*/, :message => "needs to be at least 8 characters long" }
validates :email_forward,
+ :allow_blank => true,
:format => { :with => /\A(([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,}))?\Z/, :message => "needs to be a valid email address"}
timestamps!
diff --git a/users/app/views/email_settings/edit.html.haml b/users/app/views/email_settings/edit.html.haml
new file mode 100644
index 0000000..7757a31
--- /dev/null
+++ b/users/app/views/email_settings/edit.html.haml
@@ -0,0 +1,38 @@
+- form_options = {:url => user_email_settings_path(@user), :html => {:class => 'form-horizontal'}, :validate => true}
+- alias_error_class = @email_alias.username && !@email_alias.valid? ? 'error' : ''
+
+- content_for :head do
+ :css
+ table.aliases tr:first-child td {
+ border-top: none;
+ }
+
+= simple_form_for @user, form_options.dup do |f|
+ %legend= t(:email_aliases)
+ .control-group
+ %label.control-label= t(:current_aliases)
+ .controls
+ %table.table.table-condensed.no-header.slim.aliases
+ - if @user.email_aliases.any?
+ - @user.email_aliases.each do |email|
+ %tr
+ %td= email
+ %td= link_to(icon(:remove) + t(:remove), user_email_alias_path(@user, email), :method => :delete)
+ - else
+ %tr
+ %td{:colspan=>2}= t(:none)
+ .control-group{:class => alias_error_class}
+ %label.control-label= t(:add_email_alias)
+ .controls
+ = f.simple_fields_for :email_aliases, @email_alias do |e|
+ .input-append
+ = e.input_field :username
+ = e.submit t(:add), :class => 'btn'
+ = e.error :username
+
+= simple_form_for @user, form_options do |f|
+ %legend= t(:advanced_options)
+ = f.input :email_forward
+ = f.input :public_key, :as => :text, :hint => t(:use_ascii_key), :input_html => {:class => "full-width", :rows => 4}
+ .form-actions
+ = f.submit t(:save), :class => 'btn btn-primary'
diff --git a/users/app/views/emails/_email.html.haml b/users/app/views/emails/_email.html.haml
index c81b396..ea59cec 100644
--- a/users/app/views/emails/_email.html.haml
+++ b/users/app/views/emails/_email.html.haml
@@ -3,4 +3,4 @@
- if local_assigns[:with].try(:include?, :delete)
= link_to(user_email_alias_path(@user, email), :method => :delete) do
%i.icon-remove
-.clearfix
+
diff --git a/users/app/views/overviews/show.html.haml b/users/app/views/overviews/show.html.haml
new file mode 100644
index 0000000..b8ad814
--- /dev/null
+++ b/users/app/views/overviews/show.html.haml
@@ -0,0 +1,18 @@
+.overview
+
+ %h2.first= t(:overview_welcome, :username => @user.login)
+
+ - if admin?
+ %p
+ = t(:created)
+ = @user.created_at
+ %br
+ = t(:updated)
+ = @user.updated_at
+
+ %p= t(:overview_intro)
+
+ %ul.unstyled
+ %li= icon('user') + link_to(t(:overview_account), edit_user_path(@user))
+ %li= icon('envelope') + link_to(t(:overview_email), edit_user_email_settings_path(@user))
+ %li= icon('question-sign') + link_to(t(:overview_tickets), user_tickets_path(@user))
diff --git a/users/app/views/sessions/_admin_nav.html.haml b/users/app/views/sessions/_admin_nav.html.haml
deleted file mode 100644
index 14dfbdc..0000000
--- a/users/app/views/sessions/_admin_nav.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-%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
deleted file mode 100644
index ac85bb5..0000000
--- a/users/app/views/sessions/_nav.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-- 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, :method => :delete
-- 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 6743407..c915968 100644
--- a/users/app/views/sessions/new.html.haml
+++ b/users/app/views/sessions/new.html.haml
@@ -1,9 +1,10 @@
-.span8.offset2
+.span1
+.span9
= render :partial => 'users/warnings'
%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 \ No newline at end of file
+ = f.input :login, :required => false, :label => t(:username), :input_html => { :id => :srp_username }
+ = f.input :password, :required => false, :input_html => { :id => :srp_password }
+ .form-actions
+ = f.button :submit, :value => t(:login), :class => 'btn-primary'
+ = link_to t(:cancel), root_path, :class => 'btn'
diff --git a/users/app/views/users/_cancel_account.html.haml b/users/app/views/users/_cancel_account.html.haml
deleted file mode 100644
index c5ab36a..0000000
--- a/users/app/views/users/_cancel_account.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-%legend
- - if @user == current_user
- =t :cancel_account
- %small You will not be able to login anymore.
- - else
- =t :admin_cancel_account, :username => @user.login
-= link_to user_path(@user), :method => :delete, :confirm => t(:confirm_question), :class => "btn btn-danger" do
- %i.icon-remove.icon-white
- =t :remove_account
diff --git a/users/app/views/users/_edit.html.haml b/users/app/views/users/_edit.html.haml
new file mode 100644
index 0000000..adee8a4
--- /dev/null
+++ b/users/app/views/users/_edit.html.haml
@@ -0,0 +1,37 @@
+-#
+-# edit user form, used by both show and edit actions.
+-#
+
+-#
+-# CHANGE PASSWORD
+-#
+-# * everything about this form is handled with javascript. So take care when changing any ids.
+-# * the login is required when changing the password because it is used as part of the salt when calculating the password verifier.
+-# however, we don't want the user to change their login without generating a new key, so we hide the ui for this
+-# (although it works perfectly fine to change username if the field was visible).
+-#
+- form_options = {:url => '/not-used', :html => {:class => user_form_class('form-horizontal'), :id => 'update_login_and_password'}, :validate => true}
+= simple_form_for @user, form_options do |f|
+ %legend= t(:change_password)
+ = hidden_field_tag 'user_param', @user.to_param
+ .hidden
+ = f.input :login, :label => t(:username), :required => false, :input_html => {:id => :srp_username}
+ = f.input :password, :required => false, :validate => true, :input_html => { :id => :srp_password }
+ = f.input :password_confirmation, :required => false, :input_html => { :id => :srp_password_confirmation }
+ .control-group
+ .controls
+ = f.submit t(:save), :class => 'btn btn-primary'
+
+-#
+-# DESTROY ACCOUNT
+-#
+
+%legend
+ - if @user == current_user
+ = t(:destroy_my_account)
+ - else
+ = t(:admin_destroy_account, :username => @user.login)
+%p= t(:destroy_account_info)
+= link_to user_path(@user), :method => :delete, :confirm => t(:are_you_sure), :class => "btn btn-danger" do
+ %i.icon-remove.icon-white
+ = t(:destroy_my_account)
diff --git a/users/app/views/users/_email_aliases.html.haml b/users/app/views/users/_email_aliases.html.haml
deleted file mode 100644
index 6e32700..0000000
--- a/users/app/views/users/_email_aliases.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.span6
- %ul.unstyled
- = render @user.email_aliases, :as => :li, :with => [:delete]
-.clearfix
-= f.simple_fields_for :email_aliases, @email_alias do |e|
- = e.input :username, :placeholder => "alias"
diff --git a/users/app/views/users/_email_field.html.haml b/users/app/views/users/_email_field.html.haml
deleted file mode 100644
index edf62c9..0000000
--- a/users/app/views/users/_email_field.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= f.input :email, :placeholder => "me@#{APP_CONFIG[:domain]}"
diff --git a/users/app/views/users/_email_forward_field.html.haml b/users/app/views/users/_email_forward_field.html.haml
deleted file mode 100644
index 049428f..0000000
--- a/users/app/views/users/_email_forward_field.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= f.input :email_forward
diff --git a/users/app/views/users/_form.html.haml b/users/app/views/users/_form.html.haml
deleted file mode 100644
index cb51175..0000000
--- a/users/app/views/users/_form.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-- 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)
- = yield
- .pull-right
- = f.button :submit
- - unless only
- = link_to t(:cancel), root_url, :class => :btn
- .clearfix
diff --git a/users/app/views/users/_legend_and_submit.html.haml b/users/app/views/users/_legend_and_submit.html.haml
deleted file mode 100644
index 6fc0e4a..0000000
--- a/users/app/views/users/_legend_and_submit.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%legend= t(legend)
-=yield
-.pull-right= f.button :submit, :value => t(legend)
-.clearfix
diff --git a/users/app/views/users/_login_and_password_fields.html.haml b/users/app/views/users/_login_and_password_fields.html.haml
deleted file mode 100644
index 0baefc7..0000000
--- a/users/app/views/users/_login_and_password_fields.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-= render :partial => 'login_field', :locals => {:f => f}
-= render :partial => 'password_fields', :locals => {:f => f, :password_confirmation_hint => t(:can_retype_old_password)} \ No newline at end of file
diff --git a/users/app/views/users/_login_field.html.haml b/users/app/views/users/_login_field.html.haml
deleted file mode 100644
index 8ab36c3..0000000
--- a/users/app/views/users/_login_field.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= f.input :login, :input_html => { :id => :srp_username }
diff --git a/users/app/views/users/_password_fields.html.haml b/users/app/views/users/_password_fields.html.haml
deleted file mode 100644
index 47b7b07..0000000
--- a/users/app/views/users/_password_fields.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-= f.input :password, :required => true, :validate => true, :input_html => { :id => :srp_password }
-= f.input :password_confirmation, :required => true, :hint => local_assigns[:password_confirmation_hint], :input_html => { :id => :srp_password_confirmation }
diff --git a/users/app/views/users/_public_key_field.html.haml b/users/app/views/users/_public_key_field.html.haml
deleted file mode 100644
index af88cbd..0000000
--- a/users/app/views/users/_public_key_field.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= f.input :public_key, :as => :text, :hint => t(:use_ascii_key), :input_html => {:class => "span5", :rows => 20} # will want to tweak this to be wide enough (maybe smaller text?)
diff --git a/users/app/views/users/_user.html.haml b/users/app/views/users/_user.html.haml
index ca03d34..990d9cf 100644
--- a/users/app/views/users/_user.html.haml
+++ b/users/app/views/users/_user.html.haml
@@ -1,10 +1,4 @@
%tr
- %td= link_to user.login, user
- %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
+ %td= link_to user.login, user_overview_path(user)
+ %td= l(user.created_at, :format => :short)
+ %td= l(user.updated_at, :format => :short)
diff --git a/users/app/views/users/edit.html.haml b/users/app/views/users/edit.html.haml
index 97bd48d..08e9dc3 100644
--- a/users/app/views/users/edit.html.haml
+++ b/users/app/views/users/edit.html.haml
@@ -1,17 +1 @@
-.span8.offset2
- %h2=t :settings
- - tabs = []
- - content_for :account do
- = user_form_with 'login_and_password_fields', :legend => :update_login_and_password if @user == current_user
- = render 'cancel_account'
- - tabs << :account
- - if @user == current_user
- - content_for :email do
- %legend=t :email_address
- =t :associated_email
- = render @user.email_address, :as => :span
- = user_form_with 'public_key_field', :legend => :public_key
- = user_form_with 'email_forward_field', :legend => :forward_email
- = user_form_with 'email_aliases', :legend => :add_email_alias
- - tabs << :email
- = render 'tabs/tabs', :tabs => tabs
+= render 'edit' \ No newline at end of file
diff --git a/users/app/views/users/index.html.haml b/users/app/views/users/index.html.haml
index 9e6a179..fc1001e 100644
--- a/users/app/views/users/index.html.haml
+++ b/users/app/views/users/index.html.haml
@@ -1,17 +1,13 @@
-.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
+- @show_navigation = false
+
+= form_tag users_path, :method => :get, :class => "form-search" do
+ .input-append
+ = text_field_tag :query, params[:query], :id => 'user-typeahead', :class => "search-query", :autocomplete => :off
+ %button.btn{:type => :submit}= t(:search)
+
+%table.table.table-striped
+ %tr
+ %th= t(:username)
+ %th= t(:created)
+ %th= t(:updated)
+ = render @users.all
diff --git a/users/app/views/users/new.html.haml b/users/app/views/users/new.html.haml
index 80482b2..f8d14b5 100644
--- a/users/app/views/users/new.html.haml
+++ b/users/app/views/users/new.html.haml
@@ -1,12 +1,19 @@
-.span8.offset2
+-#
+-# This form is handled entirely by javascript, so take care when changing element ids.
+-#
+
+- form_options = {:url => '/not-used', :html => {:id => 'new_user', :class => user_form_class('form-horizontal')}, :validate => true}
+
+.span1
+.span9
= render :partial => 'warnings'
%h2=t :signup
- = user_form do |f|
+ = simple_form_for(@user, form_options) do |f|
%legend= t(:signup_message)
- = render :partial => 'login_field', :locals => {:f => f}
- = render :partial => 'password_fields', :locals => {:f => f}
- .pull-right
- = f.button :submit, :class => 'btn-primary'
- = link_to t(:cancel), root_url, :class => :btn
- .clearfix
+ = f.input :login, :label => t(:username), :required => false, :input_html => { :id => :srp_username }
+ = f.input :password, :required => false, :validate => true, :input_html => { :id => :srp_password }
+ = f.input :password_confirmation, :required => false, :validate => true, :input_html => { :id => :srp_password_confirmation }
+ .form-actions
+ = f.button :submit, :value => t(:signup), :class => 'btn btn-primary'
+ = link_to t(:cancel), root_url, :class => 'btn'
diff --git a/users/app/views/users/show.html.haml b/users/app/views/users/show.html.haml
index 056ed57..08e9dc3 100644
--- a/users/app/views/users/show.html.haml
+++ b/users/app/views/users/show.html.haml
@@ -1,31 +1 @@
-.span8.offset1
- %h2= @user.login
- .small
- = link_to 'edit', edit_user_path(@user)
- %dl.offset1
- - fields = ['login', 'email_address', 'created_at', 'updated_at', 'email_forward']
- - fields.each do |field|
- %dt
- = field.titleize
- %dd
- = user_field(field)
- %dt
- =t :email_aliases
- %dd
- - aliases = @user.email_aliases
- - if aliases.present?
- %ul.pull-left.unstyled
- = render aliases
- - else
- =t :none
- .clearfix
- %dt
- =t :most_recently_updated_tickets
- %dd
- - tix = @user.most_recent_tickets
- - if tix.present?
- %table
- %tbody
- = render @user.most_recent_tickets
- - else
- =t :none \ No newline at end of file
+= render 'edit' \ No newline at end of file
diff --git a/users/config/locales/en.yml b/users/config/locales/en.yml
index 32d183b..b880887 100644
--- a/users/config/locales/en.yml
+++ b/users/config/locales/en.yml
@@ -1,45 +1,63 @@
en:
- none: "None."
- signup: "Sign up"
+ email_settings: "Email Settings"
+ account_settings: "Account Settings"
+ logout: "Logout"
+ none: "None"
+ signup: "Sign Up"
signup_message: "Please create an account."
cancel: "Cancel"
- login: "Login"
- login_message: "Please login with your account."
+ login: "Log In"
+ username: "Username"
+ password: "Password"
+ change_password: "Change Password"
+ login_message: "Please log in with your account."
invalid_user_pass: "Not a valid username/password combination"
all_strategies_failed: "Could not understand your login attempt. Please first send your login and a SRP ephemeral value A and then send the client_auth in the same session (using cookies)."
update_login_and_password: "Update Login and Password"
- cancel_account: "Cancel your account"
- remove_account: "Remove Account"
- admin_cancel_account: "Cancel the account %{username}"
+ destroy_my_account: "Destroy my account"
+ destroy_account_info: "This will permanently destroy your account and all the data associated with it. Proceed with caution!"
+ admin_destroy_account: "Destroy the account %{username}"
set_email_address: "Set email address"
- forward_email: "Forward email"
- email_aliases: "Email aliases"
+ forward_email: "Forward Email"
+ email_aliases: "Email Aliases"
public_key: "Public Key"
- add_email_alias: "Add email alias"
- confirm_question: "Are you sure?"
+ add_email_alias: "Add Email Alias"
user_updated_successfully: "Settings have been updated successfully."
user_created_successfully: "Successfully created your account."
- email_alias_destroyed_successfully: "Successfully removed the alias '%{alias}'."
- use_ascii_key: "Use ASCII-armored PGP key"
- can_retype_old_password: "Retype your old password if you would like to keep that"
- associated_email: "The associated email address is"
- cookie_disabled_warning: "You have cookies disabled. You will not be able to login until you enable cookies."
- js_required: "We are sorry, but this doesn't work without javascript enabled. This is for security reasons."
+ email_alias_destroyed_successfully: "Removed email alias %{alias}."
+ use_ascii_key: "OpenPGP public key. Do not change this value unless you know what you are doing."
+ advanced_options: "Advanced Options"
+ not_authorized: "Sorry, but you are not authorized to perform that action."
+ not_authorized_login: "Please log in to perform that action."
+ search: "Search"
+ #
+ # overview
+ #
+ overview_welcome: "Welcome %{username}."
+ overview_intro: "From this user control panel, you can:"
+ overview_tickets: "Create and check support tickets."
+ overview_email: "Modify email settings."
+ overview_account: "Change your password or delete your account."
+
+ #
+ # rails
+ #
activemodel:
models:
- user:
+ user:
one: User
other: "%{count} Users"
simple_form:
labels:
user:
- email_forward: "Email forward"
+ email_forward: "Email Forward"
hints:
user:
- email_forward: "Forward all emails to this address"
- email: "Your leap web email address"
+ email_forward: >
+ Forward all email messages to this address. Messages will be encrypted before being forwarded.
+ This is an option for advanced users who are familar with OpenPGP.
placeholders:
user:
email_forward: "my_other_email@domain.net"
-
+
diff --git a/users/config/routes.rb b/users/config/routes.rb
index 9a9a40e..b6d583e 100644
--- a/users/config/routes.rb
+++ b/users/config/routes.rb
@@ -5,7 +5,7 @@ Rails.application.routes.draw do
defaults: {format: 'json'} } do
resources :sessions, :only => [:new, :create, :update]
delete "logout" => "sessions#destroy", :as => "logout"
- resources :users, :only => [:create, :update]
+ resources :users, :only => [:create, :update, :destroy, :index]
end
get "login" => "sessions#new", :as => "login"
@@ -13,7 +13,9 @@ Rails.application.routes.draw do
resources :sessions, :only => [:new, :create, :update]
get "signup" => "users#new", :as => "signup"
- resources :users do
+ resources :users, :except => [:create, :update] do
+ resource :overview, :only => [:show]
+ resource :email_settings, :only => [:edit, :update]
resources :email_aliases, :only => [:destroy], :id => /.*/
end
diff --git a/users/leap_web_users.gemspec b/users/leap_web_users.gemspec
index c57937f..013b44a 100644
--- a/users/leap_web_users.gemspec
+++ b/users/leap_web_users.gemspec
@@ -11,8 +11,8 @@ Gem::Specification.new do |s|
s.homepage = "http://www.leap.se"
s.summary = "User registration and authorization for the leap platform"
s.description = "This this plugin for the leap platform provides user signup and login. It uses Secure Remote Password for the authentication."
-
- s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "Readme.md"]
+
+ s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile"]
s.test_files = Dir["test/**/*"]
s.add_dependency "leap_web_core", LeapWeb::VERSION
diff --git a/users/test/factories.rb b/users/test/factories.rb
index 6b094bd..777704b 100644
--- a/users/test/factories.rb
+++ b/users/test/factories.rb
@@ -13,7 +13,9 @@ FactoryGirl.define do
end
factory :admin_user do
- is_admin? true
+ after(:build) do |admin|
+ admin.stubs(:is_admin?).returns(true)
+ end
end
end
end
diff --git a/users/test/functional/users_controller_test.rb b/users/test/functional/users_controller_test.rb
index 7f81c59..92a5f6c 100644
--- a/users/test/functional/users_controller_test.rb
+++ b/users/test/functional/users_controller_test.rb
@@ -79,33 +79,6 @@ class UsersControllerTest < ActionController::TestCase
assert_redirected_to users_path
end
- test "should create new user" do
- user_attribs = record_attributes_for :user
- user = User.new(user_attribs)
- User.expects(:create).with(user_attribs).returns(user)
-
-
- post :create, :user => user_attribs, :format => :json
-
-
- assert_nil session[:user_id]
- assert_json_response user
- assert_response :success
- end
-
- test "should redirect to signup form on failed attempt" do
- user_attribs = record_attributes_for :user
- user_attribs.slice!('login')
- user = User.new(user_attribs)
- assert !user.valid?
- User.expects(:create).with(user_attribs).returns(user)
-
- post :create, :user => user_attribs, :format => :json
-
- assert_json_error user.errors.messages
- assert_response 422
- end
-
test "should get edit view" do
user = find_record :user
@@ -115,34 +88,6 @@ class UsersControllerTest < ActionController::TestCase
assert_equal user, assigns[:user]
end
- test "user can change settings" do
- user = find_record :user
- changed_attribs = record_attributes_for :user_with_settings
- user.expects(:attributes=).with(changed_attribs)
- user.expects(:changed?).returns(true)
- user.expects(:save).returns(true)
-
- login user
- put :update, :user => changed_attribs, :id => user.id, :format => :json
-
- assert_equal user, assigns[:user]
- assert_response 204
- assert_equal " ", @response.body
- end
-
- # Eventually, admin will be able to update some user fields
- test "admin cannot update user" do
- user = find_record :user
- changed_attribs = record_attributes_for :user_with_settings
-
- login :is_admin? => true
- put :update, :user => changed_attribs, :id => user.id, :format => :json
-
- assert_response :redirect
- assert_access_denied
-
- end
-
test "admin can destroy user" do
user = find_record :user
user.expects(:destroy)
@@ -189,14 +134,6 @@ class UsersControllerTest < ActionController::TestCase
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"
diff --git a/users/test/functional/v1/users_controller_test.rb b/users/test/functional/v1/users_controller_test.rb
new file mode 100644
index 0000000..0d44e50
--- /dev/null
+++ b/users/test/functional/v1/users_controller_test.rb
@@ -0,0 +1,70 @@
+require 'test_helper'
+
+class V1::UsersControllerTest < ActionController::TestCase
+
+ test "user can change settings" do
+ user = find_record :user
+ changed_attribs = record_attributes_for :user_with_settings
+ user.expects(:update_attributes).with(changed_attribs)
+
+ login user
+ put :update, :user => changed_attribs, :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
+ changed_attribs = record_attributes_for :user_with_settings
+ user.expects(:update_attributes).with(changed_attribs)
+
+ login :is_admin? => true
+ put :update, :user => changed_attribs, :id => user.id, :format => :json
+
+ assert_equal user, assigns[:user]
+ assert_response 204
+ end
+
+ test "user cannot update other user" do
+ user = find_record :user
+ login
+ put :update, :user => record_attributes_for(:user_with_settings), :id => user.id, :format => :json
+ assert_access_denied
+ end
+
+ test "should create new user" do
+ user_attribs = record_attributes_for :user
+ user = User.new(user_attribs)
+ User.expects(:create).with(user_attribs).returns(user)
+
+ post :create, :user => user_attribs, :format => :json
+
+ assert_nil session[:user_id]
+ assert_json_response user
+ assert_response :success
+ end
+
+ test "should redirect to signup form on failed attempt" do
+ user_attribs = record_attributes_for :user
+ user_attribs.slice!('login')
+ user = User.new(user_attribs)
+ assert !user.valid?
+ User.expects(:create).with(user_attribs).returns(user)
+
+ post :create, :user => user_attribs, :format => :json
+
+ assert_json_error user.errors.messages
+ assert_response 422
+ end
+
+ test "admin can autocomplete users" do
+ login :is_admin? => true
+ get :index, :query => 'a', :format => :json
+
+ 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 1698105..2e45367 100644
--- a/users/test/integration/api/account_flow_test.rb
+++ b/users/test/integration/api/account_flow_test.rb
@@ -88,10 +88,11 @@ class AccountFlowTest < RackTest
server_auth = @srp.authenticate(self)
test_public_key = 'asdlfkjslfdkjasd'
original_login = @user.login
- put "http://api.lvh.me:3000/1/users/" + @user.id + '.json', :user => {:public_key => test_public_key, :login => 'failed_login_name'}, :format => :json
+ new_login = 'zaph'
+ put "http://api.lvh.me:3000/1/users/" + @user.id + '.json', :user => {:public_key => test_public_key, :login => new_login}, :format => :json
@user.reload
assert_equal test_public_key, @user.public_key
- assert_equal original_login, @user.login
+ assert_equal new_login, @user.login
# eventually probably want to remove most of this into a non-integration functional test
# should not overwrite public key:
put "http://api.lvh.me:3000/1/users/" + @user.id + '.json', :user => {:blee => :blah}, :format => :json
diff --git a/users/test/support/auth_test_helper.rb b/users/test/support/auth_test_helper.rb
index c0fcf3a..555b5db 100644
--- a/users/test/support/auth_test_helper.rb
+++ b/users/test/support/auth_test_helper.rb
@@ -20,10 +20,18 @@ module AuthTestHelper
def assert_access_denied(denied = true, logged_in = true)
if denied
- assert_equal({:alert => "Not authorized"}, flash.to_hash)
- # todo: eventually probably eliminate separate conditions
- assert_redirected_to login_path if !logged_in
- assert_redirected_to root_path if logged_in
+ if @response.content_type == 'application/json'
+ assert_json_response('error' => I18n.t(:not_authorized))
+ assert_response :unprocessable_entity
+ else
+ if logged_in
+ assert_equal({:alert => I18n.t(:not_authorized)}, flash.to_hash)
+ assert_redirected_to root_url
+ else
+ assert_equal({:alert => I18n.t(:not_authorized_login)}, flash.to_hash)
+ assert_redirected_to login_url
+ end
+ end
else
assert flash[:alert].blank?
end
diff --git a/users/test/support/stub_record_helper.rb b/users/test/support/stub_record_helper.rb
index 168a827..8aa1973 100644
--- a/users/test/support/stub_record_helper.rb
+++ b/users/test/support/stub_record_helper.rb
@@ -1,15 +1,18 @@
module StubRecordHelper
- # Will expect find_by_param or find_by_id to be called on klass and
+ #
+ # We will stub 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(factory, attribs_hash = {})
- attribs_hash.reverse_merge!(:id => Random.rand(10000).to_s)
+ attribs_hash = attribs_hash.reverse_merge(:id => Random.rand(10000).to_s)
record = stub_record factory, attribs_hash
klass = record.class
finder = klass.respond_to?(:find_by_param) ? :find_by_param : :find
- klass.expects(finder).with(record.to_param.to_s).returns(record)
+ klass.stubs(finder).with(record.to_param.to_s).returns(record)
return record
end