Merge pull request #3700 from luisramos0/2-0-without-auth-devise

Remove dependency to spree_auth_devise
This commit is contained in:
Luis Ramos
2019-07-26 10:57:38 +01:00
committed by GitHub
42 changed files with 1161 additions and 231 deletions

View File

@@ -24,7 +24,6 @@ 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_i18n', github: 'spree/spree_i18n', branch: '1-3-stable'
# Our branch contains two changes
@@ -37,6 +36,8 @@ gem 'stripe'
# which is needed for Pin Payments (and possibly others).
gem 'activemerchant', '~> 1.78'
gem 'devise', '~> 2.2.5'
gem 'devise-encryptable', '0.1.2'
gem 'jwt', '~> 2.2'
gem 'oauth2', '~> 1.4.1' # Used for Stripe Connect

View File

@@ -66,26 +66,6 @@ GIT
state_machine (= 1.2.0)
stringex (~> 1.5.1)
truncate_html (= 0.9.2)
spree_frontend (2.0.4)
canonical-rails
deface (>= 0.9.0)
jquery-rails (~> 3.0.0)
rails (~> 3.2.13)
spree_api (= 2.0.4)
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
@@ -180,7 +160,7 @@ GEM
json (~> 1.4)
nokogiri (>= 1.4.4)
uuidtools (~> 2.1)
bcrypt (3.1.11)
bcrypt (3.1.13)
bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3)
blockenspiel (0.5.0)
@@ -189,8 +169,6 @@ GEM
builder (3.0.4)
byebug (9.0.6)
cancan (1.6.10)
canonical-rails (0.1.0)
rails (>= 3.1, < 5.1)
capybara (2.18.0)
addressable
mini_mime (>= 0.1.3)
@@ -801,6 +779,8 @@ DEPENDENCIES
deface (= 1.0.2)
delayed_job_active_record
delayed_job_web
devise (~> 2.2.5)
devise-encryptable (= 0.1.2)
diffy
eventmachine (>= 1.2.3)
factory_bot_rails
@@ -861,7 +841,6 @@ DEPENDENCIES
skylight (< 2.0)
spinjs-rails
spree_api!
spree_auth_devise!
spree_backend!
spree_core!
spree_i18n!

View File

@@ -15,7 +15,6 @@
//= require angular-animate
//= require angular-sanitize
//= require admin/spree_backend
//= require admin/spree_auth
//= require admin/spree_paypal_express
//= require ../shared/ng-infinite-scroll.min.js
//= require ../shared/ng-tags-input.min.js

View File

@@ -5,7 +5,6 @@
*
*= require admin/spree_backend
*= require admin/spree_auth
*= require jquery-ui-timepicker-addon
*= require shared/textAngular

View File

@@ -1,4 +1,5 @@
require 'open_food_network/referer_parser'
require 'spree/authentication_helpers'
class ApplicationController < ActionController::Base
protect_from_forgery
@@ -7,6 +8,7 @@ class ApplicationController < ActionController::Base
before_filter :set_cache_headers # prevent cart emptying via cache when using back button #1213
include EnterprisesHelper
include Spree::AuthenticationHelpers
def redirect_to(options = {}, response_status = {})
::Rails.logger.error("Redirected by #{begin

View File

@@ -8,7 +8,6 @@ class CheckoutController < Spree::CheckoutController
prepend_before_filter :require_order_cycle
prepend_before_filter :require_distributor_chosen
skip_before_filter :check_registration
before_filter :enable_embedded_shopfront
include OrderCyclesHelper

View File

@@ -0,0 +1,6 @@
# For the API
ActionController::Metal.class_eval do
def spree_current_user
@spree_current_user ||= env['warden'].user
end
end

View File

@@ -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

View File

@@ -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])

View File

@@ -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

View File

@@ -0,0 +1,131 @@
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)
end
render :edit
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 = 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] || '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
return unless spree_current_user == @user && @user.password.present?
sign_in(@user, event: :authentication, bypass: true)
end
def load_roles
@roles = Spree::Role.scoped
end
end
end
end

View File

@@ -0,0 +1,44 @@
module Spree
class UserPasswordsController < Devise::PasswordsController
helper 'spree/base', 'spree/store'
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
end

View File

@@ -0,0 +1,65 @@
module Spree
class UserRegistrationsController < Devise::RegistrationsController
helper 'spree/base', 'spree/store'
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
end

View File

@@ -0,0 +1,56 @@
module Spree
class UserSessionsController < Devise::SessionsController
helper 'spree/base', 'spree/store'
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
before_filter :set_checkout_redirect, only: :create
def create
authenticate_spree_user!
if spree_user_signed_in?
respond_to do |format|
format.html {
flash[:success] = t('devise.success.logged_in_succesfully')
redirect_back_or_default(after_sign_in_path_for(spree_current_user))
}
format.js {
render json: { email: spree_current_user.login }, status: :ok
}
end
else
respond_to do |format|
format.html {
flash.now[:error] = t('devise.failure.invalid')
render :new
}
format.js {
render json: { message: t('devise.failure.invalid') }, status: :unauthorized
}
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
end

View File

@@ -1,29 +0,0 @@
Spree::UserSessionsController.class_eval do
before_filter :set_checkout_redirect, only: :create
def create
authenticate_spree_user!
if spree_user_signed_in?
respond_to do |format|
format.html {
flash[:success] = t('devise.success.logged_in_succesfully')
redirect_back_or_default(after_sign_in_path_for(spree_current_user))
}
format.js {
render json: { email: spree_current_user.login }, status: :ok
}
end
else
respond_to do |format|
format.html {
flash.now[:error] = t('devise.failure.invalid')
render :new
}
format.js {
render json: { message: t('devise.failure.invalid') }, status: :unauthorized
}
end
end
end
end

View File

@@ -0,0 +1,74 @@
module Spree
class UsersController < Spree::StoreController
layout 'darkswarm'
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
include I18nHelper
before_filter :set_locale
before_filter :enable_embedded_shopfront
# Ignores invoice orders, only order where state: 'complete'
def show
@orders = @user.orders.where(state: 'complete').order('completed_at desc')
@unconfirmed_email = spree_current_user.unconfirmed_email
end
# Endpoint for queries to check if a user is already registered
def registered_email
user = Spree.user_class.find_by_email params[:email]
render json: { registered: user.present? }
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
Spree::User.reset_password_by_token(params[:user])
sign_in(@user, event: :authentication,
bypass: true)
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
end

View File

@@ -1,20 +0,0 @@
Spree::UsersController.class_eval do
layout 'darkswarm'
include I18nHelper
before_filter :set_locale
before_filter :enable_embedded_shopfront
# Override of spree_auth_devise default
# Ignores invoice orders, only order where state: 'complete'
def show
@orders = @user.orders.where(state: 'complete').order('completed_at desc')
@unconfirmed_email = spree_current_user.unconfirmed_email
end
# Endpoint for queries to check if a user is already registered
def registered_email
user = Spree.user_class.find_by_email params[:email]
render json: { registered: user.present? }
end
end

View File

@@ -0,0 +1,47 @@
# This mailer is configured to be the Devise mailer
# Some methods here override Devise::Mailer methods
module Spree
class UserMailer < BaseMailer
include I18nHelper
# Overrides `Devise::Mailer.reset_password_instructions`
def reset_password_instructions(user)
recipient = user.respond_to?(:id) ? user : Spree.user_class.find(user)
@edit_password_reset_url = spree.
edit_spree_user_password_url(reset_password_token: recipient.reset_password_token)
mail(to: recipient.email, from: from_address,
subject: Spree::Config[:site_name] + ' ' +
I18n.t(:subject, scope: [:devise, :mailer, :reset_password_instructions]))
end
# This is a OFN specific email, not from Devise::Mailer
def signup_confirmation(user)
@user = user
I18n.with_locale valid_locale(@user) do
mail(to: user.email, from: from_address,
subject: t(:welcome_to) + Spree::Config[:site_name])
end
end
# Overrides `Devise::Mailer.confirmation_instructions`
def confirmation_instructions(user, _opts)
@user = user
@instance = Spree::Config[:site_name]
@contact = ContentConfig.footer_email
I18n.with_locale valid_locale(@user) do
subject = t('spree.user_mailer.confirmation_instructions.subject')
mail(to: confirmation_email_address,
from: from_address,
subject: subject)
end
end
private
def confirmation_email_address
@user.pending_reconfirmation? ? @user.unconfirmed_email : @user.email
end
end
end

View File

@@ -1,32 +0,0 @@
Spree::UserMailer.class_eval do
include I18nHelper
def signup_confirmation(user)
@user = user
I18n.with_locale valid_locale(@user) do
mail(to: user.email, from: from_address,
subject: t(:welcome_to) + Spree::Config[:site_name])
end
end
# Overriding `Spree::UserMailer.confirmation_instructions` which is
# overriding `Devise::Mailer.confirmation_instructions`.
def confirmation_instructions(user, _opts)
@user = user
@instance = Spree::Config[:site_name]
@contact = ContentConfig.footer_email
I18n.with_locale valid_locale(@user) do
subject = t('spree.user_mailer.confirmation_instructions.subject')
mail(to: confirmation_email_address,
from: from_address,
subject: subject)
end
end
private
def confirmation_email_address
@user.pending_reconfirmation? ? @user.unconfirmed_email : @user.email
end
end

184
app/models/spree/user.rb Normal file
View File

@@ -0,0 +1,184 @@
module Spree
class User < ActiveRecord::Base
include Core::UserBanners
devise :database_authenticatable, :token_authenticatable, :registerable, :recoverable,
:rememberable, :trackable, :validatable, :encryptable, encryptor: 'authlogic_sha512'
has_many :orders
belongs_to :ship_address, foreign_key: 'ship_address_id', class_name: 'Spree::Address'
belongs_to :bill_address, foreign_key: 'bill_address_id', class_name: 'Spree::Address'
before_validation :set_login
before_destroy :check_completed_orders
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation,
:remember_me, :persistence_token, :login
users_table_name = User.table_name
roles_table_name = Role.table_name
scope :admin, lambda { includes(:spree_roles).where("#{roles_table_name}.name" => "admin") }
scope :registered, -> { where("#{users_table_name}.email NOT LIKE ?", "%@example.net") }
has_many :enterprise_roles, dependent: :destroy
has_many :enterprises, through: :enterprise_roles
has_many :owned_enterprises, class_name: 'Enterprise',
foreign_key: :owner_id, inverse_of: :owner
has_many :owned_groups, class_name: 'EnterpriseGroup',
foreign_key: :owner_id, inverse_of: :owner
has_many :customers
has_many :credit_cards
accepts_nested_attributes_for :enterprise_roles, allow_destroy: true
accepts_nested_attributes_for :bill_address
accepts_nested_attributes_for :ship_address
attr_accessible :enterprise_ids, :enterprise_roles_attributes, :enterprise_limit,
:locale, :bill_address_attributes, :ship_address_attributes
after_create :associate_customers
validate :limit_owned_enterprises
# We use the same options as Spree and add :confirmable
devise :confirmable, reconfirmable: true
# TODO: Later versions of devise have a dedicated after_confirmation callback, so use that
after_update :welcome_after_confirm, if: lambda {
confirmation_token_changed? && confirmation_token.nil?
}
class DestroyWithOrdersError < StandardError; end
# Creates an anonymous user. An anonymous user is basically an auto-generated +User+ account
# that is created for the customer behind the scenes and it's transparent to the customer.
# All +Orders+ must have a +User+ so this is necessary when adding to the "cart" (an order)
# and before the customer has a chance to provide an email or to register.
def self.anonymous!
token = User.generate_token(:persistence_token)
User.create(email: "#{token}@example.net",
password: token, password_confirmation: token, persistence_token: token)
end
def self.admin_created?
User.admin.count > 0
end
def admin?
has_spree_role?('admin')
end
def anonymous?
email =~ /@example.net$/ ? true : false
end
def send_reset_password_instructions
generate_reset_password_token!
UserMailer.reset_password_instructions(id).deliver
end
# handle_asynchronously will define send_reset_password_instructions_with_delay.
# If handle_asynchronously is called twice, we get an infinite job loop.
handle_asynchronously :send_reset_password_instructions unless method_defined? :send_reset_password_instructions_with_delay
def known_users
if admin?
Spree::User.scoped
else
Spree::User
.includes(:enterprises)
.where("enterprises.id IN (SELECT enterprise_id FROM enterprise_roles WHERE user_id = ?)",
id)
end
end
def build_enterprise_roles
Enterprise.all.find_each do |enterprise|
unless enterprise_roles.find_by_enterprise_id enterprise.id
enterprise_roles.build(enterprise: enterprise)
end
end
end
def customer_of(enterprise)
return nil unless enterprise
customers.find_by_enterprise_id(enterprise)
end
def welcome_after_confirm
# Send welcome email if we are confirming an user's email
# Note: this callback only runs on email confirmation
return unless confirmed? && unconfirmed_email.nil? && !unconfirmed_email_changed?
send_signup_confirmation
end
def send_signup_confirmation
Delayed::Job.enqueue ConfirmSignupJob.new(id)
end
def associate_customers
self.customers = Customer.where(email: email)
end
def can_own_more_enterprises?
owned_enterprises(:reload).size < enterprise_limit
end
def default_card
credit_cards.where(is_default: true).first
end
# Checks whether the specified user is a superadmin, with full control of the
# instance
#
# @return [Boolean]
def superadmin?
has_spree_role?('admin')
end
protected
def password_required?
!persisted? || password.present? || password_confirmation.present?
end
private
def check_completed_orders
raise DestroyWithOrdersError if orders.complete.present?
end
def set_login
# for now force login to be same as email, eventually we will make this configurable, etc.
self.login ||= email if email
end
# Generate a friendly string randomically to be used as token.
def self.friendly_token
SecureRandom.base64(15).tr('+/=', '-_ ').strip.delete("\n")
end
# Generate a token by looping and ensuring does not already exist.
def self.generate_token(column)
loop do
token = friendly_token
break token unless find(:first, conditions: { column => token })
end
end
def limit_owned_enterprises
return unless owned_enterprises.size > enterprise_limit
errors.add(:owned_enterprises, I18n.t(:spree_user_enterprise_limit_error,
email: email,
enterprise_limit: enterprise_limit))
end
def remove_payments_in_checkout(enterprises)
enterprises.each do |enterprise|
enterprise.distributed_orders.each do |order|
order.payments.keep_if { |payment| payment.state != "checkout" }
end
end
end
end
end

View File

@@ -1,98 +0,0 @@
Spree.user_class.class_eval do
# handle_asynchronously will define send_reset_password_instructions_with_delay.
# If handle_asynchronously is called twice, we get an infinite job loop.
handle_asynchronously :send_reset_password_instructions unless method_defined? :send_reset_password_instructions_with_delay
has_many :enterprise_roles, dependent: :destroy
has_many :enterprises, through: :enterprise_roles
has_many :owned_enterprises, class_name: 'Enterprise', foreign_key: :owner_id, inverse_of: :owner
has_many :owned_groups, class_name: 'EnterpriseGroup', foreign_key: :owner_id, inverse_of: :owner
has_many :customers
has_many :credit_cards
accepts_nested_attributes_for :enterprise_roles, allow_destroy: true
accepts_nested_attributes_for :bill_address
accepts_nested_attributes_for :ship_address
attr_accessible :enterprise_ids, :enterprise_roles_attributes, :enterprise_limit, :locale, :bill_address_attributes, :ship_address_attributes
after_create :associate_customers
validate :limit_owned_enterprises
# We use the same options as Spree and add :confirmable
devise :confirmable, reconfirmable: true
# TODO: Later versions of devise have a dedicated after_confirmation callback, so use that
after_update :welcome_after_confirm, if: lambda { confirmation_token_changed? && confirmation_token.nil? }
def known_users
if admin?
Spree::User.scoped
else
Spree::User
.includes(:enterprises)
.where("enterprises.id IN (SELECT enterprise_id FROM enterprise_roles WHERE user_id = ?)", id)
end
end
def build_enterprise_roles
Enterprise.all.find_each do |enterprise|
unless enterprise_roles.find_by_enterprise_id enterprise.id
enterprise_roles.build(enterprise: enterprise)
end
end
end
def customer_of(enterprise)
return nil unless enterprise
customers.find_by_enterprise_id(enterprise)
end
def welcome_after_confirm
# Send welcome email if we are confirming an user's email
# Note: this callback only runs on email confirmation
if confirmed? && unconfirmed_email.nil? && !unconfirmed_email_changed?
send_signup_confirmation
end
end
def send_signup_confirmation
Delayed::Job.enqueue ConfirmSignupJob.new(id)
end
def associate_customers
self.customers = Customer.where(email: email)
end
def can_own_more_enterprises?
owned_enterprises(:reload).size < enterprise_limit
end
def default_card
credit_cards.where(is_default: true).first
end
# Checks whether the specified user is a superadmin, with full control of the
# instance
#
# @return [Boolean]
def superadmin?
has_spree_role?('admin')
end
private
def limit_owned_enterprises
if owned_enterprises.size > enterprise_limit
errors.add(:owned_enterprises, I18n.t(:spree_user_enterprise_limit_error, email: email, enterprise_limit: enterprise_limit))
end
end
def remove_payments_in_checkout(enterprises)
enterprises.each do |enterprise|
enterprise.distributed_orders.each do |order|
order.payments.keep_if { |payment| payment.state != "checkout" }
end
end
end
end

View File

@@ -0,0 +1,6 @@
Deface::Override.new(virtual_path: "spree/layouts/admin",
name: "user_admin_tabs",
insert_bottom: "[data-hook='admin_tabs'], #admin_tabs[data-hook]",
partial: "spree/admin/users_tab",
disabled: false,
original: '031652cf5a054796022506622082ab6d2693699f')

View File

@@ -0,0 +1,5 @@
Deface::Override.new(virtual_path: "spree/layouts/admin",
name: "auth_admin_login_navigation_bar",
insert_top: "[data-hook='admin_login_navigation_bar'], #admin_login_navigation_bar[data-hook]",
partial: "spree/layouts/admin/login_nav",
original: '841227d0aedf7909d62237d8778df99100087715')

View File

@@ -0,0 +1,6 @@
Deface::Override.new(virtual_path: "spree/shared/_nav_bar",
name: "auth_shared_login_bar",
insert_before: "li#search-bar",
partial: "spree/shared/login_bar",
disabled: false,
original: 'eb3fa668cd98b6a1c75c36420ef1b238a1fc55ac')

View File

@@ -0,0 +1,2 @@
- if can? :admin, Spree::User
= tab(:users, url: spree.admin_users_path, icon: 'icon-user')

View File

@@ -0,0 +1,15 @@
= content_for :page_title do
= Spree.t(:new_user)
= content_for :page_actions do
%li
= button_link_to Spree.t(:back_to_users_list), spree.admin_users_path, icon: 'icon-arrow-left'
%div
= render partial: 'spree/shared/error_messages', locals: { target: @user }
%div
= form_for [:admin, @user] do |f|
= render partial: 'form', locals: { f: f }
%div
= render partial: 'spree/admin/shared/new_resource_links'

View File

@@ -0,0 +1,15 @@
%p
= f.label :email, Spree.t(:email)
%br
= f.email_field :email, class: 'title'
%div{"id" => "password-credentials"}
%p
= f.label :password, Spree.t(:password)
%br
= f.password_field :password, class: 'title'
%p
= f.label :password_confirmation, Spree.t(:confirm_password)
%br
= f.password_field :password_confirmation, class: 'title'

View File

@@ -0,0 +1,14 @@
= render partial: 'spree/shared/error_messages', locals: { target: @spree_user }
%div{"id" => "forgot-password"}
%h6= Spree.t(:forgot_password)
%p= Spree.t(:instructions_to_reset_password)
= form_for Spree::User.new, as: :spree_user, url: spree.reset_password_path do |f|
%p
= f.label :email, Spree.t(:email)
%br
= f.email_field :email
%p
= f.submit Spree.t(:reset_password), class: 'button primary'

View File

@@ -1,5 +1,146 @@
# Use this hook to configure devise mailer, warden hooks and so forth. The first
# four configuration values can also be set straight in your models.
Devise.setup do |config|
# ==> Mailer Configuration
# Configure the e-mail address which will be shown in DeviseMailer.
config.mailer_sender = 'please-change-me@config-initializers-devise.com'
# Configure the class responsible to send e-mails.
config.mailer = 'Spree::UserMailer'
# ==> ORM configuration
# Load and configure the ORM. Supports :active_record (default) and
# :mongoid (bson_ext recommended) by default. Other ORMs may be
# available as additional gems.
require 'devise/orm/active_record'
# ==> Configuration for any authentication mechanism
# Configure which keys are used when authenticating an user. By default is
# just :email. You can configure it to use [:username, :subdomain], so for
# authenticating an user, both parameters are required. Remember that those
# parameters are used only when authenticating and not when retrieving from
# session. If you need permissions, you should implement that in a before filter.
# config.authentication_keys = [ :email ]
# Tell if authentication through request.params is enabled. True by default.
# config.params_authenticatable = true
# Tell if authentication through HTTP Basic Auth is enabled. False by default.
config.http_authenticatable = true
# Set this to true to use Basic Auth for AJAX requests. True by default.
#config.http_authenticatable_on_xhr = false
# The realm used in Http Basic Authentication
config.http_authentication_realm = 'Spree Application'
# ==> Configuration for :database_authenticatable
# For bcrypt, this is the cost for hashing the password and defaults to 10. If
# using other encryptors, it sets how many times you want the password re-encrypted.
config.stretches = Rails.env.test? ? 1 : 20
# Setup a pepper to generate the encrypted password.
config.pepper = Rails.configuration.secret_token
# ==> Configuration for :confirmable
# The time you want to give your user to confirm his account. During this time
# he will be able to access your application without confirming. Default is nil.
# When confirm_within is zero, the user won't be able to sign in without confirming.
# You can use this to let your user access some features of your application
# without confirming the account, but blocking it after a certain period
# (ie 2 days).
# config.confirm_within = 2.days
# ==> Configuration for :rememberable
# The time the user will be remembered without asking for credentials again.
# config.remember_for = 2.weeks
# If true, a valid remember token can be re-used between multiple browsers.
# config.remember_across_browsers = true
# If true, extends the user's remember period when remembered via cookie.
# config.extend_remember_period = false
# ==> Configuration for :validatable
# Range for password length
# config.password_length = 6..20
# Regex to use to validate the email address
config.email_regexp = /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i
# ==> Configuration for :timeoutable
# The time you want to timeout the user session without activity. After this
# time the user will be asked for credentials again.
# config.timeout_in = 10.minutes
# ==> Configuration for :lockable
# Defines which strategy will be used to lock an account.
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
# :none = No lock strategy. You should handle locking by yourself.
# config.lock_strategy = :failed_attempts
# Defines which strategy will be used to unlock an account.
# :email = Sends an unlock link to the user email
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
# :both = Enables both strategies
# :none = No unlock strategy. You should handle unlocking by yourself.
# config.unlock_strategy = :both
# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
# config.maximum_attempts = 20
# Time interval to unlock the account if :time is enabled as unlock_strategy.
# config.unlock_in = 1.hour
# ==> Configuration for :token_authenticatable
# Defines name of the authentication token params key
config.token_authentication_key = :auth_token
# ==> Scopes configuration
# Turn scoped views on. Before rendering 'sessions/new', it will first check for
# 'users/sessions/new'. It's turned off by default because it's slower if you
# are using only default views.
# config.scoped_views = true
# Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes.
# Add a default scope to devise, to prevent it from checking
# whether other devise enabled models are signed into a session or not
config.default_scope = :spree_user
end
# Configure sign_out behavior.
# By default sign_out is scoped (i.e. /users/sign_out affects only :user scope).
# In case of sign_out_all_scopes set to true any logout action will sign out all active scopes.
# config.sign_out_all_scopes = false
# ==> Navigation configuration
# Lists the formats that should be treated as navigational. Formats like
# :html, should redirect to the sign in page when the user does not have
# access, but formats like :xml or :json, should return 401.
# If you have any extra navigational formats, like :iphone or :mobile, you
# should add them to the navigational formats lists. Default is [:html]
config.navigational_formats = [:html, :json, :xml]
# ==> Warden configuration
# If you want to use other strategies, that are not (yet) supported by Devise,
# you can configure them inside the config.warden block. The example below
# allows you to setup OAuth, using http://github.com/roman/warden_oauth
#
# config.warden do |manager|
# manager.oauth(:twitter) do |twitter|
# twitter.consumer_secret = <YOUR CONSUMER SECRET>
# twitter.consumer_key = <YOUR CONSUMER KEY>
# twitter.options :site => 'http://twitter.com'
# end
# manager.default_strategies(:scope => :user).unshift :twitter_oauth
# end
#
# Time interval you can reset your password with a reset password key.
# Don't put a too small interval or your users won't have the time to
# change their passwords.
config.reset_password_within = 6.hours
config.sign_out_via = :get
config.case_insensitive_keys = [:email]
end

View File

@@ -45,9 +45,6 @@ end
# Spree 2.0 recommends explicitly setting this here when using spree_auth_devise
Spree.user_class = 'Spree::User'
# Don't log users out when setting a new password
Spree::Auth::Config[:signout_after_password_change] = false
# TODO Work out why this is necessary
# Seems like classes within OFN module become 'uninitialized' when server reloads
# unless the empty module is explicity 'registered' here. Something to do with autoloading?

View File

@@ -1,16 +0,0 @@
# `spree_auth_devise` gem decorators get loaded in a `to_prepare` callback
# referring to Spree classes that have not been loaded yet
#
# When this initializer is loaded we're sure that those Spree classes have been
# loaded and we load again the `spree_auth_devise` decorators to effectively
# apply them.
#
# Give a look at `if defined?(Spree::Admin::BaseController)` in the following file
# to get an example:
# https://github.com/openfoodfoundation/spree_auth_devise/blob/spree-upgrade-intermediate/app/controllers/spree/admin/admin_controller_decorator.rb#L1
#
# TODO: remove this hack once we get to Spree 3.0
gem_dir = Gem::Specification.find_by_name("spree_auth_devise").gem_dir
Dir.glob(File.join(gem_dir, 'app/**/*_decorator*.rb')) do |c|
load c
end

View File

@@ -103,8 +103,8 @@ en:
confirmation_not_sent: "Error sending confirmation email"
user_registrations:
spree_user:
signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
unknown_error: "Something went wrong while creating your account. Check your email address and try again."
signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
unknown_error: "Something went wrong while creating your account. Check your email address and try again."
failure:
invalid: |
Invalid email or password.
@@ -116,6 +116,8 @@ en:
user_passwords:
spree_user:
updated_not_active: "Your password has been reset, but your email has not been confirmed yet."
updated: "Your password was changed successfully. You are now signed in."
send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes."
models:
order_cycle:
@@ -2933,6 +2935,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
inventory: Inventory
zipcode: Postcode
weight: Weight (per kg)
error_user_destroy_with_orders: "Users with completed orders may not be deleted"
actions:
update: "Update"

View File

@@ -9,6 +9,32 @@ 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
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

82
db/default/users.rb Normal file
View File

@@ -0,0 +1,82 @@
require 'highline/import'
# see last line where we create an admin if there is none, asking for email and password
def prompt_for_admin_password
if ENV['ADMIN_PASSWORD']
password = ENV['ADMIN_PASSWORD'].dup
say "Admin Password #{password}"
else
password = ask('Password [spree123]: ') do |q|
q.echo = false
q.validate = /^(|.{5,40})$/
q.responses[:not_valid] = 'Invalid password. Must be at least 5 characters long.'
q.whitespace = :strip
end
password = 'spree123' if password.blank?
end
password
end
def prompt_for_admin_email
if ENV['ADMIN_EMAIL']
email = ENV['ADMIN_EMAIL'].dup
say "Admin User #{email}"
else
email = ask('Email [spree@example.com]: ') do |q|
q.echo = true
q.whitespace = :strip
end
email = 'spree@example.com' if email.blank?
end
email
end
def create_admin_user
if ENV['AUTO_ACCEPT']
password = 'spree123'
email = 'spree@example.com'
else
puts 'Create the admin user (press enter for defaults).'
#name = prompt_for_admin_name unless name
email = prompt_for_admin_email
password = prompt_for_admin_password
end
attributes = {
:password => password,
:password_confirmation => password,
:email => email,
:login => email
}
load 'spree/user.rb'
if Spree::User.find_by_email(email)
say "\nWARNING: There is already a user with the email: #{email}, so no account changes were made. If you wish to create an additional admin user, please run rake spree_auth:admin:create again with a different email.\n\n"
else
admin = Spree::User.new(attributes)
if admin.save
role = Spree::Role.find_or_create_by_name 'admin'
admin.spree_roles << role
admin.save
say "Done!"
else
say "There was some problems with persisting new admin user:"
admin.errors.full_messages.each do |error|
say error
end
end
end
end
if Spree::User.admin.empty?
create_admin_user
else
puts 'Admin user has already been previously created.'
if agree('Would you like to create a new admin user? (yes/no)')
create_admin_user
else
puts 'No admin user created.'
end
end

View File

@@ -0,0 +1,26 @@
module Spree
module AuthenticationHelpers
def self.included(receiver)
receiver.public_send :helper_method, :spree_current_user
receiver.public_send :helper_method, :spree_login_path
receiver.public_send :helper_method, :spree_signup_path
receiver.public_send :helper_method, :spree_logout_path
end
def spree_current_user
current_spree_user
end
def spree_login_path
spree.login_path
end
def spree_signup_path
spree.signup_path
end
def spree_logout_path
spree.logout_path
end
end
end

View File

@@ -15,7 +15,7 @@ Spree::Core::ControllerHelpers::Order.class_eval do
end
alias_method_chain :current_order, :scoped_variants
# Override definition in spree/auth/app/controllers/spree/base_controller_decorator.rb
# Override definition in Spree::Core::ControllerHelpers::Order
# Do not attempt to merge incomplete and current orders. Instead, destroy the incomplete orders.
def set_current_order
if user = try_spree_current_user

View File

@@ -0,0 +1,57 @@
require 'spec_helper'
require 'spree/testing_support/bar_ability'
describe Spree::Admin::UsersController do
context '#authorize_admin' do
let(:user) { create(:user) }
let(:test_user) { create(:user) }
before do
allow(controller).to receive_messages spree_current_user: user
allow(Spree::User).to receive(:find).with(test_user.id.to_s).and_return(test_user)
user.spree_roles.clear
end
it 'should grant access to users with an admin role' do
user.spree_roles << Spree::Role.find_or_create_by_name('admin')
spree_post :index
expect(response).to render_template :index
end
it "allows admins to update a user's API key" do
user.spree_roles << Spree::Role.find_or_create_by_name('admin')
expect(test_user).to receive(:generate_spree_api_key!).and_return(true)
puts user.id
puts test_user.id
spree_put :generate_api_key, id: test_user.id
expect(response).to redirect_to(spree.edit_admin_user_path(test_user))
end
it "allows admins to clear a user's API key" do
user.spree_roles << Spree::Role.find_or_create_by_name('admin')
expect(test_user).to receive(:clear_spree_api_key!).and_return(true)
spree_put :clear_api_key, id: test_user.id
expect(response).to redirect_to(spree.edit_admin_user_path(test_user))
end
it 'should deny access to users with an bar role' do
user.spree_roles << Spree::Role.find_or_create_by_name('bar')
Spree::Ability.register_ability(BarAbility)
spree_post :index
expect(response).to redirect_to('/unauthorized')
end
it 'should deny access to users with an bar role' do
user.spree_roles << Spree::Role.find_or_create_by_name('bar')
Spree::Ability.register_ability(BarAbility)
spree_post :update, id: '9'
expect(response).to redirect_to('/unauthorized')
end
it 'should deny access to users without an admin role' do
allow(user).to receive_messages has_spree_role?: false
spree_post :index
expect(response).to redirect_to('/unauthorized')
end
end
end

View File

@@ -54,4 +54,19 @@ describe Spree::UsersController, type: :controller do
expect(json_response['registered']).to eq false
end
end
context '#load_object' do
it 'should redirect to signup path if user is not found' do
allow(controller).to receive_messages(spree_current_user: nil)
spree_put :update, user: { email: 'foobar@example.com' }
expect(response).to redirect_to('/login')
end
end
context '#create' do
it 'should create a new user' do
spree_post :create, user: { email: 'foobar@example.com', password: 'foobar123', password_confirmation: 'foobar123' }
expect(assigns[:user].new_record?).to be_falsey
end
end
end

View File

@@ -17,6 +17,12 @@ feature '
@enterprise_fees = (0..2).map { |i| create(:enterprise_fee, enterprise: @distributors[i]) }
end
context "as anonymous user" do
it "is redirected to login page when attempting to access product listing" do
expect { visit spree.admin_products_path }.not_to raise_error
end
end
describe "creating a product" do
let!(:tax_category) { create(:tax_category, name: 'Test Tax Category') }

View File

@@ -10,6 +10,63 @@ feature "Managing users" do
quick_login_as_admin
end
context "from the index page" do
before do
create(:user, :email => "a@example.com")
create(:user, :email => "b@example.com")
visit spree.admin_path
click_link "Users"
end
context "users index page with sorting" do
before(:each) do
click_link "users_email_title"
end
it "should be able to list users with order email asc" do
expect(page).to have_css('table#listing_users')
within("table#listing_users") do
expect(page).to have_content("a@example.com")
expect(page).to have_content("b@example.com")
end
end
it "should be able to list users with order email desc" do
click_link "users_email_title"
within("table#listing_users") do
expect(page).to have_content("a@example.com")
expect(page).to have_content("b@example.com")
end
end
end
context "searching users" do
it "should display the correct results for a user search" do
fill_in "q_email_cont", :with => "a@example"
click_button "Search"
within("table#listing_users") do
expect(page).to have_content("a@example")
expect(page).not_to have_content("b@example")
end
end
end
context "editing users" do
before(:each) do
click_link("a@example.com")
end
it "should let me edit the user password" do
fill_in "user_password", :with => "welcome"
fill_in "user_password_confirmation", :with => "welcome"
click_button "Update"
expect(page).to have_content("Account updated")
end
end
end
describe "creating a user" do
it "shows no confirmation message to start with" do
visit spree.new_admin_user_path

View File

@@ -180,4 +180,45 @@ describe Spree.user_class do
end
end
end
before(:all) { Spree::Role.create name: 'admin' }
it '#admin?' do
expect(create(:admin_user).admin?).to be_truthy
expect(create(:user).admin?).to be_falsey
end
context '#create' do
let(:user) { build(:user) }
it 'should not be anonymous' do
expect(user).not_to be_anonymous
end
end
context '#destroy' do
it 'can not delete if it has completed orders' do
order = build(:order, completed_at: Time.zone.now)
order.save
user = order.user
expect { user.destroy }.to raise_exception(Spree::User::DestroyWithOrdersError)
end
end
context 'anonymous!' do
let(:user) { Spree::User.anonymous! }
it 'should create a new user' do
expect(user.new_record?).to be_falsey
end
it 'should create a user with an example.net email' do
expect(user.email).to match(/@example.net$/)
end
it 'should be anonymous' do
expect(user).to be_anonymous
end
end
end

View File

@@ -128,7 +128,6 @@ RSpec.configure do |config|
spree_config.auto_capture = true
end
Spree::Auth::Config[:signout_after_password_change] = false
Spree::Api::Config[:requires_authentication] = true
end