PageRenderTime 93ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/app/controllers/application_controller.rb

https://gitlab.com/ghaedsharafi.nima/openstreetmap-website
Ruby | 457 lines | 358 code | 46 blank | 53 comment | 57 complexity | 879f63a6da3bb1e535c2d788b94d5689 MD5 | raw file
  1. class ApplicationController < ActionController::Base
  2. include SessionPersistence
  3. protect_from_forgery
  4. before_action :fetch_body
  5. def authorize_web
  6. if session[:user]
  7. @user = User.where(:id => session[:user]).where("status IN ('active', 'confirmed', 'suspended')").first
  8. if @user.status == "suspended"
  9. session.delete(:user)
  10. session_expires_automatically
  11. redirect_to :controller => "user", :action => "suspended"
  12. # don't allow access to any auth-requiring part of the site unless
  13. # the new CTs have been seen (and accept/decline chosen).
  14. elsif !@user.terms_seen && flash[:skip_terms].nil?
  15. flash[:notice] = t "user.terms.you need to accept or decline"
  16. if params[:referer]
  17. redirect_to :controller => "user", :action => "terms", :referer => params[:referer]
  18. else
  19. redirect_to :controller => "user", :action => "terms", :referer => request.fullpath
  20. end
  21. end
  22. elsif session[:token]
  23. if @user = User.authenticate(:token => session[:token])
  24. session[:user] = @user.id
  25. end
  26. end
  27. rescue StandardError => ex
  28. logger.info("Exception authorizing user: #{ex}")
  29. reset_session
  30. @user = nil
  31. end
  32. def require_user
  33. unless @user
  34. if request.get?
  35. redirect_to :controller => "user", :action => "login", :referer => request.fullpath
  36. else
  37. render :text => "", :status => :forbidden
  38. end
  39. end
  40. end
  41. def require_oauth
  42. @oauth = @user.access_token(OAUTH_KEY) if @user && defined? OAUTH_KEY
  43. end
  44. ##
  45. # requires the user to be logged in by the token or HTTP methods, or have an
  46. # OAuth token with the right capability. this method is a bit of a pain to call
  47. # directly, since it's cumbersome to call filters with arguments in rails. to
  48. # make it easier to read and write the code, there are some utility methods
  49. # below.
  50. def require_capability(cap)
  51. # when the current token is nil, it means the user logged in with a different
  52. # method, otherwise an OAuth token was used, which has to be checked.
  53. unless current_token.nil?
  54. unless current_token.read_attribute(cap)
  55. report_error "OAuth token doesn't have that capability.", :forbidden
  56. return false
  57. end
  58. end
  59. end
  60. ##
  61. # require the user to have cookies enabled in their browser
  62. def require_cookies
  63. if request.cookies["_osm_session"].to_s == ""
  64. if params[:cookie_test].nil?
  65. session[:cookie_test] = true
  66. redirect_to Hash[params].merge(:cookie_test => "true")
  67. return false
  68. else
  69. flash.now[:warning] = t "application.require_cookies.cookies_needed"
  70. end
  71. else
  72. session.delete(:cookie_test)
  73. end
  74. end
  75. # Utility methods to make the controller filter methods easier to read and write.
  76. def require_allow_read_prefs
  77. require_capability(:allow_read_prefs)
  78. end
  79. def require_allow_write_prefs
  80. require_capability(:allow_write_prefs)
  81. end
  82. def require_allow_write_diary
  83. require_capability(:allow_write_diary)
  84. end
  85. def require_allow_write_api
  86. require_capability(:allow_write_api)
  87. if REQUIRE_TERMS_AGREED && @user.terms_agreed.nil?
  88. report_error "You must accept the contributor terms before you can edit.", :forbidden
  89. return false
  90. end
  91. end
  92. def require_allow_read_gpx
  93. require_capability(:allow_read_gpx)
  94. end
  95. def require_allow_write_gpx
  96. require_capability(:allow_write_gpx)
  97. end
  98. def require_allow_write_notes
  99. require_capability(:allow_write_notes)
  100. end
  101. ##
  102. # require that the user is a moderator, or fill out a helpful error message
  103. # and return them to the index for the controller this is wrapped from.
  104. def require_moderator
  105. unless @user.moderator?
  106. if request.get?
  107. flash[:error] = t("application.require_moderator.not_a_moderator")
  108. redirect_to :action => "index"
  109. else
  110. render :text => "", :status => :forbidden
  111. end
  112. end
  113. end
  114. ##
  115. # sets up the @user object for use by other methods. this is mostly called
  116. # from the authorize method, but can be called elsewhere if authorisation
  117. # is optional.
  118. def setup_user_auth
  119. # try and setup using OAuth
  120. unless Authenticator.new(self, [:token]).allow?
  121. username, passwd = get_auth_data # parse from headers
  122. # authenticate per-scheme
  123. @user = if username.nil?
  124. nil # no authentication provided - perhaps first connect (client should retry after 401)
  125. elsif username == "token"
  126. User.authenticate(:token => passwd) # preferred - random token for user from db, passed in basic auth
  127. else
  128. User.authenticate(:username => username, :password => passwd) # basic auth
  129. end
  130. end
  131. # have we identified the user?
  132. if @user
  133. # check if the user has been banned
  134. if @user.blocks.active.exists?
  135. # NOTE: need slightly more helpful message than this.
  136. report_error t("application.setup_user_auth.blocked"), :forbidden
  137. end
  138. # if the user hasn't seen the contributor terms then don't
  139. # allow editing - they have to go to the web site and see
  140. # (but can decline) the CTs to continue.
  141. if REQUIRE_TERMS_SEEN && !@user.terms_seen && flash[:skip_terms].nil?
  142. set_locale
  143. report_error t("application.setup_user_auth.need_to_see_terms"), :forbidden
  144. end
  145. end
  146. end
  147. def authorize(realm = "Web Password", errormessage = "Couldn't authenticate you")
  148. # make the @user object from any auth sources we have
  149. setup_user_auth
  150. # handle authenticate pass/fail
  151. unless @user
  152. # no auth, the user does not exist or the password was wrong
  153. response.headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\""
  154. render :text => errormessage, :status => :unauthorized
  155. return false
  156. end
  157. end
  158. ##
  159. # to be used as a before_filter *after* authorize. this checks that
  160. # the user is a moderator and, if not, returns a forbidden error.
  161. #
  162. # NOTE: this isn't a very good way of doing it - it duplicates logic
  163. # from require_moderator - but what we really need to do is a fairly
  164. # drastic refactoring based on :format and respond_to? but not a
  165. # good idea to do that in this branch.
  166. def authorize_moderator(errormessage = "Access restricted to moderators")
  167. # check user is a moderator
  168. unless @user.moderator?
  169. render :text => errormessage, :status => :forbidden
  170. return false
  171. end
  172. end
  173. def check_database_readable(need_api = false)
  174. if STATUS == :database_offline || (need_api && STATUS == :api_offline)
  175. if request.xhr?
  176. report_error "Database offline for maintenance", :service_unavailable
  177. else
  178. redirect_to :controller => "site", :action => "offline"
  179. end
  180. end
  181. end
  182. def check_database_writable(need_api = false)
  183. if STATUS == :database_offline || STATUS == :database_readonly ||
  184. (need_api && (STATUS == :api_offline || STATUS == :api_readonly))
  185. if request.xhr?
  186. report_error "Database offline for maintenance", :service_unavailable
  187. else
  188. redirect_to :controller => "site", :action => "offline"
  189. end
  190. end
  191. end
  192. def check_api_readable
  193. if api_status == :offline
  194. report_error "Database offline for maintenance", :service_unavailable
  195. return false
  196. end
  197. end
  198. def check_api_writable
  199. unless api_status == :online
  200. report_error "Database offline for maintenance", :service_unavailable
  201. return false
  202. end
  203. end
  204. def database_status
  205. if STATUS == :database_offline
  206. :offline
  207. elsif STATUS == :database_readonly
  208. :readonly
  209. else
  210. :online
  211. end
  212. end
  213. def api_status
  214. status = database_status
  215. if status == :online
  216. if STATUS == :api_offline
  217. status = :offline
  218. elsif STATUS == :api_readonly
  219. status = :readonly
  220. end
  221. end
  222. status
  223. end
  224. def gpx_status
  225. status = database_status
  226. status = :offline if status == :online && STATUS == :gpx_offline
  227. status
  228. end
  229. def require_public_data
  230. unless @user.data_public?
  231. report_error "You must make your edits public to upload new data", :forbidden
  232. return false
  233. end
  234. end
  235. # Report and error to the user
  236. # (If anyone ever fixes Rails so it can set a http status "reason phrase",
  237. # rather than only a status code and having the web engine make up a
  238. # phrase from that, we can also put the error message into the status
  239. # message. For now, rails won't let us)
  240. def report_error(message, status = :bad_request)
  241. # TODO: some sort of escaping of problem characters in the message
  242. response.headers["Error"] = message
  243. if request.headers["X-Error-Format"] &&
  244. request.headers["X-Error-Format"].casecmp("xml").zero?
  245. result = OSM::API.new.get_xml_doc
  246. result.root.name = "osmError"
  247. result.root << (XML::Node.new("status") << "#{Rack::Utils.status_code(status)} #{Rack::Utils::HTTP_STATUS_CODES[status]}")
  248. result.root << (XML::Node.new("message") << message)
  249. render :text => result.to_s, :content_type => "text/xml"
  250. else
  251. render :text => message, :status => status, :content_type => "text/plain"
  252. end
  253. end
  254. def preferred_languages
  255. @languages ||= if params[:locale]
  256. Locale.list(params[:locale])
  257. elsif @user
  258. @user.preferred_languages
  259. else
  260. Locale.list(http_accept_language.user_preferred_languages)
  261. end
  262. end
  263. helper_method :preferred_languages
  264. def set_locale
  265. if @user && @user.languages.empty? && !http_accept_language.user_preferred_languages.empty?
  266. @user.languages = http_accept_language.user_preferred_languages
  267. @user.save
  268. end
  269. I18n.locale = Locale.available.preferred(preferred_languages)
  270. response.headers["Vary"] = "Accept-Language"
  271. response.headers["Content-Language"] = I18n.locale.to_s
  272. end
  273. def api_call_handle_error
  274. yield
  275. rescue ActiveRecord::RecordNotFound => ex
  276. render :text => "", :status => :not_found
  277. rescue LibXML::XML::Error, ArgumentError => ex
  278. report_error ex.message, :bad_request
  279. rescue ActiveRecord::RecordInvalid => ex
  280. message = "#{ex.record.class} #{ex.record.id}: "
  281. ex.record.errors.each { |attr, msg| message << "#{attr}: #{msg} (#{ex.record[attr].inspect})" }
  282. report_error message, :bad_request
  283. rescue OSM::APIError => ex
  284. report_error ex.message, ex.status
  285. rescue AbstractController::ActionNotFound => ex
  286. raise
  287. rescue StandardError => ex
  288. logger.info("API threw unexpected #{ex.class} exception: #{ex.message}")
  289. ex.backtrace.each { |l| logger.info(l) }
  290. report_error "#{ex.class}: #{ex.message}", :internal_server_error
  291. end
  292. ##
  293. # asserts that the request method is the +method+ given as a parameter
  294. # or raises a suitable error. +method+ should be a symbol, e.g: :put or :get.
  295. def assert_method(method)
  296. ok = request.send((method.to_s.downcase + "?").to_sym)
  297. raise OSM::APIBadMethodError.new(method) unless ok
  298. end
  299. ##
  300. # wrap an api call in a timeout
  301. def api_call_timeout
  302. OSM::Timer.timeout(API_TIMEOUT) do
  303. yield
  304. end
  305. rescue Timeout::Error
  306. raise OSM::APITimeoutError
  307. end
  308. ##
  309. # wrap a web page in a timeout
  310. def web_timeout
  311. OSM::Timer.timeout(WEB_TIMEOUT) do
  312. yield
  313. end
  314. rescue ActionView::Template::Error => ex
  315. ex = ex.original_exception
  316. if ex.is_a?(ActiveRecord::StatementInvalid) && ex.message =~ /execution expired/
  317. ex = Timeout::Error.new
  318. end
  319. if ex.is_a?(Timeout::Error)
  320. render :action => "timeout"
  321. else
  322. raise
  323. end
  324. rescue Timeout::Error
  325. render :action => "timeout"
  326. end
  327. ##
  328. # ensure that there is a "this_user" instance variable
  329. def lookup_this_user
  330. unless @this_user = User.active.find_by_display_name(params[:display_name])
  331. render_unknown_user params[:display_name]
  332. end
  333. end
  334. ##
  335. # render a "no such user" page
  336. def render_unknown_user(name)
  337. @title = t "user.no_such_user.title"
  338. @not_found_user = name
  339. respond_to do |format|
  340. format.html { render :template => "user/no_such_user", :status => :not_found }
  341. format.all { render :text => "", :status => :not_found }
  342. end
  343. end
  344. ##
  345. # Unfortunately if a PUT or POST request that has a body fails to
  346. # read it then Apache will sometimes fail to return the response it
  347. # is given to the client properly, instead erroring:
  348. #
  349. # https://issues.apache.org/bugzilla/show_bug.cgi?id=44782
  350. #
  351. # To work round this we call rewind on the body here, which is added
  352. # as a filter, to force it to be fetched from Apache into a file.
  353. def fetch_body
  354. request.body.rewind
  355. end
  356. def map_layout
  357. request.xhr? ? "xhr" : "map"
  358. end
  359. def preferred_editor
  360. editor = if params[:editor]
  361. params[:editor]
  362. elsif @user && @user.preferred_editor
  363. @user.preferred_editor
  364. else
  365. DEFAULT_EDITOR
  366. end
  367. editor
  368. end
  369. helper_method :preferred_editor
  370. private
  371. # extract authorisation credentials from headers, returns user = nil if none
  372. def get_auth_data
  373. if request.env.key? "X-HTTP_AUTHORIZATION" # where mod_rewrite might have put it
  374. authdata = request.env["X-HTTP_AUTHORIZATION"].to_s.split
  375. elsif request.env.key? "REDIRECT_X_HTTP_AUTHORIZATION" # mod_fcgi
  376. authdata = request.env["REDIRECT_X_HTTP_AUTHORIZATION"].to_s.split
  377. elsif request.env.key? "HTTP_AUTHORIZATION" # regular location
  378. authdata = request.env["HTTP_AUTHORIZATION"].to_s.split
  379. end
  380. # only basic authentication supported
  381. if authdata && authdata[0] == "Basic"
  382. user, pass = Base64.decode64(authdata[1]).split(":", 2)
  383. end
  384. [user, pass]
  385. end
  386. # used by oauth plugin to get the current user
  387. def current_user
  388. @user
  389. end
  390. # used by oauth plugin to set the current user
  391. def current_user=(user)
  392. @user = user
  393. end
  394. # override to stop oauth plugin sending errors
  395. def invalid_oauth_response
  396. end
  397. end