Commit 61e9a3dc authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Add 2FA filter to group members page

* Show 2fa badge on a group members page
* Make group members page UI consistent with project members page
* Fix ambiguous sql in User.with/without_two_factor methods
Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
parent 533593e9
...@@ -11,13 +11,20 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -11,13 +11,20 @@ class Groups::GroupMembersController < Groups::ApplicationController
:override :override
def index def index
can_manage_members = can?(current_user, :admin_group_member, @group)
@sort = params[:sort].presence || sort_value_name @sort = params[:sort].presence || sort_value_name
@project = @group.projects.find(params[:project_id]) if params[:project_id] @project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = GroupMembersFinder.new(@group).execute @members = GroupMembersFinder.new(@group).execute
@members = @members.non_invite unless can?(current_user, :admin_group, @group) @members = @members.non_invite unless can_manage_members
@members = @members.search(params[:search]) if params[:search].present? @members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort_by_attribute(@sort) @members = @members.sort_by_attribute(@sort)
if can_manage_members && params[:two_factor].present?
@members = @members.filter_by_2fa(params[:two_factor])
end
@members = @members.page(params[:page]).per(50) @members = @members.page(params[:page]).per(50)
@members = present_members(@members.includes(:user)) @members = present_members(@members.includes(:user))
......
...@@ -96,6 +96,17 @@ class Member < ActiveRecord::Base ...@@ -96,6 +96,17 @@ class Member < ActiveRecord::Base
joins(:user).merge(User.search(query)) joins(:user).merge(User.search(query))
end end
def filter_by_2fa(value)
case value
when 'enabled'
left_join_users.merge(User.with_two_factor_indistinct)
when 'disabled'
left_join_users.merge(User.without_two_factor)
else
all
end
end
def sort_by_attribute(method) def sort_by_attribute(method)
case method.to_s case method.to_s
when 'access_level_asc' then reorder(access_level: :asc) when 'access_level_asc' then reorder(access_level: :asc)
......
...@@ -237,14 +237,18 @@ class User < ActiveRecord::Base ...@@ -237,14 +237,18 @@ class User < ActiveRecord::Base
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) } scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
def self.with_two_factor def self.with_two_factor_indistinct
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
.where("u2f.id IS NOT NULL OR otp_required_for_login = ?", true).distinct(arel_table[:id]) .where("u2f.id IS NOT NULL OR users.otp_required_for_login = ?", true)
end
def self.with_two_factor
with_two_factor_indistinct.distinct(arel_table[:id])
end end
def self.without_two_factor def self.without_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
.where("u2f.id IS NULL AND otp_required_for_login = ?", false) .where("u2f.id IS NULL AND users.otp_required_for_login = ?", false)
end end
# #
......
- page_title "Members" - page_title "Members"
- can_manage_members = can?(current_user, :admin_group_member, @group)
.project-members-page.prepend-top-default .project-members-page.prepend-top-default
%h4 %h4
Members Members
%hr %hr
- if can?(current_user, :admin_group_member, @group) - if can_manage_members
.project-members-new.append-bottom-default .project-members-new.append-bottom-default
%p.clearfix %p.clearfix
Add new member to Add new member to
...@@ -13,20 +14,23 @@ ...@@ -13,20 +14,23 @@
= render 'shared/members/requests', membership_source: @group, requesters: @requesters = render 'shared/members/requests', membership_source: @group, requesters: @requesters
.append-bottom-default.clearfix .clearfix
%h5.member.existing-title %h5.member.existing-title
Existing members Existing members
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .panel.panel-default
.panel-heading.flex-project-members-panel
%span.flex-project-title
Members with access to
%strong= @group.name
%span.badge= @members.total_count
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group .form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search") = icon("search")
- if can_manage_members
= render 'shared/members/filter_2fa_dropdown'
= render 'shared/members/sort_dropdown' = render 'shared/members/sort_dropdown'
.panel.panel-default
.panel-heading
Members with access to
%strong= @group.name
%span.badge= @members.total_count
%ul.content-list.members-list %ul.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member = render partial: 'shared/members/member', collection: @members, as: :member
= paginate @members, theme: 'gitlab' = paginate @members, theme: 'gitlab'
- filter = params[:two_factor] || 'everyone'
- filter_options = { 'everyone' => 'Everyone', 'enabled' => 'Enabled', 'disabled' => 'Disabled' }
.dropdown.inline.member-filter-2fa-dropdown
= dropdown_toggle('2FA: ' + filter_options[filter], { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header
Filter by two-factor authentication
- filter_options.each do |value, title|
%li
= link_to filter_group_project_member_path(two_factor: value), class: ("is-active" if filter == value) do
= title
...@@ -20,6 +20,10 @@ ...@@ -20,6 +20,10 @@
%label.label.label-danger %label.label.label-danger
%strong Blocked %strong Blocked
- if user.two_factor_enabled?
%label.label.label-info
2FA
- if source.instance_of?(Group) && source != @group - if source.instance_of?(Group) && source != @group
&middot; &middot;
= link_to source.full_name, source, class: "member-group-link" = link_to source.full_name, source, class: "member-group-link"
......
---
title: Add 2FA filter to the group members page
merge_request: 18483
author:
type: changed
require 'spec_helper'
feature 'Groups > Members > Filter members' do
let(:user) { create(:user) }
let(:user_with_2fa) { create(:user, :two_factor_via_otp) }
let(:group) { create(:group) }
background do
group.add_owner(user)
group.add_master(user_with_2fa)
sign_in(user)
end
scenario 'shows all members' do
visit_members_list
expect(first_member).to include(user.name)
expect(second_member).to include(user_with_2fa.name)
expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Everyone')
end
scenario 'shows only 2FA members' do
visit_members_list(two_factor: 'enabled')
expect(first_member).to include(user_with_2fa.name)
expect(members_list.size).to eq(1)
expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Enabled')
end
scenario 'shows only non 2FA members' do
visit_members_list(two_factor: 'disabled')
expect(first_member).to include(user.name)
expect(members_list.size).to eq(1)
expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Disabled')
end
def visit_members_list(options = {})
visit group_group_members_path(group.to_param, options)
end
def members_list
page.all('ul.content-list > li')
end
def first_member
members_list.first.text
end
def second_member
members_list.last.text
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment