You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sudo_mode.rb 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. require 'active_support/core_ext/object/to_query'
  2. require 'rack/utils'
  3. module Redmine
  4. module SudoMode
  5. class SudoRequired < StandardError
  6. end
  7. class Form
  8. include ActiveModel::Validations
  9. attr_accessor :password, :original_fields
  10. validate :check_password
  11. def initialize(password = nil)
  12. self.password = password
  13. end
  14. def check_password
  15. unless password.present? && User.current.check_password?(password)
  16. errors[:password] << :invalid
  17. end
  18. end
  19. end
  20. module Helper
  21. # Represents params data from hash as hidden fields
  22. #
  23. # taken from https://github.com/brianhempel/hash_to_hidden_fields
  24. def hash_to_hidden_fields(hash)
  25. cleaned_hash = hash.to_unsafe_h.reject { |k, v| v.nil? }
  26. pairs = cleaned_hash.to_query.split(Rack::Utils::DEFAULT_SEP)
  27. tags = pairs.map do |pair|
  28. key, value = pair.split('=', 2).map { |str| Rack::Utils.unescape(str) }
  29. hidden_field_tag(key, value)
  30. end
  31. tags.join("\n").html_safe
  32. end
  33. end
  34. module Controller
  35. extend ActiveSupport::Concern
  36. included do
  37. around_action :sudo_mode
  38. end
  39. # Sudo mode Around Filter
  40. #
  41. # Checks the 'last used' timestamp from session and sets the
  42. # SudoMode::active? flag accordingly.
  43. #
  44. # After the request refreshes the timestamp if sudo mode was used during
  45. # this request.
  46. def sudo_mode
  47. if sudo_timestamp_valid?
  48. SudoMode.active!
  49. end
  50. yield
  51. update_sudo_timestamp! if SudoMode.was_used?
  52. end
  53. # This renders the sudo mode form / handles sudo form submission.
  54. #
  55. # Call this method in controller actions if sudo permissions are required
  56. # for processing this request. This approach is good in cases where the
  57. # action needs to be protected in any case or where the check is simple.
  58. #
  59. # In cases where this decision depends on complex conditions in the model,
  60. # consider the declarative approach using the require_sudo_mode class
  61. # method and a corresponding declaration in the model that causes it to throw
  62. # a SudoRequired Error when necessary.
  63. #
  64. # All parameter names given are included as hidden fields to be resubmitted
  65. # along with the password.
  66. #
  67. # Returns true when processing the action should continue, false otherwise.
  68. # If false is returned, render has already been called for display of the
  69. # password form.
  70. #
  71. # if @user.mail_changed?
  72. # require_sudo_mode :user or return
  73. # end
  74. #
  75. def require_sudo_mode(*param_names)
  76. return true if SudoMode.active?
  77. if param_names.blank?
  78. param_names = params.keys - %w(id action controller sudo_password _method authenticity_token utf8)
  79. end
  80. process_sudo_form
  81. if SudoMode.active?
  82. true
  83. else
  84. render_sudo_form param_names
  85. false
  86. end
  87. end
  88. # display the sudo password form
  89. def render_sudo_form(param_names)
  90. @sudo_form ||= SudoMode::Form.new
  91. @sudo_form.original_fields = params.slice( *param_names )
  92. # a simple 'render "sudo_mode/new"' works when used directly inside an
  93. # action, but not when called from a before_action:
  94. respond_to do |format|
  95. format.html { render 'sudo_mode/new' }
  96. format.js { render 'sudo_mode/new' }
  97. end
  98. end
  99. # handle sudo password form submit
  100. def process_sudo_form
  101. if params[:sudo_password]
  102. @sudo_form = SudoMode::Form.new(params[:sudo_password])
  103. if @sudo_form.valid?
  104. SudoMode.active!
  105. else
  106. flash.now[:error] = l(:notice_account_wrong_password)
  107. end
  108. end
  109. end
  110. def sudo_timestamp_valid?
  111. session[:sudo_timestamp].to_i > SudoMode.timeout.ago.to_i
  112. end
  113. def update_sudo_timestamp!(new_value = Time.now.to_i)
  114. session[:sudo_timestamp] = new_value
  115. end
  116. # Before Filter which is used by the require_sudo_mode class method.
  117. class SudoRequestFilter < Struct.new(:parameters, :request_methods)
  118. def before(controller)
  119. method_matches = request_methods.blank? || request_methods.include?(controller.request.method_symbol)
  120. if controller.api_request?
  121. true
  122. elsif SudoMode.possible? && method_matches
  123. controller.require_sudo_mode( *parameters )
  124. else
  125. true
  126. end
  127. end
  128. end
  129. module ClassMethods
  130. # Handles sudo requirements for the given actions, preserving the named
  131. # parameters, or any parameters if you omit the :parameters option.
  132. #
  133. # Sudo enforcement by default is active for all requests to an action
  134. # but may be limited to a certain subset of request methods via the
  135. # :only option.
  136. #
  137. # Examples:
  138. #
  139. # require_sudo_mode :account, only: :post
  140. # require_sudo_mode :update, :create, parameters: %w(role)
  141. # require_sudo_mode :destroy
  142. #
  143. def require_sudo_mode(*args)
  144. actions = args.dup
  145. options = actions.extract_options!
  146. filter = SudoRequestFilter.new Array(options[:parameters]), Array(options[:only])
  147. before_action filter, only: actions
  148. end
  149. end
  150. end
  151. # true if the sudo mode state was queried during this request
  152. def self.was_used?
  153. !!RequestStore.store[:sudo_mode_was_used]
  154. end
  155. # true if sudo mode is currently active.
  156. #
  157. # Calling this method also turns was_used? to true, therefore
  158. # it is important to only call this when sudo is actually needed, as the last
  159. # condition to determine whether a change can be done or not.
  160. #
  161. # If you do it wrong, timeout of the sudo mode will happen too late or not at
  162. # all.
  163. def self.active?
  164. if !!RequestStore.store[:sudo_mode]
  165. RequestStore.store[:sudo_mode_was_used] = true
  166. end
  167. end
  168. def self.active!
  169. RequestStore.store[:sudo_mode] = true
  170. end
  171. def self.possible?
  172. enabled? && User.current.logged?
  173. end
  174. # Turn off sudo mode (never require password entry).
  175. def self.disable!
  176. RequestStore.store[:sudo_mode_disabled] = true
  177. end
  178. # Turn sudo mode back on
  179. def self.enable!
  180. RequestStore.store[:sudo_mode_disabled] = nil
  181. end
  182. def self.enabled?
  183. Redmine::Configuration['sudo_mode'] && !RequestStore.store[:sudo_mode_disabled]
  184. end
  185. # Timespan after which sudo mode expires when unused.
  186. def self.timeout
  187. m = Redmine::Configuration['sudo_mode_timeout'].to_i
  188. (m > 0 ? m : 15).minutes
  189. end
  190. end
  191. end