diff --git a/Gemfile b/Gemfile index a58166de..5ce666f0 100644 --- a/Gemfile +++ b/Gemfile @@ -57,6 +57,7 @@ gem 'bigbluebutton-api-ruby' # Front-end. gem 'bootstrap', '~> 4.3.1' gem 'tabler-rubygem' +gem 'pagy' # For detecting the users preferred language. gem 'http_accept_language' @@ -70,6 +71,11 @@ gem 'redcarpet' # For health check endpoint gem "health_check" +# For providing user roles +gem "rolify" +# For limiting access based on user roles +gem 'cancancan', '~> 2.0' + group :production do # Use a postgres database in production. gem 'pg', '~> 0.18' diff --git a/Gemfile.lock b/Gemfile.lock index db2fbd1f..88d24127 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -56,6 +56,7 @@ GEM sassc-rails (>= 2.0.0) builder (3.2.3) byebug (10.0.2) + cancancan (2.3.0) coffee-rails (4.2.2) coffee-script (>= 2.2.0) railties (>= 4.0.0) @@ -174,6 +175,7 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack + pagy (2.1.5) parallel (1.17.0) parser (2.6.3.0) ast (~> 2.4.0) @@ -226,6 +228,7 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) + rolify (5.2.0) rspec-core (3.7.1) rspec-support (~> 3.7.0) rspec-expectations (3.7.0) @@ -336,6 +339,7 @@ DEPENDENCIES bigbluebutton-api-ruby bootstrap (~> 4.3.1) byebug + cancancan (~> 2.0) coffee-rails (~> 4.2) coveralls dotenv-rails @@ -353,6 +357,7 @@ DEPENDENCIES omniauth-ldap omniauth-microsoft-office365 (~> 0.0.7) omniauth-twitter + pagy pg (~> 0.18) puma (~> 3.0) rails (~> 5.0.7) @@ -361,6 +366,7 @@ DEPENDENCIES redcarpet redis (~> 3.0) remote_syslog_logger + rolify rspec-rails (~> 3.7) rubocop sass-rails (~> 5.0) diff --git a/app/assets/javascripts/admins.js b/app/assets/javascripts/admins.js new file mode 100644 index 00000000..4a117e9c --- /dev/null +++ b/app/assets/javascripts/admins.js @@ -0,0 +1,97 @@ +// 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(data){ + var uid = $(data.target).closest("tr").data("user-uid") + $("#delete-confirm").parent().attr("action", "/u/" + uid) + }) + + // Change the color of the color inputs when the color is changed + $(".colorinput-input").change(function(data) { + // Get the color from the input + var color = $(data.target).val() + + // Update the color in the database and reload the page + $.post($("#coloring-path").val(), {color: color}).done(function(data) { + location.reload() + }); + }); + + // Submit search if the user hits enter + $("#search-input").keypress(function(key) { + var keyPressed = key.which + if (keyPressed == 13) { + searchPage() + } + }) + + // Add listeners for sort + $("th[data-order]").click(function(data){ + var header_elem = $(data.target) + + if(header_elem.data('order') === 'asc'){ // asc + header_elem.data('order', 'desc'); + } + else if(header_elem.data('order') === 'desc'){ // desc + header_elem.data('order', 'none'); + } + else{ // none + header_elem.data('order', 'asc'); + } + + var search = $("#search-input").val() + window.location.replace(window.location.pathname + "?page=1&search=" + search + "&column=" + header_elem.data("header") + "&direction="+ header_elem.data('order')) + }) + } + + // Only run on the admins edit user page. + if (controller == "admins" && action == "edit_user") { + $("#users").click(function(data){ + var url = $("body").data("relative-root") + if (!url.endsWith("/")) { + url += "/" + } + url += "admins" + + window.location.href = url + }) + } +}); + +// Change the branding image to the image provided +function changeBrandingImage(path) { + var url = $("#branding-url").val() + $.post(path, {url: url}) +} + +// Searches the user table for the given string +function searchPage() { + var search = $("#search-input").val() + + window.location.replace(window.location.pathname + "?page=1&search=" + search) +} + +// Clears the search bar +function clearSearch() { + window.location.replace(window.location.pathname + "?page=1") +} diff --git a/app/assets/javascripts/room.js b/app/assets/javascripts/room.js index 93dbd5cc..e229c7d4 100644 --- a/app/assets/javascripts/room.js +++ b/app/assets/javascripts/room.js @@ -57,7 +57,7 @@ $(document).on('turbolinks:load', function(){ } // Display and update all fields related to creating a room in the createRoomModal - $("#create-room").click(function(){ + $("#create-room-block").click(function(){ $("#create-room-name").val("") $("#createRoomModal form").attr("action", $("body").data('relative-root')) updateDropdown($(".dropdown-item[value='default']")) diff --git a/app/assets/javascripts/settings.js b/app/assets/javascripts/settings.js index 42b7418e..bacd22b4 100644 --- a/app/assets/javascripts/settings.js +++ b/app/assets/javascripts/settings.js @@ -20,7 +20,7 @@ $(document).on('turbolinks:load', function(){ var action = $("body").data('action'); // Only run on the settings page. - if ((controller == "users" && action == "edit") || (controller == "users" && action == "update")){ + if ((controller == "users" && action == "edit") || (controller == "users" && action == "update") || (controller == "admins" && action == "index")){ var settingsButtons = $('.setting-btn'); var settingsViews = $('.setting-view'); diff --git a/app/assets/javascripts/sort.js b/app/assets/javascripts/sort.js index 09452868..a7903de6 100644 --- a/app/assets/javascripts/sort.js +++ b/app/assets/javascripts/sort.js @@ -15,8 +15,10 @@ // with BigBlueButton; if not, see . $(document).on('turbolinks:load', function(){ - // Check if there is a table on this page - if ($("table").length) { + var controller = $("body").data('controller'); + var action = $("body").data('action'); + + if(controller == "rooms" && action == "show" || controller == "rooms" && action == "update" || controller == "users" && action == "recordings"){ // Choose active header // (Name, Length or Users) diff --git a/app/assets/stylesheets/_tabler-custom.scss b/app/assets/stylesheets/_tabler-custom.scss index 66958746..c87cabeb 100644 --- a/app/assets/stylesheets/_tabler-custom.scss +++ b/app/assets/stylesheets/_tabler-custom.scss @@ -31,7 +31,7 @@ @import "tabler/nav"; @import "tabler/button"; -//@import "tabler/alert"; +@import "tabler/alert"; //@import "tabler/close"; //@import "tabler/badge"; @import "tabler/tables"; @@ -68,7 +68,7 @@ //@import "tabler/forms/custom-selectgroup"; @import "tabler/forms/custom-switch"; //@import "tabler/forms/custom-imagecheck"; -//@import "tabler/forms/custom-colorinput"; +@import "tabler/forms/custom-colorinput"; //@import "tabler/timeline"; diff --git a/app/assets/stylesheets/admins.scss b/app/assets/stylesheets/admins.scss new file mode 100644 index 00000000..3c3db40e --- /dev/null +++ b/app/assets/stylesheets/admins.scss @@ -0,0 +1,32 @@ +// 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 . + +#users-table { + .user-role:hover { + cursor: default; + } +} + +#clear-search { + z-index: 9; + position: absolute; + right: 55px; + top: 8px; + + &:hover { + cursor: pointer; + } +} diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index f883273b..93725e12 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -35,12 +35,17 @@ @import "tabler-custom"; @import "utilities/variables"; +@import "admins"; @import "main"; @import "rooms"; @import "sessions"; @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,300i,400,400i,500,500i,600,600i,700,700i&subset=latin-ext); +* { + outline: none !important; +} + html, body { position: relative; width: 100%; @@ -77,10 +82,6 @@ a { width: 100%; } -.table-responsive { - overflow: visible; -} - .background { background-color: $background-color; } @@ -135,3 +136,34 @@ a { background-color: green !important; } } + +input:focus { + border-color: $primary !important; +} + +.list-group-item-action.active { + color: $primary; +} + +.header .header-nav { + color: $text-muted !important; + + &:hover { + padding-bottom: 21px; + border-bottom: 1px solid $primary; + } + + &.active { + color: $primary !important; + padding-bottom: 21px; + border-bottom: 1px solid $primary; + } +} + +table { + thead { + th[data-order]:hover { + cursor: pointer; + } + } +} diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index 87fb2c79..dcd6dacd 100755 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -144,3 +144,7 @@ padding:10px 10px 10px 10px; } } + +.signin-button { + font-size: 16px; +} diff --git a/app/assets/stylesheets/rooms.scss b/app/assets/stylesheets/rooms.scss index 3b90cd1a..e971c0bb 100644 --- a/app/assets/stylesheets/rooms.scss +++ b/app/assets/stylesheets/rooms.scss @@ -56,3 +56,12 @@ vertical-align: middle; padding-top: 12px; } + +#create-room-block { + border: 1px dashed lightgray; + + &:hover { + cursor: pointer; + background-color: rgba(0, 0, 0, 0.04); + } +} diff --git a/app/assets/stylesheets/utilities/_primary_themes.scss b/app/assets/stylesheets/utilities/_primary_themes.scss new file mode 100644 index 00000000..33d29a56 --- /dev/null +++ b/app/assets/stylesheets/utilities/_primary_themes.scss @@ -0,0 +1,125 @@ +.btn-primary, +.btn-primary:visited, +.btn-primary i { + background-color: $primary-color !important; + border-color: $primary-color !important; + color: white !important; +} + +.btn-primary:active, +.btn-primary:active:focus, +.btn-primary:active:hover, +.btn-primary:focus, +.btn-primary:hover, +.btn-primary:hover i { + background-color: $primary-color-darken !important; + border-color: $primary-color-darken !important; + color: white !important; +} + +a { + color: $primary-color !important; +} + +.oauth-signin { + color: white !important; + + &:hover * { + color: white !important; + } +} + +.btn-outline-primary { + border-color: $primary-color !important; + color: $primary-color !important; + + &:hover { + background: $primary-color !important; + color: white !important; + } + + &:focus { + box-shadow: 0 0 0 2px $primary-color-lighten; + } +} + +.header { + & .avatar { + background-color: $primary-color !important; + color: white !important; + } + + & a:hover:not(.btn) { + color: $primary-color !important; + } + + & .header-nav { + border-color: $primary-color !important; + } + + & .header-nav.active { + color: $primary-color !important; + } +} + +.dropdown-item { + color: #6e7687 !important; + &:hover { + color: $primary-color !important; + } + &:active { + background-color: $primary-color-lighten !important; + } +} + +input:focus, select:focus { + box-shadow: 0 0 5px $primary-color !important; + border-color: $primary-color !important; +} + +.list-group-item.active { + background-color: $primary-color-lighten !important; + + &, .list-group-item.active * { + color: $primary-color !important; + } +} + +.text-primary { + color: $primary-color !important; +} + +.bg-primary { + background-color: $primary-color !important; +} + +.btn-danger { + color: white !important; +} + +#clear-search { + &:hover { + color: $primary-color-darken !important; + } +} + +.pagination { + .page-item { + &.active a { + color:white !important; + background-color: $primary-color !important; + } + + & a { + border-color: $primary-color !important; + } + + & a:hover { + background-color: $primary-color-lighten !important; + } + + & a:focus { + box-shadow: 0 0 3px $primary-color !important; + } + } +} diff --git a/app/controllers/account_activations_controller.rb b/app/controllers/account_activations_controller.rb index 549253d9..f508b056 100644 --- a/app/controllers/account_activations_controller.rb +++ b/app/controllers/account_activations_controller.rb @@ -33,11 +33,11 @@ class AccountActivationsController < ApplicationController @user.activate flash[:success] = I18n.t("verify.activated") + " " + I18n.t("verify.signin") + redirect_to signin_path else flash[:alert] = I18n.t("verify.invalid") + redirect_to root_path end - - redirect_to root_url end # GET /account_activations/resend @@ -51,7 +51,7 @@ class AccountActivationsController < ApplicationController logger.error "Error in email delivery: #{e}" flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error")) else - flash[:success] = I18n.t("email_sent") + flash[:success] = I18n.t("email_sent", email_type: t("verify.verification")) end end diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb new file mode 100644 index 00000000..4ea1a188 --- /dev/null +++ b/app/controllers/admins_controller.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +# 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 . + +class AdminsController < ApplicationController + include Pagy::Backend + authorize_resource class: false + before_action :find_user, only: [:edit_user, :promote, :demote, :ban_user, :unban_user] + before_action :verify_admin_of_user, only: [:edit_user, :promote, :demote, :ban_user, :unban_user] + before_action :find_setting, only: [:branding, :coloring] + + # GET /admins + def index + @search = params[:search] || "" + @order_column = params[:column] && params[:direction] != "none" ? params[:column] : "created_at" + @order_direction = params[:direction] && params[:direction] != "none" ? params[:direction] : "DESC" + puts @order_direction.to_s + + if Rails.configuration.loadbalanced_configuration + @pagy, @users = pagy(User.without_role(:super_admin) + .where(provider: user_settings_provider) + .where.not(id: current_user.id) + .admins_search(@search) + .admins_order(@order_column, @order_direction)) + else + @pagy, @users = pagy(User.where.not(id: current_user.id) + .admins_search(@search) + .admins_order(@order_column, @order_direction)) + end + end + + # GET /admins/edit/:user_uid + def edit_user + render "admins/index", locals: { setting_id: "account" } + end + + # POST /admins/promote/:user_uid + def promote + @user.add_role :admin + redirect_to admins_path, flash: { success: I18n.t("administrator.flash.promoted") } + end + + # POST /admins/demote/:user_uid + def demote + @user.remove_role :admin + redirect_to admins_path, flash: { success: I18n.t("administrator.flash.demoted") } + end + + # POST /admins/branding + def branding + @settings.update_value("Branding Image", params[:url]) + redirect_to admins_path + end + + # POST /admins/color + def coloring + @settings.update_value("Primary Color", params[:color]) + redirect_to admins_path(setting: "site_settings") + end + + # POST /admins/ban/:user_uid + def ban_user + @user.add_role :denied + redirect_to admins_path, flash: { success: I18n.t("administrator.flash.banned") } + end + + # POST /admins/unban/:user_uid + def unban_user + @user.remove_role :denied + redirect_to admins_path, flash: { success: I18n.t("administrator.flash.unbanned") } + end + + private + + def find_user + @user = User.find_by!(uid: params[:user_uid]) + end + + def find_setting + @settings = Setting.find_or_create_by!(provider: user_settings_provider) + end + + def verify_admin_of_user + redirect_to admins_path, + flash: { alert: I18n.t("administrator.flash.unauthorized") } unless current_user.admin_of?(@user) + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 71ab4f1f..dc241760 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -20,10 +20,13 @@ require 'bigbluebutton_api' class ApplicationController < ActionController::Base include SessionsHelper + include ThemingHelper before_action :migration_error? before_action :set_locale + before_action :check_admin_password before_action :set_user_domain + before_action :check_if_unbanned # Force SSL for loadbalancer configurations. before_action :redirect_to_https @@ -102,6 +105,21 @@ class ApplicationController < ActionController::Base } end + # Manually deal with 401 errors + rescue_from CanCan::AccessDenied do |_exception| + render "errors/not_found" + end + + # Checks to make sure that the admin has changed his password from the default + def check_admin_password + if current_user&.has_role?(:admin) && current_user&.greenlight_account? && + current_user&.authenticate(Rails.configuration.admin_password_default) + + flash.now[:alert] = I18n.t("default_admin", + edit_link: edit_user_path(user_uid: current_user.uid) + "?setting=password").html_safe + end + end + def redirect_to_https if Rails.configuration.loadbalanced_configuration && request.headers["X-Forwarded-Proto"] == "http" redirect_to protocol: "https://" @@ -116,4 +134,13 @@ class ApplicationController < ActionController::Base end end helper_method :set_user_domain + + # Checks if the user is banned and logs him out if he is + def check_if_unbanned + if current_user&.has_role?(:denied) + session.delete(:user_id) + redirect_to unauthorized_path + end + end + helper_method :check_if_unbanned end diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb index 4b0b4b25..5c388a4e 100644 --- a/app/controllers/errors_controller.rb +++ b/app/controllers/errors_controller.rb @@ -28,4 +28,8 @@ class ErrorsController < ApplicationController def internal_error render status: 500, formats: :html end + + def unauthorized + render status: 401, formats: :html + end end diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 49f8ae81..a6ac81bc 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -17,16 +17,7 @@ # with BigBlueButton; if not, see . class MainController < ApplicationController - # before_action :redirect_to_room - # GET / def index end - - private - - def redirect_to_room - # If the user is logged in already, move them along to their room. - redirect_to room_path(current_user.room) if current_user - end end diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb index 4cf87adb..280ef06f 100644 --- a/app/controllers/password_resets_controller.rb +++ b/app/controllers/password_resets_controller.rb @@ -30,7 +30,7 @@ class PasswordResetsController < ApplicationController if @user @user.create_reset_digest @user.send_password_reset_email(reset_link) - flash[:success] = I18n.t("email_sent") + flash[:success] = I18n.t("email_sent", email_type: t("reset_password.subtitle")) redirect_to root_path else flash[:alert] = I18n.t("no_user_email_exists") diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 62c8f613..0dc85d40 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -27,12 +27,18 @@ class SessionsController < ApplicationController # POST /users/login def create - user = User.find_by(email: session_params[:email], provider: @user_domain) - redirect_to(root_path, alert: I18n.t("invalid_user")) && return unless user - redirect_to(root_path, alert: I18n.t("invalid_login_method")) && return unless user.greenlight_account? + 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(root_path, alert: I18n.t("invalid_user")) && 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(root_path, alert: I18n.t("invalid_credentials")) && return unless user.try(:authenticate, session_params[:password]) - redirect_to(account_activation_path(email: user.email)) && return unless user.activated? + login(user) end diff --git a/app/controllers/themes_controller.rb b/app/controllers/themes_controller.rb new file mode 100644 index 00000000..812a3aa4 --- /dev/null +++ b/app/controllers/themes_controller.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# 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 . + +class ThemesController < ApplicationController + before_action :provider_settings + + # GET /primary + def index + color = @settings.get_value("Primary Color") || Rails.configuration.primary_color_default + file_name = Rails.root.join('app', 'assets', 'stylesheets', 'utilities', '_primary_themes.scss') + @file_contents = File.read(file_name) + + # Include the variables and covert scss file to css + @compiled = Sass::Engine.new("$primary-color:#{color};" \ + "$primary-color-lighten:lighten(#{color}, 40%);" \ + "$primary-color-darken:darken(#{color}, 10%);" + + @file_contents, syntax: :scss).render + + respond_to do |format| + format.css { render body: @compiled } + end + end + + private + + def provider_settings + @settings = Setting.find_or_create_by(provider: user_settings_provider) + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 904ef56d..a7517e01 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -44,11 +44,15 @@ class UsersController < ApplicationController logger.error "Error in email delivery: #{e}" flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error")) else - flash[:success] = I18n.t("email_sent") + flash[:success] = I18n.t("email_sent", email_type: t("verify.verification")) end redirect_to(root_path) end + # GET /signin + def signin + end + # GET /signup def new if Rails.configuration.allow_user_signup @@ -61,7 +65,7 @@ class UsersController < ApplicationController # GET /u/:user_uid/edit def edit if current_user - redirect_to current_user.room unless @user == current_user + redirect_to current_user.main_room if @user != current_user && !current_user.admin_of?(@user) else redirect_to root_path end @@ -113,6 +117,16 @@ class UsersController < ApplicationController if current_user && current_user == @user @user.destroy session.delete(:user_id) + elsif current_user.admin_of?(@user) + begin + @user.destroy + rescue => e + logger.error "Error in user deletion: #{e}" + flash[:alert] = I18n.t(params[:message], default: I18n.t("administrator.flash.delete_fail")) + else + flash[:success] = I18n.t("administrator.flash.delete") + end + redirect_to(admins_path) && return end redirect_to root_path end diff --git a/app/helpers/admins_helper.rb b/app/helpers/admins_helper.rb new file mode 100644 index 00000000..e0aa5c5c --- /dev/null +++ b/app/helpers/admins_helper.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# 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 . + +module AdminsHelper + include Pagy::Frontend +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 440a3c1f..8f96426a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -95,4 +95,11 @@ module ApplicationHelper @translations ||= I18n.backend.send(:translations) @translations[I18n.locale].with_indifferent_access[:javascript] || {} end + + # Returns the page that the logo redirects to when clicked on + def home_page + return root_path unless current_user + return admins_path if current_user.has_role? :super_admin + current_user.main_room + end end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index 9c76c731..5b25bc5a 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -31,9 +31,13 @@ module SessionsHelper # If email verification is disabled, or the user has verified, go to their room def check_email_verified(user) - if user.activated? - # Get the url to redirect the user to - url = if cookies[:return_to] && ![root_url, signup_url].include?(cookies[:return_to]) + # Admin users should be redirected to the admin page + if user.has_role? :super_admin + redirect_to admins_path + elsif user.activated? + # Dont redirect to any of these urls + dont_redirect_to = [root_url, signup_url, unauthorized_url, internal_error_url, not_found_url] + url = if cookies[:return_to] && !dont_redirect_to.include?(cookies[:return_to]) cookies[:return_to] else user.main_room diff --git a/app/helpers/theming_helper.rb b/app/helpers/theming_helper.rb new file mode 100644 index 00000000..58f0def3 --- /dev/null +++ b/app/helpers/theming_helper.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +# 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 . + +module ThemingHelper + # Returns the logo based on user's provider + def logo_image + Setting.find_or_create_by(provider: user_settings_provider) + .get_value("Branding Image") || Rails.configuration.branding_image_default + end + + # Returns the primary color based on user's provider + def user_color + Setting.find_or_create_by(provider: user_settings_provider) + .get_value("Primary Color") || Rails.configuration.primary_color_default + end + + # Returns the user's provider in the settings context + def user_settings_provider + if Rails.configuration.loadbalanced_configuration && !current_user&.has_role?(:super_admin) + current_user.provider + elsif Rails.configuration.loadbalanced_configuration + @user_domain + else + "greenlight" + end + end +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 0fe7b991..3e0743e9 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -17,6 +17,7 @@ # with BigBlueButton; if not, see . class ApplicationMailer < ActionMailer::Base + add_template_helper(ThemingHelper) default from: 'from@example.com' layout 'mailer' end diff --git a/app/models/ability.rb b/app/models/ability.rb new file mode 100644 index 00000000..07ec8358 --- /dev/null +++ b/app/models/ability.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# 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 . + +class Ability + include CanCan::Ability + + def initialize(user) + if !user + cannot :manage, AdminsController + elsif user.has_role? :super_admin + can :manage, :all + elsif user.has_role? :admin + can :manage, :all + elsif user.has_role? :user + cannot :manage, AdminsController + end + end +end diff --git a/app/models/feature.rb b/app/models/feature.rb new file mode 100644 index 00000000..01c49117 --- /dev/null +++ b/app/models/feature.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# 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 . + +class Feature < ApplicationRecord + belongs_to :setting +end diff --git a/app/models/role.rb b/app/models/role.rb new file mode 100644 index 00000000..856b92c6 --- /dev/null +++ b/app/models/role.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# 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 . + +class Role < ApplicationRecord + has_and_belongs_to_many :users, join_table: :users_roles + + belongs_to :resource, + polymorphic: true, + optional: true + + validates :resource_type, + inclusion: { in: Rolify.resource_types }, + allow_nil: true + + scopify +end diff --git a/app/models/setting.rb b/app/models/setting.rb new file mode 100644 index 00000000..c0e859e3 --- /dev/null +++ b/app/models/setting.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +# 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 . + +class Setting < ApplicationRecord + has_many :features + + # Updates the value of the feature and enables it + def update_value(name, value) + feature = features.find_or_create_by!(name: name) + + feature.update_attributes(value: value, enabled: true) + end + + # Returns the value if enabled or the default if not enabled + def get_value(name) + feature = features.find_or_create_by!(name: name) + if feature[:enabled] + feature[:value] + else + case name + when "Branding Image" + Rails.configuration.branding_image_default + when "Primary Color" + Rails.configuration.primary_color_default + end + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 7e446f80..de530c94 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,11 +19,14 @@ require 'bbb_api' class User < ApplicationRecord + rolify include ::APIConcern include ::BbbApi attr_accessor :reset_token + after_create :assign_default_role after_create :initialize_main_room + before_save { email.try(:downcase!) } before_destroy :destroy_rooms @@ -33,6 +36,7 @@ class User < ApplicationRecord validates :name, length: { maximum: 256 }, presence: true validates :provider, presence: true + validate :check_if_email_can_be_blank validates :email, length: { maximum: 256 }, allow_blank: true, uniqueness: { case_sensitive: false, scope: :provider }, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i } @@ -98,6 +102,17 @@ class User < ApplicationRecord end end + def self.admins_search(string) + search_query = "name LIKE :search OR email LIKE :search OR username LIKE :search" \ + " OR created_at LIKE :search OR provider LIKE :search" + search_param = "%#{string}%" + where(search_query, search: search_param) + end + + def self.admins_order(column, direction) + order("#{column} #{direction}") + end + def all_recordings pag_num = Rails.configuration.pagination_number @@ -199,6 +214,18 @@ class User < ApplicationRecord create_reset_activation_digest(User.new_token) end + def admin_of?(user) + if Rails.configuration.loadbalanced_configuration + if has_role? :super_admin + id != user.id + else + (has_role? :admin) && (id != user.id) && (provider == user.provider) && (!user.has_role? :super_admin) + end + else + ((has_role? :admin) || (has_role? :super_admin)) && (id != user.id) + end + end + def self.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) @@ -229,4 +256,19 @@ class User < ApplicationRecord self.main_room = Room.create!(owner: self, name: I18n.t("home_room")) save end + + # Initialize the user to use the default user role + def assign_default_role + add_role(:user) if roles.blank? + end + + def check_if_email_can_be_blank + if email.blank? + if Rails.configuration.loadbalanced_configuration && greenlight_account? + errors.add(:email, I18n.t("errors.messages.blank")) + elsif provider == "greenlight" + errors.add(:email, I18n.t("errors.messages.blank")) + end + end + end end diff --git a/app/views/admins/index.html.erb b/app/views/admins/index.html.erb new file mode 100644 index 00000000..f89ee3d0 --- /dev/null +++ b/app/views/admins/index.html.erb @@ -0,0 +1,42 @@ +<% +# 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 . +%> +
+ <%= render "shared/components/subtitle", subtitle: t("administrator.title"), search: false %> + +
+
+
+ + +
+
+ + +
+ <% if defined?(setting_id) && setting_id == "account" %> + <%= render "shared/settings/setting_view", setting_id: "account", setting_title: t("administrator.users.edit.title") %> + <% else %> + <%= render "shared/settings/setting_view", admin_view: true, setting_id: "users", setting_title: t("administrator.users.title") %> + <%= render "shared/settings/setting_view", admin_view: true, setting_id: "site_settings", setting_title: t("administrator.site_settings.subtitle") %> + <% end %> + + <%= render "shared/modals/delete_account_modal", delete_location: "/" %> +
+
+
diff --git a/app/views/errors/unauthorized.html.erb b/app/views/errors/unauthorized.html.erb new file mode 100644 index 00000000..e54591d7 --- /dev/null +++ b/app/views/errors/unauthorized.html.erb @@ -0,0 +1,20 @@ +<% +# 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 . +%> + +
+
401
+

<%= t("errors.unauthorized.message") %>

+

<%= t("errors.unauthorized.help") %>

+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 94df89f5..22e83755 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -32,7 +32,10 @@ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> - + + + <%= stylesheet_link_tag themes_primary_path %> +