From 2a1c2c673f25aa1329e045bd907babbea037a9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Ru=CC=88ttimann?= Date: Wed, 6 Feb 2019 12:17:47 +0100 Subject: add tests for 2fa requirment for all sub-entities members (subgroup and projects) --- spec/models/group_spec.rb | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index e6e7298a043..551d85bda6c 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -625,10 +625,7 @@ describe Group do group.update!(description: 'foobar') end - it 'calls #update_two_factor_requirement on each group member' do - other_user = create(:user) - group.add_user(other_user, GroupMember::OWNER) - + def expects_other_user_to_require_two_factors calls = 0 allow_any_instance_of(User).to receive(:update_two_factor_requirement) do calls += 1 @@ -638,6 +635,29 @@ describe Group do expect(calls).to eq 2 end + + it 'calls #update_two_factor_requirement on each group member' do + other_user = create(:user) + group.add_user(other_user, GroupMember::OWNER) + + expects_other_user_to_require_two_factors + end + + it 'calls #update_two_factor_requirement on each subgroup member' do + subgroup = create(:group, :nested, parent: group) + subgroup_user = create(:user) + subgroup.add_user(subgroup_user, GroupMember::OWNER) + + expects_other_user_to_require_two_factors + end + + it 'calls #update_two_factor_requirement on each child project member' do + project = create(:project, group: group) + project_user = create(:user) + project.add_user(project_user, GroupMember::OWNER) + + expects_other_user_to_require_two_factors + end end describe '#path_changed_hook' do -- cgit v1.2.3 From 34a03d90c3a0a8ebda2143fcdca5d37a9b71db6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Ru=CC=88ttimann?= Date: Wed, 6 Feb 2019 15:08:39 +0100 Subject: require update_two_factor_requirement on all sub-entities users --- app/models/group.rb | 2 +- app/models/user.rb | 3 ++- spec/models/group_spec.rb | 11 ++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/models/group.rb b/app/models/group.rb index cdb4e6e87f6..5e58b48a366 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -423,7 +423,7 @@ class Group < Namespace def update_two_factor_requirement return unless saved_change_to_require_two_factor_authentication? || saved_change_to_two_factor_grace_period? - users.find_each(&:update_two_factor_requirement) + User.from_union([users_with_descendants, project_users_with_descendants]).find_each(&:update_two_factor_requirement) end def path_changed_hook diff --git a/app/models/user.rb b/app/models/user.rb index 2eb5c63a4cc..916a0aa74f0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -728,7 +728,8 @@ class User < ApplicationRecord end def expanded_groups_requiring_two_factor_authentication - all_expanded_groups.where(require_two_factor_authentication: true) + Group.from_union([all_expanded_groups.where(require_two_factor_authentication: true), + authorized_groups.where(require_two_factor_authentication: true)]) end # rubocop: disable CodeReuse/ServiceClass diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 551d85bda6c..15ec672f165 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -654,7 +654,16 @@ describe Group do it 'calls #update_two_factor_requirement on each child project member' do project = create(:project, group: group) project_user = create(:user) - project.add_user(project_user, GroupMember::OWNER) + project.add_developer(project_user) + + expects_other_user_to_require_two_factors + end + + it 'calls #update_two_factor_requirement on each subgroups child project member' do + subgroup = create(:group, :nested, parent: group) + project = create(:project, group: subgroup) + project_user = create(:user) + project.add_developer(project_user) expects_other_user_to_require_two_factors end -- cgit v1.2.3 From 54a918d92aa606d9e8977a30b4f08756c1d39dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Ru=CC=88ttimann?= Date: Wed, 6 Feb 2019 16:35:23 +0100 Subject: first try: fix mysql problem (not all users found) --- app/models/group.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/group.rb b/app/models/group.rb index 5e58b48a366..dd8baddd789 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -423,7 +423,7 @@ class Group < Namespace def update_two_factor_requirement return unless saved_change_to_require_two_factor_authentication? || saved_change_to_two_factor_grace_period? - User.from_union([users_with_descendants, project_users_with_descendants]).find_each(&:update_two_factor_requirement) + [users_with_descendants, project_users_with_descendants].each {|set| set.find_each(&:update_two_factor_requirement)} end def path_changed_hook -- cgit v1.2.3 From dfe5ffd4edd34adc9dbb941f22a8843ee4f12e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Ru=CC=88ttimann?= Date: Wed, 6 Feb 2019 17:29:55 +0100 Subject: second try: fix mysql problem (not all users found) --- app/models/user.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 916a0aa74f0..f3deea75dbb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -728,8 +728,8 @@ class User < ApplicationRecord end def expanded_groups_requiring_two_factor_authentication - Group.from_union([all_expanded_groups.where(require_two_factor_authentication: true), - authorized_groups.where(require_two_factor_authentication: true)]) + all_expanded_groups.where(require_two_factor_authentication: true).merge( + authorized_groups.where(require_two_factor_authentication: true)) end # rubocop: disable CodeReuse/ServiceClass -- cgit v1.2.3 From 28782d5165fee8251321fce65e54c4722a4db581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Ru=CC=88ttimann?= Date: Thu, 7 Feb 2019 15:20:58 +0100 Subject: remove experiments for 2fa requirements and fix tests --- app/models/group.rb | 2 +- app/models/user.rb | 4 ++-- spec/models/group_spec.rb | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/models/group.rb b/app/models/group.rb index dd8baddd789..5e58b48a366 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -423,7 +423,7 @@ class Group < Namespace def update_two_factor_requirement return unless saved_change_to_require_two_factor_authentication? || saved_change_to_two_factor_grace_period? - [users_with_descendants, project_users_with_descendants].each {|set| set.find_each(&:update_two_factor_requirement)} + User.from_union([users_with_descendants, project_users_with_descendants]).find_each(&:update_two_factor_requirement) end def path_changed_hook diff --git a/app/models/user.rb b/app/models/user.rb index f3deea75dbb..916a0aa74f0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -728,8 +728,8 @@ class User < ApplicationRecord end def expanded_groups_requiring_two_factor_authentication - all_expanded_groups.where(require_two_factor_authentication: true).merge( - authorized_groups.where(require_two_factor_authentication: true)) + Group.from_union([all_expanded_groups.where(require_two_factor_authentication: true), + authorized_groups.where(require_two_factor_authentication: true)]) end # rubocop: disable CodeReuse/ServiceClass diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 15ec672f165..7f0b1185ba4 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -633,7 +633,11 @@ describe Group do group.update!(require_two_factor_authentication: true, two_factor_grace_period: 23) - expect(calls).to eq 2 + if Group.supports_nested_objects? + expect(calls).to eq 2 + else + expect(calls).to eq 1 + end end it 'calls #update_two_factor_requirement on each group member' do -- cgit v1.2.3 From 34289f6657e83ec34d5eb2c3b89323546fdd3690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Ru=CC=88ttimann?= Date: Thu, 7 Feb 2019 15:42:24 +0100 Subject: add changelog entry --- .../unreleased/feature-require-2fa-for-all-entities-in-group.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml diff --git a/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml b/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml new file mode 100644 index 00000000000..650adcda26e --- /dev/null +++ b/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml @@ -0,0 +1,7 @@ +--- +title: Apply the group setting "require 2FA" to be inherited across all subgroups + and descendant projects of the group where it was set. (This works only for postgresql + databases, not for mysql/mariadb) +merge_request: 24965 +author: rroger +type: changed -- cgit v1.2.3 From ff22dfbf7a4f539b676101b51e5ad892d56da920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Ru=CC=88ttimann?= Date: Thu, 7 Feb 2019 16:28:00 +0100 Subject: fix tests for mysql db. --- spec/models/group_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 7f0b1185ba4..e1c5479851c 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -625,7 +625,7 @@ describe Group do group.update!(description: 'foobar') end - def expects_other_user_to_require_two_factors + def expects_other_user_to_require_two_factors(expected_calls_mysql_db = 1) calls = 0 allow_any_instance_of(User).to receive(:update_two_factor_requirement) do calls += 1 @@ -636,7 +636,7 @@ describe Group do if Group.supports_nested_objects? expect(calls).to eq 2 else - expect(calls).to eq 1 + expect(calls).to eq expected_calls_mysql_db end end @@ -644,7 +644,7 @@ describe Group do other_user = create(:user) group.add_user(other_user, GroupMember::OWNER) - expects_other_user_to_require_two_factors + expects_other_user_to_require_two_factors(2) end it 'calls #update_two_factor_requirement on each subgroup member' do @@ -660,7 +660,7 @@ describe Group do project_user = create(:user) project.add_developer(project_user) - expects_other_user_to_require_two_factors + expects_other_user_to_require_two_factors(2) end it 'calls #update_two_factor_requirement on each subgroups child project member' do -- cgit v1.2.3 From 35d928c4a9fe7c8545f2ad1866a45ff28c1ef5d3 Mon Sep 17 00:00:00 2001 From: Roger Meier Date: Mon, 3 Jun 2019 06:15:53 +0200 Subject: refactor: apply "require 2FA" to all subgroup and ancestor group members, when changing --- app/models/group.rb | 2 +- app/models/user.rb | 3 +- ...ature-require-2fa-for-all-entities-in-group.yml | 5 +- doc/security/two_factor_authentication.md | 22 +++- spec/models/group_spec.rb | 117 ++++++++++++--------- spec/models/user_spec.rb | 25 ++++- 6 files changed, 116 insertions(+), 58 deletions(-) diff --git a/app/models/group.rb b/app/models/group.rb index 5e58b48a366..ba9f6221567 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -423,7 +423,7 @@ class Group < Namespace def update_two_factor_requirement return unless saved_change_to_require_two_factor_authentication? || saved_change_to_two_factor_grace_period? - User.from_union([users_with_descendants, project_users_with_descendants]).find_each(&:update_two_factor_requirement) + direct_and_indirect_members.find_each(&:update_two_factor_requirement) end def path_changed_hook diff --git a/app/models/user.rb b/app/models/user.rb index 916a0aa74f0..2eb5c63a4cc 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -728,8 +728,7 @@ class User < ApplicationRecord end def expanded_groups_requiring_two_factor_authentication - Group.from_union([all_expanded_groups.where(require_two_factor_authentication: true), - authorized_groups.where(require_two_factor_authentication: true)]) + all_expanded_groups.where(require_two_factor_authentication: true) end # rubocop: disable CodeReuse/ServiceClass diff --git a/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml b/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml index 650adcda26e..ff04f99c1bb 100644 --- a/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml +++ b/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml @@ -1,7 +1,4 @@ ---- -title: Apply the group setting "require 2FA" to be inherited across all subgroups - and descendant projects of the group where it was set. (This works only for postgresql - databases, not for mysql/mariadb) +title: Apply the group setting "require 2FA" across all subgroup and ancestor group members as well when changing the group setting merge_request: 24965 author: rroger type: changed diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md index ad5daef805a..49dadd5abc2 100644 --- a/doc/security/two_factor_authentication.md +++ b/doc/security/two_factor_authentication.md @@ -39,8 +39,26 @@ If you want to enforce 2FA only for certain groups, you can: To change this setting, you need to be administrator or owner of the group. -If there are multiple 2FA requirements (i.e. group + all users, or multiple -groups) the shortest grace period will be used. +> [From](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24965) GitLab 12.0, 2FA settings for a group are also applied to subgroups. + +If you want to enforce 2FA only for certain groups, you can enable it in the +group settings and specify a grace period as above. To change this setting you +need to be administrator or owner of the group. + +The following are important notes about 2FA: + +- Projects belonging to a 2FA-enabled group that + [is shared](../user/project/members/share_project_with_groups.md) + with a 2FA-disabled group will *not* require members of the 2FA-disabled group to use + 2FA for the project. For example, if project *P* belongs to 2FA-enabled group *A* and + is shared with 2FA-disabled group *B*, members of group *B* can access project *P* + without 2FA. To ensure this scenario doesn't occur, + [prevent sharing of projects](../user/group/index.md#share-with-group-lock) + for the 2FA-enabled group. +- If you add additional members to a project within a group or subgroup that has + 2FA enabled, 2FA is **not** required for those individually added members. +- If there are multiple 2FA requirements (for example, group + all users, or multiple + groups) the shortest grace period will be used. ## Disabling 2FA for everyone diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index e1c5479851c..074aec3eb07 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -603,73 +603,96 @@ describe Group do describe '#update_two_factor_requirement' do let(:user) { create(:user) } - before do - group.add_user(user, GroupMember::OWNER) - end - - it 'is called when require_two_factor_authentication is changed' do - expect_any_instance_of(User).to receive(:update_two_factor_requirement) + context 'group membership' do + before do + group.add_user(user, GroupMember::OWNER) + end - group.update!(require_two_factor_authentication: true) - end + it 'is called when require_two_factor_authentication is changed' do + expect_any_instance_of(User).to receive(:update_two_factor_requirement) - it 'is called when two_factor_grace_period is changed' do - expect_any_instance_of(User).to receive(:update_two_factor_requirement) + group.update!(require_two_factor_authentication: true) + end - group.update!(two_factor_grace_period: 23) - end + it 'is called when two_factor_grace_period is changed' do + expect_any_instance_of(User).to receive(:update_two_factor_requirement) - it 'is not called when other attributes are changed' do - expect_any_instance_of(User).not_to receive(:update_two_factor_requirement) + group.update!(two_factor_grace_period: 23) + end - group.update!(description: 'foobar') - end + it 'is not called when other attributes are changed' do + expect_any_instance_of(User).not_to receive(:update_two_factor_requirement) - def expects_other_user_to_require_two_factors(expected_calls_mysql_db = 1) - calls = 0 - allow_any_instance_of(User).to receive(:update_two_factor_requirement) do - calls += 1 + group.update!(description: 'foobar') end - group.update!(require_two_factor_authentication: true, two_factor_grace_period: 23) + it 'calls #update_two_factor_requirement on each group member' do + other_user = create(:user) + group.add_user(other_user, GroupMember::OWNER) + + calls = 0 + allow_any_instance_of(User).to receive(:update_two_factor_requirement) do + calls += 1 + end + + group.update!(require_two_factor_authentication: true, two_factor_grace_period: 23) - if Group.supports_nested_objects? expect(calls).to eq 2 - else - expect(calls).to eq expected_calls_mysql_db end end - it 'calls #update_two_factor_requirement on each group member' do - other_user = create(:user) - group.add_user(other_user, GroupMember::OWNER) + context 'sub groups and projects', :nested_groups do + it 'enables two_factor_requirement for group member' do + group.add_user(user, GroupMember::OWNER) - expects_other_user_to_require_two_factors(2) - end + group.update!(require_two_factor_authentication: true) - it 'calls #update_two_factor_requirement on each subgroup member' do - subgroup = create(:group, :nested, parent: group) - subgroup_user = create(:user) - subgroup.add_user(subgroup_user, GroupMember::OWNER) + expect(user.reload.require_two_factor_authentication_from_group).to be_truthy + end - expects_other_user_to_require_two_factors - end + context 'expanded group members', :nested_groups do + let(:indirect_user) { create(:user) } - it 'calls #update_two_factor_requirement on each child project member' do - project = create(:project, group: group) - project_user = create(:user) - project.add_developer(project_user) + it 'enables two_factor_requirement for subgroup member' do + subgroup = create(:group, :nested, parent: group) + subgroup.add_user(indirect_user, GroupMember::OWNER) - expects_other_user_to_require_two_factors(2) - end + group.update!(require_two_factor_authentication: true) + + expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_truthy + end + + it 'enables two_factor_requirement for ancestor group member' do + ancestor_group = create(:group) + ancestor_group.add_user(indirect_user, GroupMember::OWNER) + group.update!(parent: ancestor_group) + + group.update!(require_two_factor_authentication: true) + + expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_truthy + end + end - it 'calls #update_two_factor_requirement on each subgroups child project member' do - subgroup = create(:group, :nested, parent: group) - project = create(:project, group: subgroup) - project_user = create(:user) - project.add_developer(project_user) + context 'project members' do + it 'does not enable two_factor_requirement for child project member' do + project = create(:project, group: group) + project.add_maintainer(user) - expects_other_user_to_require_two_factors + group.update!(require_two_factor_authentication: true) + + expect(user.reload.require_two_factor_authentication_from_group).to be_falsey + end + + it 'does not enable two_factor_requirement for subgroup child project member', :nested_groups do + subgroup = create(:group, :nested, parent: group) + project = create(:project, group: subgroup) + project.add_maintainer(user) + + group.update!(require_two_factor_authentication: true) + + expect(user.reload.require_two_factor_authentication_from_group).to be_falsey + end + end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d1338e34bb8..c95bbb0b3f5 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2655,9 +2655,9 @@ describe User do end end - context 'with 2FA requirement on nested parent group', :nested_groups do + context 'with 2FA requirement from expanded groups', :nested_groups do let!(:group1) { create :group, require_two_factor_authentication: true } - let!(:group1a) { create :group, require_two_factor_authentication: false, parent: group1 } + let!(:group1a) { create :group, parent: group1 } before do group1a.add_user(user, GroupMember::OWNER) @@ -2685,6 +2685,27 @@ describe User do end end + context "with 2FA requirement from shared project's group" do + let!(:group1) { create :group, require_two_factor_authentication: true } + let!(:group2) { create :group } + let(:shared_project) { create(:project, namespace: group1) } + + before do + shared_project.project_group_links.create!( + group: group2, + group_access: ProjectGroupLink.default_access + ) + + group2.add_user(user, GroupMember::OWNER) + end + + it 'does not require 2FA' do + user.update_two_factor_requirement + + expect(user.require_two_factor_authentication_from_group).to be false + end + end + context 'without 2FA requirement on groups' do let(:group) { create :group } -- cgit v1.2.3 From 7bfc4f999231385f2dc24cbb2d34b09cf5ae96c1 Mon Sep 17 00:00:00 2001 From: Roger Meier Date: Thu, 13 Jun 2019 12:36:54 +0000 Subject: refactor: do not apply setting "require 2FA" for ancestor group members --- app/models/group.rb | 2 +- .../unreleased/feature-require-2fa-for-all-entities-in-group.yml | 2 +- spec/models/group_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/group.rb b/app/models/group.rb index ba9f6221567..dbec211935d 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -423,7 +423,7 @@ class Group < Namespace def update_two_factor_requirement return unless saved_change_to_require_two_factor_authentication? || saved_change_to_two_factor_grace_period? - direct_and_indirect_members.find_each(&:update_two_factor_requirement) + members_with_descendants.find_each(&:update_two_factor_requirement) end def path_changed_hook diff --git a/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml b/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml index ff04f99c1bb..0abe777fb69 100644 --- a/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml +++ b/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml @@ -1,4 +1,4 @@ -title: Apply the group setting "require 2FA" across all subgroup and ancestor group members as well when changing the group setting +title: Apply the group setting "require 2FA" across all subgroup members as well when changing the group setting merge_request: 24965 author: rroger type: changed diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 074aec3eb07..d7accbef6bd 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -662,14 +662,14 @@ describe Group do expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_truthy end - it 'enables two_factor_requirement for ancestor group member' do + it 'does not enable two_factor_requirement for ancestor group member' do ancestor_group = create(:group) ancestor_group.add_user(indirect_user, GroupMember::OWNER) group.update!(parent: ancestor_group) group.update!(require_two_factor_authentication: true) - expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_truthy + expect(indirect_user.reload.require_two_factor_authentication_from_group).to be_falsey end end -- cgit v1.2.3