From 9dd2b59037ee40873d25a34333b88f4cda1ba583 Mon Sep 17 00:00:00 2001 From: Mantas Masalskis Date: Fri, 14 Aug 2020 14:52:20 +0200 Subject: [PATCH] Maintenance: Show less error details to non-admin users --- .rubocop/todo.yml | 1 + .../javascripts/app/controllers/login.coffee | 9 +- .../javascripts/app/controllers/signup.coffee | 2 +- .../javascripts/app/views/login.jst.eco | 6 + .../application_controller/authenticates.rb | 9 +- app/controllers/users_controller.rb | 327 ++++++++------- .../mailer/signup_taken_reset/en.html.erb | 15 + spec/requests/api_auth_spec.rb | 6 +- spec/requests/user_spec.rb | 391 +++++++++++++----- spec/support/allow_forgery_protection.rb | 11 + 10 files changed, 521 insertions(+), 256 deletions(-) create mode 100644 app/views/mailer/signup_taken_reset/en.html.erb create mode 100644 spec/support/allow_forgery_protection.rb diff --git a/.rubocop/todo.yml b/.rubocop/todo.yml index d906375f6..cd6ee5d78 100644 --- a/.rubocop/todo.yml +++ b/.rubocop/todo.yml @@ -394,6 +394,7 @@ Metrics/BlockLength: - 'config/environments/development.rb' - 'config/initializers/omniauth.rb' - 'config/routes/knowledge_base.rb' + - 'config/routes/user.rb' - 'config/routes/test.rb' - 'config/routes/ticket.rb' - 'db/migrate/20120101000001_create_base.rb' diff --git a/app/assets/javascripts/app/controllers/login.coffee b/app/assets/javascripts/app/controllers/login.coffee index 6e7f608d3..c344770c9 100644 --- a/app/assets/javascripts/app/controllers/login.coffee +++ b/app/assets/javascripts/app/controllers/login.coffee @@ -96,15 +96,12 @@ class Index extends App.ControllerContent if !_.isEmpty(detailsRaw) details = JSON.parse(detailsRaw) - # add notify - @notify - type: 'error' - msg: App.i18n.translateContent(details.error || 'Wrong Username or Password combination.') - removeAll: true + errorMessage = App.i18n.translateContent(details.error || 'Could not process your request') # rerender login page @render( - username: @username + username: @username + errorMessage: errorMessage ) # login shake diff --git a/app/assets/javascripts/app/controllers/signup.coffee b/app/assets/javascripts/app/controllers/signup.coffee index b4e8ad5b1..fd10e3c0a 100644 --- a/app/assets/javascripts/app/controllers/signup.coffee +++ b/app/assets/javascripts/app/controllers/signup.coffee @@ -114,7 +114,7 @@ class Index extends App.ControllerContent @notify type: 'error' - msg: App.i18n.translateContent(details.error || 'Wrong Username or Password combination.') + msg: App.i18n.translateContent(details.error || 'Could not process your request') removeAll: true App.Config.set('signup', Index, 'Routes') diff --git a/app/assets/javascripts/app/views/login.jst.eco b/app/assets/javascripts/app/views/login.jst.eco index 26b9dbaab..3b1506dc9 100644 --- a/app/assets/javascripts/app/views/login.jst.eco +++ b/app/assets/javascripts/app/views/login.jst.eco @@ -13,6 +13,12 @@
+ <% if @item.errorMessage: %> + + <% end %> +
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