Fixes #2429 - Note is not being updated because of the lack of lastname
This commit is contained in:
parent
2b532fe414
commit
a6e387cf9b
12 changed files with 186 additions and 74 deletions
|
@ -6,9 +6,9 @@ class App.User extends App.Model
|
|||
# @hasMany 'roles', 'App.Role'
|
||||
@configure_attributes = [
|
||||
{ name: 'login', display: __('Login'), tag: 'input', type: 'text', limit: 100, null: false, autocapitalize: false, signup: false, quick: false },
|
||||
{ name: 'firstname', display: __('Firstname'), tag: 'input', type: 'text', limit: 100, null: false, signup: true, info: true, invite_agent: true, invite_customer: true },
|
||||
{ name: 'lastname', display: __('Lastname'), tag: 'input', type: 'text', limit: 100, null: false, signup: true, info: true, invite_agent: true, invite_customer: true },
|
||||
{ name: 'email', display: __('Email'), tag: 'input', type: 'email', limit: 100, null: false, signup: true, info: true, invite_agent: true, invite_customer: true },
|
||||
{ name: 'firstname', display: __('Firstname'), tag: 'input', type: 'text', limit: 100, null: true, signup: true, info: true, invite_agent: true, invite_customer: true },
|
||||
{ name: 'lastname', display: __('Lastname'), tag: 'input', type: 'text', limit: 100, null: true, signup: true, info: true, invite_agent: true, invite_customer: true },
|
||||
{ name: 'email', display: __('Email'), tag: 'input', type: 'email', limit: 100, null: true, signup: true, info: true, invite_agent: true, invite_customer: true },
|
||||
{ name: 'organization_id', display: __('Organization'), tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', signup: false, info: true, invite_customer: true },
|
||||
{ name: 'created_by_id', display: __('Created by'), relation: 'User', readonly: 1 },
|
||||
{ name: 'created_at', display: __('Created at'), tag: 'datetime', readonly: 1 },
|
||||
|
|
|
@ -84,8 +84,8 @@ module ApplicationController::HandlesErrors
|
|||
error: e.message
|
||||
}
|
||||
|
||||
if e.message =~ %r{Validation failed: (.+?)(,|$)}i
|
||||
data[:error_human] = $1
|
||||
if (message = e.try(:record)&.errors&.full_messages&.first)
|
||||
data[:error_human] = message
|
||||
elsif e.message.match?(%r{(already exists|duplicate key|duplicate entry)}i)
|
||||
data[:error_human] = __('Object already exists!')
|
||||
elsif e.message =~ %r{null value in column "(.+?)" violates not-null constraint}i || e.message =~ %r{Field '(.+?)' doesn't have a default value}i
|
||||
|
|
|
@ -982,9 +982,8 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
|
|||
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
|
||||
user.skip_ensure_uniq_email = true
|
||||
user.validate!
|
||||
|
||||
result = User.password_reset_new_token(email_taken_by.email)
|
||||
NotificationFactory::Mailer.notification(
|
||||
|
|
|
@ -44,13 +44,16 @@ class User < ApplicationModel
|
|||
has_many :data_privacy_tasks, as: :deletable
|
||||
belongs_to :organization, inverse_of: :members, optional: true
|
||||
|
||||
before_validation :check_name, :check_email, :check_login, :ensure_uniq_email, :ensure_password, :ensure_roles, :ensure_identifier
|
||||
before_validation :check_name, :check_email, :check_login, :ensure_password, :ensure_roles
|
||||
before_validation :check_mail_delivery_failed, on: :update
|
||||
before_create :check_preferences_default, :validate_preferences, :validate_ooo, :domain_based_assignment, :set_locale
|
||||
before_update :check_preferences_default, :validate_preferences, :validate_ooo, :reset_login_failed_after_password_change, :validate_agent_limit_by_attributes, :last_admin_check_by_attribute
|
||||
before_destroy :destroy_longer_required_objects, :destroy_move_dependency_ownership
|
||||
after_commit :update_caller_id
|
||||
|
||||
validate :ensure_identifier, :ensure_email
|
||||
validate :ensure_uniq_email, unless: :skip_ensure_uniq_email
|
||||
|
||||
# workflow checks should run after before_create and before_update callbacks
|
||||
include ChecksCoreWorkflow
|
||||
|
||||
|
@ -859,51 +862,49 @@ try to find correct name
|
|||
preferences.fetch(:locale) { Locale.default }
|
||||
end
|
||||
|
||||
attr_accessor :skip_ensure_uniq_email
|
||||
|
||||
private
|
||||
|
||||
def check_name
|
||||
if firstname.present?
|
||||
firstname.strip!
|
||||
end
|
||||
if lastname.present?
|
||||
lastname.strip!
|
||||
end
|
||||
firstname&.strip!
|
||||
lastname&.strip!
|
||||
|
||||
return true if firstname.present? && lastname.present?
|
||||
return if firstname.present? && lastname.present?
|
||||
|
||||
if (firstname.blank? && lastname.present?) || (firstname.present? && lastname.blank?)
|
||||
used_name = firstname.presence || lastname
|
||||
(local_firstname, local_lastname) = User.name_guess(used_name, email)
|
||||
|
||||
elsif firstname.blank? && lastname.blank? && email.present?
|
||||
(local_firstname, local_lastname) = User.name_guess('', email)
|
||||
end
|
||||
|
||||
self.firstname = local_firstname if local_firstname.present?
|
||||
self.lastname = local_lastname if local_lastname.present?
|
||||
check_name_apply(:firstname, local_firstname)
|
||||
check_name_apply(:lastname, local_lastname)
|
||||
end
|
||||
|
||||
if firstname.present? && firstname.match(%r{^[A-z]+$}) && (firstname.downcase == firstname || firstname.upcase == firstname)
|
||||
firstname.capitalize!
|
||||
end
|
||||
if lastname.present? && lastname.match(%r{^[A-z]+$}) && (lastname.downcase == lastname || lastname.upcase == lastname)
|
||||
lastname.capitalize!
|
||||
end
|
||||
true
|
||||
def check_name_apply(identifier, input)
|
||||
self[identifier] = input if input.present?
|
||||
|
||||
self[identifier].capitalize! if self[identifier]&.match? %r{^([[:upper:]]+|[[:lower:]]+)$}
|
||||
end
|
||||
|
||||
def check_email
|
||||
return true if Setting.get('import_mode')
|
||||
return true if email.blank?
|
||||
return if Setting.get('import_mode')
|
||||
return if email.blank?
|
||||
|
||||
self.email = email.downcase.strip
|
||||
return true if id == 1
|
||||
|
||||
email_address_validation = EmailAddressValidation.new(email)
|
||||
if !email_address_validation.valid_format?
|
||||
raise Exceptions::UnprocessableEntity, "Invalid email '#{email}'"
|
||||
end
|
||||
|
||||
true
|
||||
def ensure_email
|
||||
return if email.blank?
|
||||
return if id == 1
|
||||
|
||||
email_address_validation = EmailAddressValidation.new(email)
|
||||
|
||||
return if email_address_validation.valid_format?
|
||||
|
||||
errors.add :base, "Invalid email '#{email}'"
|
||||
end
|
||||
|
||||
def check_login
|
||||
|
@ -914,7 +915,7 @@ try to find correct name
|
|||
end
|
||||
|
||||
# if email has changed, login is old email, change also login
|
||||
if changes && changes['email'] && changes['email'][0] == login
|
||||
if email_changed? && email_was == login
|
||||
self.login = email
|
||||
end
|
||||
|
||||
|
@ -943,27 +944,26 @@ try to find correct name
|
|||
end
|
||||
|
||||
def ensure_roles
|
||||
return true if role_ids.present?
|
||||
return if role_ids.present?
|
||||
|
||||
self.role_ids = Role.signup_role_ids
|
||||
end
|
||||
|
||||
def ensure_identifier
|
||||
return true if email.present? || firstname.present? || lastname.present? || phone.present?
|
||||
return true if login.present? && !login.start_with?('auto-')
|
||||
return if login.present? && !login.start_with?('auto-')
|
||||
return if [email, firstname, lastname, phone].any?(&:present?)
|
||||
|
||||
raise Exceptions::UnprocessableEntity, __('Minimum one identifier (login, firstname, lastname, phone or email) for user is required.')
|
||||
errors.add :base, __('At least one identifier (firstname, lastname, phone or email) for user is required.')
|
||||
end
|
||||
|
||||
def ensure_uniq_email
|
||||
return true if Setting.get('user_email_multiple_use')
|
||||
return true if Setting.get('import_mode')
|
||||
return true if email.blank?
|
||||
return true if !changes
|
||||
return true if !changes['email']
|
||||
return true if !User.exists?(email: email.downcase.strip)
|
||||
return if Setting.get('user_email_multiple_use')
|
||||
return if Setting.get('import_mode')
|
||||
return if email.blank?
|
||||
return if !email_changed?
|
||||
return if !User.exists?(email: email.downcase.strip)
|
||||
|
||||
raise Exceptions::UnprocessableEntity, "Email address '#{email.downcase.strip}' is already used for other user."
|
||||
errors.add :base, "Email address '#{email.downcase.strip}' is already used for other user."
|
||||
end
|
||||
|
||||
def validate_roles(role)
|
||||
|
@ -1166,7 +1166,6 @@ raise 'Minimum one user need to have admin permissions'
|
|||
|
||||
def ensure_password
|
||||
self.password = ensured_password
|
||||
true
|
||||
end
|
||||
|
||||
def ensured_password
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Issue2429UserIdentifierValidation < ActiveRecord::Migration[6.0]
|
||||
def up
|
||||
return if !Setting.exists?(name: 'system_init_done')
|
||||
|
||||
%i[firstname lastname email phone].each { |elem| update_single(elem) }
|
||||
end
|
||||
|
||||
def update_single(elem)
|
||||
attr = ObjectManager::Attribute.for_object(User).find_by(name: elem)
|
||||
|
||||
attr.screens.each do |_, value|
|
||||
if value.try(:key?, 'null')
|
||||
value['null'] = true
|
||||
end
|
||||
|
||||
next if !value.is_a? Hash
|
||||
|
||||
value.each do |_, inner_value|
|
||||
if inner_value.try(:key?, 'null')
|
||||
inner_value['null'] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr.save!
|
||||
rescue => e
|
||||
Rails.logger.error e
|
||||
end
|
||||
end
|
|
@ -567,7 +567,7 @@ ObjectManager::Attribute.add(
|
|||
data_option: {
|
||||
type: 'text',
|
||||
maxlength: 150,
|
||||
null: false,
|
||||
null: true,
|
||||
item_class: 'formGroup--halfSize',
|
||||
},
|
||||
editable: false,
|
||||
|
@ -575,27 +575,27 @@ ObjectManager::Attribute.add(
|
|||
screens: {
|
||||
signup: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
invite_agent: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
invite_customer: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
view: {
|
||||
|
@ -619,7 +619,7 @@ ObjectManager::Attribute.add(
|
|||
data_option: {
|
||||
type: 'text',
|
||||
maxlength: 150,
|
||||
null: false,
|
||||
null: true,
|
||||
item_class: 'formGroup--halfSize',
|
||||
},
|
||||
editable: false,
|
||||
|
@ -627,27 +627,27 @@ ObjectManager::Attribute.add(
|
|||
screens: {
|
||||
signup: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
invite_agent: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
invite_customer: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
view: {
|
||||
|
@ -679,17 +679,17 @@ ObjectManager::Attribute.add(
|
|||
screens: {
|
||||
signup: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
invite_agent: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
invite_customer: {
|
||||
'-all-' => {
|
||||
null: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
|
|
|
@ -925,6 +925,10 @@ msgstr ""
|
|||
msgid "Assignment timeout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend."
|
||||
msgstr ""
|
||||
|
||||
#: app/models/user.rb
|
||||
msgid "At least one identifier (firstname, lastname, phone or email) for user is required."
|
||||
msgstr ""
|
||||
|
||||
#: app/models/object_manager/attribute.rb
|
||||
msgid "At least one letters is needed"
|
||||
msgstr ""
|
||||
|
@ -5784,10 +5788,6 @@ msgstr ""
|
|||
msgid "Minimum of one permission is needed!"
|
||||
msgstr ""
|
||||
|
||||
#: app/models/user.rb
|
||||
msgid "Minimum one identifier (login, firstname, lastname, phone or email) for user is required."
|
||||
msgstr ""
|
||||
|
||||
#: app/models/role.rb
|
||||
#: app/models/user.rb
|
||||
msgid "Minimum one user needs to have admin permissions."
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Issue2429UserIdentifierValidation, type: :db_migration do
|
||||
let(:elem) { ObjectManager::Attribute.for_object(User).find_by(name: 'firstname') }
|
||||
|
||||
it 'resets value directly in screen' do
|
||||
elem.screens = { screen1: { asd: true, null: false } }
|
||||
elem.save!
|
||||
|
||||
migrate
|
||||
|
||||
expect(elem.reload.screens).to eq({ screen1: { asd: true, null: true } }.deep_stringify_keys)
|
||||
end
|
||||
|
||||
it 'resets value nested in permission' do
|
||||
elem.screens = { screen1: { all: { asd: true, null: false } } }
|
||||
elem.save!
|
||||
|
||||
migrate
|
||||
|
||||
expect(elem.reload.screens).to eq({ screen1: { all: { asd: true, null: true } } }.deep_stringify_keys)
|
||||
end
|
||||
|
||||
it 'does not touch when null not present directly in screen' do
|
||||
elem.screens = { screen1: { all: { asd: true } } }
|
||||
elem.save!
|
||||
|
||||
expect { migrate }.not_to change { elem.reload.updated_at }
|
||||
end
|
||||
|
||||
it 'does not touch when null not present in permission' do
|
||||
elem.screens = { screen1: { asd: true } }
|
||||
elem.save!
|
||||
|
||||
expect { migrate }.not_to change { elem.reload.updated_at }
|
||||
end
|
||||
end
|
|
@ -603,6 +603,14 @@ RSpec.describe User, type: :model do
|
|||
expect(new_agent.login.sub!(agent.login, '')).to be_a_uuid
|
||||
end
|
||||
end
|
||||
|
||||
describe '#check_name' do
|
||||
it 'guesses user first/last name with non-ASCII characters' do
|
||||
user = create(:user, firstname: 'perkūnas ąžuolas', lastname: '')
|
||||
|
||||
expect(user).to have_attributes(firstname: 'Perkūnas', lastname: 'Ąžuolas')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Attributes:' do
|
||||
|
|
|
@ -315,7 +315,7 @@ RSpec.describe 'User', type: :request do
|
|||
post '/api/v1/users', params: params, as: :json
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(json_response).to be_truthy
|
||||
expect(json_response['error']).to eq('Minimum one identifier (login, firstname, lastname, phone or email) for user is required.')
|
||||
expect(json_response['error']).to eq('At least one identifier (firstname, lastname, phone or email) for user is required.')
|
||||
|
||||
# invalid email
|
||||
params = { firstname: 'newfirstname123', email: 'some_what', note: 'some note' }
|
||||
|
@ -1225,7 +1225,7 @@ RSpec.describe 'User', type: :request do
|
|||
|
||||
it 'requires at least one identifier' do
|
||||
make_request({ web: 'example.com' })
|
||||
expect(json_response['error']).to start_with('Minimum one identifier')
|
||||
expect(json_response['error']).to start_with('At least one identifier')
|
||||
end
|
||||
|
||||
it 'takes first name as identifier' do
|
||||
|
|
|
@ -112,16 +112,20 @@ RSpec.describe 'Manage > Users', type: :system do
|
|||
end
|
||||
|
||||
context 'updating a user' do
|
||||
before do
|
||||
create(:admin)
|
||||
end
|
||||
let(:user) { create(:admin) }
|
||||
let(:row) { find 'table.user-list tbody tr', text: user.firstname }
|
||||
|
||||
before do
|
||||
user
|
||||
|
||||
it 'handles permission checkboxes correctly' do
|
||||
visit '#manage/users'
|
||||
|
||||
within(:active_content) do
|
||||
click 'table.user-list tbody tr:first-child'
|
||||
row.click
|
||||
end
|
||||
end
|
||||
|
||||
it 'handles permission checkboxes correctly' do
|
||||
in_modal disappears: false do
|
||||
scroll_into_view 'table.settings-list'
|
||||
within 'table.settings-list tbody tr:first-child' do
|
||||
|
@ -136,6 +140,38 @@ RSpec.describe 'Manage > Users', type: :system do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows to update a user with no email/first/last/phone if login is present' do
|
||||
in_modal do
|
||||
fill_in 'Firstname', with: ''
|
||||
fill_in 'Lastname', with: ''
|
||||
fill_in 'Email', with: ''
|
||||
fill_in 'Phone', with: ''
|
||||
|
||||
click_on 'Submit'
|
||||
end
|
||||
|
||||
within :active_content do
|
||||
expect(page).to have_no_text(user.firstname)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has auto login' do
|
||||
let(:user) { create(:admin, login: "auto-#{SecureRandom.uuid}") }
|
||||
|
||||
it 'does not allow to update a user with no email/first/last/phone' do
|
||||
in_modal disappears: false do
|
||||
fill_in 'Firstname', with: ''
|
||||
fill_in 'Lastname', with: ''
|
||||
fill_in 'Email', with: ''
|
||||
fill_in 'Phone', with: ''
|
||||
|
||||
click_on 'Submit'
|
||||
|
||||
expect(page).to have_text('At least one identifier')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'check user edit permissions', authenticated_as: -> { user } do
|
||||
|
|
|
@ -496,7 +496,7 @@ class UserTest < ActiveSupport::TestCase
|
|||
assert(admin1.id)
|
||||
assert_equal(admin1.email, email1)
|
||||
|
||||
assert_raises(Exceptions::UnprocessableEntity) do
|
||||
assert_raises(ActiveRecord::RecordInvalid) do
|
||||
User.create!(
|
||||
login: "#{email1}-1",
|
||||
firstname: 'Role',
|
||||
|
@ -522,7 +522,7 @@ class UserTest < ActiveSupport::TestCase
|
|||
created_by_id: 1,
|
||||
)
|
||||
|
||||
assert_raises(Exceptions::UnprocessableEntity) do
|
||||
assert_raises(ActiveRecord::RecordInvalid) do
|
||||
admin2.email = email1
|
||||
admin2.save!
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue