Feature #19851 » 20150515_sudo_mode.diff
| app/controllers/application_controller.rb | ||
|---|---|---|
| 59 | 59 | 
    include Redmine::MenuManager::MenuController  | 
| 60 | 60 | 
    helper Redmine::MenuManager::MenuHelper  | 
| 61 | 61 | |
| 62 | 
    include Redmine::SudoMode::Controller  | 
|
| 63 | ||
| 62 | 64 | 
    def session_expiration  | 
| 63 | 65 | 
    if session[:user_id]  | 
| 64 | 66 | 
    if session_expired? && !try_to_autologin  | 
| app/controllers/auth_sources_controller.rb | ||
|---|---|---|
| 21 | 21 | |
| 22 | 22 | 
    before_filter :require_admin  | 
| 23 | 23 | 
    before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy]  | 
| 24 | 
    require_sudo_mode :update, :destroy  | 
|
| 24 | 25 | |
| 25 | 26 | 
    def index  | 
| 26 | 27 | 
    @auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25  | 
| app/controllers/email_addresses_controller.rb | ||
|---|---|---|
| 18 | 18 | 
    class EmailAddressesController < ApplicationController  | 
| 19 | 19 | 
    before_filter :find_user, :require_admin_or_current_user  | 
| 20 | 20 | 
    before_filter :find_email_address, :only => [:update, :destroy]  | 
| 21 | 
    require_sudo_mode :create, :update, :destroy  | 
|
| 21 | 22 | |
| 22 | 23 | 
    def index  | 
| 23 | 24 | 
    @addresses = @user.email_addresses.order(:id).where(:is_default => false).to_a  | 
| app/controllers/groups_controller.rb | ||
|---|---|---|
| 22 | 22 | 
    before_filter :find_group, :except => [:index, :new, :create]  | 
| 23 | 23 | 
    accept_api_auth :index, :show, :create, :update, :destroy, :add_users, :remove_user  | 
| 24 | 24 | |
| 25 | 
    require_sudo_mode :add_users, :remove_user, :create, :update, :destroy, :edit_membership, :destroy_membership  | 
|
| 26 | ||
| 25 | 27 | 
    helper :custom_fields  | 
| 26 | 28 | 
    helper :principal_memberships  | 
| 27 | 29 | |
| app/controllers/members_controller.rb | ||
|---|---|---|
| 23 | 23 | 
    before_filter :authorize  | 
| 24 | 24 | 
    accept_api_auth :index, :show, :create, :update, :destroy  | 
| 25 | 25 | |
| 26 | 
    require_sudo_mode :create, :update, :destroy  | 
|
| 27 | ||
| 26 | 28 | 
    def index  | 
| 27 | 29 | 
    @offset, @limit = api_offset_and_limit  | 
| 28 | 30 | 
    @member_count = @project.member_principals.count  | 
| app/controllers/my_controller.rb | ||
|---|---|---|
| 20 | 20 | 
    # let user change user's password when user has to  | 
| 21 | 21 | 
    skip_before_filter :check_password_change, :only => :password  | 
| 22 | 22 | |
| 23 | 
    require_sudo_mode :account, only: :post  | 
|
| 24 | 
    require_sudo_mode :reset_rss_key, :reset_api_key, :show_api_key, :destroy  | 
|
| 25 | ||
| 23 | 26 | 
    helper :issues  | 
| 24 | 27 | 
    helper :users  | 
| 25 | 28 | 
    helper :custom_fields  | 
| ... | ... | |
| 123 | 126 | 
    redirect_to my_account_path  | 
| 124 | 127 | 
    end  | 
| 125 | 128 | |
| 129 | 
    def show_api_key  | 
|
| 130 | 
    @user = User.current  | 
|
| 131 | 
    end  | 
|
| 132 | ||
| 126 | 133 | 
    # Create a new API key  | 
| 127 | 134 | 
    def reset_api_key  | 
| 128 | 135 | 
    if request.post?  | 
| app/controllers/projects_controller.rb | ||
|---|---|---|
| 25 | 25 | 
    before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]  | 
| 26 | 26 | 
    accept_rss_auth :index  | 
| 27 | 27 | 
    accept_api_auth :index, :show, :create, :update, :destroy  | 
| 28 | 
    require_sudo_mode :destroy  | 
|
| 28 | 29 | |
| 29 | 30 | 
    after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|  | 
| 30 | 31 | 
    if controller.request.post?  | 
| app/controllers/roles_controller.rb | ||
|---|---|---|
| 23 | 23 | 
    before_filter :find_role, :only => [:show, :edit, :update, :destroy]  | 
| 24 | 24 | 
    accept_api_auth :index, :show  | 
| 25 | 25 | |
| 26 | 
    require_sudo_mode :create, :update, :destroy  | 
|
| 27 | ||
| 26 | 28 | 
    def index  | 
| 27 | 29 | 
    respond_to do |format|  | 
| 28 | 30 | 
          format.html {
   | 
| app/controllers/settings_controller.rb | ||
|---|---|---|
| 23 | 23 | |
| 24 | 24 | 
    before_filter :require_admin  | 
| 25 | 25 | |
| 26 | 
    require_sudo_mode :index, :edit, :plugin  | 
|
| 27 | ||
| 26 | 28 | 
    def index  | 
| 27 | 29 | 
    edit  | 
| 28 | 30 | 
    render :action => 'edit'  | 
| app/controllers/users_controller.rb | ||
|---|---|---|
| 28 | 28 | 
    include CustomFieldsHelper  | 
| 29 | 29 | 
    helper :principal_memberships  | 
| 30 | 30 | |
| 31 | 
    require_sudo_mode :create, :update, :destroy  | 
|
| 32 | ||
| 31 | 33 | 
    def index  | 
| 32 | 34 | 
    sort_init 'login', 'asc'  | 
| 33 | 35 | 
    sort_update %w(login firstname lastname admin created_on last_login_on)  | 
| app/helpers/application_helper.rb | ||
|---|---|---|
| 25 | 25 | 
    include Redmine::I18n  | 
| 26 | 26 | 
    include GravatarHelper::PublicMethods  | 
| 27 | 27 | 
    include Redmine::Pagination::Helper  | 
| 28 | 
    include Redmine::SudoMode::Helper  | 
|
| 28 | 29 | |
| 29 | 30 | 
    extend Forwardable  | 
| 30 | 31 | 
    def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter  | 
| app/views/my/_sidebar.html.erb | ||
|---|---|---|
| 21 | 21 | 
    <% if Setting.rest_api_enabled? %>  | 
| 22 | 22 | 
    <h4><%= l(:label_api_access_key) %></h4>  | 
| 23 | 23 | 
    <div>  | 
| 24 | 
      <%= link_to_function(l(:button_show), "$('#api-access-key').toggle();")%>
   | 
|
| 25 | 
      <pre id='api-access-key' class='autoscroll'><%= @user.api_key %></pre>
   | 
|
| 24 | 
      <%= link_to l(:button_show), {:action => 'show_api_key'}, :remote => true %>
   | 
|
| 25 | 
    <pre id='api-access-key' class='autoscroll'></pre>  | 
|
| 26 | 26 | 
    </div>  | 
| 27 | 27 | 
    <%= javascript_tag("$('#api-access-key').hide();") %>
   | 
| 28 | 28 | 
    <p>  | 
| app/views/my/show_api_key.html.erb | ||
|---|---|---|
| 1 | 
    <h2><%= l :label_api_access_key %></h2>  | 
|
| 2 | ||
| 3 | 
    <div class="box">  | 
|
| 4 | 
    <pre><%= @user.api_key %></pre>  | 
|
| 5 | 
    </div>  | 
|
| 6 | ||
| 7 | 
    <p><%= link_to l(:button_back), action: 'account' %></p>  | 
|
| 8 | ||
| 9 | ||
| 10 | ||
| app/views/my/show_api_key.js.erb | ||
|---|---|---|
| 1 | 
    $('#api-access-key').html('<%= escape_javascript @user.api_key %>').toggle();
   | 
|
| app/views/sudo_mode/_new_modal.html.erb | ||
|---|---|---|
| 1 | 
    <h3 class="title"><%= l(:label_password_required) %></h3>  | 
|
| 2 | 
    <%= form_tag({}, remote: true) do %>
   | 
|
| 3 | ||
| 4 | 
    <%= hidden_field_tag '_method', request.request_method %>  | 
|
| 5 | 
    <%= hash_to_hidden_fields @sudo_form.original_fields %>  | 
|
| 6 | 
    <%= render_flash_messages %>  | 
|
| 7 | 
    <div class="box tabular">  | 
|
| 8 | 
    <p>  | 
|
| 9 | 
    <label for="sudo_password"><%= l :field_password %><span class="required">*</span></label>  | 
|
| 10 | 
    <%= password_field_tag :sudo_password, nil, size: 25 %>  | 
|
| 11 | 
    </p>  | 
|
| 12 | 
    </div>  | 
|
| 13 | ||
| 14 | 
    <p class="buttons">  | 
|
| 15 | 
    <%= submit_tag l(:button_confirm_password), onclick: "hideModal(this);" %>  | 
|
| 16 | 
    <%= submit_tag l(:button_cancel), name: nil, onclick: "hideModal(this);", type: 'button' %>  | 
|
| 17 | 
    </p>  | 
|
| 18 | 
    <% end %>  | 
|
| 19 | ||
| app/views/sudo_mode/new.html.erb | ||
|---|---|---|
| 1 | 
    <h2><%= l :label_password_required %></h2>  | 
|
| 2 | 
    <%= form_tag({}, class: 'tabular') do %>
   | 
|
| 3 | ||
| 4 | 
    <%= hidden_field_tag '_method', request.request_method %>  | 
|
| 5 | 
    <%= hash_to_hidden_fields @sudo_form.original_fields %>  | 
|
| 6 | ||
| 7 | 
    <div class="box">  | 
|
| 8 | 
    <p>  | 
|
| 9 | 
    <label for="sudo_password"><%= l :field_password %><span class="required">*</span></label>  | 
|
| 10 | 
    <%= password_field_tag :sudo_password, nil, size: 25 %>  | 
|
| 11 | 
    </p>  | 
|
| 12 | 
    </div>  | 
|
| 13 | 
    <%= submit_tag l(:button_confirm_password) %>  | 
|
| 14 | 
    <% end %>  | 
|
| 15 | 
    <%= javascript_tag "$('#sudo_password').focus();" %>
   | 
|
| 16 | ||
| 17 | ||
| app/views/sudo_mode/new.js.erb | ||
|---|---|---|
| 1 | 
    $('#ajax-modal').html('<%= escape_javascript render partial: 'sudo_mode/new_modal' %>');
   | 
|
| 2 | 
    showModal('ajax-modal', '400px');
   | 
|
| 3 | 
    $('#sudo_password').focus();
   | 
|
| 4 | ||
| config/locales/de.yml | ||
|---|---|---|
| 163 | 163 | 
    button_close: Schließen  | 
| 164 | 164 | 
    button_collapse_all: Alle einklappen  | 
| 165 | 165 | 
    button_configure: Konfigurieren  | 
| 166 | 
    button_confirm_password: Kennwort bestätigen  | 
|
| 166 | 167 | 
    button_copy: Kopieren  | 
| 167 | 168 | 
    button_copy_and_follow: Kopieren und Ticket anzeigen  | 
| 168 | 169 | 
    button_create: Anlegen  | 
| ... | ... | |
| 670 | 671 | 
    label_overview: Übersicht  | 
| 671 | 672 | 
    label_parent_revision: Vorgänger  | 
| 672 | 673 | 
    label_password_lost: Kennwort vergessen  | 
| 674 | 
    label_password_required: Bitte geben Sie Ihr Kennwort ein  | 
|
| 673 | 675 | 
    label_permissions: Berechtigungen  | 
| 674 | 676 | 
    label_permissions_report: Berechtigungsübersicht  | 
| 675 | 677 | 
    label_personalize_page: Diese Seite anpassen  | 
| config/locales/en.yml | ||
|---|---|---|
| 553 | 553 | 
    label_register: Register  | 
| 554 | 554 | 
    label_login_with_open_id_option: or login with OpenID  | 
| 555 | 555 | 
    label_password_lost: Lost password  | 
| 556 | 
    label_password_required: Confirm your password to continue  | 
|
| 556 | 557 | 
    label_home: Home  | 
| 557 | 558 | 
    label_my_page: My page  | 
| 558 | 559 | 
    label_my_account: My account  | 
| ... | ... | |
| 980 | 981 | 
    button_reset: Reset  | 
| 981 | 982 | 
    button_rename: Rename  | 
| 982 | 983 | 
    button_change_password: Change password  | 
| 984 | 
    button_confirm_password: Confirm password  | 
|
| 983 | 985 | 
    button_copy: Copy  | 
| 984 | 986 | 
    button_copy_and_follow: Copy and follow  | 
| 985 | 987 | 
    button_annotate: Annotate  | 
| config/routes.rb | ||
|---|---|---|
| 67 | 67 | 
    match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page  | 
| 68 | 68 | 
    match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post  | 
| 69 | 69 | 
    match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post  | 
| 70 | 
    match 'my/show_api_key', :controller => 'my', :action => 'show_api_key', :via => :get  | 
|
| 70 | 71 | 
    match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]  | 
| 71 | 72 | 
    match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get  | 
| 72 | 73 | 
    match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post  | 
| lib/redmine/sudo_mode.rb | ||
|---|---|---|
| 1 | 
    require 'active_support/core_ext/object/to_query'  | 
|
| 2 | 
    require 'rack/utils'  | 
|
| 3 | ||
| 4 | 
    module Redmine  | 
|
| 5 | 
    module SudoMode  | 
|
| 6 | ||
| 7 | 
    # timespan after which sudo mode expires when unused.  | 
|
| 8 | 
    MAX_INACTIVITY = 15.minutes  | 
|
| 9 | ||
| 10 | ||
| 11 | 
    class SudoRequired < StandardError  | 
|
| 12 | 
    end  | 
|
| 13 | ||
| 14 | ||
| 15 | 
    class Form  | 
|
| 16 | 
    include ActiveModel::Validations  | 
|
| 17 | ||
| 18 | 
    attr_accessor :password, :original_fields  | 
|
| 19 | 
    validate :check_password  | 
|
| 20 | ||
| 21 | 
    def initialize(password = nil)  | 
|
| 22 | 
    self.password = password  | 
|
| 23 | 
    end  | 
|
| 24 | ||
| 25 | 
    def check_password  | 
|
| 26 | 
    unless password.present? && User.current.check_password?(password)  | 
|
| 27 | 
    errors[:password] << :invalid  | 
|
| 28 | 
    end  | 
|
| 29 | 
    end  | 
|
| 30 | 
    end  | 
|
| 31 | ||
| 32 | ||
| 33 | 
    module Helper  | 
|
| 34 | 
    # Represents params data from hash as hidden fields  | 
|
| 35 | 
    #  | 
|
| 36 | 
    # taken from https://github.com/brianhempel/hash_to_hidden_fields  | 
|
| 37 | 
    def hash_to_hidden_fields(hash)  | 
|
| 38 | 
            cleaned_hash = hash.reject { |k, v| v.nil? }
   | 
|
| 39 | 
    pairs = cleaned_hash.to_query.split(Rack::Utils::DEFAULT_SEP)  | 
|
| 40 | 
    tags = pairs.map do |pair|  | 
|
| 41 | 
              key, value = pair.split('=', 2).map { |str| Rack::Utils.unescape(str) }
   | 
|
| 42 | 
    hidden_field_tag(key, value)  | 
|
| 43 | 
    end  | 
|
| 44 | 
            tags.join("\n").html_safe
   | 
|
| 45 | 
    end  | 
|
| 46 | 
    end  | 
|
| 47 | ||
| 48 | ||
| 49 | 
    module Controller  | 
|
| 50 | 
    extend ActiveSupport::Concern  | 
|
| 51 | ||
| 52 | 
    included do  | 
|
| 53 | 
    around_filter :sudo_mode  | 
|
| 54 | 
    end  | 
|
| 55 | ||
| 56 | 
    # Sudo mode Around Filter  | 
|
| 57 | 
    #  | 
|
| 58 | 
    # Checks the 'last used' timestamp from session and sets the  | 
|
| 59 | 
    # SudoMode::active? flag accordingly.  | 
|
| 60 | 
    #  | 
|
| 61 | 
    # After the request refreshes the timestamp if sudo mode was used during  | 
|
| 62 | 
    # this request.  | 
|
| 63 | 
    def sudo_mode  | 
|
| 64 | 
    if api_request?  | 
|
| 65 | 
    SudoMode.disable!  | 
|
| 66 | 
    elsif sudo_timestamp_valid?  | 
|
| 67 | 
    SudoMode.active!  | 
|
| 68 | 
    end  | 
|
| 69 | 
    yield  | 
|
| 70 | 
    update_sudo_timestamp! if SudoMode.was_used?  | 
|
| 71 | 
    end  | 
|
| 72 | ||
| 73 | 
    # This renders the sudo mode form / handles sudo form submission.  | 
|
| 74 | 
    #  | 
|
| 75 | 
    # Call this method in controller actions if sudo permissions are required  | 
|
| 76 | 
    # for processing this request. This approach is good in cases where the  | 
|
| 77 | 
    # action needs to be protected in any case or where the check is simple.  | 
|
| 78 | 
    #  | 
|
| 79 | 
    # In cases where this decision depends on complex conditions in the model,  | 
|
| 80 | 
    # consider the declarative approach using the require_sudo_mode class  | 
|
| 81 | 
    # method and a corresponding declaration in the model that causes it to throw  | 
|
| 82 | 
    # a SudoRequired Error when necessary.  | 
|
| 83 | 
    #  | 
|
| 84 | 
    # All parameter names given are included as hidden fields to be resubmitted  | 
|
| 85 | 
    # along with the password.  | 
|
| 86 | 
    #  | 
|
| 87 | 
    # Returns true when processing the action should continue, false otherwise.  | 
|
| 88 | 
    # If false is returned, render has already been called for display of the  | 
|
| 89 | 
    # password form.  | 
|
| 90 | 
    #  | 
|
| 91 | 
    # if @user.mail_changed?  | 
|
| 92 | 
    # require_sudo_mode :user or return  | 
|
| 93 | 
    # end  | 
|
| 94 | 
    #  | 
|
| 95 | 
    def require_sudo_mode(*param_names)  | 
|
| 96 | 
    return true if SudoMode.active?  | 
|
| 97 | ||
| 98 | 
    if param_names.blank?  | 
|
| 99 | 
    param_names = params.keys - %w(id action controller sudo_password)  | 
|
| 100 | 
    end  | 
|
| 101 | ||
| 102 | 
    process_sudo_form  | 
|
| 103 | ||
| 104 | 
    if SudoMode.active?  | 
|
| 105 | 
    true  | 
|
| 106 | 
    else  | 
|
| 107 | 
    render_sudo_form param_names  | 
|
| 108 | 
    false  | 
|
| 109 | 
    end  | 
|
| 110 | 
    end  | 
|
| 111 | ||
| 112 | 
    # display the sudo password form  | 
|
| 113 | 
    def render_sudo_form(param_names)  | 
|
| 114 | 
    @sudo_form ||= SudoMode::Form.new  | 
|
| 115 | 
    @sudo_form.original_fields = params.slice( *param_names )  | 
|
| 116 | 
    # a simple 'render "sudo_mode/new"' works when used directly inside an  | 
|
| 117 | 
    # action, but not when called from a before_filter:  | 
|
| 118 | 
    respond_to do |format|  | 
|
| 119 | 
              format.html { render 'sudo_mode/new' }
   | 
|
| 120 | 
              format.js   { render 'sudo_mode/new' }
   | 
|
| 121 | 
    end  | 
|
| 122 | 
    end  | 
|
| 123 | ||
| 124 | 
    # handle sudo password form submit  | 
|
| 125 | 
    def process_sudo_form  | 
|
| 126 | 
    if params[:sudo_password]  | 
|
| 127 | 
    @sudo_form = SudoMode::Form.new(params[:sudo_password])  | 
|
| 128 | 
    if @sudo_form.valid?  | 
|
| 129 | 
    SudoMode.active!  | 
|
| 130 | 
    else  | 
|
| 131 | 
    flash.now[:error] = l(:notice_account_wrong_password)  | 
|
| 132 | 
    end  | 
|
| 133 | 
    end  | 
|
| 134 | 
    end  | 
|
| 135 | ||
| 136 | 
    def sudo_timestamp_valid?  | 
|
| 137 | 
    session[:sudo_timestamp].to_i > MAX_INACTIVITY.ago.to_i  | 
|
| 138 | 
    end  | 
|
| 139 | ||
| 140 | 
    def update_sudo_timestamp!(new_value = Time.now.to_i)  | 
|
| 141 | 
    session[:sudo_timestamp] = new_value  | 
|
| 142 | 
    end  | 
|
| 143 | ||
| 144 | 
    # Before Filter which is used by the require_sudo_mode class method.  | 
|
| 145 | 
    class SudoRequestFilter < Struct.new(:parameters, :request_methods)  | 
|
| 146 | 
    def before(controller)  | 
|
| 147 | 
    method_matches = request_methods.blank? || request_methods.include?(controller.request.method_symbol)  | 
|
| 148 | 
    if SudoMode.possible? && method_matches  | 
|
| 149 | 
    controller.require_sudo_mode( *parameters )  | 
|
| 150 | 
    else  | 
|
| 151 | 
    true  | 
|
| 152 | 
    end  | 
|
| 153 | 
    end  | 
|
| 154 | 
    end  | 
|
| 155 | ||
| 156 | 
    module ClassMethods  | 
|
| 157 | ||
| 158 | 
    # Handles sudo requirements for the given actions, preserving the named  | 
|
| 159 | 
    # parameters, or any parameters if you omit the :parameters option.  | 
|
| 160 | 
    #  | 
|
| 161 | 
    # Sudo enforcement by default is active for all requests to an action  | 
|
| 162 | 
    # but may be limited to a certain subset of request methods via the  | 
|
| 163 | 
    # :only option.  | 
|
| 164 | 
    #  | 
|
| 165 | 
    # Examples:  | 
|
| 166 | 
    #  | 
|
| 167 | 
    # require_sudo_mode :account, only: :post  | 
|
| 168 | 
    # require_sudo_mode :update, :create, parameters: %w(role)  | 
|
| 169 | 
    # require_sudo_mode :destroy  | 
|
| 170 | 
    #  | 
|
| 171 | 
    def require_sudo_mode(*args)  | 
|
| 172 | 
    actions = args.dup  | 
|
| 173 | 
    options = actions.extract_options!  | 
|
| 174 | 
    filter = SudoRequestFilter.new Array(options[:parameters]), Array(options[:only])  | 
|
| 175 | 
    before_filter filter, only: actions  | 
|
| 176 | 
    end  | 
|
| 177 | 
    end  | 
|
| 178 | 
    end  | 
|
| 179 | ||
| 180 | ||
| 181 | 
    # true if the sudo mode state was queried during this request  | 
|
| 182 | 
    def self.was_used?  | 
|
| 183 | 
    !!RequestStore.store[:sudo_mode_was_used]  | 
|
| 184 | 
    end  | 
|
| 185 | ||
| 186 | 
    # true if sudo mode is currently active.  | 
|
| 187 | 
    #  | 
|
| 188 | 
    # Calling this method also turns was_used? to true, therefore  | 
|
| 189 | 
    # it is important to only call this when sudo is actually needed, as the last  | 
|
| 190 | 
    # condition to determine wether a change can be done or not.  | 
|
| 191 | 
    #  | 
|
| 192 | 
    # If you do it wrong, timeout of the sudo mode will happen too late or not at  | 
|
| 193 | 
    # all.  | 
|
| 194 | 
    def self.active?  | 
|
| 195 | 
    if !!RequestStore.store[:sudo_mode]  | 
|
| 196 | 
    RequestStore.store[:sudo_mode_was_used] = true  | 
|
| 197 | 
    end  | 
|
| 198 | 
    end  | 
|
| 199 | ||
| 200 | 
    def self.active!  | 
|
| 201 | 
    RequestStore.store[:sudo_mode] = true  | 
|
| 202 | 
    end  | 
|
| 203 | ||
| 204 | 
    def self.possible?  | 
|
| 205 | 
    !disabled? && User.current.logged?  | 
|
| 206 | 
    end  | 
|
| 207 | ||
| 208 | 
    # Turn off sudo mode (never require password entry).  | 
|
| 209 | 
    def self.disable!  | 
|
| 210 | 
    RequestStore.store[:sudo_mode_disabled] = true  | 
|
| 211 | 
    end  | 
|
| 212 | ||
| 213 | 
    # Turn sudo mode back on  | 
|
| 214 | 
    def self.enable!  | 
|
| 215 | 
    RequestStore.store[:sudo_mode_disabled] = nil  | 
|
| 216 | 
    end  | 
|
| 217 | ||
| 218 | 
    def self.disabled?  | 
|
| 219 | 
    !!RequestStore.store[:sudo_mode_disabled]  | 
|
| 220 | 
    end  | 
|
| 221 | ||
| 222 | 
    end  | 
|
| 223 | 
    end  | 
|
| 224 | ||
| test/functional/auth_sources_controller_test.rb | ||
|---|---|---|
| 22 | 22 | |
| 23 | 23 | 
    def setup  | 
| 24 | 24 | 
    @request.session[:user_id] = 1  | 
| 25 | 
    Redmine::SudoMode.disable!  | 
|
| 25 | 26 | 
    end  | 
| 26 | 27 | |
| 27 | 28 | 
    def test_index  | 
| test/functional/email_addresses_controller_test.rb | ||
|---|---|---|
| 22 | 22 | |
| 23 | 23 | 
    def setup  | 
| 24 | 24 | 
    User.current = nil  | 
| 25 | 
    Redmine::SudoMode.disable!  | 
|
| 25 | 26 | 
    end  | 
| 26 | 27 | |
| 27 | 28 | 
    def test_index_with_no_additional_emails  | 
| test/functional/groups_controller_test.rb | ||
|---|---|---|
| 22 | 22 | |
| 23 | 23 | 
    def setup  | 
| 24 | 24 | 
    @request.session[:user_id] = 1  | 
| 25 | 
    Redmine::SudoMode.disable!  | 
|
| 25 | 26 | 
    end  | 
| 26 | 27 | |
| 27 | 28 | 
    def test_index  | 
| test/functional/members_controller_test.rb | ||
|---|---|---|
| 23 | 23 | 
    def setup  | 
| 24 | 24 | 
    User.current = nil  | 
| 25 | 25 | 
    @request.session[:user_id] = 2  | 
| 26 | 
    Redmine::SudoMode.disable!  | 
|
| 26 | 27 | 
    end  | 
| 27 | 28 | |
| 28 | 29 | 
    def test_new  | 
| test/functional/my_controller_test.rb | ||
|---|---|---|
| 23 | 23 | |
| 24 | 24 | 
    def setup  | 
| 25 | 25 | 
    @request.session[:user_id] = 2  | 
| 26 | 
    Redmine::SudoMode.disable!  | 
|
| 26 | 27 | 
    end  | 
| 27 | 28 | |
| 28 | 29 | 
    def test_index  | 
| ... | ... | |
| 253 | 254 | 
    assert_redirected_to '/my/account'  | 
| 254 | 255 | 
    end  | 
| 255 | 256 | |
| 257 | 
    def test_show_api_key  | 
|
| 258 | 
    get :show_api_key  | 
|
| 259 | 
    assert_response :success  | 
|
| 260 | 
    assert_select 'pre', User.find(2).api_key  | 
|
| 261 | 
    end  | 
|
| 262 | ||
| 256 | 263 | 
    def test_reset_api_key_with_existing_key  | 
| 257 | 264 | 
    @previous_token_value = User.find(2).api_key # Will generate one if it's missing  | 
| 258 | 265 | 
    post :reset_api_key  | 
| test/functional/projects_controller_test.rb | ||
|---|---|---|
| 28 | 28 | 
    def setup  | 
| 29 | 29 | 
    @request.session[:user_id] = nil  | 
| 30 | 30 | 
    Setting.default_language = 'en'  | 
| 31 | 
    Redmine::SudoMode.disable!  | 
|
| 31 | 32 | 
    end  | 
| 32 | 33 | |
| 33 | 34 | 
    def test_index_by_anonymous_should_not_show_private_projects  | 
| test/functional/roles_controller_test.rb | ||
|---|---|---|
| 23 | 23 | 
    def setup  | 
| 24 | 24 | 
    User.current = nil  | 
| 25 | 25 | 
    @request.session[:user_id] = 1 # admin  | 
| 26 | 
    Redmine::SudoMode.disable!  | 
|
| 26 | 27 | 
    end  | 
| 27 | 28 | |
| 28 | 29 | 
    def test_index  | 
| test/functional/settings_controller_test.rb | ||
|---|---|---|
| 24 | 24 | 
    def setup  | 
| 25 | 25 | 
    User.current = nil  | 
| 26 | 26 | 
    @request.session[:user_id] = 1 # admin  | 
| 27 | 
    Redmine::SudoMode.disable!  | 
|
| 27 | 28 | 
    end  | 
| 28 | 29 | |
| 29 | 30 | 
    def test_index  | 
| test/functional/users_controller_test.rb | ||
|---|---|---|
| 30 | 30 | 
    def setup  | 
| 31 | 31 | 
    User.current = nil  | 
| 32 | 32 | 
    @request.session[:user_id] = 1 # admin  | 
| 33 | 
    Redmine::SudoMode.disable!  | 
|
| 33 | 34 | 
    end  | 
| 34 | 35 | |
| 35 | 36 | 
    def test_index  | 
| test/integration/admin_test.rb | ||
|---|---|---|
| 26 | 26 | 
    :members,  | 
| 27 | 27 | 
    :enabled_modules  | 
| 28 | 28 | |
| 29 | 
    def setup  | 
|
| 30 | 
    Redmine::SudoMode.enable!  | 
|
| 31 | 
    end  | 
|
| 32 | ||
| 33 | 
    def teardown  | 
|
| 34 | 
    Redmine::SudoMode.disable!  | 
|
| 35 | 
    end  | 
|
| 36 | ||
| 29 | 37 | 
    def test_add_user  | 
| 30 | 38 | 
        log_user("admin", "admin")
   | 
| 31 | 39 | 
    get "/users/new"  | 
| ... | ... | |
| 36 | 44 | 
    :lastname => "Smith", :mail => "psmith@somenet.foo",  | 
| 37 | 45 | 
    :language => "en", :password => "psmith09",  | 
| 38 | 46 | 
    :password_confirmation => "psmith09" }  | 
| 47 | 
    assert_response :success  | 
|
| 48 | 
        assert_nil User.find_by_login("psmith")
   | 
|
| 49 | ||
| 50 | 
    post "/users",  | 
|
| 51 | 
             :user => { :login => "psmith", :firstname => "Paul",
   | 
|
| 52 | 
    :lastname => "Smith", :mail => "psmith@somenet.foo",  | 
|
| 53 | 
    :language => "en", :password => "psmith09",  | 
|
| 54 | 
    :password_confirmation => "psmith09" },  | 
|
| 55 | 
    :sudo_password => 'admin'  | 
|
| 39 | 56 | |
| 40 | 57 | 
        user = User.find_by_login("psmith")
   | 
| 41 | 58 | 
    assert_kind_of User, user  | 
| test/integration/sudo_test.rb | ||
|---|---|---|
| 1 | 
    require File.expand_path('../../test_helper', __FILE__)
   | 
|
| 2 | ||
| 3 | 
    class SudoTest < Redmine::IntegrationTest  | 
|
| 4 | 
    fixtures :projects, :members, :member_roles, :roles, :users  | 
|
| 5 | ||
| 6 | 
    def setup  | 
|
| 7 | 
    Redmine::SudoMode.enable!  | 
|
| 8 | 
    end  | 
|
| 9 | ||
| 10 | 
    def teardown  | 
|
| 11 | 
    Redmine::SudoMode.disable!  | 
|
| 12 | 
    end  | 
|
| 13 | ||
| 14 | 
    def test_create_member_xhr  | 
|
| 15 | 
    log_user 'admin', 'admin'  | 
|
| 16 | 
    get '/projects/ecookbook/settings/members'  | 
|
| 17 | 
    assert_response :success  | 
|
| 18 | ||
| 19 | 
    assert_no_difference 'Member.count' do  | 
|
| 20 | 
          xhr :post, '/projects/ecookbook/memberships', membership: {role_ids: [1], user_id: 7}
   | 
|
| 21 | 
    end  | 
|
| 22 | ||
| 23 | 
    assert_no_difference 'Member.count' do  | 
|
| 24 | 
          xhr :post, '/projects/ecookbook/memberships', membership: {role_ids: [1], user_id: 7}, sudo_password: ''
   | 
|
| 25 | 
    end  | 
|
| 26 | ||
| 27 | 
    assert_no_difference 'Member.count' do  | 
|
| 28 | 
          xhr :post, '/projects/ecookbook/memberships', membership: {role_ids: [1], user_id: 7}, sudo_password: 'wrong'
   | 
|
| 29 | 
    end  | 
|
| 30 | ||
| 31 | 
    assert_difference 'Member.count' do  | 
|
| 32 | 
          xhr :post, '/projects/ecookbook/memberships', membership: {role_ids: [1], user_id: 7}, sudo_password: 'admin'
   | 
|
| 33 | 
    end  | 
|
| 34 | 
    assert User.find(7).member_of?(Project.find(1))  | 
|
| 35 | 
    end  | 
|
| 36 | ||
| 37 | 
    def test_create_member  | 
|
| 38 | 
    log_user 'admin', 'admin'  | 
|
| 39 | 
    get '/projects/ecookbook/settings/members'  | 
|
| 40 | 
    assert_response :success  | 
|
| 41 | ||
| 42 | 
    assert_no_difference 'Member.count' do  | 
|
| 43 | 
          post '/projects/ecookbook/memberships', membership: {role_ids: [1], user_id: 7}
   | 
|
| 44 | 
    end  | 
|
| 45 | ||
| 46 | 
    assert_no_difference 'Member.count' do  | 
|
| 47 | 
          post '/projects/ecookbook/memberships', membership: {role_ids: [1], user_id: 7}, sudo_password: ''
   | 
|
| 48 | 
    end  | 
|
| 49 | ||
| 50 | 
    assert_no_difference 'Member.count' do  | 
|
| 51 | 
          post '/projects/ecookbook/memberships', membership: {role_ids: [1], user_id: 7}, sudo_password: 'wrong'
   | 
|
| 52 | 
    end  | 
|
| 53 | ||
| 54 | 
    assert_difference 'Member.count' do  | 
|
| 55 | 
          post '/projects/ecookbook/memberships', membership: {role_ids: [1], user_id: 7}, sudo_password: 'admin'
   | 
|
| 56 | 
    end  | 
|
| 57 | ||
| 58 | 
    assert_redirected_to '/projects/ecookbook/settings/members'  | 
|
| 59 | 
    assert User.find(7).member_of?(Project.find(1))  | 
|
| 60 | 
    end  | 
|
| 61 | ||
| 62 | 
    def test_create_role  | 
|
| 63 | 
    log_user 'admin', 'admin'  | 
|
| 64 | 
    get '/roles'  | 
|
| 65 | 
    assert_response :success  | 
|
| 66 | ||
| 67 | 
    get '/roles/new'  | 
|
| 68 | 
    assert_response :success  | 
|
| 69 | ||
| 70 | 
        post '/roles', role: { }
   | 
|
| 71 | 
    assert_response :success  | 
|
| 72 | 
    assert_select 'h2', 'Confirm your password to continue'  | 
|
| 73 | 
    assert_select 'form[action="/roles"]'  | 
|
| 74 | 
    assert assigns(:sudo_form).errors.blank?  | 
|
| 75 | ||
| 76 | 
        post '/roles', role: { name: 'new role', issues_visibility: 'all' }
   | 
|
| 77 | 
    assert_response :success  | 
|
| 78 | 
    assert_select 'h2', 'Confirm your password to continue'  | 
|
| 79 | 
    assert_select 'form[action="/roles"]'  | 
|
| 80 | 
    assert_match /"new role"/, response.body  | 
|
| 81 | 
    assert assigns(:sudo_form).errors.blank?  | 
|
| 82 | ||
| 83 | 
        post '/roles', role: { name: 'new role', issues_visibility: 'all' }, sudo_password: 'wrong'
   | 
|
| 84 | 
    assert_response :success  | 
|
| 85 | 
    assert_select 'h2', 'Confirm your password to continue'  | 
|
| 86 | 
    assert_select 'form[action="/roles"]'  | 
|
| 87 | 
    assert_match /"new role"/, response.body  | 
|
| 88 | 
    assert assigns(:sudo_form).errors[:password].present?  | 
|
| 89 | ||
| 90 | 
    assert_difference 'Role.count' do  | 
|
| 91 | 
          post '/roles', role: { name: 'new role', issues_visibility: 'all', assignable: '1', permissions: %w(view_calendar) }, sudo_password: 'admin'
   | 
|
| 92 | 
    end  | 
|
| 93 | 
    assert_redirected_to '/roles'  | 
|
| 94 | 
    end  | 
|
| 95 | ||
| 96 | 
    def test_update_email_address  | 
|
| 97 | 
    log_user 'jsmith', 'jsmith'  | 
|
| 98 | 
    get '/my/account'  | 
|
| 99 | 
    assert_response :success  | 
|
| 100 | 
        post '/my/account', user: { mail: 'newmail@test.com' }
   | 
|
| 101 | 
    assert_response :success  | 
|
| 102 | 
    assert_select 'h2', 'Confirm your password to continue'  | 
|
| 103 | 
    assert_select 'form[action="/my/account"]'  | 
|
| 104 | 
    assert_match /"newmail@test\.com"/, response.body  | 
|
| 105 | 
    assert assigns(:sudo_form).errors.blank?  | 
|
| 106 | ||
| 107 | 
    # wrong password  | 
|
| 108 | 
        post '/my/account', user: { mail: 'newmail@test.com' }, sudo_password: 'wrong'
   | 
|
| 109 | 
    assert_response :success  | 
|
| 110 | 
    assert_select 'h2', 'Confirm your password to continue'  | 
|
| 111 | 
    assert_select 'form[action="/my/account"]'  | 
|
| 112 | 
    assert_match /"newmail@test\.com"/, response.body  | 
|
| 113 | 
    assert assigns(:sudo_form).errors[:password].present?  | 
|
| 114 | ||
| 115 | 
    # correct password  | 
|
| 116 | 
        post '/my/account', user: { mail: 'newmail@test.com' }, sudo_password: 'jsmith'
   | 
|
| 117 | 
    assert_redirected_to '/my/account'  | 
|
| 118 | 
        assert_equal 'newmail@test.com', User.find_by_login('jsmith').mail
   | 
|
| 119 | ||
| 120 | 
    # sudo mode should now be active and not require password again  | 
|
| 121 | 
        post '/my/account', user: { mail: 'even.newer.mail@test.com' }
   | 
|
| 122 | 
    assert_redirected_to '/my/account'  | 
|
| 123 | 
        assert_equal 'even.newer.mail@test.com', User.find_by_login('jsmith').mail
   | 
|
| 124 | 
    end  | 
|
| 125 | ||
| 126 | 
    end  | 
|