Various updates to the user management, including the creation of a

preferences table and moving tokens into a tokens table so that a user
can have more than one.
This commit is contained in:
Tom Hughes 2007-08-14 23:07:38 +00:00
parent b61e4f77e8
commit 0a8c26e596
11 changed files with 151 additions and 74 deletions

View file

@ -736,12 +736,13 @@ def array2tag(a)
end end
def getuserid(token) def getuserid(token)
token=sqlescape(token) if (token =~ /^(.+)\+(.+)$/) then
if (token=~/^(.+)\+(.+)$/) then user = User.authenticate(:username => $1, :password => $2)
return ActiveRecord::Base.connection.select_value("SELECT id FROM users WHERE active=1 AND email='#{$1}' AND pass_crypt=MD5('#{$2}')")
else else
return ActiveRecord::Base.connection.select_value("SELECT id FROM users WHERE active=1 AND token='#{token}'") user = User.authenticate(:token => token)
end end
return user ? user.id : nil;
end end

View file

@ -3,7 +3,12 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
def authorize_web def authorize_web
@user = User.find_by_token(session[:token]) if session[:user]
@user = User.find(session[:user])
elsif session[:token]
@user = User.authenticate(:token => session[:token])
session[:user] = @user.id
end
end end
def require_user def require_user
@ -16,21 +21,13 @@ class ApplicationController < ActionController::Base
if username.nil? if username.nil?
@user = nil # no authentication provided - perhaps first connect (client should retry after 401) @user = nil # no authentication provided - perhaps first connect (client should retry after 401)
elsif username == 'token' elsif username == 'token'
@user = User.authenticate_token(passwd) # preferred - random token for user from db, passed in basic auth @user = User.authenticate(:token => passwd) # preferred - random token for user from db, passed in basic auth
else else
@user = User.authenticate(username, passwd) # basic auth @user = User.authenticate(:username => username, :password => passwd) # basic auth
end end
# handle authenticate pass/fail # handle authenticate pass/fail
if @user unless @user
# user exists and password is correct ... horray!
if @user.methods.include? 'lastlogin' # note last login
@session['lastlogin'] = user.lastlogin
@user.last.login = Time.now
@user.save()
@session["User.id"] = @user.id
end
else
# no auth, the user does not exist or the password was wrong # no auth, the user does not exist or the password was wrong
response.headers["Status"] = "Unauthorized" response.headers["Status"] = "Unauthorized"
response.headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\"" response.headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\""
@ -58,8 +55,9 @@ class ApplicationController < ActionController::Base
response.headers['Error'] = message response.headers['Error'] = message
end end
private
# extract authorisation credentials from headers, returns user = nil if none # extract authorisation credentials from headers, returns user = nil if none
private
def get_auth_data def get_auth_data
if request.env.has_key? 'X-HTTP_AUTHORIZATION' # where mod_rewrite might have put it if request.env.has_key? 'X-HTTP_AUTHORIZATION' # where mod_rewrite might have put it
authdata = request.env['X-HTTP_AUTHORIZATION'].to_s.split authdata = request.env['X-HTTP_AUTHORIZATION'].to_s.split

View file

@ -46,12 +46,11 @@ class SwfController < ApplicationController
lastfile='-1' lastfile='-1'
if params['token'] if params['token']
token=sqlescape(params['token']) user=User.authenticate(:token => params[:token])
sql="SELECT gps_points.latitude*0.000001 AS lat,gps_points.longitude*0.000001 AS lon,gpx_files.id AS fileid,UNIX_TIMESTAMP(gps_points.timestamp) AS ts "+ sql="SELECT gps_points.latitude*0.000001 AS lat,gps_points.longitude*0.000001 AS lon,gpx_files.id AS fileid,UNIX_TIMESTAMP(gps_points.timestamp) AS ts "+
" FROM gpx_files,gps_points,users "+ " FROM gpx_files,gps_points "+
"WHERE gpx_files.id=gpx_id "+ "WHERE gpx_files.id=gpx_id "+
" AND gpx_files.user_id=users.id "+ " AND gpx_files.user_id=#{user.id} "+
" AND token='#{token}' "+
" AND (gps_points.longitude BETWEEN #{xminr} AND #{xmaxr}) "+ " AND (gps_points.longitude BETWEEN #{xminr} AND #{xmaxr}) "+
" AND (gps_points.latitude BETWEEN #{yminr} AND #{ymaxr}) "+ " AND (gps_points.latitude BETWEEN #{yminr} AND #{ymaxr}) "+
" AND (gps_points.timestamp IS NOT NULL) "+ " AND (gps_points.timestamp IS NOT NULL) "+

View file

@ -10,11 +10,11 @@ class UserController < ApplicationController
def save def save
@title = 'create account' @title = 'create account'
@user = User.new(params[:user]) @user = User.new(params[:user])
@user.set_defaults
if @user.save if @user.save
token = @user.tokens.create
flash[:notice] = "User was successfully created. Check your email for a confirmation note, and you\'ll be mapping in no time :-)<br>Please note that you won't be able to login until you've received and confirmed your email address." flash[:notice] = "User was successfully created. Check your email for a confirmation note, and you\'ll be mapping in no time :-)<br>Please note that you won't be able to login until you've received and confirmed your email address."
Notifier::deliver_signup_confirm(@user) Notifier::deliver_signup_confirm(@user, token)
redirect_to :action => 'login' redirect_to :action => 'login'
else else
render :action => 'new' render :action => 'new'
@ -64,11 +64,10 @@ class UserController < ApplicationController
def lost_password def lost_password
@title = 'lost password' @title = 'lost password'
if params[:user] and params[:user][:email] if params[:user] and params[:user][:email]
user = User.find_by_email(params['user']['email']) user = User.find_by_email(params[:user][:email])
if user if user
user.token = User.make_token token = user.tokens.create
user.save Notifier::deliver_lost_password(user, token)
Notifier::deliver_lost_password(user)
flash[:notice] = "Sorry you lost it :-( but an email is on its way so you can reset it soon." flash[:notice] = "Sorry you lost it :-( but an email is on its way so you can reset it soon."
else else
flash[:notice] = "Couldn't find that email address, sorry." flash[:notice] = "Couldn't find that email address, sorry."
@ -81,13 +80,15 @@ class UserController < ApplicationController
def reset_password def reset_password
@title = 'reset password' @title = 'reset password'
if params['token'] if params['token']
user = User.find_by_token(params['token']) token = UserToken.find_by_token(params[:token])
if user if token
pass = User.make_token(8) pass = OSM::make_token(8)
user = token.user
user.pass_crypt = pass user.pass_crypt = pass
user.pass_crypt_confirmation = pass user.pass_crypt_confirmation = pass
user.active = true user.active = true
user.save user.save!
token.destroy
Notifier::deliver_reset_password(user, pass) Notifier::deliver_reset_password(user, pass)
flash[:notice] = "Your password has been changed and is on its way to your mailbox :-)" flash[:notice] = "Your password has been changed and is on its way to your mailbox :-)"
else else
@ -106,19 +107,16 @@ class UserController < ApplicationController
if params[:user] if params[:user]
email = params[:user][:email] email = params[:user][:email]
pass = params[:user][:password] pass = params[:user][:password]
u = User.authenticate(email, pass) user = User.authenticate(:username => email, :password => pass)
if u if user
u.token = User.make_token session[:user] = user.id
u.timeout = 1.day.from_now
u.save
session[:token] = u.token
if params[:referer] if params[:referer]
redirect_to params[:referer] redirect_to params[:referer]
else else
redirect_to :controller => 'site', :action => 'index' redirect_to :controller => 'site', :action => 'index'
end end
return return
elsif User.authenticate(email, pass, false) elsif User.authenticate(:username => email, :password => pass, :invalid => true)
flash[:notice] = "Sorry, your account is not active yet.<br>Please click on the link in the account confirmation email to activate your account." flash[:notice] = "Sorry, your account is not active yet.<br>Please click on the link in the account confirmation email to activate your account."
else else
flash[:notice] = "Sorry, couldn't log in with those details." flash[:notice] = "Sorry, couldn't log in with those details."
@ -128,14 +126,13 @@ class UserController < ApplicationController
def logout def logout
if session[:token] if session[:token]
u = User.find_by_token(session[:token]) token = UserToken.find_by_token(session[:token])
if u if token
u.token = User.make_token token.destroy
u.timeout = Time.now
u.save
end end
session[:token] = nil
end end
session[:token] = nil session[:user] = nil
if params[:referer] if params[:referer]
redirect_to params[:referer] redirect_to params[:referer]
else else
@ -144,14 +141,14 @@ class UserController < ApplicationController
end end
def confirm def confirm
@user = User.find_by_token(params[:confirm_string]) token = UserToken.find_by_token(params[:confirm_string])
if @user && @user.active == 0 if token and !token.user.active?
@user = token.user
@user.active = true @user.active = true
@user.token = User.make_token @user.save!
@user.timeout = 1.day.from_now token.destroy
@user.save
flash[:notice] = 'Confirmed your account, thanks for signing up!' flash[:notice] = 'Confirmed your account, thanks for signing up!'
session[:token] = @user.token session[:user] = @user.id
redirect_to :action => 'account', :display_name => @user.display_name redirect_to :action => 'account', :display_name => @user.display_name
else else
flash[:notice] = 'Something went wrong confirming that user.' flash[:notice] = 'Something went wrong confirming that user.'

View file

@ -1,17 +1,17 @@
class Notifier < ActionMailer::Base class Notifier < ActionMailer::Base
def signup_confirm( user ) def signup_confirm( user, token )
@recipients = user.email @recipients = user.email
@from = 'abuse@openstreetmap.org' @from = 'abuse@openstreetmap.org'
@subject = '[OpenStreetMap] Confirm your email address' @subject = '[OpenStreetMap] Confirm your email address'
@body['url'] = "http://#{SERVER_URL}/user/confirm?confirm_string=#{user.token}" @body['url'] = "http://#{SERVER_URL}/user/confirm?confirm_string=#{token.token}"
end end
def lost_password( user ) def lost_password( user, token )
@recipients = user.email @recipients = user.email
@from = 'abuse@openstreetmap.org' @from = 'abuse@openstreetmap.org'
@subject = '[OpenStreetMap] Password reset request' @subject = '[OpenStreetMap] Password reset request'
@body['url'] = "http://#{SERVER_URL}/user/reset_password?email=#{user.email}&token=#{user.token}" @body['url'] = "http://#{SERVER_URL}/user/reset_password?email=#{user.email}&token=#{token.token}"
end end
def reset_password(user, pass) def reset_password(user, pass)

View file

@ -7,6 +7,8 @@ class User < ActiveRecord::Base
has_many :messages, :foreign_key => :to_user_id has_many :messages, :foreign_key => :to_user_id
has_many :new_messages, :class_name => "Message", :foreign_key => :to_user_id, :conditions => "message_read = 0" has_many :new_messages, :class_name => "Message", :foreign_key => :to_user_id, :conditions => "message_read = 0"
has_many :friends has_many :friends
has_many :tokens, :class_name => "UserToken"
has_many :preferences, :class_name => "UserPreference"
validates_confirmation_of :pass_crypt, :message => 'Password must match the confirmation password' validates_confirmation_of :pass_crypt, :message => 'Password must match the confirmation password'
validates_uniqueness_of :display_name, :allow_nil => true validates_uniqueness_of :display_name, :allow_nil => true
@ -18,34 +20,31 @@ class User < ActiveRecord::Base
before_save :encrypt_password before_save :encrypt_password
def set_defaults def after_initialize
self.creation_time = Time.now self.creation_time = Time.now
self.timeout = Time.now
self.token = User.make_token()
end end
def encrypt_password def encrypt_password
self.pass_crypt = Digest::MD5.hexdigest(pass_crypt) unless pass_crypt_confirmation.nil? self.pass_crypt = Digest::MD5.hexdigest(pass_crypt) unless pass_crypt_confirmation.nil?
end end
def self.authenticate(email, passwd, active = true) def self.authenticate(options)
find(:first, :conditions => [ "email = ? AND pass_crypt = ? AND active = ?", email, Digest::MD5.hexdigest(passwd), active]) if options[:username] and options[:password]
end user = find(:first, :conditions => ["email = ? OR display_name = ?", options[:username], options[:username]])
user = nil unless user.pass_crypt == Digest::MD5.hexdigest(options[:password])
def self.authenticate_token(token) elsif options[:token]
find(:first, :conditions => [ "token = ? ", token]) token = UserToken.find(:first, :include => :user, :conditions => ["user_tokens.token = ?", options[:token]])
end user = token.user if token
def self.make_token(length=30)
chars = 'abcdefghijklmnopqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
confirmstring = ''
length.times do
confirmstring += chars[(rand * chars.length).to_i].chr
end end
return confirmstring if user
end user = nil unless user.active? or options[:inactive]
end
token.update_attribute(:expiry, 1.week.from_now) if token and user
return user
end
def to_xml def to_xml
doc = OSM::API.new.get_xml_doc doc = OSM::API.new.get_xml_doc

View file

@ -0,0 +1,3 @@
class UserPreference < ActiveRecord::Base
belongs_to :user
end

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

@ -0,0 +1,8 @@
class UserToken < ActiveRecord::Base
belongs_to :user
def after_initialize
self.token = OSM::make_token() if self.token.blank?
self.expiry = 1.week.from_now if self.expiry.blank?
end
end

View file

@ -5,6 +5,8 @@
<% else %> <% else %>
<%= render :partial => 'search', :locals => { :onopen => "resizeMap();", :onclose => "resizeMap();" } %> <%= render :partial => 'search', :locals => { :onopen => "resizeMap();", :onclose => "resizeMap();" } %>
<% session[:token] = @user.tokens.create.token unless session[:token] %>
<% if params['mlon'] and params['mlat'] %> <% if params['mlon'] and params['mlat'] %>
<% lon = params['mlon'] %> <% lon = params['mlon'] %>
<% lat = params['mlat'] %> <% lat = params['mlat'] %>
@ -34,7 +36,7 @@
fo.addVariable('lat',lat); fo.addVariable('lat',lat);
fo.addVariable('long',lon); fo.addVariable('long',lon);
fo.addVariable('scale',sc); fo.addVariable('scale',sc);
fo.addVariable('token','<%= @user.token %>'); fo.addVariable('token','<%= session[:token] %>');
fo.write("map"); fo.write("map");
} }

View file

@ -0,0 +1,58 @@
class UserEnhancements < ActiveRecord::Migration
def self.up
add_column "diary_entries", "latitude", :double
add_column "diary_entries", "longitude", :double
add_column "diary_entries", "language", :string, :limit => 3
create_table "user_preferences", innodb_table do |t|
t.column "user_id", :bigint, :limit => 20, :null => false
t.column "k", :string, :null => false
t.column "v", :string, :null => false
end
add_primary_key "user_preferences", ["user_id", "k"]
create_table "user_tokens", innodb_table do |t|
t.column "id", :bigint, :limit => 20, :null => false
t.column "user_id", :bigint, :limit => 20, :null => false
t.column "token", :string, :null => false
t.column "expiry", :datetime, :null => false
end
add_primary_key "user_tokens", ["id"]
add_index "user_tokens", ["token"], :name => "user_tokens_token_idx", :unique => true
add_index "user_tokens", ["user_id"], :name => "user_tokens_user_id_idx"
change_column "user_tokens", "id", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
User.find(:all, :conditions => "token is not null").each do |user|
UserToken.create(:user_id => user.id, :token => user.token, :expiry => 1.week.from_now)
end
remove_column "users", "token"
remove_column "users", "timeout"
remove_column "users", "within_lon"
remove_column "users", "within_lat"
add_column "users", "nearby", :integer, :default => 50
add_column "users", "pass_salt", :string
User.update_all("nearby = 50");
end
def self.down
remove_column "users", "pass_salt"
remove_column "users", "nearby"
add_column "users", "within_lat", :double
add_column "users", "within_lon", :double
add_column "users", "timeout", :datetime
add_column "users", "token", :string
drop_table "user_tokens"
drop_table "user_preferences"
remove_column "diary_entries", "language"
remove_column "diary_entries", "longitude"
remove_column "diary_entries", "latitude"
end
end

View file

@ -391,4 +391,16 @@ module OSM
rescue Exception rescue Exception
return nil return nil
end end
# Construct a random token of a given length
def self.make_token(length = 30)
chars = 'abcdefghijklmnopqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
token = ''
length.times do
token += chars[(rand * chars.length).to_i].chr
end
return token
end
end end