require 'recaptcha'
require 'active_resource/exceptions'
module RailsConnector
# This class provides a default controller implementation for user functionality.
# It should be customized by subclassing.
#
# To change how all actions contacting the WebCRM behave in case of an WebCRM error,
# override +on_crm_error+ in your subclassed controller. See Crm::Callbacks for details.
#
# To override what attributes are writable by the user when registering or editing profiles,
# use +editable_attributes_on_register+ and +editable_attributes_on_edit+, respectively.
# This can be done in your rails_connector.rb or in +UserController+ directly.
#
# By default, users can submit their first name, last name, email and company name.
class DefaultUserController < ApplicationController
before_filter :check_editable_attribute_configuration
before_filter :redirect_to_login_unless_logged_in, :only => [
:edit, :edit_password, :profile
]
before_filter :check_recaptcha_keypair
around_filter :handle_crm_errors
cattr_accessor :editable_attributes_on_register, :editable_attributes_on_edit
self.editable_attributes_on_register = {
:contact => [:gender, :first_name, :last_name, :email, :phone, :language],
}
self.editable_attributes_on_edit = {
:contact => [:first_name, :last_name, :email, :phone, :language],
}
include Crm::Localizable
include Crm::Sanitization
include Crm::Callbacks
include ReCaptcha::AppHelper
def self.store_user_attrs_in_session=(fields)
raise %Q{
DefaultUserController doesn't maintain which fields are stored in the session anymore.
Please use RailsConnector::Configuration.store_user_attrs_in_session instead.
}
end
# Displays a profile page containing links to all available actions
def profile
end
# Logs a CRM user in.
#
# After successful login, user attributes are stored in session[:user].
#
# To change which fields are stored in the session use
# +RailsConnector::Configuration.store_user_attrs_in_session+.
#
# Use +current_user+ for a Contact object of the attributes stored in the session.
#
# The user will be redirected to the path given in the return_to param. If no
# return_to param is set, the user will be redirected to the profile page.
#
# If you merely want to change what happens before or after a user is authenticated,
# do not override this method but override +before_authenticate+ or +after_authenticate+.
def login
if request.post?
@user = Infopark::Crm::Contact.new(params[:user] || {:login => nil, :password => nil})
before_authenticate
@user = Infopark::Crm::Contact.authenticate(@user.login, @user.password)
if @user
after_authenticate
flash[:notice] = tcon('login_successful')
self.current_user = @user
redirect_to params[:return_to].blank? ?
user_path(:action => 'profile') :
params[:return_to]
else
flash.now[:error] = tcon('login_failed')
end
end
rescue Infopark::Crm::Errors::AuthenticationFailed, ActiveResource::ResourceInvalid
flash.now[:error] = tcon('login_failed')
ensure
@user.password = nil if @user
end
# Logs the user out by setting session[:user] to +nil+.
#
# To change the behavior before or after invalidating the session,
# override +before_logout+ or +after_logout+.
def logout
before_logout
self.current_user = nil
after_logout
redirect_to params[:return_to].blank? ? root_path : params[:return_to]
end
# Creates a WebCRM user.
#
# The user login is automatically set to his/her e-mail.
#
# If you merely want to change what happens before or after a user is registered,
# do not override this method but override +before_register+ or +after_register+.
def new
@user = Infopark::Crm::Contact.new
# Load some default attributes so that form_for is working
@user.load(Crm::CONTACT_DEFAULT_ATTRS.merge(sanitize_user_params(params[:user],
self.class.editable_attributes_on_register)))
if request.post?
if RailsConnector::Configuration.use_recaptcha_on_user_registration &&
!validate_recap(params, @user.errors)
raise ActiveResource::ResourceInvalid, "captcha failed"
end
before_register
register
after_register
redirect_to(:action => "register_pending")
end
rescue ActiveResource::ResourceInvalid
flash.now[:error] = tcon('registration_failed')
end
def register_pending
end
# Lets the user change his/her user details.
def edit
@user = Infopark::Crm::Contact.find(current_user.id)
if request.post? || request.put?
@user.load(sanitize_user_params(params[:user], self.class.editable_attributes_on_edit))
@user.save
flash[:notice] = tcon('edit_successful')
redirect_to(:action => 'profile')
end
rescue ActiveResource::ResourceInvalid
flash.now[:error] = tcon('edit_failed')
end
# Lets the user change his/her password.
#
# Validates the new password using +validate_edit_password_params_for+.
def edit_password
if request.post?
validate_edit_password_params_for(params[:user])
@user = Infopark::Crm::Contact.authenticate(current_user.login, params[:user][:old_password])
@user.password_set(params[:user][:new_password])
flash[:notice] = tcon('edit_password_successful')
redirect_to(:action => "profile")
end
rescue ActiveResource::ResourceInvalid, Infopark::Crm::Errors::AuthenticationFailed
flash.now[:error] = tcon('edit_password_failed')
end
# Lets the user request a new password (double opt-in).
#
# Uses the +ConfirmationMailer+ for sending out the confirmation message.
def forgot_password
if request.post?
user = Infopark::Crm::Contact.search(:params => {:login => params[:user][:login]}).first
if user
confirmation_link = set_password_url_for(user)
ConfirmationMailer.reset_password(user.email, confirmation_link).deliver
flash[:notice] = tcon('reset_password_successful')
redirect_to(:action => "forgot_password")
else
flash.now[:error] = tcon('request_password_failed')
end
end
end
def set_password
if request.get? && params[:token].blank?
flash[:error] = tcon('token_url_invalid')
elsif request.post?
if params[:user][:new_password].blank?
flash.now[:error] = tcon('password_cannot_be_empty')
elsif params[:user][:new_password] != params[:user][:new_password_confirm]
flash.now[:error] = tcon('password_does_not_match_confirmation')
else
Infopark::Crm::Contact.password_set(params[:user][:new_password], params[:user][:token])
flash[:notice] = tcon('password_set')
redirect_to(:action => 'login')
end
end
rescue ActiveResource::ResourceNotFound => e
flash[:error] = tcon('set_password_failed')
end
protected
def check_editable_attribute_configuration
raise RuntimeError if editable_attributes_on_edit[:contact].nil? ||
editable_attributes_on_register[:contact].nil?
rescue
raise ConfigurationError, "editable_attributes in UserController is not configured correctly"
end
def register
if @user.email.blank?
@user.errors.add(:base, tcon('email_blank'))
raise ActiveResource::ResourceInvalid.new("E-mail can't be blank")
end
@user.login = @user.email
@user.save!
confirmation_link = set_password_url_for(@user)
ConfirmationMailer.register_confirmation(@user.email, confirmation_link).deliver
flash[:notice] = tcon('registration_successful_awaiting_confirmation')
end
def tcon(x)
t("rails_connector.controllers.user.#{x}")
end
ALL_CRM_ERRORS = [
Errno::ECONNREFUSED,
ActiveResource::ForbiddenAccess,
ActiveResource::UnauthorizedAccess,
ActiveResource::BadRequest
]
# invoke user defined callback when an error related to WebCRM occurs
def handle_crm_errors
yield
rescue *ALL_CRM_ERRORS => e
on_crm_error(e)
default_render unless performed?
end
# Filter to force users to login by redirecting.
def redirect_to_login_unless_logged_in
redirect_to login_path unless logged_in?
end
# Checks if constants RCC_PUB and RCC_PRIV are set (for reCaptcha)
#
# Used as a filter in this controller.
def check_recaptcha_keypair
return true unless RailsConnector::Configuration.use_recaptcha_on_user_registration
unless Object.const_defined?(:RCC_PUB) && Object.const_defined?(:RCC_PRIV)
raise RuntimeError, <<-EOS
reCaptcha requires the constants RCC_PUB and RCC_PRIV to be set.
Please sign up for the reCaptcha webservice if you haven't already done so.
Then set your public key in RCC_PUB and your private key in RCC_PRIV.
EOS
end
true
end
# Validates the password for a given user.
#
# Used by #edit_password.
def validate_edit_password_params_for(params)
if params[:new_password].empty? || params[:new_password] != params[:new_password_confirm]
raise ActiveResource::ResourceInvalid.new(
"password is empty or does not match confirmation"
)
end
end
# Generates a URL for password confirmation.
def set_password_url_for(user)
url_for(
:action => "set_password",
:token => user.password_request(:params => {:only_get_token => true})
)
end
end
end