GRN2-260: Added the ability to merge user accounts (#938)

* Added the ability to merge user accounts

* Styling fixes
This commit is contained in:
Ahmad Farhat 2020-02-14 10:24:06 -05:00 committed by GitHub
parent 31258272c2
commit 005c738e4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 222 additions and 5 deletions

View File

@ -41,6 +41,49 @@ $(document).on('turbolinks:load', function(){
updateTabParams(this.id)
})
$('.selectpicker').selectpicker({
liveSearchPlaceholder: getLocalizedString('javascript.search.start')
});
// Fixes turbolinks issue with bootstrap select
$(window).trigger('load.bs.select.data-api');
// Display merge accounts modal with correct info
$(".merge-user").click(function() {
// Update the path of save button
$("#merge-save-access").attr("data-path", $(this).data("path"))
let userInfo = $(this).data("info")
$("#merge-to").html("<span>" + userInfo.name + "</span>" + "<span class='text-muted d-block'>" + userInfo.email + "</span>" + "<span class='text-muted d-block'>" + userInfo.uid + "</span>")
})
$("#mergeUserModal").on("show.bs.modal", function() {
$(".selectpicker").selectpicker('val','')
})
$(".bootstrap-select").on("click", function() {
$(".bs-searchbox").siblings().hide()
})
$(".bs-searchbox input").on("input", function() {
if ($(".bs-searchbox input").val() == '' || $(".bs-searchbox input").val().length < 3) {
$(".bs-searchbox").siblings().hide()
} else {
$(".bs-searchbox").siblings().show()
}
})
// User selects an option from the Room Access dropdown
$(".bootstrap-select").on("changed.bs.select", function(){
// Get the uid of the selected user
let user = $(".selectpicker").selectpicker('val')
if (user != "") {
userInfo = JSON.parse(user)
$("#merge-from").html("<span>" + userInfo.name + "</span>" + "<span class='text-muted d-block'>" + userInfo.email + "</span>" + "<span id='from-uid' class='text-muted d-block'>" + userInfo.uid + "</span>")
}
})
}
else if(action == "site_settings"){
loadColourSelectors()
@ -79,6 +122,11 @@ function changeBrandingImage(path) {
$.post(path, {value: url})
}
function mergeUsers() {
let userToMerge = $("#from-uid").text()
$.post($("#merge-save-access").data("path"), {merge: userToMerge})
}
// Filters by role
function filterRole(role) {
var search = new URL(location.href).searchParams.get('search')

View File

@ -69,7 +69,7 @@ $(document).on('turbolinks:load', function(){
})
$('.selectpicker').selectpicker({
liveSearchPlaceholder: "Start searching..."
liveSearchPlaceholder: getLocalizedString('javascript.search.start')
});
// Fixes turbolinks issue with bootstrap select
$(window).trigger('load.bs.select.data-api');

View File

@ -88,4 +88,12 @@
&:hover {
cursor: pointer;
}
}
#merge-account-arrow {
position: absolute;
top: 47%;
right: 47%;
z-index: 999;
background: white;
}

View File

@ -24,7 +24,7 @@ class AdminsController < ApplicationController
include Rolify
include Populator
manage_users = [:edit_user, :promote, :demote, :ban_user, :unban_user, :approve, :reset]
manage_users = [:edit_user, :promote, :demote, :ban_user, :unban_user, :approve, :reset, :merge_user]
manage_deleted_users = [:undelete]
authorize_resource class: false
before_action :find_user, only: manage_users
@ -41,6 +41,8 @@ class AdminsController < ApplicationController
@role = params[:role] ? Role.find_by(name: params[:role], provider: @user_domain) : nil
@tab = params[:tab] || "active"
@user_list = merge_user_list
@pagy, @users = pagy(manage_users_list)
end
@ -140,6 +142,45 @@ class AdminsController < ApplicationController
redirect_to redirect_path, flash: { success: I18n.t("administrator.flash.reset_password") }
end
# POST /admins/merge/:user_uid
def merge_user
begin
# Get uid of user that will be merged into the other account
uid_to_merge = params[:merge]
logger.info "#{current_user.uid} is attempting to merge #{uid_to_merge} into #{@user.uid}"
# Check to make sure the 2 users are unique
raise "Can not merge the user into themself" if uid_to_merge == @user.uid
# Find user to merge
user_to_merge = User.find_by(uid: uid_to_merge)
# Move over user's rooms
user_to_merge.rooms.each do |room|
room.owner = @user
room.name = "(#{I18n.t('merged')}) #{room.name}"
room.save!
end
# Reload user to update merge rooms
user_to_merge.reload
# Delete merged user
user_to_merge.destroy(true)
rescue => e
logger.info "Failed to merge #{uid_to_merge} into #{@user.uid}: #{e}"
flash[:alert] = I18n.t("administrator.flash.merge_fail")
else
logger.info "#{current_user.uid} successfully merged #{uid_to_merge} into #{@user.uid}"
flash[:success] = I18n.t("administrator.flash.merge_success")
end
redirect_to admins_path
end
# SITE SETTINGS
# POST /admins/update_settings

View File

@ -77,7 +77,18 @@ module Populator
roles_can_appear << role.name if role.get_permission("can_appear_in_share_list") && role.name != "super_admin"
end
initial_list = User.where.not(uid: current_user.uid).with_highest_priority_role(roles_can_appear)
initial_list = User.where.not(uid: current_user.uid)
.without_role(:pending)
.without_role(:denied)
.with_highest_priority_role(roles_can_appear)
return initial_list unless Rails.configuration.loadbalanced_configuration
initial_list.where(provider: @user_domain)
end
# Returns a list of users that can merged into another user
def merge_user_list
initial_list = User.where.not(uid: current_user.uid).without_role(:super_admin)
return initial_list unless Rails.configuration.loadbalanced_configuration
initial_list.where(provider: @user_domain)

View File

@ -36,7 +36,7 @@ class Ability
if highest_role.get_permission("can_manage_users")
can [:index, :roles, :edit_user, :promote, :demote, :ban_user, :unban_user,
:approve, :invite, :reset, :undelete], :admin
:approve, :invite, :reset, :undelete, :merge_user], :admin
end
can [:index, :server_recordings, :server_rooms], :admin if highest_role.get_permission("can_manage_rooms_recordings")

View File

@ -60,6 +60,11 @@ class Room < ApplicationRecord
user.rooms.include?(self)
end
# Determines whether room is a home room
def home_room?
owner.main_room == self
end
def shared_users
User.where(id: shared_access.pluck(:user_id))
end

View File

@ -124,6 +124,9 @@
<%= link_to admin_edit_user_path(user_uid: user.uid), class: "dropdown-item" do %>
<i class="dropdown-icon fas fa-user-edit"></i> <%= t("administrator.users.settings.edit") %>
<% end %>
<button class= "merge-user dropdown-item" data-path="<%= merge_user_path(user_uid: user.uid) %>" data-info="<%= user.slice(:name, :email, :uid).to_json %>" data-toggle="modal" data-target="#mergeUserModal">
<i class="dropdown-icon fas fa-user-friends"></i> <%= t("administrator.users.settings.merge") %>
</button>
<%= button_to admin_ban_path(user_uid: user.uid), class: "dropdown-item", "data-disable": "" do %>
<i class="dropdown-icon fas fa-lock"></i> <%= t("administrator.users.settings.ban") %>
<% end %>
@ -157,3 +160,4 @@
<%= render "shared/modals/invite_user_modal" %>
<%= render "shared/modals/delete_account_modal", delete_location: relative_root %>
<%= render "shared/modals/merge_user_modal" %>

View File

@ -0,0 +1,51 @@
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<div class="modal fade" id="mergeUserModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content text-center">
<div class="modal-body">
<div class="card-body p-6">
<div class="card-title">
<h3><%= t("modal.merge_user.title") %></h3>
</div>
<select class="selectpicker" title="<%= t("modal.share_access.select") %>" data-live-search="true" data-virtual-scroll="true" >
<% @user_list.each do |user| %>
<option value="<%= { uid: user.uid, email: user.email, name: user.name }.to_json %>" data-subtext="<%= user.email %>" ><%= user.name %></option>
<% end %>
</select>
<div class="mt-5 text-left row">
<div class="list-group-item col-6 text-center">
<label class="form-label text-primary"><%= t("modal.merge_user.from") %></label>
<div id="merge-from"></div>
</div>
<i id="merge-account-arrow" class="fas fa-2x fa-arrow-circle-right text-primary"></i>
<div class="list-group-item col-6 text-center">
<label class="form-label text-primary"><%= t("modal.merge_user.to") %></label>
<div id="merge-to"></div>
</div>
</div>
<div class="mt-6">
<button id="merge-save-access" class="btn btn-primary btn-block" onclick="mergeUsers()" ><%= t("modal.merge_user.save") %></button>
<button class="btn btn-secondary text-primary btn-block" onclick="$('#mergeUserModal').modal('hide')"><%= t("modal.merge_user.cancel") %></button>
</div>
</div>
<div class="card-footer">
<p><%= t("modal.merge_user.footer") %></p>
</div>
</div>
</div>
</div>
</div>

View File

@ -21,7 +21,7 @@
<div class="card-title">
<h3><%= t("modal.share_access.title") %></h3>
</div>
<select class="selectpicker" title="<%= t("modal.share_access.select") %>..." data-live-search="true" data-virtual-scroll="true" >
<select class="selectpicker" title="<%= t("modal.share_access.select") %>" data-live-search="true" data-virtual-scroll="true" >
<% @user_list.each do |user| %>
<option value="<%= user.uid %>" data-subtext="<%= user.uid %>" ><%= user.name %></option>
<% end %>

View File

@ -84,6 +84,8 @@ en:
demoted: User has been successfully demoted
invite: Invite successfully sent to %{email}
invite_email_verification: Emails must be enabled in order to use this method. Please contact your system administrator.
merge_fail: There was an issue merging the user accounts. Please check the users selected and try again
merge_success: User accounts merged successfully
perm_deleted: User has been permanently deleted
promoted: User has been successfully promoted
registration_method_updated: Registration method successfully updated
@ -245,6 +247,8 @@ en:
body: 'To view the recording, follow the link below:'
autogenerated: 'This e-mail is auto-generated by BigBlueButton.'
footer: 'BigBlueButton is an open source web conferencing system. For more information on BigBlueButton, see https://bigbluebutton.org/.'
search:
start: Start searching...
landing:
about: "%{href} is a simple front-end for your BigBlueButton open-source web conferencing server. You can create your own rooms to host sessions, or join others using a short and convenient link."
welcome: Welcome to BigBlueButton.
@ -308,6 +312,7 @@ en:
maintenance:
window_alert: Maintenance window scheduled for %{date}
max_concurrent: The maximum number of concurrent sessions allowed has been reached!
merged: Merged
modal:
create_role:
create: Create a new Role
@ -368,6 +373,13 @@ en:
save: Save Changes
cancel_changes: Cancel Changes
select: Select User
merge_user:
cancel: Cancel
from: Account to be Merged
title: Merge User Accounts
to: Primary Account
save: Merge
footer: The rooms of the account to be merged will be transfered over to the Primary Account's room list and then the account will be deleted.
name_update_success: Room name successfully changed!
no_user_email_exists: There is no existing user with the email specified. Please make sure you typed it correctly.
omniauth_error: An error occured while authenticating with omniauth. Please try again or contact an administrator!

View File

@ -50,6 +50,7 @@ Rails.application.routes.draw do
post '/approve/:user_uid', to: 'admins#approve', as: :admin_approve
get '/reset', to: 'admins#reset', as: :admin_reset
post '/undelete', to: 'admins#undelete', as: :admin_undelete
post '/merge/:user_uid', to: 'admins#merge_user', as: :merge_user
# Site Settings
post '/update_settings', to: 'admins#update_settings', as: :admin_update_settings
post '/registration_method', to: 'admins#registration_method', as: :admin_change_registration

View File

@ -197,6 +197,42 @@ describe AdminsController, type: :controller do
expect(response).to redirect_to(admins_path)
end
end
context "POST #merge_user" do
it "merges the users room to the primary account and deletes the old user" do
@request.session[:user_id] = @admin.id
@user2 = create(:user)
room1 = create(:room, owner: @user2)
room2 = create(:room, owner: @user2)
room3 = @user2.main_room
post :merge_user, params: { user_uid: @user.uid, merge: @user2.uid }
room1.reload
room2.reload
room3.reload
expect(User.exists?(uid: @user2.uid)).to be false
expect(room1.name).to start_with("(Merged)")
expect(room2.name).to start_with("(Merged)")
expect(room3.name).to start_with("(Merged)")
expect(room1.owner).to eq(@user)
expect(room2.owner).to eq(@user)
expect(room3.owner).to eq(@user)
expect(flash[:success]).to be_present
expect(response).to redirect_to(admins_path)
end
it "does not merge if trying to merge the same user into themself" do
@request.session[:user_id] = @admin.id
post :merge_user, params: { user_uid: @user.uid, merge: @user.uid }
expect(flash[:alert]).to be_present
expect(response).to redirect_to(admins_path)
end
end
end
describe "User Design" do