Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
7c57de4e
Commit
7c57de4e
authored
Feb 19, 2021
by
peterhegman
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Only show 2FA badge to project maintainers and group owners
Doesn't make sense to show 2FA badge to everyone
parent
77239704
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
165 additions
and
22 deletions
+165
-22
app/assets/javascripts/members/components/avatars/user_avatar.vue
...ts/javascripts/members/components/avatars/user_avatar.vue
+7
-1
app/assets/javascripts/members/utils.js
app/assets/javascripts/members/utils.js
+2
-2
app/helpers/projects/project_members_helper.rb
app/helpers/projects/project_members_helper.rb
+2
-2
changelogs/unreleased/24908-user-2fa-status-should-not-be-publicly-shown.yml
...ed/24908-user-2fa-status-should-not-be-publicly-shown.yml
+5
-0
doc/user/permissions.md
doc/user/permissions.md
+2
-0
ee/app/assets/javascripts/members/utils.js
ee/app/assets/javascripts/members/utils.js
+2
-2
ee/spec/frontend/members/components/avatars/user_avatar_spec.js
...c/frontend/members/components/avatars/user_avatar_spec.js
+9
-0
ee/spec/frontend/members/utils_spec.js
ee/spec/frontend/members/utils_spec.js
+8
-2
spec/features/groups/members/list_members_spec.rb
spec/features/groups/members/list_members_spec.rb
+32
-0
spec/features/projects/members/list_spec.rb
spec/features/projects/members/list_spec.rb
+32
-0
spec/frontend/members/components/avatars/user_avatar_spec.js
spec/frontend/members/components/avatars/user_avatar_spec.js
+21
-5
spec/frontend/members/mock_data.js
spec/frontend/members/mock_data.js
+2
-0
spec/frontend/members/utils_spec.js
spec/frontend/members/utils_spec.js
+39
-6
spec/helpers/projects/project_members_helper_spec.rb
spec/helpers/projects/project_members_helper_spec.rb
+2
-2
No files found.
app/assets/javascripts/members/components/avatars/user_avatar.vue
View file @
7c57de4e
...
...
@@ -5,6 +5,7 @@ import {
GlBadge
,
GlSafeHtmlDirective
as
SafeHtml
,
}
from
'
@gitlab/ui
'
;
import
{
mapState
}
from
'
vuex
'
;
import
{
generateBadges
}
from
'
ee_else_ce/members/utils
'
;
import
{
glEmojiTag
}
from
'
~/emoji
'
;
import
{
__
}
from
'
~/locale
'
;
...
...
@@ -34,11 +35,16 @@ export default {
},
},
computed
:
{
...
mapState
([
'
canManageMembers
'
]),
user
()
{
return
this
.
member
.
user
;
},
badges
()
{
return
generateBadges
(
this
.
member
,
this
.
isCurrentUser
).
filter
((
badge
)
=>
badge
.
show
);
return
generateBadges
({
member
:
this
.
member
,
isCurrentUser
:
this
.
isCurrentUser
,
canManageMembers
:
this
.
canManageMembers
,
}).
filter
((
badge
)
=>
badge
.
show
);
},
statusEmoji
()
{
return
this
.
user
?.
status
?.
emoji
;
...
...
app/assets/javascripts/members/utils.js
View file @
7c57de4e
...
...
@@ -13,7 +13,7 @@ import {
GROUP_LINK_ACCESS_LEVEL_PROPERTY_NAME
,
}
from
'
./constants
'
;
export
const
generateBadges
=
(
member
,
isCurrentUser
)
=>
[
export
const
generateBadges
=
(
{
member
,
isCurrentUser
,
canManageMembers
}
)
=>
[
{
show
:
isCurrentUser
,
text
:
__
(
"
It's you
"
),
...
...
@@ -25,7 +25,7 @@ export const generateBadges = (member, isCurrentUser) => [
variant
:
'
danger
'
,
},
{
show
:
member
.
user
?.
twoFactorEnabled
,
show
:
member
.
user
?.
twoFactorEnabled
&&
(
canManageMembers
||
isCurrentUser
)
,
text
:
__
(
'
2FA
'
),
variant
:
'
info
'
,
},
...
...
app/helpers/projects/project_members_helper.rb
View file @
7c57de4e
...
...
@@ -40,7 +40,7 @@ module Projects::ProjectMembersHelper
members:
project_members_data_json
(
project
,
members
),
member_path:
project_project_member_path
(
project
,
':id'
),
source_id:
project
.
id
,
can_manage_members:
can_manage_project_members?
(
project
)
can_manage_members:
can_manage_project_members?
(
project
)
.
to_s
}
end
...
...
@@ -49,7 +49,7 @@ module Projects::ProjectMembersHelper
members:
project_group_links_data_json
(
group_links
),
member_path:
project_group_link_path
(
project
,
':id'
),
source_id:
project
.
id
,
can_manage_members:
can_manage_project_members?
(
project
)
can_manage_members:
can_manage_project_members?
(
project
)
.
to_s
}
end
end
changelogs/unreleased/24908-user-2fa-status-should-not-be-publicly-shown.yml
0 → 100644
View file @
7c57de4e
---
title
:
Only show 2FA badge to project maintainers and group owners
merge_request
:
54646
author
:
type
:
changed
doc/user/permissions.md
View file @
7c57de4e
...
...
@@ -173,6 +173,7 @@ The following table depicts the various user permission levels in a project.
| View project Audit Events | | | ✓ (
*12*
) | ✓ | ✓ |
| Manage
[
push rules
](
../push_rules/push_rules.md
)
| | | | ✓ | ✓ |
| Manage
[
project access tokens
](
project/settings/project_access_tokens.md
)
**(FREE SELF)**
| | | | ✓ | ✓ |
| View 2FA status of members | | | | ✓ | ✓ |
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Rename project | | | | | ✓ |
...
...
@@ -293,6 +294,7 @@ group.
| View Value Stream analytics | ✓ | ✓ | ✓ | ✓ | ✓ |
| View Billing
**(FREE SAAS)**
| | | | | ✓ (4) |
| View Usage Quotas
**(FREE SAAS)**
| | | | | ✓ (4) |
| View 2FA status of members | | | | | ✓ |
| Filter members by 2FA status | | | | | ✓ |
| Administer project compliance frameworks | | | | | ✓ |
...
...
ee/app/assets/javascripts/members/utils.js
View file @
7c57de4e
...
...
@@ -14,8 +14,8 @@ export {
canUpdate
,
}
from
'
~/members/utils
'
;
export
const
generateBadges
=
(
member
,
isCurrentUser
)
=>
[
...
CEGenerateBadges
(
member
,
isCurrentUser
),
export
const
generateBadges
=
(
{
member
,
isCurrentUser
,
canManageMembers
}
)
=>
[
...
CEGenerateBadges
(
{
member
,
isCurrentUser
,
canManageMembers
}
),
{
show
:
member
.
usingLicense
,
text
:
__
(
'
Is using seat
'
),
...
...
ee/spec/frontend/members/components/avatars/user_avatar_spec.js
View file @
7c57de4e
import
{
GlBadge
}
from
'
@gitlab/ui
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
{
member
as
memberMock
}
from
'
jest/members/mock_data
'
;
import
UserAvatar
from
'
~/members/components/avatars/user_avatar.vue
'
;
Vue
.
use
(
Vuex
);
describe
(
'
UserAvatar
'
,
()
=>
{
let
wrapper
;
...
...
@@ -12,6 +16,11 @@ describe('UserAvatar', () => {
isCurrentUser
:
false
,
...
propsData
,
},
store
:
new
Vuex
.
Store
({
state
:
{
canManageMembers
:
true
,
},
}),
});
};
...
...
ee/spec/frontend/members/utils_spec.js
View file @
7c57de4e
...
...
@@ -10,7 +10,11 @@ import {
describe
(
'
Members Utils
'
,
()
=>
{
describe
(
'
generateBadges
'
,
()
=>
{
it
(
'
has correct properties for each badge
'
,
()
=>
{
const
badges
=
generateBadges
(
memberMock
,
true
);
const
badges
=
generateBadges
({
member
:
memberMock
,
isCurrentUser
:
true
,
canManageMembers
:
true
,
});
badges
.
forEach
((
badge
)
=>
{
expect
(
badge
).
toEqual
(
...
...
@@ -30,7 +34,9 @@ describe('Members Utils', () => {
${{
...
memberMock
,
groupManagedAccount
:
true
}
} |
${{
show
:
true
,
text
:
'
Managed Account
'
,
variant
:
'
info
'
}
}
${{
...
memberMock
,
canOverride
:
true
}
} |
${{
show
:
true
,
text
:
'
LDAP
'
,
variant
:
'
info
'
}
}
`
(
'
returns expected output for "$expected.text" badge
'
,
({
member
,
expected
})
=>
{
expect
(
generateBadges
(
member
,
true
)).
toContainEqual
(
expect
.
objectContaining
(
expected
));
expect
(
generateBadges
({
member
,
isCurrentUser
:
true
,
canManageMembers
:
true
}),
).
toContainEqual
(
expect
.
objectContaining
(
expected
));
});
});
...
...
spec/features/groups/members/list_members_spec.rb
View file @
7c57de4e
...
...
@@ -47,4 +47,36 @@ RSpec.describe 'Groups > Members > List members', :js do
expect
(
first_row
).
to
have_selector
(
'gl-emoji[data-name="smirk"]'
)
end
end
describe
'when user has 2FA enabled'
do
let_it_be
(
:user_with_2fa
)
{
create
(
:user
,
:two_factor_via_otp
)
}
before
do
group
.
add_guest
(
user_with_2fa
)
end
it
'shows 2FA badge to user with "Owner" access level'
do
group
.
add_owner
(
user1
)
visit
group_group_members_path
(
group
)
expect
(
find_member_row
(
user_with_2fa
)).
to
have_content
(
'2FA'
)
end
it
'does not show 2FA badge to users with access level below "Owner"'
do
group
.
add_maintainer
(
user1
)
visit
group_group_members_path
(
group
)
expect
(
find_member_row
(
user_with_2fa
)).
not_to
have_content
(
'2FA'
)
end
it
'shows 2FA badge to themselves'
do
sign_in
(
user_with_2fa
)
visit
group_group_members_path
(
group
)
expect
(
find_member_row
(
user_with_2fa
)).
to
have_content
(
'2FA'
)
end
end
end
spec/features/projects/members/list_spec.rb
View file @
7c57de4e
...
...
@@ -117,6 +117,38 @@ RSpec.describe 'Project members list' do
end
end
end
describe
'when user has 2FA enabled'
do
let_it_be
(
:user_with_2fa
)
{
create
(
:user
,
:two_factor_via_otp
)
}
before
do
project
.
add_guest
(
user_with_2fa
)
end
it
'shows 2FA badge to user with "Maintainer" access level'
do
project
.
add_maintainer
(
user1
)
visit_members_page
expect
(
find_member_row
(
user_with_2fa
)).
to
have_content
(
'2FA'
)
end
it
'does not show 2FA badge to users with access level below "Maintainer"'
do
group
.
add_developer
(
user1
)
visit_members_page
expect
(
find_member_row
(
user_with_2fa
)).
not_to
have_content
(
'2FA'
)
end
it
'shows 2FA badge to themselves'
do
sign_in
(
user_with_2fa
)
visit_members_page
expect
(
find_member_row
(
user_with_2fa
)).
to
have_content
(
'2FA'
)
end
end
end
context
'when `vue_project_members_list` feature flag is disabled'
do
...
...
spec/frontend/members/components/avatars/user_avatar_spec.js
View file @
7c57de4e
import
{
GlAvatarLink
,
GlBadge
}
from
'
@gitlab/ui
'
;
import
{
within
}
from
'
@testing-library/dom
'
;
import
{
mount
,
createWrapper
}
from
'
@vue/test-utils
'
;
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
UserAvatar
from
'
~/members/components/avatars/user_avatar.vue
'
;
import
{
member
as
memberMock
,
orphanedMember
}
from
'
../../mock_data
'
;
import
{
member
as
memberMock
,
member2faEnabled
,
orphanedMember
}
from
'
../../mock_data
'
;
Vue
.
use
(
Vuex
);
describe
(
'
UserAvatar
'
,
()
=>
{
let
wrapper
;
const
{
user
}
=
memberMock
;
const
createComponent
=
(
propsData
=
{})
=>
{
const
createComponent
=
(
propsData
=
{}
,
state
=
{}
)
=>
{
wrapper
=
mount
(
UserAvatar
,
{
propsData
:
{
member
:
memberMock
,
isCurrentUser
:
false
,
...
propsData
,
},
store
:
new
Vuex
.
Store
({
state
:
{
canManageMembers
:
true
,
...
state
,
},
}),
});
};
...
...
@@ -69,9 +79,9 @@ describe('UserAvatar', () => {
describe
(
'
badges
'
,
()
=>
{
it
.
each
`
member
| badgeText
${{
...
memberMock
,
user
:
{
...
memberMock
.
user
,
blocked
:
true
}
}}
|
${
'
Blocked
'
}
${
{
...
memberMock
,
user
:
{
...
memberMock
.
user
,
twoFactorEnabled
:
true
}
}}
|
${
'
2FA
'
}
member | badgeText
${{
...
memberMock
,
user
:
{
...
memberMock
.
user
,
blocked
:
true
}
}} |
${
'
Blocked
'
}
${
member2faEnabled
}
|
${
'
2FA
'
}
`
(
'
renders the "$badgeText" badge
'
,
({
member
,
badgeText
})
=>
{
createComponent
({
member
});
...
...
@@ -83,6 +93,12 @@ describe('UserAvatar', () => {
expect
(
getByText
(
"
It's you
"
).
exists
()).
toBe
(
true
);
});
it
(
'
does not render 2FA badge when `canManageMembers` is `false`
'
,
()
=>
{
createComponent
({
member
:
member2faEnabled
},
{
canManageMembers
:
false
});
expect
(
within
(
wrapper
.
element
).
queryByText
(
'
2FA
'
)).
toBe
(
null
);
});
});
describe
(
'
user status
'
,
()
=>
{
...
...
spec/frontend/members/mock_data.js
View file @
7c57de4e
...
...
@@ -75,3 +75,5 @@ export const membersJsonString = JSON.stringify(members);
export
const
directMember
=
{
...
member
,
isDirectMember
:
true
};
export
const
inheritedMember
=
{
...
member
,
isDirectMember
:
false
};
export
const
member2faEnabled
=
{
...
member
,
user
:
{
...
member
.
user
,
twoFactorEnabled
:
true
}
};
spec/frontend/members/utils_spec.js
View file @
7c57de4e
...
...
@@ -17,6 +17,7 @@ import {
member
as
memberMock
,
directMember
,
inheritedMember
,
member2faEnabled
,
group
,
invite
,
membersJsonString
,
...
...
@@ -30,7 +31,11 @@ const URL_HOST = 'https://localhost/';
describe
(
'
Members Utils
'
,
()
=>
{
describe
(
'
generateBadges
'
,
()
=>
{
it
(
'
has correct properties for each badge
'
,
()
=>
{
const
badges
=
generateBadges
(
memberMock
,
true
);
const
badges
=
generateBadges
({
member
:
memberMock
,
isCurrentUser
:
true
,
canManageMembers
:
true
,
});
badges
.
forEach
((
badge
)
=>
{
expect
(
badge
).
toEqual
(
...
...
@@ -44,12 +49,40 @@ describe('Members Utils', () => {
});
it
.
each
`
member
| expected
${
memberMock
}
|
${{
show
:
true
,
text
:
"
It's you
"
,
variant
:
'
success
'
}
}
${{
...
memberMock
,
user
:
{
...
memberMock
.
user
,
blocked
:
true
}
}}
|
${{
show
:
true
,
text
:
'
Blocked
'
,
variant
:
'
danger
'
}
}
${
{
...
memberMock
,
user
:
{
...
memberMock
.
user
,
twoFactorEnabled
:
true
}
}}
|
${{
show
:
true
,
text
:
'
2FA
'
,
variant
:
'
info
'
}
}
member | expected
${
memberMock
}
|
${{
show
:
true
,
text
:
"
It's you
"
,
variant
:
'
success
'
}
}
${{
...
memberMock
,
user
:
{
...
memberMock
.
user
,
blocked
:
true
}
}} |
${{
show
:
true
,
text
:
'
Blocked
'
,
variant
:
'
danger
'
}
}
${
member2faEnabled
}
|
${{
show
:
true
,
text
:
'
2FA
'
,
variant
:
'
info
'
}
}
`
(
'
returns expected output for "$expected.text" badge
'
,
({
member
,
expected
})
=>
{
expect
(
generateBadges
(
member
,
true
)).
toContainEqual
(
expect
.
objectContaining
(
expected
));
expect
(
generateBadges
({
member
,
isCurrentUser
:
true
,
canManageMembers
:
true
}),
).
toContainEqual
(
expect
.
objectContaining
(
expected
));
});
describe
(
'
when `canManageMembers` argument is `false`
'
,
()
=>
{
describe
(
'
when member is not the current user
'
,
()
=>
{
it
(
'
sets `show` to `false` for 2FA badge
'
,
()
=>
{
const
badges
=
generateBadges
({
member
:
member2faEnabled
,
isCurrentUser
:
false
,
canManageMembers
:
false
,
});
expect
(
badges
.
find
((
badge
)
=>
badge
.
text
===
'
2FA
'
).
show
).
toBe
(
false
);
});
});
describe
(
'
when member is the current user
'
,
()
=>
{
it
(
'
sets `show` to `false` for 2fA badge
'
,
()
=>
{
const
badges
=
generateBadges
({
member
:
member2faEnabled
,
isCurrentUser
:
true
,
canManageMembers
:
false
,
});
expect
(
badges
.
find
((
badge
)
=>
badge
.
text
===
'
2FA
'
).
show
).
toBe
(
true
);
});
});
});
});
...
...
spec/helpers/projects/project_members_helper_spec.rb
View file @
7c57de4e
...
...
@@ -166,7 +166,7 @@ RSpec.describe Projects::ProjectMembersHelper do
members:
helper
.
project_members_data_json
(
project
,
present_members
(
project_members
)),
member_path:
'/foo-bar/-/project_members/:id'
,
source_id:
project
.
id
,
can_manage_members:
true
can_manage_members:
'true'
})
end
end
...
...
@@ -193,7 +193,7 @@ RSpec.describe Projects::ProjectMembersHelper do
members:
helper
.
project_group_links_data_json
(
project_group_links
),
member_path:
'/foo-bar/-/group_links/:id'
,
source_id:
project
.
id
,
can_manage_members:
true
can_manage_members:
'true'
})
end
end
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment