diff options
Diffstat (limited to 'users')
46 files changed, 484 insertions, 413 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/srp b/users/app/assets/javascripts/srp -Subproject e7a0b830b8f994316a560001a9e7397422b184b +Subproject 926a5d5960db51903e33c8496487da59f9f4124 diff --git a/users/app/assets/javascripts/users.js b/users/app/assets/javascripts/users.js new file mode 100644 index 0000000..4195df8 --- /dev/null +++ b/users/app/assets/javascripts/users.js @@ -0,0 +1,78 @@ +(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) { +	if (field == 'base') { +	  alert_message(message.errors[field]); +	  next; +	} +        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 c9cc182..0000000 --- a/users/app/assets/javascripts/users.js.coffee +++ /dev/null @@ -1,63 +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 -    display_errors(message.errors) -  else -    alert(message) - -display_errors = (errors) -> -  clear_errors(); -  for field, error of errors -    if field == 'base' -      display_base_error(error); -    else -      display_field_error(field, error); - -clear_errors = -> -  $('#messages').empty(); - -display_field_error = (field, error) -> -  element = $('form input[name$="['+field+']"]') -  return unless element -  element.trigger('element:validate:fail.ClientSideValidations', error).data('valid', false) - -display_base_error = (message) -> -  messages = $('#messages') -  messages.append "<div class=\"alert alert-error\">" + message + "</div></div>" - -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 0dca29c..6daffdb 100644 --- a/users/app/controllers/controller_extension/authentication.rb +++ b/users/app/controllers/controller_extension/authentication.rb @@ -39,9 +39,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 dff1ed5..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 : login_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 756170b..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, :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 dded88c..b880887 100644 --- a/users/config/locales/en.yml +++ b/users/config/locales/en.yml @@ -1,44 +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" +  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 fd8869a..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) @@ -162,7 +107,7 @@ class UsersControllerTest < ActionController::TestCase      delete :destroy, :id => @current_user.id      assert_response :redirect -    assert_redirected_to login_path +    assert_redirected_to root_path    end    test "non-admin can't destroy user" do @@ -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 | 
