Merge blocking branch to head.

This commit is contained in:
Tom Hughes 2009-10-06 12:37:49 +00:00
commit 32f8631671
37 changed files with 948 additions and 24 deletions

View file

@ -178,6 +178,7 @@ class AmfController < ApplicationController
amf_handle_error("'startchangeset'",nil,nil) do
user = getuser(usertoken)
if !user then return -1,"You are not logged in, so Potlatch can't write any changes to the database." end
unless user.active_blocks.empty? then return -1,t('application.setup_user_auth.blocked') end
# close previous changeset and add comment
if closeid
@ -472,7 +473,8 @@ class AmfController < ApplicationController
def findgpx(searchterm, usertoken)
amf_handle_error_with_timeout("'findgpx'" ,nil,nil) do
user = getuser(usertoken)
if !uid then return -1,"You must be logged in to search for GPX traces.",[] end
if !user then return -1,"You must be logged in to search for GPX traces.",[] end
unless user.active_blocks.empty? then return -1,t('application.setup_user_auth.blocked'),[] end
gpxs = []
if searchterm.to_i>0 then
@ -538,6 +540,7 @@ class AmfController < ApplicationController
amf_handle_error("'putrelation' #{relid}" ,'relation',relid) do
user = getuser(usertoken)
if !user then return -1,"You are not logged in, so the relation could not be saved." end
unless user.active_blocks.empty? then return -1,t('application.setup_user_auth.blocked') end
if !tags_ok(tags) then return -1,"One of the tags is invalid. Please pester Adobe to fix Flash on Linux." end
tags = strip_non_xml_chars tags
@ -625,6 +628,7 @@ class AmfController < ApplicationController
user = getuser(usertoken)
if !user then return -1,"You are not logged in, so the way could not be saved." end
unless user.active_blocks.empty? then return -1,t('application.setup_user_auth.blocked') end
if pointlist.length < 2 then return -2,"Server error - way is only #{points.length} points long." end
if !tags_ok(attributes) then return -1,"One of the tags is invalid. Please pester Adobe to fix Flash on Linux." end
attributes = strip_non_xml_chars attributes
@ -729,6 +733,7 @@ class AmfController < ApplicationController
amf_handle_error("'putpoi' #{id}", 'node',id) do
user = getuser(usertoken)
if !user then return -1,"You are not logged in, so the point could not be saved." end
unless user.active_blocks.empty? then return -1,t('application.setup_user_auth.blocked') end
if !tags_ok(tags) then return -1,"One of the tags is invalid. Please pester Adobe to fix Flash on Linux." end
tags = strip_non_xml_chars tags
@ -811,6 +816,7 @@ class AmfController < ApplicationController
amf_handle_error("'deleteway' #{way_id}" ,'way',id) do
user = getuser(usertoken)
unless user then return -1,"You are not logged in, so the way could not be deleted." end
unless user.active_blocks.empty? then return -1,t('application.setup_user_auth.blocked') end
way_id = way_id.to_i
nodeversions = {}

View file

@ -78,6 +78,12 @@ class ApplicationController < ActionController::Base
@user = User.authenticate(:username => username, :password => passwd) # basic auth
end
end
# check if the user has been banned
unless @user.nil? or @user.active_blocks.empty?
# NOTE: need slightly more helpful message than this.
render :text => t('application.setup_user_auth.blocked'), :status => :forbidden
end
end
def authorize(realm='Web Password', errormessage="Couldn't authenticate you")

View file

@ -0,0 +1,156 @@
class UserBlocksController < ApplicationController
layout 'site'
before_filter :authorize_web
before_filter :set_locale
before_filter :require_user, :only => [:new, :create, :edit, :update, :revoke]
before_filter :require_moderator, :only => [:create, :update, :revoke]
before_filter :lookup_this_user, :only => [:new, :create, :blocks_on, :blocks_by]
before_filter :lookup_user_block, :only => [:show, :edit, :update, :revoke]
before_filter :require_valid_params, :only => [:create, :update]
before_filter :check_database_readable
before_filter :check_database_writable, :only => [:create, :update, :revoke]
def index
@user_blocks_pages, @user_blocks = paginate(:user_blocks,
:include => [:user, :creator, :revoker],
:order => "user_blocks.ends_at DESC",
:per_page => 20)
end
def show
if @user and @user.id == @user_block.user_id
@user_block.needs_view = false
@user_block.save!
end
end
def new
@user_block = UserBlock.new
end
def edit
params[:user_block_period] = ((@user_block.ends_at - Time.now.getutc) / 1.hour).ceil.to_s
end
def create
unless @valid_params
redirect_to :action => "new"
return
end
@user_block = UserBlock.new(:user_id => @this_user.id,
:creator_id => @user.id,
:reason => params[:user_block][:reason],
:ends_at => Time.now.getutc() + @block_period.hours,
:needs_view => params[:user_block][:needs_view])
if @user_block.save
flash[:notice] = t('user_block.create.flash', :name => @this_user.display_name)
redirect_to @user_block
else
render :action => "new"
end
end
def update
unless @valid_params
redirect_to :action => "edit"
return
end
if @user_block.creator_id != @user.id
flash[:notice] = t('user_block.update.only_creator_can_edit')
redirect_to :action => "edit"
return
end
if @user_block.update_attributes({ :ends_at => Time.now.getutc() + @block_period.hours,
:reason => params[:user_block][:reason],
:needs_view => params[:user_block][:needs_view] })
flash[:notice] = t('user_block.update.success')
redirect_to(@user_block)
else
render :action => "edit"
end
end
##
# revokes the block, setting the end_time to now
def revoke
if params[:confirm]
if @user_block.revoke! @user
flash[:notice] = t'user_block.revoke.flash'
redirect_to(@user_block)
end
end
end
##
# shows a list of all the blocks on the given user
def blocks_on
@user_blocks_pages, @user_blocks = paginate(:user_blocks,
:include => [:user, :creator, :revoker],
:conditions => {:user_id => @this_user.id},
:order => "user_blocks.ends_at DESC",
:per_page => 20)
end
##
# shows a list of all the blocks by the given user.
def blocks_by
@user_blocks_pages, @user_blocks = paginate(:user_blocks,
:include => [:user, :creator, :revoker],
:conditions => {:creator_id => @this_user.id},
:order => "user_blocks.ends_at DESC",
:per_page => 20)
end
private
##
# require that the user is a moderator, or fill out a helpful error message
# and return them to the blocks index.
def require_moderator
unless @user.moderator?
flash[:notice] = t('user_block.filter.not_a_moderator')
redirect_to :action => 'index'
end
end
##
# ensure that there is a "this_user" instance variable
def lookup_this_user
@this_user = User.find_by_display_name(params[:display_name])
rescue ActiveRecord::RecordNotFound
redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name] unless @this_user
end
##
# ensure that there is a "user_block" instance variable
def lookup_user_block
@user_block = UserBlock.find(params[:id])
rescue ActiveRecord::RecordNotFound
render :action => "not_found", :status => :not_found
end
##
# check that the input parameters are valid, setting an instance
# variable if not. note that this doesn't do any redirection, as it's
# called before two different actions, each of which should redirect
# to a different place.
def require_valid_params
@block_period = params[:user_block_period].to_i
@valid_params = false
if !UserBlock::PERIODS.include?(@block_period)
flash[:notice] = t('user_block.filter.block_period')
elsif @user_block and !@user_block.active?
flash[:notice] = t('user_block.filter.block_expired')
else
@valid_params = true
end
end
end

View file

@ -142,9 +142,28 @@ class UserController < ApplicationController
end
def login
if params[:user] and session[:user].nil?
email_or_display_name = params[:user][:email]
pass = params[:user][:password]
user = User.authenticate(:username => email_or_display_name, :password => pass)
if user
session[:user] = user.id
elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
@notice = t 'user.login.account not active'
else
@notice = t 'user.login.auth failure'
end
end
if session[:user]
# The user is logged in already, if the referer param exists, redirect them to that
if params[:referer]
# The user is logged in, if the referer param exists, redirect them to that
# unless they've also got a block on them, in which case redirect them to
# the block so they can clear it.
user = User.find(session[:user])
block = user.blocked_on_view
if block
redirect_to block, :referrer => params[:referrer]
elsif params[:referer]
redirect_to params[:referer]
else
redirect_to :controller => 'site', :action => 'index'
@ -153,25 +172,6 @@ class UserController < ApplicationController
end
@title = t 'user.login.title'
if params[:user]
email_or_display_name = params[:user][:email]
pass = params[:user][:password]
user = User.authenticate(:username => email_or_display_name, :password => pass)
if user
session[:user] = user.id
if params[:referer]
redirect_to params[:referer]
else
redirect_to :controller => 'site', :action => 'index'
end
return
elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
@notice = t 'user.login.account not active'
else
@notice = t 'user.login.auth failure'
end
end
end
def logout

View file

@ -0,0 +1,83 @@
class UserRolesController < ApplicationController
layout 'site'
before_filter :authorize_web
before_filter :require_user
before_filter :lookup_this_user
before_filter :require_administrator
before_filter :require_valid_role
before_filter :not_in_role, :only => [:grant]
before_filter :in_role, :only => [:revoke]
around_filter :setup_nonce
def grant
@this_user.roles.create(:role => @role, :granter_id => @user.id)
redirect_to :controller => 'user', :action => 'view', :display_name => @this_user.display_name
end
def revoke
UserRole.delete_all({:user_id => @this_user.id, :role => @role})
redirect_to :controller => 'user', :action => 'view', :display_name => @this_user.display_name
end
private
def require_administrator
unless @user.administrator?
flash[:notice] = t'user_role.filter.not_an_administrator'
redirect_to :controller => 'user', :action => 'view', :display_name => @this_user.display_name
end
end
##
# ensure that there is a "this_user" instance variable
def lookup_this_user
@this_user = User.find_by_display_name(params[:display_name])
rescue ActiveRecord::RecordNotFound
redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name] unless @this_user
end
##
# the random nonce here which isn't predictable, making an CSRF
# procedure much, much more difficult. setup the nonce. if the given
# nonce matches the session nonce then yield into the actual method.
# otherwise, just sets up the nonce for the form.
def setup_nonce
if params[:nonce] and params[:nonce] == session[:nonce]
@nonce = params[:nonce]
yield
else
@nonce = OAuth::Helper.generate_nonce
session[:nonce] = @nonce
render
end
end
##
# require that the given role is valid. the role is a URL
# parameter, so should always be present.
def require_valid_role
@role = params[:role]
unless UserRole::ALL_ROLES.include?(@role)
flash[:notice] = t('user_role.filter.not_a_role', :role => @role)
redirect_to :controller => 'user', :action => 'view', :display_name => @this_user.display_name
end
end
##
# checks that the user doesn't already have this role
def not_in_role
if @this_user.has_role? @role
flash[:notice] = t('user_role.filter.already_has_role', :role => @role)
redirect_to :controller => 'user', :action => 'view', :display_name => @this_user.display_name
end
end
##
# checks that the user already has this role
def in_role
unless @this_user.has_role? @role
flash[:notice] = t('user_role.filter.doesnt_have_role', :role => @role)
redirect_to :controller => 'user', :action => 'view', :display_name => @this_user.display_name
end
end
end

View file

@ -0,0 +1,20 @@
module UserBlocksHelper
##
# returns a translated string representing the status of the
# user block (i.e: whether it's active, what the expiry time is)
def block_status(block)
if block.active?
if block.needs_view?
I18n.t('user_block.helper.until_login')
else
I18n.t('user_block.helper.time_future', :time => distance_of_time_in_words_to_now(block.ends_at))
end
else
# the max of the last update time or the ends_at time is when this block finished
# either because the user viewed the block (updated_at) or it expired or was
# revoked (ends_at)
last_time = [block.ends_at, block.updated_at].max
I18n.t('user_block.helper.time_past', :time => distance_of_time_in_words_to_now(last_time))
end
end
end

View file

@ -14,6 +14,9 @@ class User < ActiveRecord::Base
has_many :client_applications
has_many :oauth_tokens, :class_name => "OauthToken", :order => "authorized_at desc", :include => [:client_application]
has_many :active_blocks, :class_name => "UserBlock", :conditions => ['user_blocks.ends_at > \'#{Time.now.getutc.xmlschema(5)}\' or user_blocks.needs_view']
has_many :roles, :class_name => "UserRole"
validates_presence_of :email, :display_name
validates_confirmation_of :email#, :message => ' addresses must match'
validates_confirmation_of :pass_crypt#, :message => ' must match the confirmation password'
@ -125,6 +128,31 @@ class User < ActiveRecord::Base
return false
end
##
# returns true if the user has the moderator role, false otherwise
def moderator?
has_role? 'moderator'
end
##
# returns true if the user has the administrator role, false otherwise
def administrator?
has_role? 'administrator'
end
##
# returns true if the user has the requested role
def has_role?(role)
roles.any? { |r| r.role == role }
end
##
# returns the first active block which would require users to view
# a message, or nil if there are none.
def blocked_on_view
active_blocks.detect { |b| b.needs_view? }
end
def delete
self.active = false
self.display_name = "user_#{self.id}"

35
app/models/user_block.rb Normal file
View file

@ -0,0 +1,35 @@
class UserBlock < ActiveRecord::Base
validate :moderator_permissions
belongs_to :user, :class_name => "User", :foreign_key => :user_id
belongs_to :creator, :class_name => "User", :foreign_key => :creator_id
belongs_to :revoker, :class_name => "User", :foreign_key => :revoker_id
PERIODS = APP_CONFIG['user_block_periods']
##
# returns true if the block is currently active (i.e: the user can't
# use the API).
def active?
needs_view or ends_at > Time.now.getutc
end
##
# revokes the block, allowing the user to use the API again. the argument
# is the user object who is revoking the ban.
def revoke!(revoker)
update_attributes({ :ends_at => Time.now.getutc(),
:revoker_id => revoker.id,
:needs_view => false })
end
private
##
# validate that only moderators are allowed to change the
# block. this should be caught and dealt with in the controller,
# but i've also included it here just in case.
def moderator_permissions
errors.add_to_base(I18n.t('user_block.model.non_moderator_update')) if creator_id_changed? and !creator.moderator?
errors.add_to_base(I18n.t('user_block.model.non_moderator_revoke')) unless revoker_id.nil? or revoker.moderator?
end
end

8
app/models/user_role.rb Normal file
View file

@ -0,0 +1,8 @@
class UserRole < ActiveRecord::Base
belongs_to :user
ALL_ROLES = ['administrator', 'moderator']
validates_inclusion_of :role, :in => ALL_ROLES
validates_uniqueness_of :role, :scope => :user_id
end

View file

@ -1,4 +1,15 @@
<h2><%= h(@this_user.display_name) %></h2>
<h2><%= h(@this_user.display_name) %>
<% UserRole::ALL_ROLES.each do |role| %>
<% if @user and @user.administrator? %>
<% if @this_user.has_role? role %>
<%= link_to(image_tag("roles/#{role}.png", :size => "20x20", :border => 0, :alt => t("user.view.role.revoke.#{role}"), :title => t("user.view.role.revoke.#{role}")), :controller => 'user_roles', :action => 'revoke', :display_name => @this_user.display_name, :role => role) %>
<% else %>
<%= link_to(image_tag("roles/blank_#{role}.png", :size => "20x20", :border => 0, :alt => t("user.view.role.grant.#{role}"), :title => t("user.view.role.grant.#{role}")), :controller => 'user_roles', :action => 'grant', :display_name => @this_user.display_name, :role => role) %>
<% end %>
<% elsif @this_user.has_role? role %>
<%= image_tag("roles/#{role}.png", :size => "20x20", :border => 0, :alt => t("user.view.role.#{role}"), :title => t("user.view.role.#{role}")) %>
<% end %>
<% end %></h2>
<div id="userinformation">
<% if @user and @this_user.id == @user.id %>
<!-- Displaying user's own profile page -->
@ -7,6 +18,10 @@
| <%= link_to t('user.view.my edits'), :controller => 'changeset', :action => 'list', :display_name => @user.display_name %>
| <%= link_to t('user.view.my traces'), :controller => 'trace', :action=>'mine' %>
| <%= link_to t('user.view.my settings'), :controller => 'user', :action => 'account', :display_name => @user.display_name %>
| <%= link_to t('user.view.blocks on me'), :controller => 'user_blocks', :action => 'blocks_on', :display_name => @user.display_name %>
<% if @user and @user.moderator? %>
| <%= link_to t('user.view.blocks by me'), :controller => 'user_blocks', :action => 'blocks_by', :display_name => @user.display_name %>
<% end %>
<% else %>
<!-- Displaying another user's profile page -->
<%= link_to t('user.view.send message'), :controller => 'message', :action => 'new', :display_name => @this_user.display_name %>
@ -18,6 +33,13 @@
<% else %>
<%= link_to t('user.view.add as friend'), :controller => 'user', :action => 'make_friend', :display_name => @this_user.display_name %>
<% end %>
| <%= link_to t('user.view.block_history'), :controller => 'user_blocks', :action => 'blocks_on', :display_name => @this_user.display_name %>
<% if @this_user.moderator? %>
| <%= link_to t('user.view.moderator_history'), :controller => 'user_blocks', :action => 'blocks_by', :display_name => @this_user.display_name %>
<% end %>
<% if @user and @user.moderator? %>
| <%= link_to t('user.view.create_block'), :controller => 'user_blocks', :action => 'new', :display_name => @this_user.display_name %>
<% end %>
<% end %>
</div>

View file

@ -0,0 +1,24 @@
<tr>
<% c1 = cycle('table0', 'table1') %>
<% if show_user_name %>
<td class="<%= c1 %>"><%= link_to h(block.user.display_name), :controller => 'user', :action => 'view', :display_name => block.user.display_name %></td>
<% end %>
<% if show_creator_name %>
<td class="<%= c1 %>"><%= link_to h(block.creator.display_name), :controller => 'user', :action => 'view', :display_name => block.creator.display_name %></td>
<% end %>
<td class="<%= c1 %>"><%=h truncate(block.reason) %></td>
<td class="<%= c1 %>"><%=h block_status(block) %></td>
<td class="<%= c1 %>">
<% if block.revoker_id.nil? %>
<%= t('user_block.partial.not_revoked') %>
<% else %>
<%= link_to h(block.revoker.display_name), :controller => 'user', :action => 'view', :display_name => block.revoker.display_name %>
<% end %>
</td>
<td class="<%= c1 %>"><%= link_to t('user_block.partial.show'), block %></td>
<td class="<%= c1 %>"><% if @user and @user.id == block.creator_id and block.active? %><%= link_to t('user_block.partial.edit'), edit_user_block_path(block) %><% end %></td>
<% if show_revoke_link %>
<td class="<%= c1 %>"><% if block.active? %><%= link_to t('user_block.partial.revoke'), block, :confirm => t('user_block.partial.confirm'), :action => :revoke %><% end %></td>
<% end %>
</tr>

View file

@ -0,0 +1,23 @@
<% unless @user_blocks.empty? %>
<table id="block_list" cellpadding="3">
<tr>
<% if show_user_name %>
<th><%= t'user_block.partial.display_name' %></th>
<% end %>
<% if show_creator_name %>
<th><%= t'user_block.partial.creator_name' %></th>
<% end %>
<th><%= t'user_block.partial.reason' %></th>
<th><%= t'user_block.partial.status' %></th>
<th><%= t'user_block.partial.revoker_name' %></th>
<th></th>
<th></th>
<% if show_revoke_link %>
<th></th>
<% end %>
</tr>
<%= render :partial => 'block', :locals => {:show_revoke_link => show_revoke_link, :show_user_name => show_user_name, :show_creator_name => show_creator_name }, :collection => @user_blocks %>
</table>
<% else %>
<p><%= t "user_block.#{blocks_by_on}.empty", :name => h(@this_user.display_name) %></p>
<% end %>

View file

@ -0,0 +1,4 @@
<% @title = t('user_block.blocks_by.title', :name => h(@this_user.display_name)) %>
<h1><%= t('user_block.blocks_by.heading', :name => link_to(h(@this_user.display_name), {:controller => 'user', :action => 'view', :display_name => @this_user.display_name})) %></h1>
<%= render :partial => 'blocks', :locals => { :blocks_by_on => 'blocks_by', :show_revoke_link => (@user and @user.moderator?), :show_user_name => true, :show_creator_name => false } %>

View file

@ -0,0 +1,4 @@
<% @title = t('user_block.blocks_on.title', :name => h(@this_user.display_name)) %>
<h1><%= t('user_block.blocks_on.heading', :name => link_to(h(@this_user.display_name), {:controller => 'user', :action => 'view', :display_name => @this_user.display_name})) %></h1>
<%= render :partial => 'blocks', :locals => { :blocks_by_on => 'blocks_on', :show_revoke_link => (@user and @user.moderator?), :show_user_name => false, :show_creator_name => true } %>

View file

@ -0,0 +1,28 @@
<% @title = t 'user_block.edit.title', :name => h(@user_block.user.display_name) %>
<h1><%= t('user_block.edit.title',
:name => link_to(
h(@user_block.user.display_name),
{:controller => 'user', :action => 'view', :display_name => @user_block.user.display_name})) %></h1>
<% form_for(@user_block) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :reason, t('user_block.edit.reason', :name => h(@user_block.user.display_name)) %><br />
<%= f.text_area :reason, :cols => 80, :rows => 5 %>
</p>
<p>
<%= label_tag 'user_block_period', t('user_block.edit.period') %><br />
<%= select_tag('user_block_period', options_for_select(UserBlock::PERIODS.collect { |h| [t('user_block.period', :count => h), h.to_s] }, params[:user_block_period])) %>
</p>
<p>
<%= f.check_box :needs_view %>
<%= f.label :needs_view, t('user_block.edit.needs_view') %>
</p>
<p>
<%= f.submit t('user_block.edit.submit') %>
</p>
<% end %>
<%= link_to t('user_block.edit.show'), @user_block %> |
<%= link_to t('user_block.edit.back'), user_blocks_path %>

View file

@ -0,0 +1,4 @@
<% @title = t('user_block.index.title') %>
<h1><%= t('user_block.index.heading') %></h1>
<%= render :partial => 'blocks', :locals => { :show_revoke_link => (@user and @user.moderator?), :show_user_name => true, :show_creator_name => true } %>

View file

@ -0,0 +1,28 @@
<% @title = t 'user_block.new.title', :name => h(@this_user.display_name) %>
<h1><%= t('user_block.new.heading',
:name => link_to(
h(@this_user.display_name),
{:controller => 'user', :action => 'view', :display_name => @this_user.display_name})) %></h1>
<% form_for(@user_block) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :reason, t('user_block.new.reason', :name => @this_user.display_name) %><br />
<%= f.text_area :reason, :cols => 80, :rows => 5 %>
</p>
<p>
<%= label_tag 'user_block_period', t('user_block.new.period') %><br />
<%= select_tag('user_block_period', options_for_select(UserBlock::PERIODS.collect { |h| [t('user_block.period', :count => h), h.to_s] }, params[:user_block_period] )) %>
</p>
<p>
<%= f.check_box :needs_view %>
<%= f.label :needs_view, t('user_block.new.needs_view') %>
</p>
<p>
<%= hidden_field_tag 'display_name', @this_user.display_name %>
<%= f.submit t('user_block.new.submit') %>
</p>
<% end %>
<%= link_to t('user_block.new.back'), user_blocks_path %>

View file

@ -0,0 +1,3 @@
<p><%= t'user_block.not_found.sorry', :id => params[:id] %></p>
<%= link_to t('user_block.not_found.back'), user_blocks_path %>

View file

@ -0,0 +1,32 @@
<% @title = t('user_block.revoke.title',
:block_on => h(@user_block.user.display_name),
:block_by => h(@user_block.creator.display_name)) %>
<h1><%= t('user_block.revoke.heading',
:block_on => link_to(
h(@user_block.user.display_name),
{:controller => 'user', :action => 'view', :display_name => @user_block.user.display_name}),
:block_by => link_to(
h(@user_block.creator.display_name),
{:controller => 'user', :action => 'view', :display_name => @user_block.creator.display_name})) %></h1>
<% if @user_block.ends_at > Time.now %>
<p><b>
<%= t('user_block.revoke.time_future', :time => distance_of_time_in_words_to_now(@user_block.ends_at)) %>
</b></p>
<% form_for :revoke, :url => { :action => "revoke" } do |f| %>
<%= f.error_messages %>
<p>
<%= check_box_tag 'confirm', 'yes' %>
<%= label_tag 'confirm', t('user_block.revoke.confirm') %>
</p>
<p>
<%= submit_tag t('user_block.revoke.revoke') %>
</p>
<% end %>
<% else %>
<p>
<%= t('user_block.revoke.past', :time => distance_of_time_in_words_to_now(@user_block.ends_at)) %>
</p>
<% end %>

View file

@ -0,0 +1,34 @@
<% @title = t('user_block.show.title',
:block_on => @user_block.user.display_name,
:block_by => @user_block.creator.display_name) %>
<h1><%= t('user_block.show.heading',
:block_on => link_to(
h(@user_block.user.display_name),
{:controller => 'user', :action => 'view', :display_name => @user_block.user.display_name}),
:block_by => link_to(
h(@user_block.creator.display_name),
{:controller => 'user', :action => 'view', :display_name => @user_block.creator.display_name})) %></h1>
<% if @user_block.revoker %>
<p>
<b><%= t'user_block.show.revoker' %></b>
<%= link_to h(@user_block.revoker.display_name), :controller => 'user', :action => 'view', :display_name => @user_block.revoker.display_name %>
</p>
<% end %>
<p><b><%= t'user_block.show.status' %></b>: <%= block_status(@user_block) %></p>
<p>
<b><%= t'user_block.show.reason' %></b>
<%=h @user_block.reason %>
</p>
<% if @user_block.ends_at > Time.now.getutc %>
<% if @user and @user.id == @user_block.creator_id %>
<%= link_to t('user_block.show.edit'), edit_user_block_path(@user_block) %> |
<% end %>
<% if @user and @user.moderator? %>
<%= link_to(t('user_block.show.revoke'),{:controller => 'user_blocks', :action => 'revoke', :id => @user_block.id}) %> |
<% end %>
<% end %>
<%= link_to t('user_block.show.back'), user_blocks_path %>

View file

@ -0,0 +1,7 @@
<% form_tag request.request_uri do %>
<%= hidden_field_tag 'nonce', @nonce %>
<% @title = t('user_role.grant.heading') %>
<h1><%= t('user_role.grant.heading') %></h1>
<p><%= t('user_role.grant.are_you_sure', :name => params[:display_name], :role => params[:role]) %></p>
<p><%= submit_tag t('user_role.grant.confirm') %></p>
<% end %>

View file

@ -0,0 +1,7 @@
<% form_tag request.request_uri do %>
<%= hidden_field_tag 'nonce', @nonce %>
<% @title = t('user_role.revoke.heading') %>
<h1><%= t('user_role.revoke.heading') %></h1>
<p><%= t('user_role.revoke.are_you_sure', :name => params[:display_name], :role => params[:role]) %></p>
<p><%= submit_tag t'user_role.revoke.confirm' %></p>
<% end %>

View file

@ -13,6 +13,8 @@ standard_settings: &standard_settings
geonames_zoom: 12
# Timeout for API calls in seconds
api_timeout: 300
# Periods (in hours) which are allowed for user blocks
user_block_periods: [0, 1, 3, 6, 12, 24, 48, 96]
development:
<<: *standard_settings

View file

@ -805,6 +805,9 @@ en:
scheduled_for_deletion: "Track scheduled for deletion"
make_public:
made_public: "Track made public"
application:
setup_user_auth:
blocked: "Your access to the API has been blocked. Please log-in to the web interface to find out more."
oauth:
oauthorize:
request_access: "The application {{app_name}} is requesting access to your account. Please check whether you would like the application to have the following capabilities. You may choose as many or as few as you like."
@ -924,6 +927,8 @@ en:
my edits: my edits
my traces: my traces
my settings: my settings
blocks on me: blocks on me
blocks by me: blocks by me
send message: send message
diary: diary
edits: edits
@ -949,6 +954,18 @@ en:
no nearby users: "There are no users who admit to mapping nearby yet."
change your settings: change your settings
my_oauth_details: "View my OAuth details"
role:
administrator: "This user is an administrator"
moderator: "This user is a moderator"
grant:
administrator: "Grant administrator access"
moderator: "Grant moderator access"
revoke:
administrator: "Revoke administrator access"
moderator: "Revoke moderator access"
block_history: "view blocks received"
moderator_history: "view blocks given"
create_block: "block this user"
friend_map:
your location: Your location
nearby mapper: "Nearby mapper: [[nearby_user]]"
@ -998,6 +1015,112 @@ en:
remove_friend:
success: "{{name}} was removed from your friends."
not_a_friend: "{{name}} is not one of your friends."
user_role:
filter:
not_an_administrator: "Only administrators can perform user role management, and you are not an administrator."
not_a_role: "The string `{{role}}' isn't a valid role."
already_has_role: "The user already has role {{role}}."
doesnt_have_role: "The user does not have role {{role}}."
grant:
title: Confirm role granting
heading: Confirm role granting
are_you_sure: "Are you sure you want to grant the role `{{role}}' to the user `{{name}}'?"
confirm: "Confirm"
fail: "Couldn't grant role `{{role}}' to user `{{name}}'. Please check that the user and role are both valid."
revoke:
title: Confirm role revoking
heading: Confirm role revoking
are_you_sure: "Are you sure you want to revoke the role `{{role}}' from the user `{{name}}'?"
confirm: "Confirm"
fail: "Couldn't revoke role `{{role}}' from user `{{name}}'. Please check that the user and role are both valid."
user_block:
model:
non_moderator_update: "Must be a moderator to create or update a block."
non_moderator_revoke: "Must be a moderator to revoke a block."
not_found:
sorry: "Sorry, the user block with ID {{id}} could not be found."
back: "Back to index"
new:
title: "Creating block on {{name}}"
heading: "Creating block on {{name}}"
reason: "The reason why {{name}} is being blocked. Please be as calm and as reasonable as possible, giving as much detail as you can about the situation, remembering that the message will be publicly visible. Bear in mind that not all users understand the community jargon, so please try to use laymans terms."
period: "How long, starting now, the user will be blocked from the API for."
submit: "Create block"
tried_contacting: "I have contacted the user and asked them to stop."
tried_waiting: "I have given a reasonable amount of time for the user to respond to those communications."
needs_view: "Does the user need to log in before this block will be cleared?"
back: "View all blocks"
edit:
title: "Editing block on {{name}}"
heading: "Editing block on {{name}}"
reason: "The reason why {{name}} is being blocked. Please be as calm and as reasonable as possible, giving as much detail as you can about the situation. Bear in mind that not all users understand the community jargon, so please try to use laymans terms."
period: "How long, starting now, the user will be blocked from the API for."
submit: "Update block"
show: "View this block"
back: "View all blocks"
needs_view: "Does the user need to log in before this block will be cleared?"
filter:
not_a_moderator: "You need to be a moderator to perform that action."
block_expired: "The block has already expired and cannot be edited."
block_period: "The blocking period must be one of the values selectable in the drop-down list."
create:
try_contacting: "Please try contacting the user before blocking them and giving them a reasonable time to respond."
try_waiting: "Please try giving the user a reasonable time to respond before blocking them."
flash: "Created a block on user {{name}}."
update:
only_creator_can_edit: "Only the moderator who created this block can edit it."
success: "Block updated."
index:
title: "User blocks"
heading: "List of user blocks"
revoke:
title: "Revoking block on {{block_on}}"
heading: "Revoking block on {{block_on}} by {{block_by}}"
time_future: "This block will end in {{time}}."
past: "This block ended {{time}} ago and cannot be revoked now."
confirm: "Are you sure you wish to revoke this block?"
revoke: "Revoke!"
flash: "This block has been revoked."
period:
one: "1 hour"
other: "{{count}} hours"
partial:
show: "Show"
edit: "Edit"
revoke: "Revoke!"
confirm: "Are you sure?"
display_name: "Blocked User"
creator_name: "Creator"
reason: "Reason for block"
status: "Status"
revoker_name: "Revoked by"
not_revoked: "(not revoked)"
helper:
time_future: "Ends in {{time}}."
until_login: "Active until the user logs in."
time_past: "Ended {{time}} ago."
blocks_on:
title: "Blocks on {{name}}"
heading: "List of blocks on {{name}}"
empty: "{{name}} has not been blocked yet."
blocks_by:
title: "Blocks by {{name}}"
heading: "List of blocks by {{name}}"
empty: "{{name}} has not made any blocks yet."
show:
title: "{{block_on}} blocked by {{block_by}}"
heading: "{{block_on}} blocked by {{block_by}}"
time_future: "Ends in {{time}}"
time_past: "Ended {{time}} ago"
status: "Status"
show: "Show"
edit: "Edit"
revoke: "Revoke!"
confirm: "Are you sure?"
reason: "Reason for block:"
back: "View all blocks"
revoker: "Revoker:"
needs_view: "The user needs to log in before this block will be cleared."
javascripts:
map:
base:

View file

@ -1,5 +1,4 @@
ActionController::Routing::Routes.draw do |map|
# API
map.connect "api/capabilities", :controller => 'api', :action => 'capabilities'
map.connect "api/#{API_VERSION}/capabilities", :controller => 'api', :action => 'capabilities'
@ -202,6 +201,15 @@ ActionController::Routing::Routes.draw do |map|
map.access_token '/oauth/access_token', :controller => 'oauth', :action => 'access_token'
map.test_request '/oauth/test_request', :controller => 'oauth', :action => 'test_request'
# roles and banning pages
map.connect '/user/:display_name/role/:role/grant', :controller => 'user_roles', :action => 'grant'
map.connect '/user/:display_name/role/:role/revoke', :controller => 'user_roles', :action => 'revoke'
map.connect '/user/:display_name/blocks', :controller => 'user_blocks', :action => 'blocks_on'
map.connect '/user/:display_name/blocks_by', :controller => 'user_blocks', :action => 'blocks_by'
map.resources :user_blocks, :as => 'blocks'
map.connect '/blocks/:id/revoke', :controller => 'user_blocks', :action => 'revoke'
map.connect '/blocks/new/:display_name', :controller => 'user_blocks', :action => 'new'
# fall through
map.connect ':controller/:id/:action'
map.connect ':controller/:action'

View file

@ -0,0 +1,32 @@
require 'lib/migrate'
class CreateUserRoles < ActiveRecord::Migration
def self.up
create_enumeration :user_role_enum, ["administrator", "moderator"]
create_table :user_roles do |t|
t.column :user_id, :bigint, :null => false
t.timestamps
end
add_column :user_roles, :role, :user_role_enum, :null => false
User.all(:conditions => ['administrator = ?', true]).each do |user|
UserRole.create(:user_id => user.id, :role => "administrator")
end
remove_column :users, :administrator
add_foreign_key :user_roles, [:user_id], :users, [:id]
end
def self.down
add_column :users, :administrator, :boolean, :default => false, :null => false
UserRole.all(:conditions => ['role = ?', "administrator"]).each do |role|
user = User.find(role.user_id)
user.administrator = true
user.save!
end
drop_table :user_roles
drop_enumeration :user_role_enum
end
end

View file

@ -0,0 +1,26 @@
require 'lib/migrate'
class CreateUserBlocks < ActiveRecord::Migration
def self.up
create_table :user_blocks do |t|
t.column :user_id, :bigint, :null => false
t.column :moderator_id, :bigint, :null => false
t.column :reason, :text, :null => false
t.column :end_at, :datetime, :null => false
t.column :needs_view, :boolean, :null => false, :default => false
t.column :revoker_id, :bigint
t.timestamps
end
add_foreign_key :user_blocks, [:user_id], :users, [:id]
add_foreign_key :user_blocks, [:moderator_id], :users, [:id]
add_foreign_key :user_blocks, [:revoker_id], :users, [:id]
add_index :user_blocks, [:user_id]
end
def self.down
drop_table :user_blocks
end
end

View file

@ -0,0 +1,29 @@
require 'lib/migrate'
class AlterUserRolesAndBlocks < ActiveRecord::Migration
def self.up
# the initial granter IDs can be "self" - there are none of these
# in the current live DB, but there may be some in people's own local
# copies.
add_column :user_roles, :granter_id, :bigint
UserRole.update_all("granter_id = user_id")
change_column :user_roles, :granter_id, :bigint, :null => false
add_foreign_key :user_roles, [:granter_id], :users, [:id]
# make sure that [user_id, role] is unique
add_index :user_roles, [:user_id, :role], :name => "user_roles_id_role_unique", :unique => true
# change the user_blocks to have a creator_id rather than moderator_id
rename_column :user_blocks, :moderator_id, :creator_id
# change the "end_at" column to the more grammatically correct "ends_at"
rename_column :user_blocks, :end_at, :ends_at
end
def self.down
remove_column :user_roles, :granter_id
remove_index :user_roles, :name => "user_roles_id_role_unique"
rename_column :user_blocks, :creator_id, :moderator_id
rename_column :user_blocks, :ends_at, :end_at
end
end

12
public/403.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<img src="http://www.openstreetmap.org/images/osm_logo.png" style="float:left; margin:10px">
<div style="float:left;">
<h1>Forbidden</h1>
<p>The operation you requested on the OpenStreetMap server is only available to administrators (HTTP 403)</p>
<p>Feel free to <a href="http://wiki.openstreetmap.org/wiki/Contact" title="Various contact channels explained">contact</a> the OpenStreetMap community if you have found a broken link / bug. Make a note of the exact URL of your request.</p>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 B

11
test/fixtures/user_roles.yml vendored Normal file
View file

@ -0,0 +1,11 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
administrator:
user_id: 6
role: administrator
granter_id: 6
moderator:
user_id: 5
role: moderator
granter_id: 6

View file

@ -51,3 +51,20 @@ second_public_user:
home_lon: 87
home_zoom: 12
moderator_user:
id: 5
email: moderator@example.com
active: true
pass_crypt: <%= Digest::MD5.hexdigest('test') %>
creation_time: "2008-05-01 01:23:45"
display_name: moderator
data_public: true
administrator_user:
id: 6
email: administrator@example.com
active: true
pass_crypt: <%= Digest::MD5.hexdigest('test') %>
creation_time: "2008-05-01 01:23:45"
display_name: administrator
data_public: true

View file

@ -0,0 +1,56 @@
require File.dirname(__FILE__) + '/../test_helper'
class UserBlocksTest < ActionController::IntegrationTest
fixtures :users, :user_blocks, :user_roles
def auth_header(user, pass)
{"HTTP_AUTHORIZATION" => "Basic %s" % Base64.encode64("#{user}:#{pass}")}
end
def test_api_blocked
blocked_user = users(:public_user)
get "/api/#{API_VERSION}/user/details"
assert_response :unauthorized
get "/api/#{API_VERSION}/user/details", nil, auth_header(blocked_user.display_name, "test")
assert_response :success
# now block the user
UserBlock.create(:user_id => blocked_user.id,
:creator_id => users(:moderator_user).id,
:reason => "testing",
:ends_at => Time.now.getutc + 5.minutes)
get "/api/#{API_VERSION}/user/details", nil, auth_header(blocked_user.display_name, "test")
assert_response :forbidden
end
def test_api_revoke
blocked_user = users(:public_user)
moderator = users(:moderator_user)
block = UserBlock.create(:user_id => blocked_user.id,
:creator_id => moderator.id,
:reason => "testing",
:ends_at => Time.now.getutc + 5.minutes)
get "/api/#{API_VERSION}/user/details", nil, auth_header(blocked_user.display_name, "test")
assert_response :forbidden
# revoke the ban
post '/login', {'user[email]' => moderator.email, 'user[password]' => "test", :referer => "/blocks/#{block.id}/revoke"}
assert_response :redirect
follow_redirect!
assert_response :success
assert_template 'user_blocks/revoke'
post "/blocks/#{block.id}/revoke", {'confirm' => "yes"}
assert_response :redirect
follow_redirect!
assert_response :success
assert_template 'user_blocks/show'
reset!
# access the API again. this time it should work
get "/api/#{API_VERSION}/user/details", nil, auth_header(blocked_user.display_name, "test")
assert_response :success
end
end

View file

@ -0,0 +1,46 @@
require File.dirname(__FILE__) + '/../test_helper'
class UserRolesControllerTest < ActionController::IntegrationTest
fixtures :users, :user_roles
test "grant" do
check_fail(:grant, :public_user, :moderator)
check_fail(:grant, :moderator_user, :moderator)
check_success(:grant, :administrator_user, :moderator)
end
test "revoke" do
check_fail(:revoke, :public_user, :moderator)
check_fail(:revoke, :moderator_user, :moderator)
# this other user doesn't have moderator role, so this fails
check_fail(:revoke, :administrator_user, :moderator)
end
def check_fail(action, user, role)
post '/login', {'user[email]' => users(user).email, 'user[password]' => "test", :referer => "/"}
assert_response :redirect
follow_redirect!
assert_response :success
get "/user/#{users(:second_public_user).display_name}/role/#{role}/#{action}"
assert_response :redirect
assert_redirected_to :controller => 'user', :action => 'view', :display_name => users(:second_public_user).display_name
reset!
end
def check_success(action, user, role)
post '/login', {'user[email]' => users(user).email, 'user[password]' => "test", :referer => "/"}
assert_response :redirect
follow_redirect!
assert_response :success
get "/user/#{users(:second_public_user).display_name}/role/#{role}/#{action}"
assert_response :success
post "/user/#{users(:second_public_user).display_name}/role/#{role}/#{action}", {:confirm => "yes", :nonce => session[:nonce]}
assert_response :redirect
assert_redirected_to :controller => 'user', :action => 'view', :display_name => users(:second_public_user).display_name
reset!
end
end