Invite users by email

This commit is contained in:
Oliver Günther
2025-10-01 13:58:16 +02:00
parent 0f1f0e62cb
commit f342732b25
6 changed files with 75 additions and 14 deletions
+1
View File
@@ -219,6 +219,7 @@ Rails/DynamicFindBy:
- "modules/*/spec/features/**/*.rb"
Whitelist:
- find_by_login
- find_by_mail
# Allow reorder to prevent find each cop triggering
Rails/FindEach:
+17 -10
View File
@@ -55,21 +55,28 @@ module MemberHelper
def invite_new_user(id, send_notification: true) # rubocop:disable Metrics/PerceivedComplexity
if id.present? && (id.to_i == 0 || EmailValidator.valid?(id)) # we've got an email - invite that user
# Users with create_user permission or invite_members_by_email permission can add users.
if (current_user.allowed_globally?(:create_user) ||
current_user.allowed_in_project?(:invite_members_by_email, @project)) &&
enterprise_allow_new_users?
# The invitation can pretty much only fail due to the user already
# having been invited. So look them up if it does.
user = UserInvitation.invite_new_user(email: id, send_notification:) || User.find_by_mail(id) # rubocop:disable Rails/DynamicFindBy
user&.id
end
invite_existing_or_new_users(email: id, send_notification:)
else
id
end
end
##
# When inviting a user, it might be that the user already exists but is not visible to the inviting user.
# In that case, we just return the existing user.
# Otherwise, send an invitation and return the newly created invited user
# Users with create_user permission or invite_members_by_email permission can add users.
def invite_existing_or_new_users(email:, send_notification:)
return unless user_allowed_to_invite?(current_user) && enterprise_allow_new_users?
user = User.find_by_mail(email) || UserInvitation.invite_new_user(email:, send_notification:)
user&.id
end
def user_allowed_to_invite?(user)
user.allowed_globally?(:create_user) || user.allowed_in_project?(:invite_members_by_email, @project)
end
def invite_new_users(user_ids, send_notification: true)
user_ids.filter_map do |id|
invite_new_user(id, send_notification:)
+5 -4
View File
@@ -177,10 +177,11 @@ class MembersController < ApplicationController
}
end
def suggest_invite_via_email?(user, query, principals)
user.allowed_globally?(:create_user) &&
query =~ mail_regex &&
principals.none? { |p| p.mail == query || p.login == query } &&
def suggest_invite_via_email?(user, query, visible_principals)
return false unless user_allowed_to_invite?(user)
query =~ mail_regex &&
visible_principals.none? { |p| p.mail == query || p.login == query } &&
query # finally return email
end
+6
View File
@@ -193,6 +193,12 @@ Rails.application.reloader.to_prepare do
dependencies: :view_members,
contract_actions: { members: %i[create update destroy] }
map.permission :invite_members_by_email,
{},
permissible_on: :project,
require: :member,
dependencies: :manage_members
map.permission :view_members,
{
members: %i[index menu],
+5
View File
@@ -3976,6 +3976,11 @@ en:
permission_edit_wiki_pages: "Edit wiki pages"
permission_export_work_packages: "Export work packages"
permission_export_wiki_pages: "Export wiki pages"
permission_invite_members_by_email: "Invite members by email"
permission_invite_members_by_email_explanation: >
Allows users to invite new members by email.
Invited users will receive an email with a link to set their password and activate their account.
Depends on the permission to manage members
permission_list_attachments: "List attachments"
permission_log_own_time: "Log own time"
permission_log_time: "Log time for other users"
@@ -264,6 +264,47 @@ RSpec.describe MembersController do
end
end
describe "#create with reduced visibility" do
let(:project_permissions) { %i[manage_members invite_members_by_email] }
let!(:other_project) { create(:project) }
let!(:other_user) { create(:user, member_with_permissions: { other_project => %i[view_project] }) }
let!(:user) do
create(:user, member_with_permissions: { project => project_permissions, other_project => %i[view_project] })
end
before do
login_as(user)
end
context "when inviting by email an existing user who is not visible" do
let!(:hidden_user) { create(:user, mail: "hidden@example.com") }
let(:params) do
{
project_id: project.id,
member: {
role_ids: [role.id],
user_ids: [hidden_user.mail]
}
}
end
it "adds the existing user as a member instead of creating a new invitation" do
expect { post :create, params: }
.to not_change(User, :count).by(0)
.and change(Member, :count).by(1)
expect(response).to redirect_to "/projects/pet_project/members?status=all"
# The hidden user should now be a member of the project
hidden_user.reload
expect(hidden_user).to be_member_of(project)
# No invitation email should be sent since the user already exists
expect(ActionMailer::Base.deliveries).to be_empty
end
end
end
describe "#create" do
render_views
let(:user2) { create(:user) }