require 'xml/libxml'
skip_before_filter :verify_authenticity_token
+ before_filter :authorize, :only => [ :redact ]
+ before_filter :require_allow_write_api, :only => [ :redact ]
before_filter :check_api_readable
+ before_filter :check_api_writable, :only => [ :redact ]
after_filter :compress_output
around_filter :api_call_handle_error, :api_call_timeout
def history
- node = Node.find(params[:id])
+ # TODO - maybe a bit heavyweight to do this on every
+ # call, perhaps try lazy auth.
+ setup_user_auth
+
+ node = Node.find(params[:id].to_i)
doc = OSM::API.new.get_xml_doc
node.old_nodes.each do |old_node|
- doc.root << old_node.to_xml_node
+ unless old_node.redacted? and (@user.nil? or not @user.moderator?)
+ doc.root << old_node.to_xml_node
+ end
end
render :text => doc.to_s, :content_type => "text/xml"
def version
if old_node = OldNode.where(:node_id => params[:id], :version => params[:version]).first
- response.last_modified = old_node.timestamp
-
- doc = OSM::API.new.get_xml_doc
- doc.root << old_node.to_xml_node
+ # TODO - maybe a bit heavyweight to do this on every
+ # call, perhaps try lazy auth.
+ setup_user_auth
+
+ if old_node.redacted? and (@user.nil? or not @user.moderator?)
+ render :nothing => true, :status => :forbidden
+ else
- render :text => doc.to_s, :content_type => "text/xml"
+ response.last_modified = old_node.timestamp
+
+ doc = OSM::API.new.get_xml_doc
+ doc.root << old_node.to_xml_node
+
+ render :text => doc.to_s, :content_type => "text/xml"
+ end
else
render :nothing => true, :status => :not_found
end
end
+
+ def redact
+ if @user && @user.moderator?
+ render :nothing => true
+
+ else
+ render :nothing => true, :status => :forbidden
+ end
+ end
end
include GeoRecord
include ConsistencyValidations
+ include NotRedactable
self.table_name = "current_nodes"
class OldNode < ActiveRecord::Base
include GeoRecord
include ConsistencyValidations
+ include Redactable
self.table_name = "nodes"
self.primary_keys = "node_id", "version"
validates_associated :changeset
belongs_to :changeset
-
+ belongs_to :redaction
+ belongs_to :current_node, :class_name => "Node", :foreign_key => "node_id"
+
def validate_position
errors.add(:base, "Node is not in the world") unless in_world?
end
def containing_relation_members
return []
end
+
+ # check whether this element is the latest version - that is,
+ # has the same version as its "current" counterpart.
+ def is_latest_version?
+ current_node.version == self.version
+ end
end
class OldRelation < ActiveRecord::Base
include ConsistencyValidations
+ include Redactable
self.table_name = "relations"
self.primary_keys = "relation_id", "version"
belongs_to :changeset
+ belongs_to :redaction
+ belongs_to :current_relation, :class_name => "Relation", :foreign_key => "relation_id"
has_many :old_members, :class_name => 'OldRelationMember', :foreign_key => [:relation_id, :version], :order => :sequence_id
has_many :old_tags, :class_name => 'OldRelationTag', :foreign_key => [:relation_id, :version]
def containing_relation_members
return []
end
+
+ # check whether this element is the latest version - that is,
+ # has the same version as its "current" counterpart.
+ def is_latest_version?
+ current_relation.version == self.version
+ end
end
class OldWay < ActiveRecord::Base
include ConsistencyValidations
-
+ include Redactable
+
self.table_name = "ways"
self.primary_keys = "way_id", "version"
belongs_to :changeset
+ belongs_to :redaction
+ belongs_to :current_way, :class_name => "Way", :foreign_key => "way_id"
has_many :old_nodes, :class_name => 'OldWayNode', :foreign_key => [:way_id, :version]
has_many :old_tags, :class_name => 'OldWayTag', :foreign_key => [:way_id, :version]
def containing_relation_members
return []
end
+
+ # check whether this element is the latest version - that is,
+ # has the same version as its "current" counterpart.
+ def is_latest_version?
+ current_way.version == self.version
+ end
end
--- /dev/null
+##
+# Redaction represents a record associated with a particular
+# action on the database to hide revisions from the history
+# which are not appropriate to redistribute any more.
+#
+# The circumstances of the redaction can be recorded in the
+# record's title and description fields, which can be
+# displayed linked from the redacted records.
+#
+class Redaction < ActiveRecord::Base
+ has_many :nodes
+ has_many :ways
+ has_many :relations
+end
require 'xml/libxml'
include ConsistencyValidations
-
+ include NotRedactable
+
self.table_name = "current_relations"
belongs_to :changeset
require 'xml/libxml'
include ConsistencyValidations
+ include NotRedactable
self.table_name = "current_ways"
match 'api/0.6/node/:id/ways' => 'way#ways_for_node', :via => :get, :id => /\d+/
match 'api/0.6/node/:id/relations' => 'relation#relations_for_node', :via => :get, :id => /\d+/
match 'api/0.6/node/:id/history' => 'old_node#history', :via => :get, :id => /\d+/
+ match 'api/0.6/node/:id/:version/redact' => 'old_node#redact', :version => /\d+/, :id => /\d+/
match 'api/0.6/node/:id/:version' => 'old_node#version', :via => :get, :id => /\d+/, :version => /\d+/
match 'api/0.6/node/:id' => 'node#read', :via => :get, :id => /\d+/
match 'api/0.6/node/:id' => 'node#update', :via => :put, :id => /\d+/
--- /dev/null
+require 'osm'
+
+module NotRedactable
+ def redacted?
+ false
+ end
+
+ def redact!(r)
+ raise OSM::APICannotRedactError.new
+ end
+end
end
end
+ ##
+ # raised when someone tries to redact a current version of
+ # an element - only historical versions can be redacted.
+ class APICannotRedactError < APIError
+ def status
+ :bad_request
+ end
+
+ def to_s
+ "Cannot redact current version of element, only historical versions may be redacted."
+ end
+ end
+
# Helper methods for going to/from mercator and lat/lng.
class Mercator
include Math
--- /dev/null
+require 'osm'
+
+module Redactable
+ def redacted?
+ not self.redaction.nil?
+ end
+
+ def redact!(redaction)
+ # check that this version isn't the current version
+ raise OSM::APICannotRedactError.new if self.is_latest_version?
+
+ # make the change
+ self.redaction = redaction
+ end
+end
tile: <%= QuadTile.tile_for_point(1,1) %>
timestamp: 2007-01-01 00:00:00
+redacted_node:
+ id: 17
+ latitude: <%= 1*SCALE %>
+ longitude: <%= 1*SCALE %>
+ changeset_id: 2
+ visible: false
+ version: 2
+ tile: <%= QuadTile.tile_for_point(1,1) %>
+ timestamp: 2007-01-01 00:00:00
+
tile: <%= QuadTile.tile_for_point(1,1) %>
timestamp: 2007-01-01 00:00:00
+redacted_node_redacted_version:
+ node_id: 17
+ latitude: <%= 1*SCALE %>
+ longitude: <%= 1*SCALE %>
+ changeset_id: 2
+ visible: true
+ version: 1
+ tile: <%= QuadTile.tile_for_point(1,1) %>
+ timestamp: 2007-01-01 00:00:00
+ redaction_id: 1
+
+redacted_node_current_version:
+ node_id: 17
+ latitude: <%= 1*SCALE %>
+ longitude: <%= 1*SCALE %>
+ changeset_id: 2
+ visible: false
+ version: 2
+ tile: <%= QuadTile.tile_for_point(1,1) %>
+ timestamp: 2007-01-01 00:00:00
--- /dev/null
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
+
+example:
+ id: 1
+ title: Example redaction
+ description: Usually some text would go here to say something about why the redaction happened.
check_current_version(current_nodes(:node_used_by_relationship))
check_current_version(current_nodes(:node_with_versions))
end
-
+
+ ##
+ # test the redaction of an old version of a node, while not being
+ # authorised.
+ def test_redact_node_unauthorised
+ do_redact_node(nodes(:node_with_versions_v3),
+ redactions(:example))
+ assert_response :unauthorized, "should need to be authenticated to redact."
+ end
+
+ ##
+ # test the redaction of an old version of a node, while being
+ # authorised as a normal user.
+ def test_redact_node_normal_user
+ basic_authorization(users(:public_user).email, "test")
+
+ do_redact_node(nodes(:node_with_versions_v3),
+ redactions(:example))
+ assert_response :forbidden, "should need to be moderator to redact."
+ end
+
+ ##
+ # test that, even as moderator, the current version of a node
+ # can't be redacted.
+ def test_redact_node_current_version
+ basic_authorization(users(:moderator_user).email, "test")
+
+ do_redact_node(nodes(:node_with_versions_v4),
+ redactions(:example))
+ assert_response :forbidden, "shouldn't be OK to redact current version as moderator."
+ end
+
+ ##
+ # test that redacted nodes aren't visible, regardless of
+ # authorisation except as moderator...
+ def test_version_redacted
+ node = nodes(:redacted_node_redacted_version)
+
+ get :version, :id => node.node_id, :version => node.version
+ assert_response :forbidden, "Redacted node shouldn't be visible via the version API."
+
+ # not even to a logged-in user
+ basic_authorization(users(:public_user).email, "test")
+ get :version, :id => node.node_id, :version => node.version
+ assert_response :forbidden, "Redacted node shouldn't be visible via the version API, even when logged in."
+ end
+
+ ##
+ # test that redacted nodes aren't visible in the history
+ def test_history_redacted
+ node = nodes(:redacted_node_redacted_version)
+
+ get :history, :id => node.node_id
+ assert_response :success, "Redaction shouldn't have stopped history working."
+ assert_select "osm node[id=#{node.node_id}][version=#{node.version}]", 0, "redacted node #{node.node_id} version #{node.version} shouldn't be present in the history."
+
+ # not even to a logged-in user
+ basic_authorization(users(:public_user).email, "test")
+ get :version, :id => node.node_id, :version => node.version
+ get :history, :id => node.node_id
+ assert_response :success, "Redaction shouldn't have stopped history working."
+ assert_select "osm node[id=#{node.node_id}][version=#{node.version}]", 0, "redacted node #{node.node_id} version #{node.version} shouldn't be present in the history, even when logged in."
+ end
+
+ ##
+ # test the redaction of an old version of a node, while being
+ # authorised as a moderator.
+ def test_redact_node_moderator
+ node = nodes(:node_with_versions_v3)
+ basic_authorization(users(:moderator_user).email, "test")
+
+ do_redact_node(node, redactions(:example))
+ assert_response :success, "should be OK to redact old version as moderator."
+
+ # check moderator can still see the redacted data
+ get :version, :id => node.node_id, :version => node.version
+ assert_response :success, "After redaction, node should not be gone for moderator."
+
+ # and when accessed via history
+ get :history, :id => node.node_id
+ assert_response :success, "Redaction shouldn't have stopped history working."
+ assert_select "osm node[id=#{node.node_id}][version=#{node.version}]", 1, "node #{node.node_id} version #{node.version} should still be present in the history for moderators."
+ end
+
+ # testing that if the moderator drops auth, he can't see the
+ # redacted stuff any more.
+ def test_redact_node_is_redacted
+ node = nodes(:node_with_versions_v3)
+ basic_authorization(users(:moderator_user).email, "test")
+
+ do_redact_node(node, redactions(:example))
+ assert_response :success, "should be OK to redact old version as moderator."
+
+ # re-auth as non-moderator
+ basic_authorization(users(:public_user).email, "test")
+
+ # check can't see the redacted data
+ get :version, :id => node.node_id, :version => node.version
+ assert_response :forbidden, "Redacted node shouldn't be visible via the version API."
+
+ # and when accessed via history
+ get :version, :id => node.node_id, :version => node.version
+ get :history, :id => node.node_id
+ assert_response :success, "Redaction shouldn't have stopped history working."
+ assert_select "osm node[id=#{node.node_id}][version=#{node.version}]", 0, "redacted node #{node.node_id} version #{node.version} shouldn't be present in the history."
+ end
+
+ def do_redact_node(node, redaction)
+ get :version, :id => node.node_id, :version => node.version
+ assert_response :success, "should be able to get version #{node.version} of node #{node.node_id}."
+
+ # now redact it
+ post :redact, :id => node.node_id, :version => node.version, :redaction => redaction.id
+ end
+
def check_current_version(node_id)
# get the current version of the node
current_node = with_controller(NodeController.new) do
set_fixture_class :gpx_file_tags => 'Tracetag'
fixtures :client_applications
+
+ fixtures :redactions
end
##
api_fixtures
def test_node_count
- assert_equal 16, Node.count
+ assert_equal 17, Node.count
end
def test_node_too_far_north
api_fixtures
def test_old_node_count
- assert_equal 19, OldNode.count
+ assert_equal 21, OldNode.count
end
def test_node_too_far_north
--- /dev/null
+require File.dirname(__FILE__) + '/../test_helper'
+require 'osm'
+
+class RedactionTest < ActiveSupport::TestCase
+ api_fixtures
+ fixtures :redactions
+
+ def test_cannot_redact_current
+ n = current_nodes(:node_with_versions)
+ r = redactions(:example)
+ assert_equal(false, n.redacted?, "Expected node to not be redacted already.")
+ assert_raise(OSM::APICannotRedactError) do
+ n.redact!(r)
+ end
+ end
+
+ def test_cannot_redact_current_via_old
+ n = nodes(:node_with_versions_v4)
+ r = redactions(:example)
+ assert_equal(false, n.redacted?, "Expected node to not be redacted already.")
+ assert_raise(OSM::APICannotRedactError) do
+ n.redact!(r)
+ end
+ end
+
+ def test_can_redact_old
+ n = nodes(:node_with_versions_v3)
+ r = redactions(:example)
+ assert_equal(false, n.redacted?, "Expected node to not be redacted already.")
+ assert_nothing_raised(OSM::APICannotRedactError) do
+ n.redact!(r)
+ end
+ assert_equal(true, n.redacted?, "Expected node to be redacted after redact! call.")
+ end
+
+end