]> git.openstreetmap.org Git - rails.git/commitdiff
Merge branch 'master' into openid
authorTom Hughes <tom@compton.nu>
Fri, 7 May 2010 21:28:07 +0000 (22:28 +0100)
committerTom Hughes <tom@compton.nu>
Fri, 7 May 2010 21:28:07 +0000 (22:28 +0100)
Conflicts:
app/controllers/user_controller.rb

1  2 
app/controllers/user_controller.rb
app/models/user.rb
config/environment.rb
config/locales/en.yml
public/stylesheets/common.css
test/fixtures/users.yml

index 77f63dfc619706256fdaa43d24bdee27a299b81d,222840a0610a60ed1117594c65e746eca7742ef5..41a08363cc5bb1b3bbca51df6ac49a075afc7ff3
@@@ -11,12 -11,12 +11,12 @@@ class UserController < ApplicationContr
    before_filter :require_allow_read_prefs, :only => [:api_details]
    before_filter :require_allow_read_gpx, :only => [:api_gpx_files]
    before_filter :require_cookies, :only => [:login, :confirm]
-   before_filter :require_administrator, :only => [:activate, :deactivate, :hide, :unhide, :delete]
-   before_filter :lookup_this_user, :only => [:activate, :deactivate, :hide, :unhide, :delete]
+   before_filter :require_administrator, :only => [:set_status, :delete, :list]
+   before_filter :lookup_this_user, :only => [:set_status, :delete]
  
    filter_parameter_logging :password, :pass_crypt, :pass_crypt_confirmation
  
-   cache_sweeper :user_sweeper, :only => [:account, :hide, :unhide, :delete]
+   cache_sweeper :user_sweeper, :only => [:account, :set_status, :delete]
  
    def save
      @title = t 'user.new.title'
      if Acl.find_by_address(request.remote_ip, :conditions => {:k => "no_account_creation"})
        render :action => 'new'
      else
 -      @user = User.new(params[:user])
 -
 -      @user.status = "pending"
 -      @user.data_public = true
 -      @user.description = "" if @user.description.nil?
 -      @user.creation_ip = request.remote_ip
 -      @user.languages = request.user_preferred_languages
 +      #The redirect from the OpenID provider reenters here again 
 +      #and we need to pass the parameters through to the  
 +      #open_id_authentication function a second time 
 +      if params[:open_id_complete] 
 +        openid_verify('', true) 
 +        #We have set the user.openid_url to nil beforehand. If it hasn't 
 +        #been set to a new valid openid_url, it means the openid couldn't be validated 
 +        if @user.nil? or @user.openid_url.nil? 
 +          render :action => 'new' 
 +          return 
 +        end   
 +      else
 +        @user = User.new(params[:user])
 +
-         @user.visible = true
++        @user.status = "pending"
 +        @user.data_public = true
 +        @user.description = "" if @user.description.nil?
 +        @user.creation_ip = request.remote_ip
 +        @user.languages = request.user_preferred_languages
 +        #Set the openid_url to nil as for one it is used 
 +        #to check if the openid could be validated and secondly 
 +        #to not get dupplicate conflicts for an empty openid  
 +        @user.openid_url = nil
 +
 +        if (!params[:user][:openid_url].nil? and params[:user][:openid_url].length > 0)
 +          if (@user.pass_crypt.nil? or @user.pass_crypt.length == 0)
 +            #if the password is empty, but we have a openid 
 +            #then generate a random passowrd to disable 
 +            #loging in via password 
 +            @user.pass_crypt = ActiveSupport::SecureRandom.base64(16) 
 +            @user.pass_crypt_confirmation = @user.pass_crypt 
 +          end
 +          #Validate all of the other fields before
 +          #redirecting to the openid provider
 +          if !@user.valid?
 +            render :action => 'new'
 +          else        
 +            #TODO: Is it a problem to store the user variable with respect to password safty in the session variables?
 +            #Store the user variable in the session for it to be accessible when redirecting back from the openid provider
 +            session[:new_usr] = @user
 +            begin
 +              @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url])
 +            rescue
 +              flash.now[:error] = t 'user.login.openid invalid'
 +              render :action => 'new'
 +              return
 +            end
 +            #Verify that the openid provided is valid and that the user is the owner of the id
 +            openid_verify(@norm_openid_url, true)
 +            #openid_verify can return in two ways:
 +            #Either it returns with a redirect to the openid provider who then freshly
 +            #redirects back to this url if the openid is valid, or if the openid is not plausible
 +            #and no provider for it could be found it just returns
 +            #we want to just let the redirect through
 +            if response.headers["Location"].nil?
 +              render :action => 'new'
 +            end
 +          end
 +          #At this point there was either an error and the page has been rendered,
 +          #or there is a redirect to the openid provider and the rest of the method
 +          #gets executed whenn this method gets reentered after redirecting back
 +          #from the openid provider
 +          return
 +        end
 +      end
  
        if @user.save
          flash[:notice] = t 'user.new.flash create success message'
      @title = t 'user.account.title'
      @tokens = @user.oauth_tokens.find :all, :conditions => 'oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null'
  
 +    #The redirect from the OpenID provider reenters here again
 +    #and we need to pass the parameters through to the 
 +    #open_id_authentication function
 +    if params[:open_id_complete]
 +      openid_verify('', false)
 +      @user.save
 +      return
 +    end
 +
      if params[:user] and params[:user][:display_name] and params[:user][:description]
        @user.display_name = params[:user][:display_name]
        @user.new_email = params[:user][:new_email]
        @user.home_lat = params[:user][:home_lat]
        @user.home_lon = params[:user][:home_lon]
  
 +      @user.openid_url = nil if (params[:user][:openid_url].length == 0)
 +
        if @user.save
          set_locale
  
          if @user.new_email.nil? or @user.new_email.empty?
-           flash.now[:notice] = t 'user.account.flash update success'
+           flash[:notice] = t 'user.account.flash update success'
          else
-           flash.now[:notice] = t 'user.account.flash update success confirm needed'
+           flash[:notice] = t 'user.account.flash update success confirm needed'
  
            begin
              Notifier.deliver_email_confirm(@user, @user.tokens.create)
              # Ignore errors sending email
            end
          end
+         redirect_to :action => "account", :display_name => @user.display_name
        end
 +
 +      if (params[:user][:openid_url].length > 0)
 +        begin
 +          @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url])
 +          if (@norm_openid_url != @user.openid_url)
 +            #If the OpenID has changed, we want to check that it is a valid OpenID and one
 +            #the user has control over before saving the openID as a password equivalent for
 +            #the user.
 +            openid_verify(@norm_openid_url, false)
 +          end
 +        rescue
 +          flash.now[:error] = t 'user.login.openid invalid'
 +        end
 +      end
 +
      else
        if flash[:errors]
          flash[:errors].each do |attr,msg|
      end
    end
  
 +  def openid_specialcase_mapping(openid_url)
 +    #Special case gmail.com, as it is pontentially a popular OpenID provider and unlike
 +    #yahoo.com, where it works automatically, Google have hidden their OpenID endpoint
 +    #somewhere obscure making it less userfriendly.
 +    if (openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/) )
 +      return 'https://www.google.com/accounts/o8/id'
 +    end
 +
 +    return nil
 +  end  
 +
 +  def openid_verify(openid_url,account_create)
 +    authenticate_with_open_id(openid_url) do |result, identity_url|
 +      if result.successful?
 +        #We need to use the openid url passed back from the OpenID provider
 +        #rather than the one supplied by the user, as these can be different.
 +        #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
 +        #only once it comes back from the OpenID provider do we know the unique address for
 +        #the user.
 +        @user = session[:new_usr] unless @user #this is used for account creation when the user is not yet in the database
 +        @user.openid_url = identity_url
 +      elsif result.missing?
 +        mapped_id = openid_specialcase_mapping(openid_url)
 +        if mapped_id
 +          openid_verify(mapped_id, account_create)
 +        else
 +          flash.now[:error] = t 'user.login.openid missing provider'
 +        end
 +      elsif result.invalid?
 +        flash.now[:error] = t 'user.login.openid invalid'
 +      else
 +        flash.now[:error] = t 'user.login.auth failure'
 +      end
 +    end
 +  end
 +
 +  def open_id_authentication(openid_url)
 +    #TODO: only ask for nickname and email, if we don't already have a user for that openID, in which case
 +    #email and nickname are already filled out. I don't know how to do that with ruby syntax though, as we
 +    #don't want to duplicate the do block
 +    #On the other hand it also doesn't matter too much if we ask every time, as the OpenID provider should
 +    #remember these results, and shouldn't repromt the user for these data each time.
 +    user = nil
 +    authenticate_with_open_id(openid_url, :return_to => request.protocol + request.host_with_port + '/login?referer=' + params[:referer], :optional => [:nickname, :email]) do |result, identity_url, registration|
 +      if result.successful?
 +        #We need to use the openid url passed back from the OpenID provider
 +        #rather than the one supplied by the user, as these can be different.
 +        #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
 +        #only once it comes back from the OpenID provider do we know the unique address for
 +        #the user.
 +        user = User.find_by_openid_url(identity_url)
 +        if user
 +          if user.visible? and user.active?
 +            session[:user] = user.id
 +            session_expires_after 1.month if session[:remember]
 +            return user
 +          else
 +            user = nil
 +            flash.now[:error] = t 'user.login.account not active'
 +          end
 +        else
 +          #We don't have a user registered to this OpenID. Redirect to the create account page
 +          #with username and email filled in if they have been given by the OpenID provider through
 +          #the simple registration protocol
 +          redirect_to :controller => 'user', :action => 'new', :nickname => registration['nickname'], :email => registration['email'], :openid => identity_url
 +        end
 +      else if result.missing?
 +             #Try and apply some heuristics to make common cases more userfriendly
 +             mapped_id = openid_specialcase_mapping(openid_url)
 +             if mapped_id
 +               open_id_authentication(mapped_id)
 +             else
 +               flash.now[:error] = t 'user.login.openid missing provider'
 +             end
 +           else if result.invalid?
 +                  flash.now[:error] = t 'user.login.openid invalid'
 +                else
 +                  flash.now[:error] = t 'user.login.auth failure'
 +                end
 +           end
 +      end
 +    end
 +    return user
 +  end
 +
    def go_public
      @user.data_public = true
      @user.save
      @title = t 'user.lost_password.title'
  
      if params[:user] and params[:user][:email]
-       user = User.find_by_email(params[:user][:email], :conditions => {:visible => true})
+       user = User.find_by_email(params[:user][:email], :conditions => {:status => ["pending", "active", "confirmed"]})
  
        if user
          token = user.tokens.create
          if params[:user]
            @user.pass_crypt = params[:user][:pass_crypt]
            @user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
-           @user.active = true
+           @user.status = "active"
            @user.email_valid = true
  
            if @user.save
      # The user is logged in already, so don't show them the signup
      # page, instead send them to the home page
      redirect_to :controller => 'site', :action => 'index' if session[:user]
 +
 +    @nickname = params['nickname']
 +    @email = params['email']
 +    @openID = params['openid']
 +      
 +    if !params['openid'].nil?
 +        flash.now[:notice] = t 'user.new.openID association'
 +    end
    end
  
    def login
      @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
 -        session_expires_after 1.month if params[:remember_me]
 -
 -        # 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.
 -        if user.blocked_on_view
 -          redirect_to user.blocked_on_view, :referrer => params[:referrer]
 -        elsif params[:referer]
 -          redirect_to params[:referer]
 +    #The redirect from the OpenID provider reenters here again
 +    #and we need to pass the parameters through to the 
 +    # open_id_authentication function
 +    if params[:open_id_complete]
 +      user = open_id_authentication('')
 +    elsif params[:user]
 +      if !params[:user][:openid_url].nil? and !params[:user][:openid_url].empty?
 +        session[:remember] = params[:remember_me]
 +        #construct the openid request. This will redirect to the OpenID server to ask for validation
 +        #The external OpenID server will then redirect back to the login method and reenters at the top
 +        open_id_authentication(params[:user][:openid_url])
 +        return
 +      else
 +        email_or_display_name = params[:user][:email]
 +        pass = params[:user][:password]
 +
 +        if user = User.authenticate(:username => email_or_display_name, :password => pass)
 +          session[:user] = user.id
 +          session_expires_after 1.month if params[:remember_me]
-         elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
++        elsif User.authenticate(:username => email_or_display_name, :password => pass, :pending => true)
 +          flash.now[:error] = t 'user.login.account not active'
++        elsif User.authenticate(:username => email_or_display_name, :password => pass, :suspended => true)
++          flash.now[:error] = t 'user.login.account suspended'
          else
 -          redirect_to :controller => 'site', :action => 'index'
 +          flash.now[:error] = t 'user.login.auth failure'
          end
 -      elsif User.authenticate(:username => email_or_display_name, :password => pass, :pending => true)
 -        flash.now[:error] = t 'user.login.account not active'
 -      elsif User.authenticate(:username => email_or_display_name, :password => pass, :suspended => true)
 -        flash.now[:error] = t 'user.login.account suspended'
 +      end
 +    end
 +
 +    if user
 +      # 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.
 +      if user.blocked_on_view
 +        redirect_to user.blocked_on_view, :referrer => params[:referrer]
 +      elsif params[:referer]
 +        redirect_to params[:referer]
        else
 -        flash.now[:error] = t 'user.login.auth failure'
 +        redirect_to :controller => 'site', :action => 'index'
        end
      end
    end
        token = UserToken.find_by_token(params[:confirm_string])
        if token and !token.user.active?
          @user = token.user
-         @user.active = true
+         @user.status = "active"
          @user.email_valid = true
          @user.save!
          referer = token.referer
          @user = token.user
          @user.email = @user.new_email
          @user.new_email = nil
-         @user.active = true
          @user.email_valid = true
          if @user.save
            flash[:notice] = t 'user.confirm_email.success'
    def make_friend
      if params[:display_name]
        name = params[:display_name]
-       new_friend = User.find_by_display_name(name, :conditions => {:visible => true})
+       new_friend = User.find_by_display_name(name, :conditions => {:status => ["active", "confirmed"]})
        friend = Friend.new
        friend.user_id = @user.id
        friend.friend_user_id = new_friend.id
    def remove_friend
      if params[:display_name]
        name = params[:display_name]
-       friend = User.find_by_display_name(name, :conditions => {:visible => true})
+       friend = User.find_by_display_name(name, :conditions => {:status => ["active", "confirmed"]})
        if @user.is_friends_with?(friend)
          Friend.delete_all "user_id = #{@user.id} AND friend_user_id = #{friend.id}"
          flash[:notice] = t 'user.remove_friend.success', :name => friend.display_name
    end
  
    ##
-   # activate a user, allowing them to log in
-   def activate
-     @this_user.update_attributes(:active => true)
+   # sets a user's status
+   def set_status
+     @this_user.update_attributes(:status => params[:status])
      redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
    end
  
    ##
-   # deactivate a user, preventing them from logging in
-   def deactivate
-     @this_user.update_attributes(:active => false)
+   # delete a user, marking them as deleted and removing personal data
+   def delete
+     @this_user.delete
      redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
    end
  
    ##
-   # hide a user, marking them as logically deleted
-   def hide
-     @this_user.update_attributes(:visible => false)
-     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
-   end
+   # display a list of users matching specified criteria
+   def list
+     if request.post?
+       ids = params[:user].keys.collect { |id| id.to_i }
  
-   ##
-   # unhide a user, clearing the logically deleted flag
-   def unhide
-     @this_user.update_attributes(:visible => true)
-     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
-   end
+       User.update_all("status = 'confirmed'", :id => ids) if params[:confirm]
+       User.update_all("status = 'deleted'", :id => ids) if params[:hide]
  
-   ##
-   # delete a user, marking them as deleted and removing personal data
-   def delete
-     @this_user.delete
-     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
+       redirect_to url_for(:status => params[:status], :ip => params[:ip], :page => params[:page])
+     else
+       conditions = Hash.new
+       conditions[:status] = params[:status] if params[:status]
+       conditions[:creation_ip] = params[:ip] if params[:ip]
+       @user_pages, @users = paginate(:users,
+                                      :conditions => conditions,
+                                      :order => :id,
+                                      :per_page => 50)
+     end
    end
  private
    ##
    # require that the user is a administrator, or fill out a helpful error message
    # and return them to the user page.
    def require_administrator
-     unless @user.administrator?
+     if @user and not @user.administrator?
        flash[:error] = t('user.filter.not_an_administrator')
-       redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
+       if params[:display_name]
+         redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
+       else
+         redirect_to :controller => 'user', :action => 'login', :referer => request.request_uri
+       end
+     elsif not @user
+       redirect_to :controller => 'user', :action => 'login', :referer => request.request_uri
      end
    end
  
diff --combined app/models/user.rb
index 09e1a7d35611a9c3178a4832a3b19845b5faf0fe,31b0f27a03428ef9223b3c00bfe05bd89504c27f..f99ad3b40a8a51680b97f0b7c19b4fcd5504e13b
@@@ -3,10 -3,11 +3,11 @@@ class User < ActiveRecord::Bas
  
    has_many :traces, :conditions => { :visible => true }
    has_many :diary_entries, :order => 'created_at DESC'
+   has_many :diary_comments, :order => 'created_at DESC'
    has_many :messages, :foreign_key => :to_user_id, :conditions => { :to_user_visible => true }, :order => 'sent_on DESC'
    has_many :new_messages, :class_name => "Message", :foreign_key => :to_user_id, :conditions => { :to_user_visible => true, :message_read => false }, :order => 'sent_on DESC'
    has_many :sent_messages, :class_name => "Message", :foreign_key => :from_user_id, :conditions => { :from_user_visible => true }, :order => 'sent_on DESC'
-   has_many :friends, :include => :befriendee, :conditions => ["users.visible = ?", true]
+   has_many :friends, :include => :befriendee, :conditions => "users.status IN ('active', 'confirmed')"
    has_many :tokens, :class_name => "UserToken"
    has_many :preferences, :class_name => "UserPreference"
    has_many :changesets
@@@ -22,7 -23,6 +23,7 @@@
    validates_confirmation_of :pass_crypt#, :message => ' must match the confirmation password'
    validates_uniqueness_of :display_name, :allow_nil => true
    validates_uniqueness_of :email
 +  validates_uniqueness_of :openid_url, :allow_nil => true
    validates_length_of :pass_crypt, :within => 8..255
    validates_length_of :display_name, :within => 3..255, :allow_nil => true
    validates_email_format_of :email
        user = token.user if token
      end
  
-     if user
-       user = nil unless user.visible? and (user.active? or options[:inactive])
+     if user and
+       ( user.status == "deleted" or
+         ( user.status == "pending" and not options[:pending] ) or
+         ( user.status == "suspended" and not options[:suspended] ) )
+       user = nil
      end
  
      token.update_attribute(:expiry, 1.week.from_now) if token and user
        bounds = gc.bounds(radius)
        sql_for_distance = gc.sql_for_distance("home_lat", "home_lon")
        nearby = User.find(:all, 
-                          :conditions => ["id != ? AND visible = ? AND data_public = ? AND #{sql_for_distance} <= ?", id, true, true, radius], :order => sql_for_distance, :limit => num)
+                          :conditions => ["id != ? AND status IN (\'active\', \'confirmed\') AND data_public = ? AND #{sql_for_distance} <= ?", id, true, radius],
+                          :order => sql_for_distance, :limit => num)
      else
        nearby = []
      end
      return false
    end
  
+   ##
+   # returns true if a user is visible
+   def visible?
+     ["pending","active","confirmed"].include? self.status
+   end
+   ##
+   # returns true if a user is active
+   def active?
+     ["active","confirmed"].include? self.status
+   end
    ##
    # returns true if the user has the moderator role, false otherwise
    def moderator?
      active_blocks.detect { |b| b.needs_view? }
    end
  
+   ##
+   # delete a user - leave the account but purge most personal data
    def delete
-     self.active = false
      self.display_name = "user_#{self.id}"
      self.description = ""
      self.home_lat = nil
      self.image = nil
      self.email_valid = false
      self.new_email = nil
-     self.visible = false
+     self.status = "deleted"
      self.save
    end
  
+   ##
+   # return a spam score for a user
+   def spam_score
+     changeset_score = self.changesets.find(:all, :limit => 10).length * 50
+     trace_score = self.traces.find(:all, :limit => 10).length * 50
+     diary_entry_score = self.diary_entries.inject(0) { |s,e| s += OSM.spam_score(e.body) }
+     diary_comment_score = self.diary_comments.inject(0) { |s,e| s += OSM.spam_score(e.body) }
+     score = OSM.spam_score(self.description) * 2
+     score += diary_entry_score / self.diary_entries.length if self.diary_entries.length > 0
+     score += diary_comment_score / self.diary_comments.length if self.diary_comments.length > 0
+     score -= changeset_score
+     score -= trace_score
+     return score.to_i
+   end
  end
diff --combined config/environment.rb
index 7f9343ce0b3181a57060f0f61e7005f9d38bcbf1,d8f9b2fc85ca0503146b1c55ff390c86bf1003cc..224b907061b340808b964418a65a83ff79f024cd
@@@ -56,7 -56,6 +56,7 @@@ Rails::Initializer.run do |config
    config.gem 'rmagick', :lib => 'RMagick'
    config.gem 'oauth', :version => '>= 0.3.6'
    config.gem 'httpclient'
 +  config.gem 'ruby-openid', :lib => 'openid', :version => '>=2.0.4'
    config.gem 'SystemTimer', :version => '>= 1.1.3', :lib => 'system_timer'
    config.gem 'sanitize'
  
    config.active_record.schema_format = :sql
  
    # Activate observers that should always be running
-   # config.active_record.observers = :cacher, :garbage_collector
+   config.active_record.observers = :spam_observer
  
    # Make Active Record use UTC-base instead of local time
    config.active_record.default_timezone = :utc
diff --combined config/locales/en.yml
index cb4de862ebe887a41843d67f546fa5923ce727be,fbf8e7a0462ab620397b0f0f7133c744718234a4..97551164f6a387f6e56ab52a3550025ac9defa16
        create_account: "create an account"
        email or username: "Email Address or Username:"
        password: "Password:"
 +      openid: "OpenID:"
 +      openid description: "Use your OpenID to login"
 +      username_heading: "Login with username and password:"
 +      openid_heading: "Login with an OpenID:"
        remember: "Remember me:"
        lost password link: "Lost your password?"
        login_button: "Login"
        account not active: "Sorry, your account is not active yet.<br />Please click on the link in the account confirmation email to activate your account."
+       account suspended: Sorry, your account has been suspended due to suspicious activity.<br />Please contact the <a href="mailto:webmaster@openstreetmap.org">webmaster</a> if you wish to discuss this.
        auth failure: "Sorry, could not log in with those details."
 +      openid missing provider: "Sorry, could not contact your OpenID provider"
 +      openid invalid: "Sorry, your OpenID seems misformed"
 +      openid_logo_alt: "Log in with an OpenID"
 +      openid_providers:
 +        openid:
 +          title: Login with an OpenID URL
 +          alt: Login with an OpenID URL
 +        yahoo:
 +          title: Login with a Yahoo! OpenID
 +          alt: Login with a Yahoo! OpenID
 +        google:
 +          title: Login with a Google OpenID
 +          alt: Login with a Google OpenID
 +        myopenid:
 +          title: Login with a myOpenID OpenID
 +          alt: Login with a myOpenID OpenID
 +        wordpress:
 +          title: Login with a Wordpress.com OpenID
 +          alt: Login with a Wordpress.com OpenID
 +        myspace:
 +          title: Login with a MySpace OpenID
 +          alt: Login with a MySpace OpenID
      logout:
        title: "Logout"
        heading: "Logout from OpenStreetMap"
        display name description: "Your publicly displayed username. You can change this later in the preferences."
        password: "Password:"
        confirm password: "Confirm Password:"
 +      openID associate: "Associate an OpenID with your account"
 +      openID: "OpenID:"
 +      openID description: '(Optional) If you have an <a href="http://wiki.openstreetmap.org/wiki/openID">OpenID</a> you can associate it with this account to login'
 +      openID nopassword: "With OpenID, you don't need to specify a password during signup, but some extra OpenStreetMap services or tools may still need one"
 +      openID association: |
 +        Your OpenID is not associated with a OpenStreetMap account yet.
 +        <ul>
 +          <li>If you are new to OpenStreetMap, please create a new account using the form below.</li>
 +          <li>
 +            If you already have an account, you can normally login to
 +            your account and then associate the account with your openID
 +            in your user settings
 +          </li>
 +        </ul> 
        signup: Signup
        flash create success message: "User was successfully created. Check your email for a confirmation note, and you will be mapping in no time :-)<br /><br />Please note that you will not be able to login until you've received and confirmed your email address.<br /><br />If you use an antispam system which sends confirmation requests then please make sure you whitelist webmaster@openstreetmap.org as we are unable to reply to any confirmation requests."
      no_such_user:
        ago: "({{time_in_words_ago}} ago)"
        email address: "Email address:"
        created from: "Created from:"
+       status: "Status:"
+       spam score: "Spam Score:"
        description: Description
        user location: User location
        if set location: "If you set your location, a pretty map and stuff will appear here. You can set your home location on your {{settings_link}} page."
        create_block: "block this user"
        activate_user: "activate this user"
        deactivate_user: "deactivate this user"
+       confirm_user: "confirm this user"
        hide_user: "hide this user"
        unhide_user: "unhide this user"
        delete_user: "delete this user"
        current email address: "Current Email Address:"
        new email address: "New Email Address:"
        email never displayed publicly: "(never displayed publicly)"
 +      openid:
 +        openid: "OpenID:"
 +        link: "http://wiki.openstreetmap.org/wiki/OpenID"
 +        link text: "what is this?"
        public editing:
          heading: "Public editing:"
          enabled: "Enabled. Not anonymous and can edit data."
        not_a_friend: "{{name}} is not one of your friends."
      filter:
        not_an_administrator: "You need to be an administrator to perform that action."
+     list:
+       title: Users
+       heading: Users
+       showing:
+         one: Showing page {{page}} ({{first_item}} of {{items}})
+         other: Showing page {{page}} ({{first_item}}-{{last_item}} of {{items}})
+       summary: "{{name}} created from {{ip_address}} on {{date}}"
+       summary_no_ip: "{{name}} created on {{date}}"
+       confirm: Confirm Selected Users
+       hide: Hide Selected Users
+       empty: No matching users found
+     suspended:
+       title: Account Suspended
+       heading: Account Suspended
+       body: |
+         <p>
+           Sorry, your account has been automatically suspended due to
+           suspicious activity.
+         </p>
+         <p>
+           This decision will be reviewed by an administrator shortly, or
+           you may contact the <a href="mailto:{{webmaster}}">webmaster</a> if
+           you wish to discuss this.
+         </p>
    user_role:
      filter:
        not_an_administrator: "Only administrators can perform user role management, and you are not an administrator."
index c92d748e2b1d9c18d8c67ed9a5494ea1f8efbbfa,73f0fb788b1e4391c614c6ed4f78d28b702e2b79..c61cf928b4b75a2715db92b942800e543218746f
@@@ -562,6 -562,27 +562,27 @@@ hr 
    color: gray;
  }
  
+ /* Rules for the user list */
+ #user_list {
+   width: 100%;
+   font-size: small;
+ }
+ #user_list tr {
+   vertical-align: center;
+ }
+ #user_list p {
+   margin-top: 0px;
+   margin-bottom: 0px;
+ }
+ #user_list_actions {
+   float: right;
+   margin-top: 10px;
+ }
  /* Rules for the account settings page */
  
  #accountForm td {
@@@ -739,24 -760,3 +760,24 @@@ abbr.geo 
  .table1 { 
    background: #fff;
  }
 +
 +input.openid_url { 
 +  background: url('../images/openid-inputicon.gif') repeat-y left;
 +  padding-left: 16px;
 +}
 +
 +/* Rules for Login page */
 +.loginBox {
 +  float: left;
 +  width: 400px;
 +  height: 200px;
 +  margin-bottom: 40px;
 +  border-style: solid;
 +  border-width: 1px;
 +  padding-left: 10px;
 +  padding-right: 10px;
 +}
 +
 +.loginBox img {
 +  border: 0;
 +}
diff --combined test/fixtures/users.yml
index 8c8577520d71a5fd116123858d619a8a6b7e6168,eb3c6ef6d590547fd78b029dd0be720a805a3749..25b46f6f8a8967dc1e109bdbe834c4b5d1b712a4
@@@ -2,7 -2,7 +2,7 @@@
  normal_user:
    id: 1
    email: test@openstreetmap.org
-   active: true
+   status: active
    pass_crypt: <%= Digest::MD5.hexdigest('test') %>
    creation_time: "2007-01-01 00:00:00"
    display_name: test
@@@ -15,7 -15,7 +15,7 @@@
  public_user:
    id: 2
    email: test@example.com
-   active: true
+   status: active
    pass_crypt: <%= Digest::MD5.hexdigest('test') %>
    creation_time: "2008-05-01 01:23:45"
    display_name: test2
@@@ -28,7 -28,7 +28,7 @@@
  inactive_user:
    id: 3
    email: inactive@openstreetmap.org
-   active: false
+   status: pending
    pass_crypt: <%= Digest::MD5::hexdigest('test2') %>
    creation_time: "2008-07-01 02:23:45"
    display_name: Inactive User
@@@ -41,7 -41,7 +41,7 @@@
  second_public_user:
    id: 4
    email: public@OpenStreetMap.org
-   active: true
+   status: active
    pass_crypt: <%= Digest::MD5.hexdigest('test') %>
    creation_time: "2008-05-01 01:23:45"
    display_name: pulibc_test2
@@@ -54,7 -54,7 +54,7 @@@
  moderator_user:
    id: 5
    email: moderator@example.com
-   active: true
+   status: active
    pass_crypt: <%= Digest::MD5.hexdigest('test') %>
    creation_time: "2008-05-01 01:23:45"
    display_name: moderator
  administrator_user:
    id: 6
    email: administrator@example.com
-   active: true
+   status: active
    pass_crypt: <%= Digest::MD5.hexdigest('test') %>
    creation_time: "2008-05-01 01:23:45"
    display_name: administrator
    data_public: true
 +
 +openid_user:
 +  id: 7
 +  email: openid-user@example.com
 +  active: true
 +  pass_crypt: <%= Digest::MD5.hexdigest('test') %>
 +  creation_time: "2008-05-01 01:23:45"
 +  display_name: openIDuser
 +  data_public: true
 +  openid_url: http://localhost:1123/john.doe?openid.success=true