diff --git a/.rubocop.yml b/.rubocop.yml
index b3e193c2..0f39657b 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -9,6 +9,9 @@ AllCops:
Bundler/OrderedGems:
Enabled: false
+Style/BlockDelimiters:
+ Enabled: false
+
# Checks if uses of quotes match the configured preference.
Style/StringLiterals:
Enabled: false
@@ -97,6 +100,9 @@ Layout/AlignArguments:
Layout/IndentationWidth:
Enabled: false
+Layout/CaseIndentation:
+ Enabled: false
+
# Checks for ambiguous block association with method when param passed without parentheses.
Lint/AmbiguousBlockAssociation:
Enabled: false
diff --git a/app/assets/javascripts/admins.js b/app/assets/javascripts/admins.js
index a7a4245e..13a64baf 100644
--- a/app/assets/javascripts/admins.js
+++ b/app/assets/javascripts/admins.js
@@ -21,17 +21,6 @@ $(document).on('turbolinks:load', function(){
// Only run on the admins page.
if (controller == "admins") {
if(action == "index") {
- // show the modal with the correct form action url
- $(".delete-user").click(function(data){
- var uid = $(data.target).closest("tr").data("user-uid")
- var url = $("body").data("relative-root")
- if (!url.endsWith("/")) {
- url += "/"
- }
- url += "u/" + uid
- $("#delete-confirm").parent().attr("action", url)
- })
-
//clear the role filter if user clicks on the x
$(".clear-role").click(function() {
var search = new URL(location.href).searchParams.get('search')
@@ -44,6 +33,14 @@ $(document).on('turbolinks:load', function(){
window.location.replace(url);
})
+
+ // Handle selected user tags
+ $(".manage-users-tab").click(function() {
+ $(".manage-users-tab").removeClass("selected")
+ $(this).addClass("selected")
+
+ updateTabParams(this.id)
+ })
}
else if(action == "site_settings"){
loadColourSelectors()
@@ -95,6 +92,20 @@ function filterRole(role) {
window.location.replace(url);
}
+function updateTabParams(tab) {
+ var search_params = new URLSearchParams(window.location.search)
+
+ if (window.location.href.includes("tab=")) {
+ search_params.set("tab", tab)
+ } else {
+ search_params.append("tab", tab)
+ }
+
+ search_params.delete("page")
+
+ window.location.search = search_params.toString()
+}
+
function loadColourSelectors() {
const pickrRegular = new Pickr({
el: '#colorinput-regular',
diff --git a/app/assets/javascripts/delete.js b/app/assets/javascripts/delete.js
new file mode 100644
index 00000000..1b8ef556
--- /dev/null
+++ b/app/assets/javascripts/delete.js
@@ -0,0 +1,57 @@
+// 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 .
+
+$(document).on('turbolinks:load', function(){
+ var controller = $("body").data('controller');
+ var action = $("body").data('action');
+
+ // Only run on the admins page.
+ if (controller == "admins" && action == "index") {
+ // show the modal with the correct form action url
+ $(".delete-user").click(function(){
+ $("#delete-confirm").parent().attr("action", $(this).data("path"))
+
+ if ($(this).data("delete") == "temp-delete") {
+ $("#perm-delete").hide()
+ $("#delete-warning").show()
+ } else {
+ $("#perm-delete").show()
+ $("#delete-warning").hide()
+ }
+ })
+ }
+
+ $(".delete-user").click(function(data){
+ document.getElementById("delete-checkbox").checked = false
+ $("#delete-confirm").prop("disabled", "disabled")
+
+ if ($(data.target).data("delete") == "temp-delete") {
+ $("#perm-delete").hide()
+ $("#delete-warning").show()
+ } else {
+ $("#perm-delete").show()
+ $("#delete-warning").hide()
+ }
+ })
+
+ $("#delete-checkbox").click(function(data){
+ if (document.getElementById("delete-checkbox").checked) {
+ $("#delete-confirm").removeAttr("disabled")
+ } else {
+ $("#delete-confirm").prop("disabled", "disabled")
+ }
+ })
+})
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
index 9c8e0c5c..f0da579c 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/search.js
@@ -69,10 +69,12 @@ function searchPage() {
// Check if the user filtered by role
var role = new URL(location.href).searchParams.get('role')
+ var tab = new URL(location.href).searchParams.get('tab')
var url = window.location.pathname + "?page=1&search=" + search
if (role) { url += "&role=" + role }
+ if (tab) { url += "&tab=" + tab }
window.location.replace(addRecordingTable(url));
}
@@ -80,12 +82,16 @@ function searchPage() {
// Clears the search bar
function clearSearch() {
var role = new URL(location.href).searchParams.get('role')
+ var tab = new URL(location.href).searchParams.get('tab')
var url = window.location.pathname + "?page=1"
if (role) { url += "&role=" + role }
+ if (tab) { url += "&tab=" + tab }
window.location.replace(addRecordingTable(url));
+
+ var search_params = new URLSearchParams(window.location.search)
}
function addRecordingTable(url) {
diff --git a/app/assets/stylesheets/admins.scss b/app/assets/stylesheets/admins.scss
index 6fe0260a..2265448d 100644
--- a/app/assets/stylesheets/admins.scss
+++ b/app/assets/stylesheets/admins.scss
@@ -75,8 +75,17 @@
.custom-role-tag{
color: white !important;
+ // Make it consistent with the manage users tab tags
+ padding-top: 1px;
+ padding-bottom: 1px;
}
.user-role-tag{
color: white !important;
+}
+
+.manage-users-tab {
+ &:hover {
+ cursor: pointer;
+ }
}
\ No newline at end of file
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index d91d28c6..3f8df722 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -175,3 +175,7 @@ table {
.cursor-pointer{
cursor: pointer;
}
+
+#delete-confirm:disabled {
+ cursor: not-allowed;
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/utilities/_primary_themes.scss b/app/assets/stylesheets/utilities/_primary_themes.scss
index b23f6db4..6c8eb8f3 100644
--- a/app/assets/stylesheets/utilities/_primary_themes.scss
+++ b/app/assets/stylesheets/utilities/_primary_themes.scss
@@ -182,4 +182,14 @@ input:focus, select:focus {
.custom-switch-input:focus ~ .custom-switch-indicator {
box-shadow: 0 0 0 2px $primary-color-lighten;
border-color: $primary-color-darken !important;
+}
+
+.custom-control-label::before {
+ border-color: $primary-color-darken !important;
+}
+
+.manage-users-tab {
+ &.selected {
+ @extend .btn-primary;
+ }
}
\ No newline at end of file
diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb
index 41e7e9f8..24fd1843 100644
--- a/app/controllers/admins_controller.rb
+++ b/app/controllers/admins_controller.rb
@@ -24,10 +24,11 @@ class AdminsController < ApplicationController
include Rolify
manage_users = [:edit_user, :promote, :demote, :ban_user, :unban_user, :approve, :reset]
-
+ manage_deleted_users = [:undelete]
authorize_resource class: false
before_action :find_user, only: manage_users
- before_action :verify_admin_of_user, only: manage_users
+ before_action :find_deleted_user, only: manage_deleted_users
+ before_action :verify_admin_of_user, only: [manage_users, manage_deleted_users]
# GET /admins
def index
@@ -37,6 +38,7 @@ class AdminsController < ApplicationController
@order_direction = params[:direction] && params[:direction] != "none" ? params[:direction] : "DESC"
@role = params[:role] ? Role.find_by(name: params[:role], provider: @user_domain) : nil
+ @tab = params[:tab] || "active"
@pagy, @users = pagy(user_list)
end
@@ -88,6 +90,15 @@ class AdminsController < ApplicationController
redirect_to admins_path, flash: { success: I18n.t("administrator.flash.approved") }
end
+ # POST /admins/approve/:user_uid
+ def undelete
+ # Undelete the user and all of his rooms
+ @user.undelete!
+ @user.rooms.deleted.each(&:undelete!)
+
+ redirect_to admins_path, flash: { success: I18n.t("administrator.flash.restored") }
+ end
+
# POST /admins/invite
def invite
emails = params[:invite_user][:email].split(",")
@@ -208,6 +219,10 @@ class AdminsController < ApplicationController
@user = User.where(uid: params[:user_uid]).includes(:roles).first
end
+ def find_deleted_user
+ @user = User.deleted.where(uid: params[:user_uid]).includes(:roles).first
+ end
+
# Verifies that admin is an administrator of the user in the action
def verify_admin_of_user
redirect_to admins_path,
@@ -216,18 +231,31 @@ class AdminsController < ApplicationController
# Gets the list of users based on your configuration
def user_list
+ current_role = @role
+
+ initial_user = case @tab
+ when "active"
+ User.without_role(:pending).without_role(:denied)
+ when "deleted"
+ User.deleted
+ else
+ User
+ end
+
+ current_role = Role.find_by(name: @tab, provider: @user_domain) if @tab == "pending" || @tab == "denied"
+
initial_list = if current_user.has_role? :super_admin
- User.where.not(id: current_user.id)
+ initial_user.where.not(id: current_user.id)
else
- User.without_role(:super_admin).where.not(id: current_user.id)
+ initial_user.without_role(:super_admin).where.not(id: current_user.id)
end
if Rails.configuration.loadbalanced_configuration
initial_list.where(provider: @user_domain)
- .admins_search(@search, @role)
+ .admins_search(@search, current_role)
.admins_order(@order_column, @order_direction)
else
- initial_list.admins_search(@search, @role)
+ initial_list.admins_search(@search, current_role)
.admins_order(@order_column, @order_direction)
end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 05a79fca..9d713079 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -63,17 +63,22 @@ class SessionsController < ApplicationController
def create
logger.info "Support: #{session_params[:email]} is attempting to login."
- admin = User.find_by(email: session_params[:email])
- if admin&.has_role? :super_admin
- user = admin
- else
- user = User.find_by(email: session_params[:email], provider: @user_domain)
- redirect_to(signin_path, alert: I18n.t("invalid_credentials")) && return unless user
- redirect_to(root_path, alert: I18n.t("invalid_login_method")) && return unless user.greenlight_account?
- redirect_to(account_activation_path(email: user.email)) && return unless user.activated?
- end
- redirect_to(signin_path, alert: I18n.t("invalid_credentials")) && return unless user.try(:authenticate,
+ user = User.include_deleted.find_by(email: session_params[:email], provider: @user_domain)
+
+ # Check user with that email exists
+ return redirect_to(signin_path, alert: I18n.t("invalid_credentials")) unless user
+ # Check correct password was entered
+ return redirect_to(signin_path, alert: I18n.t("invalid_credentials")) unless user.try(:authenticate,
session_params[:password])
+ # Check that the user is not deleted
+ return redirect_to root_path, flash: { alert: I18n.t("registration.banned.fail") } if user.deleted?
+
+ unless user.has_role? :super_admin
+ # Check that the user is a Greenlight account
+ return redirect_to(root_path, alert: I18n.t("invalid_login_method")) unless user.greenlight_account?
+ # Check that the user has verified their account
+ return redirect_to(account_activation_path(email: user.email)) unless user.activated?
+ end
login(user)
end
@@ -153,8 +158,19 @@ class SessionsController < ApplicationController
end
def check_user_exists
- provider = @auth['provider'] == "bn_launcher" ? @auth['info']['customer'] : @auth['provider']
- User.exists?(social_uid: @auth['uid'], provider: provider)
+ User.exists?(social_uid: @auth['uid'], provider: current_provider)
+ end
+
+ def check_user_deleted(email)
+ User.deleted.exists?(email: email, provider: @user_domain)
+ end
+
+ def check_auth_deleted
+ User.deleted.exists?(social_uid: @auth['uid'], provider: current_provider)
+ end
+
+ def current_provider
+ @auth['provider'] == "bn_launcher" ? @auth['info']['customer'] : @auth['provider']
end
# Check if the user already exists, if not then check for invitation
@@ -172,6 +188,9 @@ class SessionsController < ApplicationController
return redirect_to root_path, flash: { alert: I18n.t("registration.deprecated.twitter_signup") }
end
+ # Check if user is deleted
+ return redirect_to root_path, flash: { alert: I18n.t("registration.banned.fail") } if check_auth_deleted
+
# If using invitation registration method, make sure user is invited
return redirect_to root_path, flash: { alert: I18n.t("registration.invite.no_invite") } unless passes_invite_reqs
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 6bffe8ad..846c714d 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -24,7 +24,7 @@ class UsersController < ApplicationController
include Recorder
include Rolify
- before_action :find_user, only: [:edit, :change_password, :delete_account, :update, :destroy]
+ before_action :find_user, only: [:edit, :change_password, :delete_account, :update]
before_action :ensure_unauthenticated_except_twitter, only: [:create]
before_action :check_user_signup_allowed, only: [:create]
before_action :check_admin_of, only: [:edit, :change_password, :delete_account]
@@ -122,22 +122,41 @@ class UsersController < ApplicationController
# DELETE /u/:user_uid
def destroy
+ # Include deleted users in the check
+ @user = User.include_deleted.find_by(uid: params[:user_uid])
+
logger.info "Support: #{current_user.email} is deleting #{@user.email}."
self_delete = current_user == @user
+ redirect_url = self_delete ? root_path : admins_path
+
begin
if current_user && (self_delete || current_user.admin_of?(@user))
- @user.destroy
+ # Permanently delete if the user is deleting themself
+ perm_delete = self_delete || (params[:permanent].present? && params[:permanent] == "true")
+
+ # Permanently delete the rooms under the user if they have not been reassigned
+ if perm_delete
+ @user.rooms.include_deleted.each do |room|
+ room.destroy(true)
+ end
+ end
+
+ @user.destroy(perm_delete)
+
+ # Log the user out if they are deleting themself
session.delete(:user_id) if self_delete
- return redirect_to admins_path, flash: { success: I18n.t("administrator.flash.delete") } unless self_delete
+ return redirect_to redirect_url, flash: { success: I18n.t("administrator.flash.delete") } unless self_delete
+ else
+ flash[:alert] = I18n.t("administrator.flash.delete_fail")
end
rescue => e
logger.error "Support: Error in user deletion: #{e}"
flash[:alert] = I18n.t(params[:message], default: I18n.t("administrator.flash.delete_fail"))
end
- redirect_to root_path
+ redirect_to redirect_url
end
# GET /u/:user_uid/recordings
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 8be21b91..649bf02e 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -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], :admin
+ :approve, :invite, :reset, :undelete], :admin
end
if !highest_role.get_permission("can_edit_site_settings") && !highest_role.get_permission("can_edit_roles") &&
diff --git a/app/models/concerns/deleteable.rb b/app/models/concerns/deleteable.rb
index c74a7aa1..0907ea1b 100644
--- a/app/models/concerns/deleteable.rb
+++ b/app/models/concerns/deleteable.rb
@@ -26,20 +26,28 @@ module Deleteable
scope :deleted, -> { include_deleted.where(deleted: true) }
end
- def destroy
- run_callbacks :destroy
- update_attribute(:deleted, true)
+ def destroy(permanent = false)
+ if permanent
+ super()
+ else
+ run_callbacks :destroy do end
+ update_attribute(:deleted, true)
+ end
end
- def delete
- destroy
- end
-
- def undelete
- assign_attributes(deleted: false)
+ def delete(permanent = false)
+ destroy(permanent)
end
def undelete!
update_attribute(:deleted, false)
end
+
+ def permanent_delete
+ destroy(true)
+ end
+
+ def deleted?
+ deleted
+ end
end
diff --git a/app/views/admins/components/_admins_tags.html.erb b/app/views/admins/components/_admins_tags.html.erb
index 27f46b40..01b5fea4 100644
--- a/app/views/admins/components/_admins_tags.html.erb
+++ b/app/views/admins/components/_admins_tags.html.erb
@@ -13,7 +13,7 @@
# with BigBlueButton; if not, see .
%>
-