diff --git a/Gemfile b/Gemfile index 871a08794c..6cbec48c6b 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem 'spree_api', github: 'openfoodfoundation/spree', branch: '2-0-4-stable' gem 'spree_backend', github: 'openfoodfoundation/spree', branch: '2-0-4-stable' gem 'spree_core', github: 'openfoodfoundation/spree', branch: '2-0-4-stable' -gem 'spree_auth_devise', github: 'spree/spree_auth_devise', branch: '2-0-stable' +gem 'spree_auth_devise', github: 'luisramos0/spree_auth_devise', branch: '2-0-without-ctrls' gem 'spree_i18n', github: 'spree/spree_i18n', branch: '1-3-stable' # Our branch contains two changes diff --git a/Gemfile.lock b/Gemfile.lock index 890c8bdf5b..5dc405918e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,6 +12,18 @@ GIT specs: custom_error_message (1.1.1) +GIT + remote: https://github.com/luisramos0/spree_auth_devise.git + revision: 562ac6b0b821173f36405457ccfad103e9c5dc6a + branch: 2-0-without-ctrls + specs: + spree_auth_devise (2.0.0) + devise (~> 2.2.5) + devise-encryptable (= 0.1.2) + spree_backend (~> 2.0.0) + spree_core (~> 2.0.0) + spree_frontend (~> 2.0.0) + GIT remote: https://github.com/openfoodfoundation/better_spree_paypal_express.git revision: 27ad7165ea4c6e8c5f120b42b676cb9c2c272100 @@ -75,18 +87,6 @@ GIT spree_core (= 2.0.4) stringex (~> 1.5.1) -GIT - remote: https://github.com/spree/spree_auth_devise.git - revision: 0181835fb6ac77a05191d26f6f32a0f4a548d851 - branch: 2-0-stable - specs: - spree_auth_devise (2.0.0) - devise (~> 2.2.5) - devise-encryptable (= 0.1.2) - spree_backend (~> 2.0.0) - spree_core (~> 2.0.0) - spree_frontend (~> 2.0.0) - GIT remote: https://github.com/spree/spree_i18n.git revision: 752eb67204e9c5a4e22b62591a8fd55fe2285e43 @@ -180,7 +180,7 @@ GEM json (~> 1.4) nokogiri (>= 1.4.4) uuidtools (~> 2.1) - bcrypt (3.1.11) + bcrypt (3.1.12) bcrypt-ruby (3.1.5) bcrypt (>= 3.1.3) blockenspiel (0.5.0) diff --git a/app/controllers/spree/admin/base_controller_decorator.rb b/app/controllers/spree/admin/base_controller_decorator.rb index 6bdd57f0e8..941b679c59 100644 --- a/app/controllers/spree/admin/base_controller_decorator.rb +++ b/app/controllers/spree/admin/base_controller_decorator.rb @@ -47,6 +47,16 @@ Spree::Admin::BaseController.class_eval do end end + protected + + def model_class + const_name = controller_name.classify + if Spree.const_defined?(const_name) + return "Spree::#{const_name}".constantize + end + nil + end + private def active_distributors_not_ready_for_checkout diff --git a/app/controllers/spree/admin/orders/customer_details_controller_decorator.rb b/app/controllers/spree/admin/orders/customer_details_controller_decorator.rb index 41a3d721b1..47e829d3cb 100644 --- a/app/controllers/spree/admin/orders/customer_details_controller_decorator.rb +++ b/app/controllers/spree/admin/orders/customer_details_controller_decorator.rb @@ -1,4 +1,5 @@ Spree::Admin::Orders::CustomerDetailsController.class_eval do + before_filter :check_authorization before_filter :set_guest_checkout_status, only: :update def update @@ -25,6 +26,17 @@ Spree::Admin::Orders::CustomerDetailsController.class_eval do private + def check_authorization + load_order + session[:access_token] ||= params[:token] + + resource = @order + action = params[:action].to_sym + action = :edit if action == :show # show route renders :edit for this controller + + authorize! action, resource, session[:access_token] + end + def set_guest_checkout_status registered_user = Spree::User.find_by_email(params[:order][:email]) diff --git a/app/controllers/spree/admin/resource_controller_decorator.rb b/app/controllers/spree/admin/resource_controller_decorator.rb index 19293545ec..e7ba7ceb5b 100644 --- a/app/controllers/spree/admin/resource_controller_decorator.rb +++ b/app/controllers/spree/admin/resource_controller_decorator.rb @@ -14,3 +14,7 @@ module AuthorizeOnLoadResource end Spree::Admin::ResourceController.prepend(AuthorizeOnLoadResource) + +Spree::Admin::ResourceController.class_eval do + rescue_from CanCan::AccessDenied, :with => :unauthorized +end diff --git a/app/controllers/spree/admin/users_controller.rb b/app/controllers/spree/admin/users_controller.rb new file mode 100644 index 0000000000..9ecaff2845 --- /dev/null +++ b/app/controllers/spree/admin/users_controller.rb @@ -0,0 +1,124 @@ +module Spree + module Admin + class UsersController < ResourceController + rescue_from Spree::User::DestroyWithOrdersError, :with => :user_destroy_with_orders_error + + after_filter :sign_in_if_change_own_password, :only => :update + + # http://spreecommerce.com/blog/2010/11/02/json-hijacking-vulnerability/ + before_filter :check_json_authenticity, :only => :index + before_filter :load_roles, :only => [:edit, :new, :update, :create, :generate_api_key, :clear_api_key] + + def index + respond_with(@collection) do |format| + format.html + format.json { render :json => json_data } + end + end + + def create + if params[:user] + roles = params[:user].delete("spree_role_ids") + end + + @user = Spree::User.new(params[:user]) + if @user.save + + if roles + @user.spree_roles = roles.reject(&:blank?).collect{|r| Spree::Role.find(r)} + end + + flash.now[:success] = Spree.t(:created_successfully) + render :edit + else + render :new + end + end + + def update + if params[:user] + roles = params[:user].delete("spree_role_ids") + end + + if @user.update_attributes(params[:user]) + if roles + @user.spree_roles = roles.reject(&:blank?).collect{|r| Spree::Role.find(r)} + end + + flash.now[:success] = Spree.t(:account_updated) + render :edit + else + render :edit + end + end + + def generate_api_key + if @user.generate_spree_api_key! + flash[:success] = Spree.t('api.key_generated') + end + redirect_to edit_admin_user_path(@user) + end + + def clear_api_key + if @user.clear_spree_api_key! + flash[:success] = Spree.t('api.key_cleared') + end + redirect_to edit_admin_user_path(@user) + end + + protected + + def collection + return @collection if @collection.present? + if request.xhr? && params[:q].present? + #disabling proper nested include here due to rails 3.1 bug + #@collection = User.includes(:bill_address => [:state, :country], :ship_address => [:state, :country]). + @collection = Spree::User.includes(:bill_address, :ship_address) + .where("spree_users.email #{LIKE} :search + OR (spree_addresses.firstname #{LIKE} :search AND spree_addresses.id = spree_users.bill_address_id) + OR (spree_addresses.lastname #{LIKE} :search AND spree_addresses.id = spree_users.bill_address_id) + OR (spree_addresses.firstname #{LIKE} :search AND spree_addresses.id = spree_users.ship_address_id) + OR (spree_addresses.lastname #{LIKE} :search AND spree_addresses.id = spree_users.ship_address_id)", + { :search => "#{params[:q].strip}%" }) + .limit(params[:limit] || 100) + else + @search = Spree::User.registered.ransack(params[:q]) + @collection = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page]) + end + end + + private + + # handling raise from Spree::Admin::ResourceController#destroy + def user_destroy_with_orders_error + invoke_callbacks(:destroy, :fails) + render :status => :forbidden, :text => Spree.t(:error_user_destroy_with_orders) + end + + # Allow different formats of json data to suit different ajax calls + def json_data + json_format = params[:json_format] or 'default' + case json_format + when 'basic' + collection.map { |u| { 'id' => u.id, 'name' => u.email } }.to_json + else + address_fields = [:firstname, :lastname, :address1, :address2, :city, :zipcode, :phone, :state_name, :state_id, :country_id] + includes = { :only => address_fields , :include => { :state => { :only => :name }, :country => { :only => :name } } } + + collection.to_json(:only => [:id, :email], :include => + { :bill_address => includes, :ship_address => includes }) + end + end + + def sign_in_if_change_own_password + if spree_current_user == @user && @user.password.present? + sign_in(@user, :event => :authentication, :bypass => true) + end + end + + def load_roles + @roles = Spree::Role.scoped + end + end + end +end diff --git a/app/controllers/spree/base_controller_decorator.rb b/app/controllers/spree/base_controller_decorator.rb new file mode 100644 index 0000000000..f907c70105 --- /dev/null +++ b/app/controllers/spree/base_controller_decorator.rb @@ -0,0 +1,18 @@ +Spree::BaseController.class_eval do + def spree_login_path + spree.login_path + end + + def spree_signup_path + spree.signup_path + end + + def spree_logout_path + spree.destroy_spree_user_session_path + end + + def spree_current_user + current_spree_user + end +end + diff --git a/app/controllers/spree/checkout_controller.rb b/app/controllers/spree/checkout_controller.rb index a01750072f..b75418f179 100644 --- a/app/controllers/spree/checkout_controller.rb +++ b/app/controllers/spree/checkout_controller.rb @@ -25,8 +25,52 @@ module Spree redirect_to main_app.checkout_path end + before_filter :check_registration, :except => [:registration, :update_registration] + + helper 'spree/users' + + def registration + @user = Spree::User.new + end + + def update_registration + fire_event("spree.user.signup", :order => current_order) + # hack - temporarily change the state to something other than cart so we can validate the order email address + current_order.state = current_order.checkout_steps.first + current_order.update_attribute(:email, params[:order][:email]) + # Run validations, then check for errors + # valid? may return false if the address state validations are present + current_order.valid? + if current_order.errors[:email].blank? + redirect_to checkout_path + else + flash[:registration_error] = t(:email_is_invalid, :scope => [:errors, :messages]) + @user = Spree::User.new + render 'registration' + end + end + private + def skip_state_validation? + %w(registration update_registration).include?(params[:action]) + end + + # Introduces a registration step whenever the +registration_step+ preference is true. + def check_registration + return unless Spree::Auth::Config[:registration_step] + return if spree_current_user or current_order.email + store_location + redirect_to spree.checkout_registration_path + end + + # Overrides the equivalent method defined in Spree::Core. This variation of the method will ensure that users + # are redirected to the tokenized order url unless authenticated as a registered user. + def completion_route + return order_path(@order) if spree_current_user + spree.token_order_path(@order, @order.token) + end + def load_order @order = current_order redirect_to main_app.cart_path && return unless @order diff --git a/app/controllers/spree/user_passwords_controller.rb b/app/controllers/spree/user_passwords_controller.rb new file mode 100644 index 0000000000..f18570a4fd --- /dev/null +++ b/app/controllers/spree/user_passwords_controller.rb @@ -0,0 +1,47 @@ +class Spree::UserPasswordsController < Devise::PasswordsController + helper 'spree/users', 'spree/base', 'spree/store' + + if defined?(Spree::Dash) + helper 'spree/analytics' + end + + include Spree::Core::ControllerHelpers::Auth + include Spree::Core::ControllerHelpers::Common + include Spree::Core::ControllerHelpers::Order + include Spree::Core::ControllerHelpers::SSL + + ssl_required + + # Overridden due to bug in Devise. + # respond_with resource, :location => new_session_path(resource_name) + # is generating bad url /session/new.user + # + # overridden to: + # respond_with resource, :location => spree.login_path + # + def create + self.resource = resource_class.send_reset_password_instructions(params[resource_name]) + + if resource.errors.empty? + set_flash_message(:notice, :send_instructions) if is_navigational_format? + respond_with resource, :location => spree.login_path + else + respond_with_navigational(resource) { render :new } + end + end + + # Devise::PasswordsController allows for blank passwords. + # Silly Devise::PasswordsController! + # Fixes spree/spree#2190. + def update + if params[:spree_user][:password].blank? + self.resource = resource_class.new + resource.reset_password_token = params[:spree_user][:reset_password_token] + set_flash_message(:error, :cannot_be_blank) + render :edit + else + super + end + end + +end diff --git a/app/controllers/spree/user_registrations_controller.rb b/app/controllers/spree/user_registrations_controller.rb new file mode 100644 index 0000000000..d36a3896be --- /dev/null +++ b/app/controllers/spree/user_registrations_controller.rb @@ -0,0 +1,67 @@ +class Spree::UserRegistrationsController < Devise::RegistrationsController + helper 'spree/users', 'spree/base', 'spree/store' + + if defined?(Spree::Dash) + helper 'spree/analytics' + end + + include Spree::Core::ControllerHelpers::Auth + include Spree::Core::ControllerHelpers::Common + include Spree::Core::ControllerHelpers::Order + include Spree::Core::ControllerHelpers::SSL + + ssl_required + before_filter :check_permissions, :only => [:edit, :update] + skip_before_filter :require_no_authentication + + # GET /resource/sign_up + def new + super + @user = resource + end + + # POST /resource/sign_up + def create + @user = build_resource(params[:spree_user]) + if resource.save + set_flash_message(:notice, :signed_up) + sign_in(:spree_user, @user) + session[:spree_user_signup] = true + associate_user + respond_with resource, location: after_sign_up_path_for(resource) + else + clean_up_passwords(resource) + render :new + end + end + + # GET /resource/edit + def edit + super + end + + # PUT /resource + def update + super + end + + # DELETE /resource + def destroy + super + end + + # GET /resource/cancel + # Forces the session data which is usually expired after sign + # in to be expired now. This is useful if the user wants to + # cancel oauth signing in/up in the middle of the process, + # removing all OAuth session data. + def cancel + super + end + + protected + def check_permissions + authorize!(:create, resource) + end + +end diff --git a/app/controllers/spree/user_sessions_controller.rb b/app/controllers/spree/user_sessions_controller.rb new file mode 100644 index 0000000000..98ae252b1e --- /dev/null +++ b/app/controllers/spree/user_sessions_controller.rb @@ -0,0 +1,56 @@ +class Spree::UserSessionsController < Devise::SessionsController + helper 'spree/users', 'spree/base', 'spree/store' + if defined?(Spree::Dash) + helper 'spree/analytics' + end + + include Spree::Core::ControllerHelpers::Auth + include Spree::Core::ControllerHelpers::Common + include Spree::Core::ControllerHelpers::Order + include Spree::Core::ControllerHelpers::SSL + + ssl_required :new, :create, :destroy, :update + ssl_allowed :login_bar + + def create + authenticate_spree_user! + + if spree_user_signed_in? + respond_to do |format| + format.html { + flash[:success] = Spree.t(:logged_in_succesfully) + redirect_back_or_default(after_sign_in_path_for(spree_current_user)) + } + format.js { + render :json => {:user => spree_current_user, + :ship_address => spree_current_user.ship_address, + :bill_address => spree_current_user.bill_address}.to_json + } + end + else + respond_to do |format| + format.html { + flash.now[:error] = t('devise.failure.invalid') + render :new + } + format.js { + render :json => { error: t('devise.failure.invalid') }, status: :unprocessable_entity + } + end + end + end + + def nav_bar + render :partial => 'spree/shared/nav_bar' + end + + private + def accurate_title + Spree.t(:login) + end + + def redirect_back_or_default(default) + redirect_to(session["spree_user_return_to"] || default) + session["spree_user_return_to"] = nil + end +end diff --git a/app/controllers/spree/users_controller.rb b/app/controllers/spree/users_controller.rb new file mode 100644 index 0000000000..286181aeb4 --- /dev/null +++ b/app/controllers/spree/users_controller.rb @@ -0,0 +1,57 @@ +class Spree::UsersController < Spree::StoreController + ssl_required + skip_before_filter :set_current_order, :only => :show + prepend_before_filter :load_object, :only => [:show, :edit, :update] + prepend_before_filter :authorize_actions, :only => :new + + include Spree::Core::ControllerHelpers + + def show + @orders = @user.orders.complete.order('completed_at desc') + end + + def create + @user = Spree::User.new(params[:user]) + if @user.save + + if current_order + session[:guest_token] = nil + end + + redirect_back_or_default(root_url) + else + render :new + end + end + + def update + if @user.update_attributes(params[:user]) + if params[:user][:password].present? + # this logic needed b/c devise wants to log us out after password changes + user = Spree::User.reset_password_by_token(params[:user]) + sign_in(@user, :event => :authentication, :bypass => !Spree::Auth::Config[:signout_after_password_change]) + end + redirect_to spree.account_url, :notice => Spree.t(:account_updated) + else + render :edit + end + end + + private + def load_object + @user ||= spree_current_user + if @user + authorize! params[:action].to_sym, @user + else + redirect_to spree.login_path + end + end + + def authorize_actions + authorize! params[:action].to_sym, Spree::User.new + end + + def accurate_title + Spree.t(:my_account) + end +end diff --git a/config/routes/spree.rb b/config/routes/spree.rb index cc221fc8d0..a5469bdf8b 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -9,6 +9,35 @@ Spree::Core::Engine.routes.draw do :skip => [:unlocks, :omniauth_callbacks], :path_names => { :sign_out => 'logout' }, :path_prefix => :user + + resources :users, :only => [:edit, :update] + + devise_scope :spree_user do + get '/login' => 'user_sessions#new', :as => :login + post '/login' => 'user_sessions#create', :as => :create_new_session + get '/logout' => 'user_sessions#destroy', :as => :logout + get '/signup' => 'user_registrations#new', :as => :signup + post '/signup' => 'user_registrations#create', :as => :registration + get '/password/recover' => 'user_passwords#new', :as => :recover_password + post '/password/recover' => 'user_passwords#create', :as => :reset_password + get '/password/change' => 'user_passwords#edit', :as => :edit_password + put '/password/change' => 'user_passwords#update', :as => :update_password + end + + match '/checkout/registration' => 'checkout#registration', :via => :get, :as => :checkout_registration + match '/checkout/registration' => 'checkout#update_registration', :via => :put, :as => :update_checkout_registration + + resource :session do + member do + get :nav_bar + end + end + + resource :account, :controller => 'users' + + namespace :admin do + resources :users + end end Spree::Core::Engine.routes.prepend do