diff --git a/app/controllers/application_controller/authenticates.rb b/app/controllers/application_controller/authenticates.rb
index 0810eb9b4..5e3ac287b 100644
--- a/app/controllers/application_controller/authenticates.rb
+++ b/app/controllers/application_controller/authenticates.rb
@@ -135,7 +135,7 @@ module ApplicationController::Authenticates
def authenticate_with_password
user = User.authenticate(params[:username], params[:password])
- raise Exceptions::NotAuthorized, 'Wrong Username or Password combination.' if !user
+ raise_unified_login_error if !user
session.delete(:switched_from_user_id)
authentication_check_prerequesits(user, 'session', {})
@@ -158,7 +158,8 @@ module ApplicationController::Authenticates
def authentication_check_prerequesits(user, auth_type, auth_param)
raise Exceptions::NotAuthorized, 'Maintenance mode enabled!' if in_maintenance_mode?(user)
- raise Exceptions::NotAuthorized, 'User is inactive!' if !user.active
+
+ raise_unified_login_error if !user.active
if auth_param[:permission]
ActiveSupport::Deprecation.warn("Parameter ':permission' is deprecated. Use Pundit policy and `authorize!` instead.")
@@ -173,4 +174,8 @@ module ApplicationController::Authenticates
logger.debug { "#{auth_type} for '#{user.login}'" }
user
end
+
+ def raise_unified_login_error
+ raise Exceptions::NotAuthorized, 'Login failed. Have you double-checked your credentials and completed the email verification step?'
+ end
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index e85a19dff..a3f806f94 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -5,7 +5,7 @@ class UsersController < ApplicationController
prepend_before_action -> { authorize! }, only: %i[import_example import_start search history]
prepend_before_action :authentication_check, except: %i[create password_reset_send password_reset_verify image email_verify email_verify_send]
- prepend_before_action :authentication_check_only, only: [:create]
+ prepend_before_action :authentication_check_only, only: %i[create]
# @path [GET] /users
#
@@ -99,158 +99,16 @@ class UsersController < ApplicationController
# @path [POST] /users
#
- # @summary Creates a User record with the provided attribute values.
- # @notes TODO.
- #
- # @parameter User(required,body) [User] The attribute value structure needed to create a User record.
- #
- # @response_message 200 [User] Created User record.
- # @response_message 401 Invalid session.
+ # @summary processes requests as CRUD-like record creation, admin creation or user signup depending on circumstances
+ # @see #create_internal #create_admin #create_signup
def create
- clean_params = User.association_name_to_id_convert(params)
- clean_params = User.param_cleanup(clean_params, true)
-
- # check if it's first user, the admin user
- # initial admin account
- count = User.all.count
- admin_account_exists = true
- if count <= 2
- admin_account_exists = false
- end
-
- # if it's a signup, add user to customer role
- if !current_user
-
- # check if feature is enabled
- if admin_account_exists && !Setting.get('user_create_account')
- raise Exceptions::UnprocessableEntity, 'Feature not enabled!'
- end
-
- # check signup option only after admin account is created
- if admin_account_exists && !params[:signup]
- raise Exceptions::UnprocessableEntity, 'Only signup with not authenticate user possible!'
- end
-
- # check if user already exists
- if clean_params[:email].blank?
- raise Exceptions::UnprocessableEntity, 'Attribute \'email\' required!'
- end
-
- # check if user already exists
- exists = User.exists?(email: clean_params[:email].downcase.strip)
- raise Exceptions::UnprocessableEntity, "Email address '#{clean_params[:email].downcase.strip}' is already used for other user." if exists
-
- # check password policy
- if clean_params[:password].present?
- result = password_policy(clean_params[:password])
- raise Exceptions::UnprocessableEntity, result if result != true
- end
-
- user = User.new(clean_params)
- user.associations_from_param(params)
- user.updated_by_id = 1
- user.created_by_id = 1
-
- # add first user as admin/agent and to all groups
- group_ids = []
- role_ids = []
- if count <= 2
- Role.where(name: %w[Admin Agent]).each do |role|
- role_ids.push role.id
- end
- Group.all.each do |group|
- group_ids.push group.id
- end
-
- # everybody else will go as customer per default
- else
- role_ids = Role.signup_role_ids
- end
- user.role_ids = role_ids
- user.group_ids = group_ids
-
- # remember source (in case show email verify banner)
- # if not initial user creation
- if admin_account_exists
- user.source = 'signup'
- end
-
- # else do assignment as defined
+ if current_user
+ create_internal
+ elsif params[:signup]
+ create_signup
else
-
- # permission check
- check_attributes_by_current_user_permission(params)
-
- user = User.new(clean_params)
- user.associations_from_param(params)
+ create_admin
end
-
- user.save!
-
- # if first user was added, set system init done
- if !admin_account_exists
- Setting.set('system_init_done', true)
-
- # fetch org logo
- if user.email.present?
- Service::Image.organization_suggest(user.email)
- end
-
- # load calendar
- Calendar.init_setup(request.remote_ip)
-
- # load text modules
- begin
- TextModule.load(request.env['HTTP_ACCEPT_LANGUAGE'] || 'en-us')
- rescue => e
- logger.error "Unable to load text modules #{request.env['HTTP_ACCEPT_LANGUAGE'] || 'en-us'}: #{e.message}"
- end
- end
-
- # send invitation if needed / only if session exists
- if params[:invite].present? && current_user
- sleep 5 if ENV['REMOTE_URL'].present?
- token = Token.create(action: 'PasswordReset', user_id: user.id)
- NotificationFactory::Mailer.notification(
- template: 'user_invite',
- user: user,
- objects: {
- token: token,
- user: user,
- current_user: current_user,
- }
- )
- end
-
- # send email verify
- if params[:signup].present? && !current_user
- result = User.signup_new_token(user)
- NotificationFactory::Mailer.notification(
- template: 'signup',
- user: user,
- objects: result,
- )
- end
-
- if response_expand?
- user = user.reload.attributes_with_association_names
- user.delete('password')
- render json: user, status: :created
- return
- end
-
- if response_full?
- result = {
- id: user.id,
- assets: user.assets({}),
- }
- render json: result, status: :created
- return
- end
-
- user = user.reload.attributes_with_association_ids
- user.delete('password')
- render json: user, status: :created
end
# @path [PUT] /users/{id}
@@ -1043,4 +901,173 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
true
end
+
+ def clean_user_params
+ User.param_cleanup(User.association_name_to_id_convert(params), true)
+ end
+
+ # @summary Creates a User record with the provided attribute values.
+ # @notes For creating a user via agent interface
+ #
+ # @parameter User(required,body) [User] The attribute value structure needed to create a User record.
+ #
+ # @response_message 200 [User] Created User record.
+ # @response_message 401 Invalid session.
+ def create_internal
+ # permission check
+ check_attributes_by_current_user_permission(params)
+
+ user = User.new(clean_user_params)
+ user.associations_from_param(params)
+
+ user.save!
+
+ if params[:invite].present?
+ sleep 5 if ENV['REMOTE_URL'].present?
+ token = Token.create(action: 'PasswordReset', user_id: user.id)
+ NotificationFactory::Mailer.notification(
+ template: 'user_invite',
+ user: user,
+ objects: {
+ token: token,
+ user: user,
+ current_user: current_user,
+ }
+ )
+ end
+
+ if response_expand?
+ user = user.reload.attributes_with_association_names
+ user.delete('password')
+ render json: user, status: :created
+ return
+ end
+
+ if response_full?
+ result = {
+ id: user.id,
+ assets: user.assets({}),
+ }
+ render json: result, status: :created
+ return
+ end
+
+ user = user.reload.attributes_with_association_ids
+ user.delete('password')
+ render json: user, status: :created
+ end
+
+ # @summary Creates a User record with the provided attribute values.
+ # @notes For creating a user via public signup form
+ #
+ # @parameter User(required,body) [User] The attribute value structure needed to create a User record.
+ #
+ # @response_message 200 [User] Created User record.
+ # @response_message 401 Invalid session.
+ def create_signup
+ # check if feature is enabled
+ if !Setting.get('user_create_account')
+ raise Exceptions::UnprocessableEntity, 'Feature not enabled!'
+ end
+
+ # check signup option only after admin account is created
+ if !params[:signup]
+ raise Exceptions::UnprocessableEntity, 'Only signup with not authenticate user possible!'
+ end
+
+ # check if user already exists
+ if clean_user_params[:email].blank?
+ raise Exceptions::UnprocessableEntity, 'Attribute \'email\' required!'
+ end
+
+ email_taken_by = User.find_by email: clean_user_params[:email].downcase.strip
+
+ result = (password = clean_user_params[:password]) && password_policy(password)
+ raise Exceptions::UnprocessableEntity, 'Only signup with a password!' if result.nil?
+ raise Exceptions::UnprocessableEntity, result if result != true
+
+ user = User.new(clean_user_params)
+ user.associations_from_param(params)
+ user.role_ids = Role.signup_role_ids
+ user.source = 'signup'
+
+ if email_taken_by # show fake OK response to avoid leaking that email is already in use
+ User.without_callback :validation, :before, :ensure_uniq_email do # skip unique email validation
+ user.valid? # trigger errors raised in validations
+ end
+
+ result = User.password_reset_new_token(email_taken_by.email)
+ NotificationFactory::Mailer.notification(
+ template: 'signup_taken_reset',
+ user: email_taken_by,
+ objects: result,
+ )
+
+ render json: { message: 'ok' }, status: :created
+ return
+ end
+
+ UserInfo.ensure_current_user_id do
+ user.save!
+ end
+
+ result = User.signup_new_token(user)
+ NotificationFactory::Mailer.notification(
+ template: 'signup',
+ user: user,
+ objects: result,
+ )
+
+ render json: { message: 'ok' }, status: :created
+ end
+
+ # @summary Creates a User record with the provided attribute values.
+ # @notes For creating an administrator account when setting up the system
+ #
+ # @parameter User(required,body) [User] The attribute value structure needed to create a User record.
+ #
+ # @response_message 200 [User] Created User record.
+ # @response_message 401 Invalid session.
+ def create_admin
+ if User.count > 2 # system and example users
+ raise Exceptions::UnprocessableEntity, 'Administrator account already created'
+ end
+
+ # check if user already exists
+ if clean_user_params[:email].blank?
+ raise Exceptions::UnprocessableEntity, 'Attribute \'email\' required!'
+ end
+
+ # check password policy
+ result = (password = clean_user_params[:password]) && password_policy(password)
+ raise Exceptions::UnprocessableEntity, result if result != true
+
+ user = User.new(clean_user_params)
+ user.associations_from_param(params)
+ user.role_ids = Role.where(name: %w[Admin Agent]).pluck(:id)
+ user.group_ids = Group.all.pluck(:id)
+
+ UserInfo.ensure_current_user_id do
+ user.save!
+ end
+
+ Setting.set('system_init_done', true)
+
+ # fetch org logo
+ if user.email.present?
+ Service::Image.organization_suggest(user.email)
+ end
+
+ # load calendar
+ Calendar.init_setup(request.remote_ip)
+
+ # load text modules
+ begin
+ TextModule.load(request.env['HTTP_ACCEPT_LANGUAGE'] || 'en-us')
+ rescue => e
+ logger.error "Unable to load text modules #{request.env['HTTP_ACCEPT_LANGUAGE'] || 'en-us'}: #{e.message}"
+ end
+
+ render json: { message: 'ok' }, status: :created
+ end
end
diff --git a/app/views/mailer/signup_taken_reset/en.html.erb b/app/views/mailer/signup_taken_reset/en.html.erb
new file mode 100644
index 000000000..a3f459301
--- /dev/null
+++ b/app/views/mailer/signup_taken_reset/en.html.erb
@@ -0,0 +1,15 @@
+Reset your #{config.product_name} password
+
+
Hi #{user.firstname},
+
+
You or someone else tried to sign up with this email address. However, this email is already being used.
+
+
If you want to reset your password, click on the link below (or copy and paste the URL into your browser):
+
+
+
+
This link takes you to a page where you can change your password.
+
+
If you don't want to reset your password, please ignore this message. Your password will not be reset.
+
+
Your #{config.product_name} Team
diff --git a/spec/requests/api_auth_spec.rb b/spec/requests/api_auth_spec.rb
index 6b14bf706..743ff7b72 100644
--- a/spec/requests/api_auth_spec.rb
+++ b/spec/requests/api_auth_spec.rb
@@ -142,7 +142,7 @@ RSpec.describe 'Api Auth', type: :request do
get '/api/v1/sessions', params: {}, as: :json
expect(response).to have_http_status(:unauthorized)
expect(json_response).to be_a_kind_of(Hash)
- expect(json_response['error']).to eq('User is inactive!')
+ expect(json_response['error']).to eq('Login failed. Have you double-checked your credentials and completed the email verification step?')
admin_token.preferences[:permission] = ['admin.session']
admin_token.save!
@@ -150,7 +150,7 @@ RSpec.describe 'Api Auth', type: :request do
get '/api/v1/sessions', params: {}, as: :json
expect(response).to have_http_status(:unauthorized)
expect(json_response).to be_a_kind_of(Hash)
- expect(json_response['error']).to eq('User is inactive!')
+ expect(json_response['error']).to eq('Login failed. Have you double-checked your credentials and completed the email verification step?')
admin.active = true
admin.save!
@@ -344,7 +344,7 @@ RSpec.describe 'Api Auth', type: :request do
expect(response).to have_http_status(:unauthorized)
expect(response.header).not_to be_key('Access-Control-Allow-Origin')
expect(json_response).to be_a_kind_of(Hash)
- expect(json_response['error']).to eq('User is inactive!')
+ expect(json_response['error']).to eq('Login failed. Have you double-checked your credentials and completed the email verification step?')
end
it 'does token auth - expired' do
diff --git a/spec/requests/user_spec.rb b/spec/requests/user_spec.rb
index 7223798c2..19cc3183c 100644
--- a/spec/requests/user_spec.rb
+++ b/spec/requests/user_spec.rb
@@ -1,82 +1,71 @@
require 'rails_helper'
-RSpec.describe 'User', type: :request, searchindex: true do
+RSpec.describe 'User', type: :request do
- let!(:admin) do
- create(
- :admin,
- groups: Group.all,
- login: 'rest-admin',
- firstname: 'Rest',
- lastname: 'Agent',
- email: 'rest-admin@example.com',
- )
- end
- let!(:admin_with_pw) do
- create(
- :admin,
- groups: Group.all,
- login: 'rest-admin-pw',
- firstname: 'Rest',
- lastname: 'Agent',
- email: 'rest-admin-pw@example.com',
- password: 'adminpw',
- )
- end
- let!(:agent) do
- create(
- :agent,
- groups: Group.all,
- login: 'rest-agent@example.com',
- firstname: 'Rest',
- lastname: 'Agent',
- email: 'rest-agent@example.com',
- )
- end
- let!(:customer) do
- create(
- :customer,
- login: 'rest-customer1@example.com',
- firstname: 'Rest',
- lastname: 'Customer1',
- email: 'rest-customer1@example.com',
- )
- end
- let!(:organization) do
- create(:organization, name: 'Rest Org')
- end
- let!(:organization2) do
- create(:organization, name: 'Rest Org #2')
- end
- let!(:organization3) do
- create(:organization, name: 'Rest Org #3')
- end
- let!(:customer2) do
- create(
- :customer,
- organization: organization,
- login: 'rest-customer2@example.com',
- firstname: 'Rest',
- lastname: 'Customer2',
- email: 'rest-customer2@example.com',
- )
- end
-
- before do
- configure_elasticsearch do
-
- travel 1.minute
-
- rebuild_searchindex
-
- # execute background jobs
- Scheduler.worker(true)
-
- sleep 6
+ describe 'request handling', searchindex: true do
+ let!(:admin) do
+ create(
+ :admin,
+ groups: Group.all,
+ login: 'rest-admin',
+ firstname: 'Rest',
+ lastname: 'Agent',
+ email: 'rest-admin@example.com',
+ )
+ end
+ let!(:admin_with_pw) do
+ create(
+ :admin,
+ groups: Group.all,
+ login: 'rest-admin-pw',
+ firstname: 'Rest',
+ lastname: 'Agent',
+ email: 'rest-admin-pw@example.com',
+ password: 'adminpw',
+ )
+ end
+ let!(:agent) do
+ create(
+ :agent,
+ groups: Group.all,
+ login: 'rest-agent@example.com',
+ firstname: 'Rest',
+ lastname: 'Agent',
+ email: 'rest-agent@example.com',
+ )
+ end
+ let!(:customer) do
+ create(
+ :customer,
+ login: 'rest-customer1@example.com',
+ firstname: 'Rest',
+ lastname: 'Customer1',
+ email: 'rest-customer1@example.com',
+ )
+ end
+ let!(:organization) do
+ create(:organization, name: 'Rest Org')
+ end
+ let!(:organization2) do
+ create(:organization, name: 'Rest Org #2')
+ end
+ let!(:organization3) do
+ create(:organization, name: 'Rest Org #3')
+ end
+ let!(:customer2) do
+ create(
+ :customer,
+ organization: organization,
+ login: 'rest-customer2@example.com',
+ firstname: 'Rest',
+ lastname: 'Customer2',
+ email: 'rest-customer2@example.com',
+ )
end
- end
- describe 'request handling' do
+ before do
+ configure_elasticsearch(rebuild: true)
+ end
it 'does user create tests - no user' do
@@ -87,7 +76,7 @@ RSpec.describe 'User', type: :request, searchindex: true do
token = @response.headers['CSRF-TOKEN']
# token based on form
- params = { email: 'some_new_customer@example.com', authenticity_token: token }
+ params = { email: 'some_new_customer@example.com', signup: true, authenticity_token: token }
post '/api/v1/users', params: params, as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(json_response['error']).to be_truthy
@@ -95,7 +84,7 @@ RSpec.describe 'User', type: :request, searchindex: true do
# token based on headers
headers = { 'X-CSRF-Token' => token }
- params = { email: 'some_new_customer@example.com' }
+ params = { email: 'some_new_customer@example.com', signup: true }
post '/api/v1/users', params: params, headers: headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(json_response['error']).to be_truthy
@@ -103,19 +92,18 @@ RSpec.describe 'User', type: :request, searchindex: true do
Setting.set('user_create_account', true)
- # no signup param with enabled feature
- params = { email: 'some_new_customer@example.com' }
+ # no signup param without password
+ params = { email: 'some_new_customer@example.com', signup: true }
post '/api/v1/users', params: params, headers: headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(json_response['error']).to be_truthy
- expect(json_response['error']).to eq('Only signup with not authenticate user possible!')
+ expect(json_response['error']).to eq('Only signup with a password!')
- # already existing user with enabled feature
- params = { email: 'rest-customer1@example.com', signup: true }
+ # already existing user with enabled feature, pretend signup is successful
+ params = { email: 'rest-customer1@example.com', password: 'asd1ASD', signup: true }
post '/api/v1/users', params: params, headers: headers, as: :json
- expect(response).to have_http_status(:unprocessable_entity)
- expect(json_response['error']).to be_truthy
- expect(json_response['error']).to eq("Email address 'rest-customer1@example.com' is already used for other user.")
+ expect(response).to have_http_status(:created)
+ expect(json_response).to be_truthy
# email missing with enabled feature
params = { firstname: 'some firstname', signup: true }
@@ -132,38 +120,35 @@ RSpec.describe 'User', type: :request, searchindex: true do
expect(json_response['error']).to eq('Attribute \'email\' required!')
# create user with enabled feature (take customer role)
- params = { firstname: 'Me First', lastname: 'Me Last', email: 'new_here@example.com', signup: true }
+ params = { firstname: 'Me First', lastname: 'Me Last', password: 'asd1ASD', email: 'new_here@example.com', signup: true }
post '/api/v1/users', params: params, headers: headers, as: :json
expect(response).to have_http_status(:created)
expect(json_response).to be_truthy
+ expect(json_response['message']).to eq('ok')
- expect(json_response['firstname']).to eq('Me First')
- expect(json_response['lastname']).to eq('Me Last')
- expect(json_response['login']).to eq('new_here@example.com')
- expect(json_response['email']).to eq('new_here@example.com')
- user = User.find(json_response['id'])
+ user = User.find_by email: 'new_here@example.com'
expect(user).not_to be_role('Admin')
expect(user).not_to be_role('Agent')
expect(user).to be_role('Customer')
# create user with admin role (not allowed for signup, take customer role)
role = Role.lookup(name: 'Admin')
- params = { firstname: 'Admin First', lastname: 'Admin Last', email: 'new_admin@example.com', role_ids: [ role.id ], signup: true }
+ params = { firstname: 'Admin First', lastname: 'Admin Last', email: 'new_admin@example.com', password: 'asd1ASD', role_ids: [ role.id ], signup: true }
post '/api/v1/users', params: params, headers: headers, as: :json
expect(response).to have_http_status(:created)
expect(json_response).to be_truthy
- user = User.find(json_response['id'])
+ user = User.find_by email: 'new_admin@example.com'
expect(user).not_to be_role('Admin')
expect(user).not_to be_role('Agent')
expect(user).to be_role('Customer')
# create user with agent role (not allowed for signup, take customer role)
role = Role.lookup(name: 'Agent')
- params = { firstname: 'Agent First', lastname: 'Agent Last', email: 'new_agent@example.com', role_ids: [ role.id ], signup: true }
+ params = { firstname: 'Agent First', lastname: 'Agent Last', email: 'new_agent@example.com', password: 'asd1ASD', role_ids: [ role.id ], signup: true }
post '/api/v1/users', params: params, headers: headers, as: :json
expect(response).to have_http_status(:created)
expect(json_response).to be_truthy
- user = User.find(json_response['id'])
+ user = User.find_by email: 'new_agent@example.com'
expect(user).not_to be_role('Admin')
expect(user).not_to be_role('Agent')
expect(user).to be_role('Customer')
@@ -1142,4 +1127,222 @@ RSpec.describe 'User', type: :request, searchindex: true do
end
end
end
+
+ describe 'POST /api/v1/users', authenticated_as: -> { create(:admin) }, searchindex: false do
+ def make_request(params)
+ post '/api/v1/users', params: params, as: :json
+ end
+
+ let(:successful_params) { { email: 'non_existant@example.com' } }
+ let(:params_with_role) { successful_params.merge({ role_ids: [Role.find_by(name: 'Admin').id] } ) }
+ let(:params_with_invite) { successful_params.merge({ invite: true } ) }
+
+ it 'succeeds' do
+ make_request successful_params
+
+ expect(response).to have_http_status(:created)
+ end
+
+ it 'returns user data' do
+ make_request successful_params
+
+ expect(json_response).to have_key('email').and(have_value(successful_params[:email]))
+ end
+
+ it 'no session treated as signup', authenticated_as: false do
+ make_request successful_params
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'does not accept requests from customers', authenticated_as: -> { create(:customer) } do
+ make_request successful_params
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'admins can give any role', authenticated_as: -> { create(:admin) } do
+ make_request params_with_role
+ expect(User.last).to be_role 'Admin'
+ end
+
+ it 'agents can not give roles', authenticated_as: -> { create(:agent) } do
+ make_request params_with_role
+ expect(User.last).not_to be_role 'Admin'
+ end
+
+ it 'does not send email verification notifications' do
+ allow(NotificationFactory::Mailer).to receive(:notification)
+ make_request successful_params
+ expect(NotificationFactory::Mailer).not_to have_received(:notification) { |arguments| arguments[:template] == 'signup' }
+ end
+
+ it 'does not send invitation notification by default' do
+ allow(NotificationFactory::Mailer).to receive(:notification)
+ make_request successful_params
+ expect(NotificationFactory::Mailer).not_to have_received(:notification) { |arguments| arguments[:template] == 'user_invite' }
+ end
+
+ it 'sends invitation notification when required' do
+ allow(NotificationFactory::Mailer).to receive(:notification)
+ make_request params_with_invite
+ expect(NotificationFactory::Mailer).to have_received(:notification) { |arguments| arguments[:template] == 'user_invite' }
+ end
+
+ it 'requires at least one identifier' do
+ make_request({ web: 'example.com' })
+ expect(json_response['error']).to start_with('Minimum one identifier')
+ end
+
+ it 'takes first name as identifier' do
+ make_request({ firstname: 'name' })
+ expect(response).to have_http_status(:created)
+ end
+
+ it 'takes last name as identifier' do
+ make_request({ lastname: 'name' })
+ expect(response).to have_http_status(:created)
+ end
+
+ it 'takes login as identifier' do
+ make_request({ login: 'name' })
+ expect(response).to have_http_status(:created)
+ end
+
+ it 'requires valid email if present' do
+ make_request({ email: 'not_valid_email' })
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+
+ describe 'POST /api/v1/users processed by #create_admin' do
+ before do
+ User.all[2...].each(&:destroy) # destroy previously created users
+ end
+
+ def make_request(params)
+ post '/api/v1/users', params: params, as: :json
+ end
+
+ let(:successful_params) { { firstname: 'Admin First', lastname: 'Admin Last', email: 'new_admin@example.com', password: 'asd1ASD' } }
+
+ it 'succeds' do
+ make_request successful_params
+ expect(response).to have_http_status(:created)
+ end
+
+ it 'returns success message' do
+ make_request successful_params
+ expect(json_response).to have_key('message').and(have_value('ok'))
+ end
+
+ it 'does not allow to create 2nd administrator account' do
+ create(:admin)
+ make_request successful_params
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'requires email' do
+ make_request successful_params.merge(email: nil)
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'requires valid email' do
+ make_request successful_params.merge(email: 'invalid_email')
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'loads calendar' do
+ allow(Calendar).to receive(:init_setup)
+ make_request successful_params
+ expect(Calendar).to have_received(:init_setup)
+ end
+
+ it 'loads text module' do
+ allow(TextModule).to receive(:load)
+ make_request successful_params
+ expect(TextModule).to have_received(:load)
+ end
+
+ it 'does not send any notifications' do
+ allow(NotificationFactory::Mailer).to receive(:notification)
+ make_request successful_params
+ expect(NotificationFactory::Mailer).not_to have_received(:notification)
+ end
+ end
+
+ describe 'POST /api/v1/users processed by #create_signup', authenticated_as: false do
+ def make_request(params)
+ post '/api/v1/users', params: params, as: :json
+ end
+
+ let(:successful_params) { { firstname: 'Customer First', lastname: 'Customer Last', email: 'new_customer@example.com', password: 'asd1ASD', signup: true } }
+
+ before do
+ create(:admin) # simulate functional system with admin created
+ end
+
+ it 'succeeds' do
+ make_request successful_params
+ expect(response).to have_http_status(:created)
+ end
+
+ it 'requires csrf', allow_forgery_protection: true do
+ make_request successful_params
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'requires honeypot attribute' do
+ params = successful_params.clone
+ params.delete :signup
+
+ make_request params
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'requires signup to be enabled' do
+ Setting.set('user_create_account', false)
+ make_request successful_params
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'requires email' do
+ make_request successful_params.merge(email: nil)
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'requires valid email' do
+ make_request successful_params.merge(email: 'not_valid_email')
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+
+ it 'returns false positive when email already used' do
+ create(:customer, email: successful_params[:email])
+ make_request successful_params
+ expect(response).to have_http_status(:created)
+ end
+
+ it 'sends email verification notifications' do
+ allow(NotificationFactory::Mailer).to receive(:notification)
+ make_request successful_params
+ expect(NotificationFactory::Mailer).to have_received(:notification) { |arguments| arguments[:template] == 'signup' }
+ end
+
+ it 'sends password reset notification when email already used' do
+ create(:customer, email: successful_params[:email])
+ allow(NotificationFactory::Mailer).to receive(:notification)
+ make_request successful_params
+ expect(NotificationFactory::Mailer).to have_received(:notification) { |arguments| arguments[:template] == 'signup_taken_reset' }
+ end
+
+ it 'sets role to Customer' do
+ make_request successful_params
+ expect(User.last).to be_role('Customer')
+ end
+
+ it 'ignores given Agent role' do
+ make_request successful_params.merge(role_ids: [Role.find_by(name: 'Agent').id])
+ expect(User.last).not_to be_role('Agent')
+ end
+ end
end
diff --git a/spec/support/allow_forgery_protection.rb b/spec/support/allow_forgery_protection.rb
new file mode 100644
index 000000000..5c07b4905
--- /dev/null
+++ b/spec/support/allow_forgery_protection.rb
@@ -0,0 +1,11 @@
+RSpec.configure do |config|
+ config.around(:each, :allow_forgery_protection) do |example|
+ orig = ActionController::Base.allow_forgery_protection
+
+ ActionController::Base.allow_forgery_protection = true
+
+ example.run
+ ensure
+ ActionController::Base.allow_forgery_protection = orig
+ end
+end