class ApiController < ApplicationController
+ skip_before_action :verify_authenticity_token
- def map
- # Figure out the bbox
- bbox = params['bbox']
- unless bbox and bbox.count(',') == 3
- render :nothing => true, :status => 400
- return
- end
+ private
- bbox = bbox.split(',')
+ ##
+ # Set default request format to xml unless a client requests a specific format,
+ # which can be done via (a) URL suffix and/or (b) HTTP Accept header, where
+ # the URL suffix always takes precedence over the Accept header.
+ def set_default_request_format
+ unless params[:format]
+ accept_header = request.headers["HTTP_ACCEPT"]
+ if accept_header.nil?
+ # e.g. unit tests don't set an Accept: header by default, force XML in this case
+ request.format = "xml"
+ return
+ end
- min_lon = bbox[0].to_f
- min_lat = bbox[1].to_f
- max_lon = bbox[2].to_f
- max_lat = bbox[3].to_f
+ req_mimetypes = []
- # get all the nodes
- nodes = Node.find(:all, :conditions => ['latitude > ? AND longitude > ? AND latitude < ? AND longitude < ? AND visible = 1', min_lat, min_lon, max_lat, max_lon])
+ # Some clients (JOSM) send Accept headers which cannot be parsed by Rails, example: *; q=.2
+ # To be fair, JOSM's Accept header doesn't adhere to RFC 7231, section 5.3.1, et al. either
+ # As a workaround for backwards compatibility, we're assuming XML format
+ begin
+ req_mimetypes = Mime::Type.parse(accept_header)
+ rescue Mime::Type::InvalidMimeType
+ request.format = "xml"
+ return
+ end
- node_ids = nodes.collect {|node| node.id }
- node_ids_sql = "(#{node_ids.join(',')})"
+ # req_mimetypes contains all Accept header MIME types with descending priority
+ req_mimetypes.each do |mime|
+ if mime.symbol == :xml
+ request.format = "xml"
+ break
+ end
- # get the referenced segments
- segments = Segment.find_by_sql "select * from segments where node_a in #{node_ids_sql} or node_b in #{node_ids_sql}"
+ if mime.symbol == :json
+ request.format = "json"
+ break
+ end
- # see if we have nay missing nodes
- segments_nodes = segments.collect {|segment| segment.node_a }
- segments_nodes += segments.collect {|segment| segment.node_b }
+ # Any format, not explicitly requesting XML or JSON -> assume XML as default
+ if mime == "*/*"
+ request.format = "xml"
+ break
+ end
+ end
+ end
+ end
- segments_nodes.uniq!
+ def authorize(realm = "Web Password", errormessage = "Couldn't authenticate you")
+ # make the current_user object from any auth sources we have
+ setup_user_auth
- missing_nodes = segments_nodes - node_ids
+ # handle authenticate pass/fail
+ unless current_user
+ # no auth, the user does not exist or the password was wrong
+ response.headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\""
+ render :plain => errormessage, :status => :unauthorized
+ false
+ end
+ end
- # get missing nodes if there are any
- nodes += Node.find(missing_nodes) if missing_nodes.length > 0
+ def current_ability
+ # Use capabilities from the oauth token if it exists and is a valid access token
+ if Authenticator.new(self, [:token]).allow?
+ ApiAbility.new(nil).merge(ApiCapability.new(current_token))
+ else
+ ApiAbility.new(current_user)
+ end
+ end
- doc = XML::Document.new
- doc.encoding = 'UTF-8'
- root = XML::Node.new 'osm'
- root['version'] = '0.4'
- root['generator'] = 'OpenStreetMap server'
- doc.root = root
-
- nodes.each do |node|
- root << node.to_xml_node()
+ def deny_access(_exception)
+ if current_token
+ set_locale
+ report_error t("oauth.permissions.missing"), :forbidden
+ elsif current_user
+ head :forbidden
+ else
+ realm = "Web Password"
+ errormessage = "Couldn't authenticate you"
+ response.headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\""
+ render :plain => errormessage, :status => :unauthorized
end
+ end
+
+ def gpx_status
+ status = database_status
+ status = "offline" if status == "online" && Settings.status == "gpx_offline"
+ status
+ end
- segments.each do |segment|
- root << segment.to_xml_node()
- end
+ ##
+ # sets up the current_user for use by other methods. this is mostly called
+ # from the authorize method, but can be called elsewhere if authorisation
+ # is optional.
+ def setup_user_auth
+ # try and setup using OAuth
+ unless Authenticator.new(self, [:token]).allow?
+ username, passwd = get_auth_data # parse from headers
+ # authenticate per-scheme
+ self.current_user = if username.nil?
+ nil # no authentication provided - perhaps first connect (client should retry after 401)
+ elsif username == "token"
+ User.authenticate(:token => passwd) # preferred - random token for user from db, passed in basic auth
+ else
+ User.authenticate(:username => username, :password => passwd) # basic auth
+ end
+ end
- render :text => doc.to_s
+ # have we identified the user?
+ if current_user
+ # check if the user has been banned
+ user_block = current_user.blocks.active.take
+ unless user_block.nil?
+ set_locale
+ if user_block.zero_hour?
+ report_error t("application.setup_user_auth.blocked_zero_hour"), :forbidden
+ else
+ report_error t("application.setup_user_auth.blocked"), :forbidden
+ end
+ end
+ # if the user hasn't seen the contributor terms then don't
+ # allow editing - they have to go to the web site and see
+ # (but can decline) the CTs to continue.
+ if !current_user.terms_seen && flash[:skip_terms].nil?
+ set_locale
+ report_error t("application.setup_user_auth.need_to_see_terms"), :forbidden
+ end
+ end
end
end