diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index 10c4cd9d00e5..4428102bd1a9 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -118,14 +118,20 @@ def authorize_for(controller, action) current_user.allowed_in_project?({ controller:, action: }, @project) end + def user_allowed_to_view_emails? + current_user.allowed_globally?(:view_user_email) + end + def build_members paths = API::V3::Utilities::PathHelper::ApiV3Path principals = @principals.map do |principal| - { + member = { id: principal.id, name: principal.name, href: paths.send(principal.type.underscore, principal.id) } + member[:email] = principal.mail if user_allowed_to_view_emails? + member end if @email @@ -195,7 +201,7 @@ def set_roles_and_principles! def possible_members(criteria, limit) Principal .possible_member(@project) - .like(criteria) + .like(criteria, email: user_allowed_to_view_emails?) .limit(limit) end diff --git a/app/models/global_role.rb b/app/models/global_role.rb index 5600bfd1e0dd..bda2366dac60 100644 --- a/app/models/global_role.rb +++ b/app/models/global_role.rb @@ -31,4 +31,15 @@ def self.givable super .where(type: "GlobalRole") end + + def self.standard + standard_global_role = where(builtin: BUILTIN_STANDARD_GLOBAL).first + if standard_global_role.nil? + standard_global_role = create(name: "Standard global role", position: 0) do |role| + role.builtin = BUILTIN_STANDARD_GLOBAL + end + raise "Unable to create the standard global role." if standard_global_role.new_record? + end + standard_global_role + end end diff --git a/app/models/principals/scopes/like.rb b/app/models/principals/scopes/like.rb index ea8064cab837..3fd1e92c0827 100644 --- a/app/models/principals/scopes/like.rb +++ b/app/models/principals/scopes/like.rb @@ -36,21 +36,27 @@ module Like extend ActiveSupport::Concern class_methods do - def like(query) + def like(query, email: true) firstnamelastname = "((firstname || ' ') || lastname)" lastnamefirstname = "((lastname || ' ') || firstname)" s = "%#{query.to_s.downcase.strip.tr(',', '')}%" - sql = <<~SQL + sql = <<~SQL.squish LOWER(login) LIKE :s OR unaccent(LOWER(#{firstnamelastname})) LIKE unaccent(:s) OR unaccent(LOWER(#{lastnamefirstname})) LIKE unaccent(:s) - OR LOWER(mail) LIKE :s SQL + order_clause = %i[type login lastname firstname] + + if email + sql += " OR LOWER(mail) LIKE :s" + order_clause << :mail + end + where([sql, { s: }]) - .order(:type, :login, :lastname, :firstname, :mail) + .order(*order_clause) end end end diff --git a/app/models/queries/filters/shared/any_user_name_attribute_filter.rb b/app/models/queries/filters/shared/any_user_name_attribute_filter.rb index 498466a67f9c..964076c8b9fa 100644 --- a/app/models/queries/filters/shared/any_user_name_attribute_filter.rb +++ b/app/models/queries/filters/shared/any_user_name_attribute_filter.rb @@ -43,21 +43,25 @@ def available_operators Queries::Operators::NotContains] end + def email_field_allowed? + true + end + private def sql_concat_name - <<-SQL.squish - LOWER( - CONCAT( - users.firstname, ' ', users.lastname, - ' ', - users.lastname, ' ', users.firstname, - ' ', - users.login, - ' ', - users.mail - ) - ) + fields = <<~SQL.squish + users.firstname, ' ', users.lastname, + ' ', + users.lastname, ' ', users.firstname, + ' ', + users.login + SQL + + fields << ", ' ',users.mail" if email_field_allowed? + + <<~SQL.squish + LOWER(CONCAT(#{fields})) SQL end end diff --git a/app/models/queries/principals/filters/typeahead_filter.rb b/app/models/queries/principals/filters/typeahead_filter.rb index 374f3a5ca110..5d69b4c6ac3c 100644 --- a/app/models/queries/principals/filters/typeahead_filter.rb +++ b/app/models/queries/principals/filters/typeahead_filter.rb @@ -35,6 +35,10 @@ def type :search end + def email_field_allowed? + User.current.allowed_globally?(:view_user_email) + end + def human_name I18n.t("label_search") end diff --git a/app/models/role.rb b/app/models/role.rb index bbaff72cd4a4..77c940dc55b2 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -36,6 +36,7 @@ class Role < ApplicationRecord BUILTIN_WORK_PACKAGE_EDITOR = 5 BUILTIN_PROJECT_QUERY_VIEW = 6 BUILTIN_PROJECT_QUERY_EDIT = 7 + BUILTIN_STANDARD_GLOBAL = 8 HIDDEN_ROLE_TYPES = [ "WorkPackageRole", @@ -93,7 +94,8 @@ def self.givable Role::BUILTIN_WORK_PACKAGE_COMMENTER, Role::BUILTIN_WORK_PACKAGE_EDITOR, Role::BUILTIN_PROJECT_QUERY_VIEW, - Role::BUILTIN_PROJECT_QUERY_EDIT + Role::BUILTIN_PROJECT_QUERY_EDIT, + Role::BUILTIN_STANDARD_GLOBAL ] ) .order(Arel.sql("position")) diff --git a/app/seeders/basic_data/base_role_seeder.rb b/app/seeders/basic_data/base_role_seeder.rb index e1ffed74629e..f3a09bfb4041 100644 --- a/app/seeders/basic_data/base_role_seeder.rb +++ b/app/seeders/basic_data/base_role_seeder.rb @@ -49,6 +49,7 @@ def builtin(value) case value when :non_member then Role::BUILTIN_NON_MEMBER when :anonymous then Role::BUILTIN_ANONYMOUS + when :standard_global then Role::BUILTIN_STANDARD_GLOBAL when :work_package_editor then Role::BUILTIN_WORK_PACKAGE_EDITOR when :work_package_commenter then Role::BUILTIN_WORK_PACKAGE_COMMENTER when :work_package_viewer then Role::BUILTIN_WORK_PACKAGE_VIEWER diff --git a/app/seeders/common.yml b/app/seeders/common.yml index 16ca0a3560ca..2b3544a5237f 100644 --- a/app/seeders/common.yml +++ b/app/seeders/common.yml @@ -398,3 +398,9 @@ global_roles: - :create_user - :manage_user - :manage_placeholder_user + - reference: :default_role_standard_global + t_name: Standard global role + position: 7 + builtin: :standard_global + permissions: + - :view_user_email diff --git a/app/services/authorization/user_global_roles_query.rb b/app/services/authorization/user_global_roles_query.rb index 4fc93822512c..8300997fe945 100644 --- a/app/services/authorization/user_global_roles_query.rb +++ b/app/services/authorization/user_global_roles_query.rb @@ -35,7 +35,8 @@ class Authorization::UserGlobalRolesQuery < Authorization::UserRolesQuery Role::BUILTIN_ANONYMOUS end - builtin_role_condition = roles_table[:builtin].eq(builtin_role) + builtin_roles = [builtin_role, Role::BUILTIN_STANDARD_GLOBAL] + builtin_role_condition = roles_table[:builtin].in(builtin_roles) statement.or(builtin_role_condition) end diff --git a/app/views/principals/_available_global_roles.html.erb b/app/views/principals/_available_global_roles.html.erb index ae3e852f8458..c4510e0b7dd9 100644 --- a/app/views/principals/_available_global_roles.html.erb +++ b/app/views/principals/_available_global_roles.html.erb @@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> -<% available_roles = GlobalRole.all - (global_member&.roles || []) %> +<% available_roles = GlobalRole.givable - (global_member&.roles || []) %>
diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index a47ee9006cdf..fcb5dd7e7edc 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -81,6 +81,11 @@ require: :loggedin, contract_actions: { placeholder_users: %i[create read update] } + map.permission :view_user_email, + {}, + permissible_on: :global, + require: :loggedin + map.permission :view_project, { projects: [:show] }, permissible_on: :project, diff --git a/config/locales/en.yml b/config/locales/en.yml index eb9ff6dd055c..81979583af5f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3049,6 +3049,7 @@ en: permission_view_shared_work_packages: "View work package shares" permission_view_time_entries: "View spent time" permission_view_timelines: "View timelines" + permission_view_user_email: "View users' mail addresses" permission_view_wiki_edits: "View wiki history" permission_view_wiki_pages: "View wiki" permission_work_package_assigned: "Become assignee/responsible" diff --git a/db/migrate/20241001205821_add_standard_global_role.rb b/db/migrate/20241001205821_add_standard_global_role.rb new file mode 100644 index 000000000000..7976567c11bc --- /dev/null +++ b/db/migrate/20241001205821_add_standard_global_role.rb @@ -0,0 +1,12 @@ +class AddStandardGlobalRole < ActiveRecord::Migration[7.1] + def change + standard_global_role ||= GlobalRole.find_or_initialize_by(builtin: Role::BUILTIN_STANDARD_GLOBAL) + + standard_global_role.update!( + name: I18n.t("seeds.common.global_roles.item_1.name", default: "Standard global role"), + permissions: %i[ + view_user_email + ] + ) + end +end diff --git a/lib/api/v3/users/user_representer.rb b/lib/api/v3/users/user_representer.rb index fa3f8f2c62ca..d76b3f80a90c 100644 --- a/lib/api/v3/users/user_representer.rb +++ b/lib/api/v3/users/user_representer.rb @@ -143,7 +143,7 @@ class UserRepresenter < ::API::V3::Principals::PrincipalRepresenter getter: ->(*) { represented.mail }, setter: ->(fragment:, represented:, **) { represented.mail = fragment }, exec_context: :decorator, - cache_if: -> { represented.pref.can_expose_mail? || represented.new_record? || current_user_can_manage? } + cache_if: -> { current_user_can_view_user_email? || represented.new_record? || current_user_can_manage? } property :avatar, exec_context: :decorator, @@ -235,6 +235,10 @@ def current_user_can_manage? ) end + def current_user_can_view_user_email? + current_user&.allowed_globally?(:view_user_email) + end + private ## diff --git a/modules/team_planner/spec/features/query_handling_spec.rb b/modules/team_planner/spec/features/query_handling_spec.rb index 812e9def0f3b..3a9b71757b7c 100644 --- a/modules/team_planner/spec/features/query_handling_spec.rb +++ b/modules/team_planner/spec/features/query_handling_spec.rb @@ -33,6 +33,7 @@ require_relative "../../../../spec/features/views/shared_examples" RSpec.describe "Team planner query handling", :js, :with_cuprite, with_ee: %i[team_planner_view] do + shared_let(:standard) { create(:standard_global_role) } shared_let(:type_task) { create(:type_task) } shared_let(:type_bug) { create(:type_bug) } shared_let(:project) do diff --git a/modules/team_planner/spec/features/shared_context.rb b/modules/team_planner/spec/features/shared_context.rb index 0a2b06780053..5a7db6dfebab 100644 --- a/modules/team_planner/spec/features/shared_context.rb +++ b/modules/team_planner/spec/features/shared_context.rb @@ -30,6 +30,7 @@ require_relative "../support/pages/team_planner" RSpec.shared_context "with team planner full access" do + shared_let(:standard) { create(:standard_global_role) } shared_let(:project) do create(:project) end diff --git a/spec/controllers/members_controller_spec.rb b/spec/controllers/members_controller_spec.rb index cdfe4cdd2447..ef1cecda1f61 100644 --- a/spec/controllers/members_controller_spec.rb +++ b/spec/controllers/members_controller_spec.rb @@ -98,25 +98,91 @@ end describe "#autocomplete_for_member" do - let(:params) { { "project_id" => project.identifier.to_s } } + let(:params) { { "project_id" => project.identifier.to_s, "q" => query } } + let(:query) { "" } + let(:json_response) { response.parsed_body } + let(:global_permissions) { [] } + let(:project_permissions) { [] } - before { login_as(user) } + subject { post(:autocomplete_for_member, xhr: true, params:) } - describe "WHEN the user is authorized WHEN a project is provided" do - before do - role.add_permission! :manage_members - member + before do + mock_permissions_for(user) do |mock| + mock.allow_globally(*global_permissions) + mock.allow_in_project(*project_permissions, project:) end + login_as(user) + end + + describe "WHEN the user is authorized WHEN a project is provided" do + let(:project_permissions) { [:manage_members] } + it "is success" do - post(:autocomplete_for_member, xhr: true, params:) + subject expect(response).to be_successful end + + context "when the user is not authorized to see email addresses" do + it "returns id, name and href" do + subject + expect(json_response).to be_an(Array) + expect(json_response).to include( + { + "id" => admin.id, + "name" => admin.name, + "href" => "/api/v3/users/#{admin.id}" + } + ) + end + + context "when searching email addresses" do + let(:query) { admin.mail } + + it "does not return matches from emails" do + subject + expect(json_response).to be_empty + end + end + end + + context "when the user is authorized to see email addresses" do + let(:global_permissions) { [:view_user_email] } + + it "returns id, name, email and href" do + subject + expect(json_response).to be_an(Array) + expect(json_response).to include( + { + "id" => admin.id, + "name" => admin.name, + "email" => admin.mail, + "href" => "/api/v3/users/#{admin.id}" + } + ) + end + + context "when searching email addresses" do + let(:query) { admin.mail } + + it "returns matches from emails" do + subject + expect(json_response).to include( + { + "id" => admin.id, + "name" => admin.name, + "email" => admin.mail, + "href" => "/api/v3/users/#{admin.id}" + } + ) + end + end + end end describe "WHEN the user is not authorized" do it "is forbidden" do - post(:autocomplete_for_member, xhr: true, params:) + subject expect(response.response_code).to eq(403) end end diff --git a/spec/factories/global_role_factory.rb b/spec/factories/global_role_factory.rb index 7c55f7389baf..aee02ca6da4d 100644 --- a/spec/factories/global_role_factory.rb +++ b/spec/factories/global_role_factory.rb @@ -29,5 +29,12 @@ FactoryBot.define do factory :global_role do sequence(:name) { |n| "Global Role #{n}" } + + factory :standard_global_role do + name { "Standard global role" } + builtin { Role::BUILTIN_STANDARD_GLOBAL } + initialize_with { GlobalRole.where(builtin: Role::BUILTIN_STANDARD_GLOBAL).first_or_initialize } + permissions { [:view_user_email] } + end end end diff --git a/spec/features/global_roles/global_role_assignment_spec.rb b/spec/features/global_roles/global_role_assignment_spec.rb index c91901321b16..5a98f14ee932 100644 --- a/spec/features/global_roles/global_role_assignment_spec.rb +++ b/spec/features/global_roles/global_role_assignment_spec.rb @@ -40,7 +40,7 @@ let!(:global_role1) { create(:global_role, name: "global_role1", permissions: %i[global1]) } let!(:global_role2) { create(:global_role, name: "global_role2", permissions: %i[global2]) } - + let!(:standard_global_role) { create(:standard_global_role) } let!(:user) { create(:user) } let!(:global_member) do create(:global_member, @@ -57,9 +57,12 @@ page.within("#table_principal_roles") do expect(page).to have_text "global_role1" end + + # And I should not see "Standard global role" within "#available_principal_roles" # And I should not see "global_role1" within "#available_principal_roles" # And I should see "global_role2" within "#available_principal_roles" page.within("#available_principal_roles") do + expect(page).to have_no_text "Standard global role" expect(page).to have_no_text "global_role1" expect(page).to have_text "global_role2" end @@ -78,9 +81,11 @@ expect(page).to have_text "global_role2" end + # And I should not see "Standard global role" within "#available_principal_roles" # And I should not see "global_role1" within "#available_principal_roles" # And I should not see "global_role2" within "#available_principal_roles" page.within("#available_principal_roles") do + expect(page).to have_no_text "Standard global role" expect(page).to have_no_text "global_role1" expect(page).to have_no_text "global_role2" end @@ -92,9 +97,11 @@ wait_for_network_idle + # And I should not see "Standard global role" within "#available_principal_roles" # Then I should see "global_role1" within "#available_principal_roles" # And I should not see "global_role2" within "#available_principal_roles" page.within("#available_principal_roles") do + expect(page).to have_no_text "Standard global role" expect(page).to have_text "global_role1" expect(page).to have_no_text "global_role2" end diff --git a/spec/features/projects/project_custom_fields/overview_page/shared_context.rb b/spec/features/projects/project_custom_fields/overview_page/shared_context.rb index 5b1a0852bbab..4d616bedaf2c 100644 --- a/spec/features/projects/project_custom_fields/overview_page/shared_context.rb +++ b/spec/features/projects/project_custom_fields/overview_page/shared_context.rb @@ -34,6 +34,7 @@ let!(:second_version) { create(:version, name: "Version 2", project:) } let!(:third_version) { create(:version, name: "Version 3", project:) } + shared_let(:standard) { create(:standard_global_role) } shared_let(:reader_role_without_project_attributes) do create(:project_role, permissions: %i[view_work_packages]) end diff --git a/spec/features/users/invite_user_modal/invite_user_modal_spec.rb b/spec/features/users/invite_user_modal/invite_user_modal_spec.rb index a1cc83e0199c..635c3fd71695 100644 --- a/spec/features/users/invite_user_modal/invite_user_modal_spec.rb +++ b/spec/features/users/invite_user_modal/invite_user_modal_spec.rb @@ -29,6 +29,7 @@ require "spec_helper" RSpec.describe "Invite user modal", :js, :with_cuprite do + shared_let(:standard) { create(:standard_global_role) } shared_let(:project) { create(:project) } shared_let(:work_package) { create(:work_package, project:) } diff --git a/spec/features/users/invite_user_modal/permission_lacking_current_project_spec.rb b/spec/features/users/invite_user_modal/permission_lacking_current_project_spec.rb index 0fa0a6524f07..978d2adb3aec 100644 --- a/spec/features/users/invite_user_modal/permission_lacking_current_project_spec.rb +++ b/spec/features/users/invite_user_modal/permission_lacking_current_project_spec.rb @@ -29,6 +29,7 @@ require "spec_helper" RSpec.describe "Inviting user in project the current user is lacking permission in", :js, :with_cuprite do + shared_let(:standard) { create(:standard_global_role) } let(:modal) do Components::Users::InviteUserModal.new project: invite_project, principal: other_user, diff --git a/spec/features/users/invite_user_modal/subproject_invite_spec.rb b/spec/features/users/invite_user_modal/subproject_invite_spec.rb index 81fab8697f30..8cc18f7c07bb 100644 --- a/spec/features/users/invite_user_modal/subproject_invite_spec.rb +++ b/spec/features/users/invite_user_modal/subproject_invite_spec.rb @@ -29,6 +29,7 @@ require "spec_helper" RSpec.describe "Invite user modal subprojects", :js, :with_cuprite do + shared_let(:standard) { create(:standard_global_role) } shared_let(:project) { create(:project, name: "Parent project") } shared_let(:subproject) { create(:project, name: "Subproject", parent: project) } shared_let(:work_package) { create(:work_package, project: subproject) } diff --git a/spec/lib/api/v3/users/user_representer_spec.rb b/spec/lib/api/v3/users/user_representer_spec.rb index 5979bd79c49f..9e06bedc00e0 100644 --- a/spec/lib/api/v3/users/user_representer_spec.rb +++ b/spec/lib/api/v3/users/user_representer_spec.rb @@ -106,7 +106,7 @@ end describe "email" do - let(:user) { build_stubbed(:user, status: 1, preference:) } + let(:user) { build_stubbed(:user, status: 1) } shared_examples_for "shows the users E-Mail address" do it do @@ -114,18 +114,19 @@ end end - context "if user shows his E-Mail address" do - let(:preference) { build(:user_preference, hide_mail: false) } + context "when the current user can view user emails" do + before do + mock_permissions_for(current_user) do |mock| + mock.allow_globally(:view_user_email) + end + end it_behaves_like "shows the users E-Mail address" end - context "if user hides his E-Mail address" do - let(:preference) { build(:user_preference, hide_mail: true) } - + context "when the current user cannot view user emails" do it "does not render the users E-Mail address" do - expect(subject) - .not_to have_json_path("email") + expect(subject).not_to have_json_path("email") end context "if an admin inquires" do @@ -134,11 +135,19 @@ it_behaves_like "shows the users E-Mail address" end - context "if the user inquires himself" do + context "if the user inquires about themselves" do let(:current_user) { user } it_behaves_like "shows the users E-Mail address" end + + context "if the user is new" do + before do + allow(user).to receive(:new_record?).and_return(true) + end + + it_behaves_like "shows the users E-Mail address" + end end end diff --git a/spec/models/global_role_spec.rb b/spec/models/global_role_spec.rb index 023c642af793..1b49fc3cd3fd 100644 --- a/spec/models/global_role_spec.rb +++ b/spec/models/global_role_spec.rb @@ -29,16 +29,14 @@ require "spec_helper" RSpec.describe GlobalRole do - before { GlobalRole.create name: "globalrole", permissions: ["permissions"] } + let!(:global_role) { create(:global_role, name: "globalrole", permissions: ["permissions"]) } it { is_expected.to validate_presence_of :name } it { is_expected.to validate_uniqueness_of :name } it { is_expected.to validate_length_of(:name).is_at_most(256) } describe "attributes" do - before { @role = GlobalRole.new } - - subject { @role } + subject(:role) { described_class.new } it { is_expected.to respond_to :name } it { is_expected.to respond_to :permissions } @@ -46,17 +44,13 @@ end describe "instance methods" do - before do - @role = GlobalRole.new - end - describe "WITH no attributes set" do - before do - @role = GlobalRole.new - end + let(:role) { described_class.new } + + subject { role } describe "#permissions" do - subject { @role.permissions } + subject { role.permissions } it { is_expected.to be_an_instance_of(Array) } @@ -66,38 +60,68 @@ end describe "#has_permission?" do - it { expect(@role.has_permission?(:perm)).to be_falsey } + it { is_expected.not_to have_permission(:perm) } end describe "#allowed_to?" do describe "WITH requested permission" do - it { expect(@role.allowed_to?(:perm1)).to be_falsey } + it { is_expected.not_to be_allowed_to(:perm1) } end end end describe "WITH set permissions" do - before { @role = GlobalRole.new permissions: %i[perm1 perm2 perm3] } + subject(:role) { described_class.new permissions: %i[perm1 perm2 perm3] } describe "#has_permission?" do - it { expect(@role.has_permission?(:perm1)).to be_truthy } - it { expect(@role.has_permission?("perm1")).to be_truthy } - it { expect(@role.has_permission?(:perm5)).to be_falsey } + it { is_expected.to have_permission(:perm1) } + it { is_expected.to have_permission("perm1") } + it { is_expected.not_to have_permission(:perm5) } end describe "#allowed_to?" do describe "WITH requested permission" do - it { expect(@role.allowed_to?(:perm1)).to be_truthy } - it { expect(@role.allowed_to?(:perm5)).to be_falsey } + it { is_expected.to be_allowed_to(:perm1) } + it { is_expected.not_to be_allowed_to(:perm5) } end end end describe "WITH set name" do - before { @role = GlobalRole.new name: "name" } + let(:role) { described_class.new name: "name" } describe "#to_s" do - it { expect(@role.to_s).to eql("name") } + it { expect(role.to_s).to eql("name") } + end + end + end + + describe ".givable" do + let!(:builtin_role) { create(:standard_global_role) } + + it { expect(described_class.givable).to contain_exactly global_role } + end + + describe ".standard" do + subject { described_class.standard } + + it "has the constant's builtin value" do + expect(subject.builtin) + .to eql(Role::BUILTIN_STANDARD_GLOBAL) + end + + it "is builtin" do + expect(subject) + .to be_builtin + end + + context "with a missing standard role" do + before do + described_class.where(builtin: Role::BUILTIN_STANDARD_GLOBAL).delete_all + end + + it "creates a new standard role" do + expect { subject }.to change(described_class, :count).by(1) end end end diff --git a/spec/models/principals/scopes/like_spec.rb b/spec/models/principals/scopes/like_spec.rb index 022e200f96fc..7a84ba2a0243 100644 --- a/spec/models/principals/scopes/like_spec.rb +++ b/spec/models/principals/scopes/like_spec.rb @@ -74,5 +74,10 @@ expect(Principal.like("mail")) .to contain_exactly(mail, mail2) end + + it "does not finds by mail when mail disabled" do + expect(Principal.like("mail", email: false)) + .to be_empty + end end end diff --git a/spec/requests/api/v3/principals/principals_resource_spec.rb b/spec/requests/api/v3/principals/principals_resource_spec.rb index 67080eeff450..2e415a14fdd5 100644 --- a/spec/requests/api/v3/principals/principals_resource_spec.rb +++ b/spec/requests/api/v3/principals/principals_resource_spec.rb @@ -88,8 +88,8 @@ end it "succeeds" do - expect(response.status) - .to eq(200) + expect(response) + .to have_http_status(200) end it_behaves_like "API V3 collection response", 4, 4 do @@ -145,6 +145,27 @@ it_behaves_like "API V3 collection response", 1, 1, "User" end + context "with a filter for typeahead" do + let(:permissions) { [:view_user_email] } + let(:filter) do + [{ typeahead: { operator: "**", values: ["aaaa@example.com"] } }] + end + + before do + mock_permissions_for(current_user) do |mock| + mock.allow_globally(*permissions) + end + end + + it_behaves_like "API V3 collection response", 1, 1, "User" + + context "when user does not have permission to view user emails" do + let(:permissions) { [] } + + it_behaves_like "API V3 collection response", 0, 0 + end + end + context "with a filter for id" do let(:filter) do [{ id: { operator: "=", values: [user.id.to_s] } }] diff --git a/spec/seeders/root_seeder_standard_edition_spec.rb b/spec/seeders/root_seeder_standard_edition_spec.rb index 90b089cebcd2..2d5f48c85d9f 100644 --- a/spec/seeders/root_seeder_standard_edition_spec.rb +++ b/spec/seeders/root_seeder_standard_edition_spec.rb @@ -57,7 +57,7 @@ expect(Query.count).to eq 26 expect(ProjectRole.count).to eq 5 expect(WorkPackageRole.count).to eq 3 - expect(GlobalRole.count).to eq 1 + expect(GlobalRole.count).to eq 2 expect(Grids::Overview.count).to eq 2 expect(Version.count).to eq 4 expect(VersionSetting.count).to eq 4 @@ -126,7 +126,7 @@ include_examples "it creates records", model: Color, expected_count: 144 include_examples "it creates records", model: DocumentCategory, expected_count: 3 - include_examples "it creates records", model: GlobalRole, expected_count: 1 + include_examples "it creates records", model: GlobalRole, expected_count: 2 include_examples "it creates records", model: WorkPackageRole, expected_count: 3 include_examples "it creates records", model: ProjectRole, expected_count: 5 include_examples "it creates records", model: ProjectQueryRole, expected_count: 2 @@ -166,7 +166,7 @@ expect(Query.count).to eq 26 expect(ProjectRole.count).to eq 5 expect(WorkPackageRole.count).to eq 3 - expect(GlobalRole.count).to eq 1 + expect(GlobalRole.count).to eq 2 expect(Grids::Overview.count).to eq 2 expect(Version.count).to eq 4 expect(VersionSetting.count).to eq 4 diff --git a/spec/services/authorization/user_global_roles_query_spec.rb b/spec/services/authorization/user_global_roles_query_spec.rb index 2d1b6c04b12d..7aced4d96536 100644 --- a/spec/services/authorization/user_global_roles_query_spec.rb +++ b/spec/services/authorization/user_global_roles_query_spec.rb @@ -38,6 +38,7 @@ let(:role2) { build(:project_role) } let(:anonymous_role) { build(:anonymous_role) } let(:non_member) { build(:non_member) } + let(:standard_global) { build(:standard_global_role) } let(:member) do build(:member, project:, roles: [role], @@ -69,6 +70,7 @@ before do non_member.save! anonymous_role.save! + standard_global.save! user.save! end @@ -81,8 +83,8 @@ member.save! end - it "is the member and non member role" do - expect(described_class.query(user)).to contain_exactly(role, non_member) + it "is the member, non member role, and standard global role" do + expect(described_class.query(user)).to contain_exactly(role, non_member, standard_global) end end @@ -92,20 +94,20 @@ member2.save! end - it "is both member and the non member role" do - expect(described_class.query(user)).to contain_exactly(role, role2, non_member) + it "is both member roles, the non member role, and standard global role" do + expect(described_class.query(user)).to contain_exactly(role, role2, non_member, standard_global) end end context "without the user being a member in a project" do - it "is the non member role" do - expect(described_class.query(user)).to contain_exactly(non_member) + it "is the non member role and standard global role" do + expect(described_class.query(user)).to contain_exactly(non_member, standard_global) end end context "with the user being anonymous" do - it "is the anonymous role" do - expect(described_class.query(anonymous)).to contain_exactly(anonymous_role) + it "is the anonymous role and the global role" do + expect(described_class.query(anonymous)).to contain_exactly(anonymous_role, standard_global) end end @@ -114,8 +116,8 @@ global_member.save! end - it "is the global role and non member role" do - expect(described_class.query(user)).to contain_exactly(global_role, non_member) + it "is the global role, non member role, and standard global role" do + expect(described_class.query(user)).to contain_exactly(global_role, non_member, standard_global) end end @@ -125,8 +127,8 @@ global_member.save! end - it "is the global role, member role and non member role" do - expect(described_class.query(user)).to contain_exactly(global_role, role, non_member) + it "is the global role, member role, non member role, and standard global role" do + expect(described_class.query(user)).to contain_exactly(global_role, role, non_member, standard_global) end end @@ -137,8 +139,8 @@ work_package_member.save! end - it "is the global role, member role and non member role" do - expect(described_class.query(user)).to contain_exactly(global_role, role, non_member) + it "is the global role, member role, non member role, and standard global role" do + expect(described_class.query(user)).to contain_exactly(global_role, role, non_member, standard_global) end end end diff --git a/spec/support/api/v3/shared_available_principals_examples.rb b/spec/support/api/v3/shared_available_principals_examples.rb index 223bf06bb005..d152194940aa 100644 --- a/spec/support/api/v3/shared_available_principals_examples.rb +++ b/spec/support/api/v3/shared_available_principals_examples.rb @@ -55,14 +55,9 @@ let(:permissions) { base_permissions } let(:assignable_permissions) { [:work_package_assigned] } - - shared_context "request available #{principals}" do - before { get href } - end - describe "response" do shared_examples_for "returns available #{principals}" do |total, count, klass| - include_context "request available #{principals}" + before { get href } it_behaves_like "API V3 collection response", total, count, klass end @@ -83,10 +78,36 @@ # and the current user end - if work_package_scope - it_behaves_like "returns available #{principals}", 3, 3, "User" - else - it_behaves_like "returns available #{principals}", 2, 2, "User" + user_count = work_package_scope ? 3 : 2 + + it_behaves_like "returns available #{principals}", user_count, user_count, "User" do + let(:json_response) { JSON.parse(last_response.body) } + + context "without permissions to view user email" do + it "returns no email key for other users" do + expect(json_response["_embedded"]["elements"]) + .not_to include( + a_hash_including( + "id" => other_user.id, + "email" => other_user.mail + ) + ) + end + end + + context "with permissions to view user email" do + let(:permissions) { base_permissions + assignable_permissions + [:view_user_email] } + + it "returns email key for other users" do + expect(json_response["_embedded"]["elements"]) + .to include( + a_hash_including( + "id" => other_user.id, + "email" => other_user.mail + ) + ) + end + end end end