1 class ApplicationController < ActionController::Base
4 include SessionPersistence
6 protect_from_forgery :with => :exception
8 add_flash_types :warning, :error
10 rescue_from CanCan::AccessDenied, :with => :deny_access
13 rescue_from RailsParam::InvalidParameterError, :with => :invalid_parameter
15 before_action :fetch_body
17 attr_accessor :current_user, :oauth_token
19 helper_method :current_user
20 helper_method :oauth_token
22 def self.allow_thirdparty_images(**options)
23 content_security_policy(options) do |policy|
28 def self.allow_social_login(**options)
29 content_security_policy(options) do |policy|
30 policy.form_action(*policy.form_action, "accounts.google.com", "*.facebook.com", "login.microsoftonline.com", "github.com", "meta.wikimedia.org")
34 def self.allow_all_form_action(**options)
35 content_security_policy(options) do |policy|
36 policy.form_action(nil)
44 self.current_user = User.find_by(:id => session[:user], :status => %w[active confirmed suspended])
46 if session[:fingerprint] &&
47 session[:fingerprint] != current_user.fingerprint
49 self.current_user = nil
50 elsif current_user.status == "suspended"
52 session_expires_automatically
54 redirect_to :controller => "users", :action => "suspended"
56 # don't allow access to any auth-requiring part of the site unless
57 # the new CTs have been seen (and accept/decline chosen).
58 elsif !current_user.terms_seen && flash[:skip_terms].nil?
59 flash[:notice] = t "users.terms.you need to accept or decline"
61 redirect_to :controller => "users", :action => "terms", :referer => params[:referer]
63 redirect_to :controller => "users", :action => "terms", :referer => request.fullpath
68 session[:fingerprint] = current_user.fingerprint if current_user && session[:fingerprint].nil?
69 rescue StandardError => e
70 logger.info("Exception authorizing user: #{e}")
72 self.current_user = nil
78 redirect_to login_path(:referer => request.fullpath)
86 @oauth_token = current_user.oauth_token(Settings.oauth_application) if current_user && Settings.key?(:oauth_application)
89 def require_oauth_10a_support
90 report_error t("application.oauth_10a_disabled", :link => t("application.auth_disabled_link")), :forbidden unless Settings.oauth_10a_support
94 # require the user to have cookies enabled in their browser
96 if request.cookies["_osm_session"].to_s == ""
97 if params[:cookie_test].nil?
98 session[:cookie_test] = true
99 redirect_to params.to_unsafe_h.merge(:only_path => true, :cookie_test => "true")
102 flash.now[:warning] = t "application.require_cookies.cookies_needed"
105 session.delete(:cookie_test)
109 def check_database_readable(need_api: false)
110 if Settings.status == "database_offline" || (need_api && Settings.status == "api_offline")
112 report_error "Database offline for maintenance", :service_unavailable
114 redirect_to :controller => "site", :action => "offline"
119 def check_database_writable(need_api: false)
120 if Settings.status == "database_offline" || Settings.status == "database_readonly" ||
121 (need_api && (Settings.status == "api_offline" || Settings.status == "api_readonly"))
123 report_error "Database offline for maintenance", :service_unavailable
125 redirect_to :controller => "site", :action => "offline"
130 def check_api_readable
131 if api_status == "offline"
132 report_error "Database offline for maintenance", :service_unavailable
137 def check_api_writable
138 unless api_status == "online"
139 report_error "Database offline for maintenance", :service_unavailable
146 when "database_offline"
148 when "database_readonly"
156 status = database_status
157 if status == "online"
168 def require_public_data
169 unless current_user.data_public?
170 report_error "You must make your edits public to upload new data", :forbidden
175 # Report and error to the user
176 # (If anyone ever fixes Rails so it can set a http status "reason phrase",
177 # rather than only a status code and having the web engine make up a
178 # phrase from that, we can also put the error message into the status
179 # message. For now, rails won't let us)
180 def report_error(message, status = :bad_request)
181 # TODO: some sort of escaping of problem characters in the message
182 response.headers["Error"] = message
184 if request.headers["X-Error-Format"]&.casecmp("xml")&.zero?
185 result = OSM::API.new.xml_doc
186 result.root.name = "osmError"
187 result.root << (XML::Node.new("status") << "#{Rack::Utils.status_code(status)} #{Rack::Utils::HTTP_STATUS_CODES[status]}")
188 result.root << (XML::Node.new("message") << message)
190 render :xml => result.to_s
192 render :plain => message, :status => status
196 def preferred_languages
197 @preferred_languages ||= if params[:locale]
198 Locale.list(params[:locale])
200 current_user.preferred_languages
202 Locale.list(http_accept_language.user_preferred_languages)
206 helper_method :preferred_languages
209 if current_user&.languages&.empty? && !http_accept_language.user_preferred_languages.empty?
210 current_user.languages = http_accept_language.user_preferred_languages
214 I18n.locale = Locale.available.preferred(preferred_languages)
216 response.headers["Vary"] = "Accept-Language"
217 response.headers["Content-Language"] = I18n.locale.to_s
221 # wrap a web page in a timeout
222 def web_timeout(&block)
223 Timeout.timeout(Settings.web_timeout, &block)
224 rescue ActionView::Template::Error => e
227 if e.is_a?(Timeout::Error) ||
228 (e.is_a?(ActiveRecord::StatementInvalid) && e.message.include?("execution expired"))
229 ActiveRecord::Base.connection.raw_connection.cancel
230 render :action => "timeout"
234 rescue Timeout::Error
235 ActiveRecord::Base.connection.raw_connection.cancel
236 render :action => "timeout"
240 # Unfortunately if a PUT or POST request that has a body fails to
241 # read it then Apache will sometimes fail to return the response it
242 # is given to the client properly, instead erroring:
244 # https://issues.apache.org/bugzilla/show_bug.cgi?id=44782
246 # To work round this we call rewind on the body here, which is added
247 # as a filter, to force it to be fetched from Apache into a file.
253 policy = request.content_security_policy.clone
255 policy.child_src(*policy.child_src, "http://127.0.0.1:8111", "https://127.0.0.1:8112")
256 policy.frame_src(*policy.frame_src, "http://127.0.0.1:8111", "https://127.0.0.1:8112")
257 policy.connect_src(*policy.connect_src, Settings.nominatim_url, Settings.overpass_url, Settings.fossgis_osrm_url, Settings.graphhopper_url, Settings.fossgis_valhalla_url)
258 policy.form_action(*policy.form_action, "render.openstreetmap.org")
259 policy.style_src(*policy.style_src, :unsafe_inline)
261 request.content_security_policy = policy
264 when "database_offline", "api_offline"
265 flash.now[:warning] = t("layouts.osm_offline")
266 when "database_readonly", "api_readonly"
267 flash.now[:warning] = t("layouts.osm_read_only")
270 request.xhr? ? "xhr" : "map"
276 elsif current_user&.preferred_editor
277 current_user.preferred_editor
279 Settings.default_editor
283 helper_method :preferred_editor
286 if Settings.key?(:totp_key)
287 cookies["_osm_totp_token"] = {
288 :value => ROTP::TOTP.new(Settings.totp_key, :interval => 3600).now,
289 :domain => "openstreetmap.org",
290 :expires => 1.hour.from_now
296 Ability.new(current_user)
299 def deny_access(_exception)
300 if doorkeeper_token || current_token
302 report_error t("oauth.permissions.missing"), :forbidden
305 respond_to do |format|
306 format.html { redirect_to :controller => "/errors", :action => "forbidden" }
307 format.any { report_error t("application.permission_denied"), :forbidden }
310 respond_to do |format|
311 format.html { redirect_to login_path(:referer => request.fullpath) }
312 format.any { head :forbidden }
319 def invalid_parameter(_exception)
321 respond_to do |format|
322 format.html { redirect_to :controller => "/errors", :action => "bad_request" }
323 format.any { head :bad_request }
330 # extract authorisation credentials from headers, returns user = nil if none
332 if request.env.key? "X-HTTP_AUTHORIZATION" # where mod_rewrite might have put it
333 authdata = request.env["X-HTTP_AUTHORIZATION"].to_s.split
334 elsif request.env.key? "REDIRECT_X_HTTP_AUTHORIZATION" # mod_fcgi
335 authdata = request.env["REDIRECT_X_HTTP_AUTHORIZATION"].to_s.split
336 elsif request.env.key? "HTTP_AUTHORIZATION" # regular location
337 authdata = request.env["HTTP_AUTHORIZATION"].to_s.split
339 # only basic authentication supported
340 user, pass = Base64.decode64(authdata[1]).split(":", 2) if authdata && authdata[0] == "Basic"
344 # override to stop oauth plugin sending errors
345 def invalid_oauth_response; end
347 # clean any referer parameter
348 def safe_referer(referer)
350 referer = URI.parse(referer)
352 if referer.scheme == "http" || referer.scheme == "https"
356 elsif referer.scheme || referer.host || referer.port
360 referer = nil if referer&.path&.first != "/"
361 rescue URI::InvalidURIError
368 def scope_enabled?(scope)
369 doorkeeper_token&.includes_scope?(scope) || current_token&.includes_scope?(scope)
372 helper_method :scope_enabled?