diff options
-rw-r--r-- | CHANGELOG | 1 | ||||
-rw-r--r-- | app/controllers/admin/identities_controller.rb | 41 | ||||
-rw-r--r-- | app/controllers/admin/users_controller.rb | 11 | ||||
-rw-r--r-- | app/views/admin/identities/_form.html.haml | 19 | ||||
-rw-r--r-- | app/views/admin/identities/_identity.html.haml | 12 | ||||
-rw-r--r-- | app/views/admin/identities/edit.html.haml | 6 | ||||
-rw-r--r-- | app/views/admin/identities/index.html.haml | 13 | ||||
-rw-r--r-- | app/views/admin/users/_head.html.haml | 23 | ||||
-rw-r--r-- | app/views/admin/users/groups.html.haml | 19 | ||||
-rw-r--r-- | app/views/admin/users/keys.html.haml | 3 | ||||
-rw-r--r-- | app/views/admin/users/projects.html.haml | 43 | ||||
-rw-r--r-- | app/views/admin/users/show.html.haml | 386 | ||||
-rw-r--r-- | config/routes.rb | 5 | ||||
-rw-r--r-- | features/admin/users.feature | 20 | ||||
-rw-r--r-- | features/steps/admin/users.rb | 41 | ||||
-rw-r--r-- | lib/gitlab/o_auth/provider.rb | 19 |
16 files changed, 424 insertions, 238 deletions
diff --git a/CHANGELOG b/CHANGELOG index 43a4f0f2798..6ba09e952bf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 7.13.0 (unreleased) - Rename "Design" profile settings page to "Preferences". - Allow users to customize their default Dashboard page. - Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8 + - Admin can edit and remove user identities - Convert CRLF newlines to LF when committing using the web editor. - API request /projects/:project_id/merge_requests?state=closed will return only closed merge requests without merged one. If you need ones that were merged - use state=merged. - Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled. diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb new file mode 100644 index 00000000000..d28614731f9 --- /dev/null +++ b/app/controllers/admin/identities_controller.rb @@ -0,0 +1,41 @@ +class Admin::IdentitiesController < Admin::ApplicationController + before_action :user + before_action :identity, except: :index + + def index + @identities = @user.identities + end + + def edit + end + + def update + if @identity.update_attributes(identity_params) + redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully updated.' + else + render :edit + end + end + + def destroy + if @identity.destroy + redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully removed.' + else + redirect_to admin_user_identities_path(@user), alert: 'Failed to remove user identity.' + end + end + + protected + + def user + @user ||= User.find_by!(username: params[:user_id]) + end + + def identity + @identity ||= user.identities.find(params[:id]) + end + + def identity_params + params.require(:identity).permit(:provider, :extern_uid) + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 2bc236871b0..ec29c320654 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,5 +1,5 @@ class Admin::UsersController < Admin::ApplicationController - before_action :user, only: [:show, :edit, :update, :destroy] + before_action :user, except: [:index, :new, :create] def index @users = User.order_name_asc.filter(params[:filter]) @@ -9,8 +9,17 @@ class Admin::UsersController < Admin::ApplicationController end def show + end + + def projects @personal_projects = user.personal_projects @joined_projects = user.projects.joined(@user) + end + + def groups + end + + def keys @keys = user.keys end diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml new file mode 100644 index 00000000000..b405aa6e8e3 --- /dev/null +++ b/app/views/admin/identities/_form.html.haml @@ -0,0 +1,19 @@ += form_for [:admin, @user, @identity], html: { class: 'form-horizontal fieldset-form' } do |f| + - if @identity.errors.any? + #error_explanation + .alert.alert-danger + - @identity.errors.full_messages.each do |msg| + %p= msg + + .form-group + = f.label :provider, class: 'control-label' + .col-sm-10 + = f.select :provider, Gitlab::OAuth::Provider.names, { allow_blank: false }, class: 'form-control' + .form-group + = f.label :extern_uid, "Identifier", class: 'control-label' + .col-sm-10 + = f.text_field :extern_uid, required: true, class: 'form-control', required: true + + .form-actions + = f.submit 'Save changes', class: "btn btn-save" + diff --git a/app/views/admin/identities/_identity.html.haml b/app/views/admin/identities/_identity.html.haml new file mode 100644 index 00000000000..671c4fbc677 --- /dev/null +++ b/app/views/admin/identities/_identity.html.haml @@ -0,0 +1,12 @@ +%tr + %td + = identity.provider + %td + = identity.extern_uid + %td + = link_to edit_admin_user_identity_path(@user, identity), class: 'btn btn-xs btn-grouped' do + Edit + = link_to [:admin, @user, identity], method: :delete, + class: 'btn btn-xs btn-danger', + data: { confirm: "Are you sure you want to remove this identity?" } do + Delete diff --git a/app/views/admin/identities/edit.html.haml b/app/views/admin/identities/edit.html.haml new file mode 100644 index 00000000000..515d46b0f29 --- /dev/null +++ b/app/views/admin/identities/edit.html.haml @@ -0,0 +1,6 @@ +- page_title "Edit", @identity.provider, "Identities", @user.name, "Users" +%h3.page-title + Edit identity for #{@user.name} +%hr + += render 'form' diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml new file mode 100644 index 00000000000..ae57e3adc4d --- /dev/null +++ b/app/views/admin/identities/index.html.haml @@ -0,0 +1,13 @@ +- page_title "Identities", @user.name, "Users" += render 'admin/users/head' + +- if @identities.present? + %table.table + %thead + %tr + %th Provider + %th Identifier + %th + = render @identities +- else + %h4 This user has no identities diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml new file mode 100644 index 00000000000..9d5e934c8ba --- /dev/null +++ b/app/views/admin/users/_head.html.haml @@ -0,0 +1,23 @@ +%h3.page-title + = @user.name + - if @user.blocked? + %span.cred (Blocked) + - if @user.admin + %span.cred (Admin) + + .pull-right + = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do + %i.fa.fa-pencil-square-o + Edit +%hr +%ul.nav.nav-tabs + = nav_link(path: 'users#show') do + = link_to "Account", admin_user_path(@user) + = nav_link(path: 'users#groups') do + = link_to "Groups", groups_admin_user_path(@user) + = nav_link(path: 'users#projects') do + = link_to "Projects", projects_admin_user_path(@user) + = nav_link(path: 'users#keys') do + = link_to "SSH keys", keys_admin_user_path(@user) + = nav_link(controller: :identities) do + = link_to "Identities", admin_user_identities_path(@user) diff --git a/app/views/admin/users/groups.html.haml b/app/views/admin/users/groups.html.haml new file mode 100644 index 00000000000..dbecb7bbfd6 --- /dev/null +++ b/app/views/admin/users/groups.html.haml @@ -0,0 +1,19 @@ +- page_title "Groups", @user.name, "Users" += render 'admin/users/head' + +- if @user.group_members.present? + .panel.panel-default + .panel-heading Groups: + %ul.well-list + - @user.group_members.each do |group_member| + - group = group_member.group + %li.group_member + %span{class: ("list-item-name" unless group_member.owner?)} + %strong= link_to group.name, admin_group_path(group) + .pull-right + %span.light= group_member.human_access + - unless group_member.owner? + = link_to group_group_member_path(group, group_member), data: { confirm: remove_user_from_group_message(group, group_member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do + %i.fa.fa-times.fa-inverse +- else + .nothing-here-block This user has no groups. diff --git a/app/views/admin/users/keys.html.haml b/app/views/admin/users/keys.html.haml new file mode 100644 index 00000000000..07110717082 --- /dev/null +++ b/app/views/admin/users/keys.html.haml @@ -0,0 +1,3 @@ +- page_title "Keys", @user.name, "Users" += render 'admin/users/head' += render 'profiles/keys/key_table', admin: true diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml new file mode 100644 index 00000000000..0d7a1a25a80 --- /dev/null +++ b/app/views/admin/users/projects.html.haml @@ -0,0 +1,43 @@ +- page_title "Projects", @user.name, "Users" += render 'admin/users/head' + +- if @user.groups.any? + .panel.panel-default + .panel-heading Group projects + %ul.well-list + - @user.groups.each do |group| + %li + %strong= group.name + – access to + #{pluralize(group.projects.count, 'project')} + +.row + .col-md-6 + - if @personal_projects.present? + = render 'users/projects', projects: @personal_projects + - else + .nothing-here-block This user has no personal projects. + + + .col-md-6 + .panel.panel-default + .panel-heading Joined projects (#{@joined_projects.count}) + %ul.well-list + - @joined_projects.sort_by(&:name_with_namespace).each do |project| + - member = project.team.find_member(@user.id) + %li.project_member + .list-item-name + = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do + = project.name_with_namespace + + - if member + .pull-right + - if member.owner? + %span.light Owner + - else + %span.light= member.human_access + + - if member.respond_to? :project + = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_from_project_team_message(project, member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do + %i.fa.fa-times + diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 48cd22fc34b..2662b3569ec 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -1,240 +1,154 @@ - page_title @user.name, "Users" -%h3.page-title - User: - = @user.name - - if @user.blocked? - %span.cred (Blocked) - - if @user.admin - %span.cred (Admin) - - .pull-right - = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do - %i.fa.fa-pencil-square-o - Edit -%hr -%ul.nav.nav-tabs - %li.active - %a{"data-toggle" => "tab", href: "#account"} Account - %li - %a{"data-toggle" => "tab", href: "#profile"} Profile - %li - %a{"data-toggle" => "tab", href: "#groups"} Groups - %li - %a{"data-toggle" => "tab", href: "#projects"} Projects - %li - %a{"data-toggle" => "tab", href: "#ssh-keys"} SSH keys - -.tab-content - #account.tab-pane.active - .row - .col-md-6 - .panel.panel-default - .panel-heading - Account: - %ul.well-list - %li - %span.light Name: - %strong= @user.name - %li - %span.light Username: - %strong - = @user.username - %li - %span.light Email: - %strong - = mail_to @user.email - - @user.emails.each do |email| - %li - %span.light Secondary email: - %strong= email.email - = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-xs btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do - %i.fa.fa-times - - %li.two-factor-status - %span.light Two-factor Authentication: - %strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'} - - if @user.two_factor_enabled? - Enabled - - else - Disabled - - %li - %span.light Can create groups: - %strong - = @user.can_create_group ? "Yes" : "No" - %li - %span.light Personal projects limit: - %strong - = @user.projects_limit - %li - %span.light Member since: - %strong - = @user.created_at.stamp("Nov 12, 2031") - - if @user.confirmed_at - %li - %span.light Confirmed at: - %strong - = @user.confirmed_at.stamp("Nov 12, 2031") += render 'admin/users/head' + +.row + .col-md-6 + .panel.panel-default + .panel-heading + = @user.name + %ul.well-list + %li + = image_tag avatar_icon(@user.email, 60), class: "avatar s60" + %li + %span.light Profile page: + %strong + = link_to user_path(@user) do + = @user.username + = render 'users/profile', user: @user + + .panel.panel-default + .panel-heading + Account: + %ul.well-list + %li + %span.light Name: + %strong= @user.name + %li + %span.light Username: + %strong + = @user.username + %li + %span.light Email: + %strong + = mail_to @user.email + - @user.emails.each do |email| + %li + %span.light Secondary email: + %strong= email.email + = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-xs btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do + %i.fa.fa-times + + %li.two-factor-status + %span.light Two-factor Authentication: + %strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'} + - if @user.two_factor_enabled? + Enabled - else - %li - %span.light Confirmed: - %strong.cred - No - - %li - %span.light Current sign-in at: - %strong - - if @user.current_sign_in_at - = @user.current_sign_in_at.stamp("Nov 12, 2031") - - else - never - - %li - %span.light Last sign-in at: - %strong - - if @user.last_sign_in_at - = @user.last_sign_in_at.stamp("Nov 12, 2031") - - else - never - - %li - %span.light Sign-in count: - %strong - = @user.sign_in_count - - - if @user.ldap_user? - %li - %span.light LDAP uid: - %strong - = @user.ldap_identity.extern_uid - - - if @user.created_by - %li - %span.light Created by: - %strong - = link_to @user.created_by.name, [:admin, @user.created_by] - - .col-md-6 - - unless @user == current_user - - if @user.blocked? - .panel.panel-info - .panel-heading - This user is blocked - .panel-body - %p Blocking user has the following effects: - %ul - %li User will not be able to login - %li User will not be able to access git repositories - %li Personal projects will be left - %li Owned groups will be left - %br - = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } - - else - .panel.panel-warning - .panel-heading - Block this user - .panel-body - %p Blocking user has the following effects: - %ul - %li User will not be able to login - %li User will not be able to access git repositories - %li User will be removed from joined projects and groups - %li Personal projects will be left - %li Owned groups will be left - %br - = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning" - - .panel.panel-danger - .panel-heading - Remove user - .panel-body - - if @user.can_be_removed? - %p Deleting a user has the following effects: - %ul - %li All user content like authored issues, snippets, comments will be removed - - rp = @user.personal_projects.count - - unless rp.zero? - %li #{pluralize rp, 'personal project'} will be removed and cannot be restored - %br - = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" - - else - - if @user.solo_owned_groups.present? - %p - This user is currently an owner in these groups: - %strong #{@user.solo_owned_groups.map(&:name).join(', ')} - %p - You must transfer ownership or delete these groups before you can delete this user. + Disabled + + %li + %span.light Can create groups: + %strong + = @user.can_create_group ? "Yes" : "No" + %li + %span.light Personal projects limit: + %strong + = @user.projects_limit + %li + %span.light Member since: + %strong + = @user.created_at.stamp("Nov 12, 2031") + - if @user.confirmed_at + %li + %span.light Confirmed at: + %strong + = @user.confirmed_at.stamp("Nov 12, 2031") + - else + %li + %span.light Confirmed: + %strong.cred + No + + %li + %span.light Current sign-in at: + %strong + - if @user.current_sign_in_at + = @user.current_sign_in_at.stamp("Nov 12, 2031") + - else + never - #profile.tab-pane - .row - .col-md-6 - .panel.panel-default + %li + %span.light Last sign-in at: + %strong + - if @user.last_sign_in_at + = @user.last_sign_in_at.stamp("Nov 12, 2031") + - else + never + + %li + %span.light Sign-in count: + %strong + = @user.sign_in_count + + - if @user.ldap_user? + %li + %span.light LDAP uid: + %strong + = @user.ldap_identity.extern_uid + + - if @user.created_by + %li + %span.light Created by: + %strong + = link_to @user.created_by.name, [:admin, @user.created_by] + + .col-md-6 + - unless @user == current_user + - if @user.blocked? + .panel.panel-info .panel-heading - = @user.name - %ul.well-list - %li - = image_tag avatar_icon(@user.email, 60), class: "avatar s60" - %li - %span.light Profile page: - %strong - = link_to user_path(@user) do - = @user.username - .col-md-6 - = render 'users/profile', user: @user - - #groups.tab-pane - - if @user.group_members.present? - .panel.panel-default - .panel-heading Groups: - %ul.well-list - - @user.group_members.each do |group_member| - - group = group_member.group - %li.group_member - %span{class: ("list-item-name" unless group_member.owner?)} - %strong= link_to group.name, admin_group_path(group) - .pull-right - %span.light= group_member.human_access - - unless group_member.owner? - = link_to group_group_member_path(group, group_member), data: { confirm: remove_user_from_group_message(group, group_member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do - %i.fa.fa-times.fa-inverse - - else - .nothing-here-block This user has no groups. - - #projects.tab-pane - - if @user.groups.any? - .panel.panel-default - .panel-heading Group projects - %ul.well-list - - @user.groups.each do |group| - %li - %strong= group.name - – access to - #{pluralize(group.projects.count, 'project')} - - .row - .col-md-6 - = render 'users/projects', projects: @personal_projects - - .col-md-6 - .panel.panel-default - .panel-heading Joined projects (#{@joined_projects.count}) - %ul.well-list - - @joined_projects.sort_by(&:name_with_namespace).each do |project| - - member = project.team.find_member(@user.id) - %li.project_member - .list-item-name - = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do - = project.name_with_namespace - - - if member - .pull-right - - if member.owner? - %span.light Owner - - else - %span.light= member.human_access - - - if member.respond_to? :project - = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_from_project_team_message(project, member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do - %i.fa.fa-times - #ssh-keys.tab-pane - = render 'profiles/keys/key_table', admin: true + This user is blocked + .panel-body + %p Blocking user has the following effects: + %ul + %li User will not be able to login + %li User will not be able to access git repositories + %li Personal projects will be left + %li Owned groups will be left + %br + = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } + - else + .panel.panel-warning + .panel-heading + Block this user + .panel-body + %p Blocking user has the following effects: + %ul + %li User will not be able to login + %li User will not be able to access git repositories + %li User will be removed from joined projects and groups + %li Personal projects will be left + %li Owned groups will be left + %br + = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning" + + .panel.panel-danger + .panel-heading + Remove user + .panel-body + - if @user.can_be_removed? + %p Deleting a user has the following effects: + %ul + %li All user content like authored issues, snippets, comments will be removed + - rp = @user.personal_projects.count + - unless rp.zero? + %li #{pluralize rp, 'personal project'} will be removed and cannot be restored + %br + = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" + - else + - if @user.solo_owned_groups.present? + %p + This user is currently an owner in these groups: + %strong #{@user.solo_owned_groups.map(&:name).join(', ')} + %p + You must transfer ownership or delete these groups before you can delete this user. diff --git a/config/routes.rb b/config/routes.rb index d60bc796fdb..33f55dde476 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -149,7 +149,12 @@ Gitlab::Application.routes.draw do namespace :admin do resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do resources :keys, only: [:show, :destroy] + resources :identities, only: [:index, :edit, :update, :destroy] + member do + get :projects + get :keys + get :groups put :team_update put :block put :unblock diff --git a/features/admin/users.feature b/features/admin/users.feature index 1a8720dd77e..6755645778a 100644 --- a/features/admin/users.feature +++ b/features/admin/users.feature @@ -28,7 +28,7 @@ Feature: Admin Users When I submit modified user Then I see user attributes changed -@javascript + @javascript Scenario: Remove users secondary email Given I visit admin users page And I view the user with secondary email @@ -40,8 +40,26 @@ Feature: Admin Users Given user "Pete" with ssh keys And I visit admin users page And click on user "Pete" + And click on ssh keys tab Then I should see key list And I click on the key title Then I should see key details And I click on remove key Then I should see the key removed + + Scenario: Show user identities + Given user "Pete" with twitter account + And I visit "Pete" identities page in admin + Then I should see twitter details + + Scenario: Update user identities + Given user "Pete" with twitter account + And I visit "Pete" identities page in admin + And I modify twitter identity + Then I should see twitter details updated + + Scenario: Remove user identities + Given user "Pete" with twitter account + And I visit "Pete" identities page in admin + And I remove twitter identity + Then I should not see twitter details diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb index 34a3ed9f615..6c4b91586d6 100644 --- a/features/steps/admin/users.rb +++ b/features/steps/admin/users.rb @@ -114,4 +114,45 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps step 'I should see the key removed' do expect(page).not_to have_content 'ssh-rsa Key2' end + + step 'user "Pete" with twitter account' do + @user = create(:user, name: 'Pete') + @user.identities.create!(extern_uid: '123456', provider: 'twitter') + end + + step 'I visit "Pete" identities page in admin' do + allow(Gitlab::OAuth::Provider).to receive(:names).and_return(%w(twitter twitter_updated)) + visit admin_user_identities_path(@user) + end + + step 'I should see twitter details' do + expect(page).to have_content 'Pete' + expect(page).to have_content 'twitter' + end + + step 'I modify twitter identity' do + find('.table').find(:link, 'Edit').click + fill_in 'identity_extern_uid', with: '654321' + select 'twitter_updated', from: 'identity_provider' + click_button 'Save changes' + end + + step 'I should see twitter details updated' do + expect(page).to have_content 'Pete' + expect(page).to have_content 'twitter_updated' + expect(page).to have_content '654321' + end + + step 'I remove twitter identity' do + click_link 'Delete' + end + + step 'I should not see twitter details' do + expect(page).to have_content 'Pete' + expect(page).to_not have_content 'twitter' + end + + step 'click on ssh keys tab' do + click_link 'SSH keys' + end end diff --git a/lib/gitlab/o_auth/provider.rb b/lib/gitlab/o_auth/provider.rb new file mode 100644 index 00000000000..f986499a27c --- /dev/null +++ b/lib/gitlab/o_auth/provider.rb @@ -0,0 +1,19 @@ +module Gitlab + module OAuth + class Provider + def self.names + providers = [] + + Gitlab.config.ldap.servers.values.each do |server| + providers << server['provider_name'] + end + + Gitlab.config.omniauth.providers.each do |provider| + providers << provider['name'] + end + + providers + end + end + end +end |