First version of blocking feature. Allows both time-based (for map protection) and notice-based (for education) blocks on users. Also introduces user roles and a simple UI for displaying and administering these.

This commit is contained in:
Matt Amos 2009-09-28 16:01:00 +00:00
parent 52fa09ecae
commit daa2496024
33 changed files with 766 additions and 23 deletions

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.blocks.empty?
# NOTE: need slightly more helpful message than this.
render :text => "You got banned!", :status => :forbidden
end
end
def authorize(realm='Web Password', errormessage="Couldn't authenticate you")

View file

@ -0,0 +1,145 @@
class UserBlocksController < ApplicationController
layout 'site'
before_filter :authorize_web
before_filter :set_locale
before_filter :require_user, :only => [:new, :create, :edit, :delete]
before_filter :require_moderator, :only => [:new, :create, :edit, :delete]
def index
@user_blocks_pages, @user_blocks = paginate(:user_blocks,
:include => [:user, :moderator, :revoker],
:order => "user_blocks.end_at DESC",
:per_page => 20)
end
def show
@user_block = UserBlock.find(params[:id])
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
@display_name = params[:display_name]
@this_user = User.find_by_display_name(@display_name, :conditions => {:visible => true})
end
# GET /user_blocks/1/edit
def edit
@user_block = UserBlock.find(params[:id])
params[:user_block_period] = ((@user_block.end_at - Time.now.getutc) / 1.hour).ceil.to_s
end
def create
@display_name = params[:display_name]
@this_user = User.find_by_display_name(@display_name, :conditions => {:visible => true})
block_period = [UserBlock::PERIODS.max, params[:user_block_period].to_i].min
@user_block = UserBlock.new(:user_id => @this_user.id,
:moderator_id => @user.id,
:reason => params[:user_block][:reason],
:end_at => Time.now.getutc() + block_period.hours,
:needs_view => params[:user_block][:needs_view])
if (@this_user and @user.moderator? and
params[:tried_contacting] == "yes" and
params[:tried_waiting] == "yes" and
block_period >= 0)
if @user_block.save
flash[:notice] = t('user_block.create.flash', :name => @display_name)
redirect_to @user_block
else
render :action => "new"
end
else
if !@user.moderator?
flash[:notice] = t('user_block.create.not_a_moderator')
elsif params[:tried_contacting] != "yes"
flash[:notice] = t('user_block.create.try_contacting')
elsif params[:tried_waiting] != "yes"
flash[:notice] = t('user_block.create.try_waiting')
else
flash[:notice] = t('user_block.create.bad_parameters')
end
@display_name = @this_user.nil? ? '' : @this_user.display_name
render :action => "new"
end
end
def update
@user_block = UserBlock.find(params[:id])
block_period = [72, params[:user_block_period].to_i].min
if @user_block.moderator_id != @user.id
flash[:notice] = t('user_block.update.only_creator_can_edit')
redirect_to(@user_block)
elsif !@user_block.active?
flash[:notice] = t('user_block.update.block_expired')
redirect_to(@user_block)
elsif @user_block.update_attributes({ :end_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
@user_block = UserBlock.find(params[:id])
if !@user.moderator?
flash[:notice] = t('user_block.create.not_a_moderator')
redirect_to @user_block
elsif params[:confirm]
if @user_block.revoke!
flash[:notice] = t'user_block.revoke.flash'
redirect_to(@user_block)
else
flash[:notice] = t'user_block.revoke.error'
render :action => "edit"
end
end
end
##
# shows a list of all the blocks on the given user
def blocks_on
@this_user = User.find_by_display_name(params[:display_name])
@user_blocks_pages, @user_blocks = paginate(:user_blocks,
:include => [:user, :moderator, :revoker],
:conditions => {:user_id => @this_user.id},
:order => "user_blocks.end_at DESC",
:per_page => 20)
end
##
# shows a list of all the blocks by the given user.
def blocks_by
@this_user = User.find_by_display_name(params[:display_name])
@user_blocks_pages, @user_blocks = paginate(:user_blocks,
:include => [:user, :moderator, :revoker],
:conditions => {:moderator_id => @this_user.id},
:order => "user_blocks.end_at DESC",
:per_page => 20)
end
private
def require_moderator
redirect_to "/403.html" unless @user.moderator?
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,33 @@
class UserRolesController < ApplicationController
layout 'site'
before_filter :authorize_web
before_filter :require_user
before_filter :require_administrator
def grant
this_user = User.find_by_display_name(params[:display_name], :conditions => {:visible => true})
if this_user and UserRole::ALL_ROLES.include? params[:role]
this_user.roles.create(:role => params[:role])
else
flash[:notice] = t('user_role.grant.fail', :role => params[:role], :name => params[:display_name])
end
redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
end
def revoke
this_user = User.find_by_display_name(params[:display_name], :conditions => {:visible => true})
if this_user and UserRole::ALL_ROLES.include? params[:role]
UserRole.delete_all({:user_id => this_user.id, :role => params[:role]})
else
flash[:notice] = t('user_role.revoke.fail', :role => params[:role], :name => params[:display_name])
end
redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
end
private
def require_administrator
redirect_to "/403.html" unless @user.administrator?
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 :blocks, :class_name => "UserBlock", :conditions => ["user_blocks.end_at > now() 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 moderator role, false otherwise
def administrator?
has_role? 'administrator'
end
##
# returns true if the user has the requested role
def has_role?(role)
roles.inject(false) { |x, r| x or 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
blocks.inject(nil) { |s,x| s || (x.needs_view? ? x : nil) }
end
def delete
self.active = false
self.display_name = "user_#{self.id}"

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

@ -0,0 +1,36 @@
class UserBlock < ActiveRecord::Base
validate :moderator_permissions
belongs_to :user, :class_name => "User", :foreign_key => :user_id
belongs_to :moderator, :class_name => "User", :foreign_key => :moderator_id
belongs_to :revoker, :class_name => "User", :foreign_key => :revoker_id
PERIODS = [0, 1, 3, 6, 12, 24, 48, 96]
##
# returns true if the block is currently active (i.e: the user can't
# use the API).
def active?
needs_view or end_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)
attrs = { :end_at => Time.now.getutc(),
:revoker_id => @user.id,
:needs_view => false }
revoker.moderator? and update_attributes(attrs)
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("Must be a moderator to create or update a block.") if moderator_id_changed? and !moderator.moderator?
errors.add_to_base("Must be a moderator to revoke a block.") 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
ALL_ROLES = ['administrator', 'moderator']
validates_inclusion_of :role, :in => ALL_ROLES
belongs_to :user
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.#{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.#{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}")) %>
<% end %>
<% end %></h2>
<div id="userinformation">
<% if @user and @this_user.id == @user.id %>
<!-- Displaying user's own profile page -->
@ -18,6 +29,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,34 @@
<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_moderator_name %>
<td class="<%= c1 %>"><%= link_to h(block.moderator.display_name), :controller => 'user', :action => 'view', :display_name => block.moderator.display_name %></td>
<% end %>
<td class="<%= c1 %>"><%=h block.reason %></td>
<td class="<%= c1 %>">
<% if block.active? %>
<% if block.needs_view? %>
<%= t'user_block.partial.until_login' %>
<% else %>
<%= t('user_block.partial.time_future', :time => distance_of_time_in_words_to_now(block.end_at)) %>
<% end %>
<% else %>
<%= t'user_block.partial.not_active' %>
<% end %>
</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.moderator_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,19 @@
<table id="block_list" cellpadding="3">
<tr>
<% if show_user_name %>
<th><%= t'user_block.partial.display_name' %></th>
<% end %>
<% if show_moderator_name %>
<th><%= t'user_block.partial.moderator_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_moderator_name => show_moderator_name }, :collection => @user_blocks unless @user_blocks.nil? %>
</table>

View file

@ -0,0 +1,3 @@
<h1><%= t('user_block.blocks_by.heading', :name => @this_user.display_name) %></h1>
<%= render :partial => 'blocks', :locals => { :show_revoke_link => (@user and @user.moderator?), :show_user_name => true, :show_moderator_name => false } %>

View file

@ -0,0 +1,3 @@
<h1><%= t('user_block.blocks_on.heading', :name => @this_user.display_name) %></h1>
<%= render :partial => 'blocks', :locals => { :show_revoke_link => (@user and @user.moderator?), :show_user_name => false, :show_moderator_name => true } %>

View file

@ -0,0 +1,25 @@
<h1><%= t('user_block.edit.title', :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 => @user_block.user.display_name) %><br />
<%= f.text_area :reason %>
</p>
<p>
<%= label_tag 'user_block_period', t('user_block.edit.period') %><br />
<%= hidden_field_tag 'what is the period', params[:user_block_period] %>
<%= 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,3 @@
<h1><%= t('user_block.index.heading') %></h1>
<%= render :partial => 'blocks', :locals => { :show_revoke_link => (@user and @user.moderator?), :show_user_name => true, :show_moderator_name => true } %>

View file

@ -0,0 +1,32 @@
<h1><%= t('user_block.new.title', :name => @display_name) %></h1>
<% form_for(@user_block) do |f| %>
<%= f.error_messages %>
<p>
<%= check_box_tag 'tried_contacting', 'yes', (params[:tried_contacting] == "yes") %>
<%= label_tag 'tried_contacting', t('user_block.new.tried_contacting') %>
</p>
<p>
<%= check_box_tag 'tried_waiting', 'yes', (params[:tried_waiting] == "yes") %>
<%= label_tag 'tried_waiting', t('user_block.new.tried_waiting') %>
</p>
<p>
<%= f.label :reason, t('user_block.new.reason', :name => @display_name) %><br />
<%= f.text_area :reason %>
</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', @display_name %>
<%= f.submit t('user_block.new.submit') %>
</p>
<% end %>
<%= link_to 'Back', user_blocks_path %>

View file

@ -0,0 +1,25 @@
<h1><%= t('user_block.revoke.heading',
:block_on => @user_block.user.display_name,
:block_by => @user_block.moderator.display_name) %></h1>
<% if @user_block.end_at > Time.now %>
<p><b>
<%= t('user_block.revoke.time_future', :time => distance_of_time_in_words_to_now(@user_block.end_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.end_at)) %>
</p>
<% end %>

View file

@ -0,0 +1,38 @@
<h1><%= t('user_block.show.heading',
:block_on => @user_block.user.display_name,
:block_by => @user_block.moderator.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>
<% if @user_block.end_at > Time.now %>
<%= t('user_block.show.time_future', :time => distance_of_time_in_words_to_now(@user_block.end_at)) %>
<% else %>
<%= t('user_block.show.time_past', :time => distance_of_time_in_words_to_now(@user_block.end_at)) %>
<% end %>
</p>
<% if @user_block.needs_view %>
<p><%= t'user_block.show.needs_view' %></p>
<% end %>
<p>
<b><%= t'user_block.show.reason' %></b>
<%=h @user_block.reason %>
</p>
<% if @user_block.end_at > Time.now.getutc %>
<% if @user and @user.id == @user_block.moderator_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,20 @@
<h1>Editing user_role</h1>
<% form_for(@user_role) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :user_id %><br />
<%= f.text_field :user_id %>
</p>
<p>
<%= f.label :role %><br />
<%= f.text_field :role %>
</p>
<p>
<%= f.submit 'Update' %>
</p>
<% end %>
<%= link_to 'Show', @user_role %> |
<%= link_to 'Back', user_roles_path %>

View file

@ -0,0 +1,22 @@
<h1>Listing user_roles</h1>
<table>
<tr>
<th>User</th>
<th>Role</th>
</tr>
<% @user_roles.each do |user_role| %>
<tr>
<td><%=h user_role.user_id %></td>
<td><%=h user_role.role %></td>
<td><%= link_to 'Show', user_role %></td>
<td><%= link_to 'Edit', edit_user_role_path(user_role) %></td>
<td><%= link_to 'Destroy', user_role, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New user_role', new_user_role_path %>

View file

@ -0,0 +1,19 @@
<h1>New user_role</h1>
<% form_for(@user_role) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :user_id %><br />
<%= f.text_field :user_id %>
</p>
<p>
<%= f.label :role %><br />
<%= f.text_field :role %>
</p>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', user_roles_path %>

View file

@ -0,0 +1,13 @@
<p>
<b>User:</b>
<%=h @user_role.user_id %>
</p>
<p>
<b>Role:</b>
<%=h @user_role.role %>
</p>
<%= link_to 'Edit', edit_user_role_path(@user_role) %> |
<%= link_to 'Back', user_roles_path %>