diff --git a/.dockerignore b/.dockerignore index 29b75eabd3..b58e3ee979 100644 --- a/.dockerignore +++ b/.dockerignore @@ -13,6 +13,7 @@ tmp /db/**/*.sqlite3 /db/**/*.sqlite3-journal /db/production +/db/production-postgres public/assets public/b coverage/ diff --git a/.gitignore b/.gitignore index 20667ff430..7a58b32398 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ vendor/bundle # Ignore production paths. /db/production +/db/production-postgres # Ignore all logfiles and tempfiles. /log/* diff --git a/Dockerfile b/Dockerfile index 544e6bd9b1..d2fbe32e6a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,58 @@ -FROM ruby:2.5 +FROM ruby:2.5.1-alpine AS base -# Install app dependencies. -RUN apt-get update -qq && apt-get install -y build-essential libpq-dev curl - -ADD https://dl.yarnpkg.com/debian/pubkey.gpg /tmp/yarn-pubkey.gpg - -RUN apt-key add /tmp/yarn-pubkey.gpg && rm /tmp/yarn-pubkey.gpg && \ -echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list && \ -curl -sL https://deb.nodesource.com/setup_10.x | bash - && \ -apt-get update && apt-get install -y nodejs yarn - -# Set an environment variable for the install location. -ENV RAILS_ROOT /usr/src/app +# Set a variable for the install location. +ARG RAILS_ROOT=/usr/src/app +# Set Rails environment. +ENV RAILS_ENV production +ENV BUNDLE_APP_CONFIG="$RAILS_ROOT/.bundle" # Make the directory and set as working. RUN mkdir -p $RAILS_ROOT WORKDIR $RAILS_ROOT -# Set Rails environment. -ENV RAILS_ENV production +ARG BUILD_PACKAGES="build-base curl-dev git" +ARG DEV_PACKAGES="postgresql-dev sqlite-libs sqlite-dev yaml-dev zlib-dev nodejs yarn" +ARG RUBY_PACKAGES="tzdata" + +# Install app dependencies. +RUN apk update \ + && apk upgrade \ + && apk add --update --no-cache $BUILD_PACKAGES $DEV_PACKAGES $RUBY_PACKAGES COPY Gemfile* ./ -RUN bundle install --without development test --deployment --clean +COPY Gemfile Gemfile.lock $RAILS_ROOT/ + +RUN bundle config --global frozen 1 \ + && bundle install --deployment --without development:test:assets -j4 --path=vendor/bundle \ + && rm -rf vendor/bundle/ruby/2.5.0/cache/*.gem \ + && find vendor/bundle/ruby/2.5.0/gems/ -name "*.c" -delete \ + && find vendor/bundle/ruby/2.5.0/gems/ -name "*.o" -delete # Adding project files. COPY . . -# Precompile assets -RUN SECRET_KEY_BASE="$(bundle exec rake secret)" bundle exec rake assets:clean -RUN SECRET_KEY_BASE="$(bundle exec rake secret)" bundle exec rake assets:precompile +# Remove folders not needed in resulting image +RUN rm -rf tmp/cache spec + +############### Build step done ############### + +FROM ruby:2.5.1-alpine + +# Set a variable for the install location. +ARG RAILS_ROOT=/usr/src/app +ARG PACKAGES="tzdata curl postgresql-client sqlite-libs yarn nodejs bash" + +ENV RAILS_ENV=production +ENV BUNDLE_APP_CONFIG="$RAILS_ROOT/.bundle" + +WORKDIR $RAILS_ROOT + +RUN apk update \ + && apk upgrade \ + && apk add --update --no-cache $PACKAGES + + +COPY --from=base $RAILS_ROOT $RAILS_ROOT # Expose port 80. EXPOSE 80 diff --git a/Gemfile b/Gemfile index b69213de57..d99e1818e5 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,7 @@ gem 'uglifier', '>= 1.3.0' gem 'coffee-rails', '~> 4.2' # See https://github.com/rails/execjs#readme for more supported runtimes -gem 'mini_racer', platforms: :ruby +# gem 'mini_racer', platforms: :ruby # Use jquery as the JavaScript library gem 'jquery-rails', '~> 4.3.3' @@ -77,6 +77,7 @@ gem 'cancancan', '~> 2.0' group :production do # Use a postgres database in production. gem 'pg', '~> 0.18' + gem 'sequel' # For a better logging library in production gem "lograge" @@ -121,7 +122,7 @@ end gem 'remote_syslog_logger' # Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem 'tzinfo-data' gem 'coveralls', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 1a651c4b3d..861a2fe2cd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,7 +153,6 @@ GEM railties (>= 3.2.16) json (2.2.0) jwt (2.2.1) - libv8 (7.3.492.27.1) listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -173,8 +172,6 @@ GEM mimemagic (0.3.3) mini_mime (1.0.2) mini_portile2 (2.4.0) - mini_racer (0.2.6) - libv8 (>= 6.9.411) minitest (5.11.3) msgpack (1.3.0) multi_json (1.13.1) @@ -295,6 +292,7 @@ GEM sprockets (> 3.0) sprockets-rails tilt + sequel (5.29.0) shoulda-matchers (3.1.3) activesupport (>= 4.0.0) simplecov (0.16.1) @@ -326,6 +324,8 @@ GEM turbolinks-source (5.2.0) tzinfo (1.2.5) thread_safe (~> 0.1) + tzinfo-data (1.2019.3) + tzinfo (>= 1.0.0) uglifier (4.1.20) execjs (>= 0.3.0, < 3) unicode-display_width (1.6.0) @@ -369,7 +369,6 @@ DEPENDENCIES jquery-ui-rails listen (~> 3.0.5) lograge - mini_racer net-ldap omniauth omniauth-bn-launcher! @@ -389,6 +388,7 @@ DEPENDENCIES rspec-rails (~> 3.7) rubocop sassc-rails + sequel shoulda-matchers (~> 3.1) spring spring-watcher-listen (~> 2.0.0) diff --git a/app/assets/javascripts/admins.js b/app/assets/javascripts/admins.js index 13a64baffb..53fcfb571b 100644 --- a/app/assets/javascripts/admins.js +++ b/app/assets/javascripts/admins.js @@ -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("" + userInfo.name + "" + "" + userInfo.email + "" + "" + userInfo.uid + "") + + }) + + $("#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("" + userInfo.name + "" + "" + userInfo.email + "" + "" + userInfo.uid + "") + } + }) } 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') diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index fdab7fc0c0..6c82146b05 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -34,4 +34,5 @@ //= require jquery-ui/widget //= require jquery-ui/widgets/sortable //= require pickr.min.js +//= require bootstrap-select.min.js //= require_tree . diff --git a/app/assets/javascripts/room.js b/app/assets/javascripts/room.js index 7da6580e48..e3afbd8a58 100644 --- a/app/assets/javascripts/room.js +++ b/app/assets/javascripts/room.js @@ -49,7 +49,16 @@ $(document).on('turbolinks:load', function(){ $("#create-room-block").click(function(){ showCreateRoom(this) }) + } + // Autofocus on the Room Name label when creating a room only + $('#createRoomModal').on('shown.bs.modal', function (){ + if ($(".create-only").css("display") == "block"){ + $('#create-room-name').focus() + } + }) + + if (controller == "rooms" && action == "show" || controller == "admins" && action == "server_rooms"){ // Display and update all fields related to creating a room in the createRoomModal $(".update-room").click(function(){ showUpdateRoom(this) @@ -58,6 +67,69 @@ $(document).on('turbolinks:load', function(){ $(".delete-room").click(function() { showDeleteRoom(this) }) + + $('.selectpicker').selectpicker({ + liveSearchPlaceholder: getLocalizedString('javascript.search.start') + }); + // Fixes turbolinks issue with bootstrap select + $(window).trigger('load.bs.select.data-api'); + + $(".share-room").click(function() { + // Update the path of save button + $("#save-access").attr("data-path", $(this).data("path")) + + // Get list of users shared with and display them + displaySharedUsers($(this).data("users-path")) + }) + + $("#shareRoomModal").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() + } + }) + + $(".remove-share-room").click(function() { + $("#remove-shared-confirm").parent().attr("action", $(this).data("path")) + }) + + // User selects an option from the Room Access dropdown + $(".bootstrap-select").on("changed.bs.select", function(){ + // Get the uid of the selected user + let uid = $(".selectpicker").selectpicker('val') + + // If the value was changed to blank, ignore it + if (uid == "") return + + let currentListItems = $("#user-list li").toArray().map(user => $(user).data("uid")) + + // Check to make sure that the user is not already there + if (!currentListItems.includes(uid)) { + // Create the faded list item and display it + let option = $("option[value='" + uid + "']") + + let listItem = document.createElement("li") + listItem.setAttribute('class', 'list-group-item text-left not-saved add-access'); + listItem.setAttribute("data-uid", uid) + + let spanItem = "" + option.text().charAt(0) + " " + + option.text() + " " + option.data("subtext") + "" + + "" + + listItem.innerHTML = spanItem + + $("#user-list").append(listItem) + } + }) } }); @@ -88,9 +160,10 @@ function showCreateRoom(target) { function showUpdateRoom(target) { var modal = $(target) - var room_block_uid = modal.closest("#room-block").data("room-uid") - $("#create-room-name").val(modal.closest("tbody").find("#room-name h4").text()) - $("#createRoomModal form").attr("action", room_block_uid + "/update_settings") + var update_path = modal.closest("#room-block").data("path") + var settings_path = modal.data("settings-path") + $("#create-room-name").val(modal.closest("#room-block").find("#room-name-text").text()) + $("#createRoomModal form").attr("action", update_path) //show all elements & their children with a update-only class $(".update-only").each(function() { @@ -104,7 +177,7 @@ function showUpdateRoom(target) { if($(this).children().length > 0) { $(this).children().attr('style',"display:none !important") } }) - updateCurrentSettings(modal.closest("#room-block").data("room-settings")) + updateCurrentSettings(settings_path) var accessCode = modal.closest("#room-block").data("room-access-code") @@ -123,12 +196,15 @@ function showDeleteRoom(target) { } //Update the createRoomModal to show the correct current settings -function updateCurrentSettings(settings){ - //set checkbox - $("#room_mute_on_join").prop("checked", settings.muteOnStart) - $("#room_require_moderator_approval").prop("checked", settings.requireModeratorApproval) - $("#room_anyone_can_start").prop("checked", settings.anyoneCanStart) - $("#room_all_join_moderator").prop("checked", settings.joinModerator) +function updateCurrentSettings(settings_path){ + // Get current room settings and set checkbox + $.get(settings_path, function(room_settings) { + var settings = JSON.parse(room_settings) + $("#room_mute_on_join").prop("checked", settings.muteOnStart) + $("#room_require_moderator_approval").prop("checked", settings.requireModeratorApproval) + $("#room_anyone_can_start").prop("checked", settings.anyoneCanStart) + $("#room_all_join_moderator").prop("checked", settings.joinModerator) + }) } function generateAccessCode(){ @@ -148,3 +224,44 @@ function ResetAccessCode(){ $("#create-room-access-code").text(getLocalizedString("modal.create_room.access_code_placeholder")) $("#room_access_code").val(null) } + +function saveAccessChanges() { + let listItemsToAdd = $("#user-list li:not(.remove-shared)").toArray().map(user => $(user).data("uid")) + + $.post($("#save-access").data("path"), {add: listItemsToAdd}) +} + +// Get list of users shared with and display them +function displaySharedUsers(path) { + $.get(path, function(users) { + // Create list element and add to user list + var user_list_html = "" + + users.forEach(function(user) { + user_list_html += "
  • " + + if (user.image) { + user_list_html += "" + } else { + user_list_html += "" + user.name.charAt(0) + "" + } + user_list_html += "" + user.name + "" + user.uid + "" + user_list_html += "" + user_list_html += "
  • " + }) + + $("#user-list").html(user_list_html) + }); +} + +// Removes the user from the list of shared users +function removeSharedUser(target) { + let parentLI = target.closest("li") + + if (parentLI.classList.contains("not-saved")) { + parentLI.parentNode.removeChild(parentLI) + } else { + parentLI.removeChild(target) + parentLI.classList.add("remove-shared") + } +} \ No newline at end of file diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js index f0da579c7c..b4ecbe4726 100644 --- a/app/assets/javascripts/search.js +++ b/app/assets/javascripts/search.js @@ -23,7 +23,8 @@ $(document).on('turbolinks:load', function(){ (controller == "rooms" && action == "update") || (controller == "rooms" && action == "join") || (controller == "users" && action == "recordings") || - (controller == "admins" && action == "server_recordings")) { + (controller == "admins" && action == "server_recordings") || + (controller == "admins" && action == "server_rooms")) { // Submit search if the user hits enter $("#search-input").keypress(function(key) { if (key.which == 13) { diff --git a/app/assets/javascripts/sort.js b/app/assets/javascripts/sort.js index effa8ea6bf..124868143c 100644 --- a/app/assets/javascripts/sort.js +++ b/app/assets/javascripts/sort.js @@ -52,15 +52,12 @@ $(document).on('turbolinks:load', function(){ // Modify the ui for the tables var configure_order = function(header_elem){ if(header_elem.data('order') === 'asc'){ // asc - header_elem.text(header_elem.data("header") + " ↓"); header_elem.data('order', 'desc'); } else if(header_elem.data('order') === 'desc'){ // desc - header_elem.text(header_elem.data("header")); header_elem.data('order', 'none'); } else{ // none - header_elem.text(header_elem.data("header") + " ↑"); header_elem.data('order', 'asc'); } } diff --git a/app/assets/stylesheets/admins.scss b/app/assets/stylesheets/admins.scss index 2265448d84..3e872d74d8 100644 --- a/app/assets/stylesheets/admins.scss +++ b/app/assets/stylesheets/admins.scss @@ -88,4 +88,12 @@ &:hover { cursor: pointer; } +} + +#merge-account-arrow { + position: absolute; + top: 47%; + right: 47%; + z-index: 999; + background: white; } \ No newline at end of file diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index adde898849..03fce05333 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -36,14 +36,16 @@ @import "tabler-custom"; @import "font-awesome-sprockets"; @import "font-awesome"; +@import "monolith.min.scss"; +@import "bootstrap-select.min"; @import "utilities/variables"; @import "admins"; @import "main"; @import "rooms"; @import "sessions"; -@import "monolith.min.scss"; @import "utilities/fonts"; +@import "users"; * { outline: none !important; diff --git a/app/assets/stylesheets/rooms.scss b/app/assets/stylesheets/rooms.scss index 0be5082305..57f7fdf673 100644 --- a/app/assets/stylesheets/rooms.scss +++ b/app/assets/stylesheets/rooms.scss @@ -83,3 +83,20 @@ margin-top: -6rem; font-size: 5rem; } + +.bootstrap-select .dropdown-menu li.active small.text-muted{ + color: #9aa0ac !important +} + +.not-saved { + color: grey; + background: rgba(0, 40, 100, 0.12); +} + +.dropdown-menu.show { + min-height: 0px !important; +} + +.remove-shared { + text-decoration: line-through; +} \ No newline at end of file diff --git a/app/assets/stylesheets/users.scss b/app/assets/stylesheets/users.scss index a0ec9aeece..8d44447a74 100644 --- a/app/assets/stylesheets/users.scss +++ b/app/assets/stylesheets/users.scss @@ -21,4 +21,12 @@ .user-role-tag{ color: white !important; +} + +.shared-user { + line-height: 30px; +} + +.bootstrap-select { + border: 1px solid rgba(0, 40, 100, 0.12); } \ No newline at end of file diff --git a/app/channels/waiting_channel.rb b/app/channels/waiting_channel.rb index 0d5e327235..64056388ff 100644 --- a/app/channels/waiting_channel.rb +++ b/app/channels/waiting_channel.rb @@ -18,11 +18,6 @@ class WaitingChannel < ApplicationCable::Channel def subscribed - Rails.logger.info "subscribed [#{params[:useruid]}:#{params[:roomuid]}]" stream_from "#{params[:roomuid]}_waiting_channel" end - - def unsubscribed - Rails.logger.info "unsubscribed [#{params[:useruid]}:#{params[:roomuid]}]" - end end diff --git a/app/controllers/account_activations_controller.rb b/app/controllers/account_activations_controller.rb index cc859f095e..21a37a6615 100644 --- a/app/controllers/account_activations_controller.rb +++ b/app/controllers/account_activations_controller.rb @@ -51,6 +51,7 @@ def resend flash[:alert] = I18n.t("verify.already_verified") else # Resend + @user.create_activation_token send_activation_email(@user) end @@ -60,14 +61,10 @@ def resend private def find_user - @user = User.find_by!(email: params[:email], provider: @user_domain) + @user = User.find_by!(activation_digest: User.digest(params[:token]), provider: @user_domain) end def ensure_unauthenticated redirect_to current_user.main_room if current_user end - - def email_params - params.require(:email).permit(:email, :token) - end end diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb index 32dcb8faa4..ddef4f5f7c 100644 --- a/app/controllers/admins_controller.rb +++ b/app/controllers/admins_controller.rb @@ -22,8 +22,9 @@ class AdminsController < ApplicationController include Emailer include Recorder 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 @@ -40,7 +41,9 @@ def index @role = params[:role] ? Role.find_by(name: params[:role], provider: @user_domain) : nil @tab = params[:tab] || "active" - @pagy, @users = pagy(user_list) + @user_list = merge_user_list + + @pagy, @users = pagy(manage_users_list) end # GET /admins/site_settings @@ -49,11 +52,7 @@ def site_settings # GET /admins/server_recordings def server_recordings - server_rooms = if Rails.configuration.loadbalanced_configuration - Room.includes(:owner).where(users: { provider: @user_domain }).pluck(:bbb_id) - else - Room.pluck(:bbb_id) - end + server_rooms = rooms_list_for_recordings @search, @order_column, @order_direction, recs = all_recordings(server_rooms, params.permit(:search, :column, :direction), true, true) @@ -61,24 +60,40 @@ def server_recordings @pagy, @recordings = pagy_array(recs) end + # GET /admins/rooms + def server_rooms + @search = params[:search] || "" + @order_column = params[:column] && params[:direction] != "none" ? params[:column] : "created_at" + @order_direction = params[:direction] && params[:direction] != "none" ? params[:direction] : "DESC" + + @running_room_bbb_ids = all_running_meetings[:meetings].pluck(:meetingID) + + @user_list = shared_user_list if shared_access_allowed + + @pagy, @rooms = pagy_array(server_rooms_list) + end + # MANAGE USERS # GET /admins/edit/:user_uid def edit_user + session[:prev_url] = request.referer if request.referer.present? end # POST /admins/ban/:user_uid def ban_user @user.roles = [] @user.add_role :denied - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.banned") } + + redirect_back fallback_location: admins_path, flash: { success: I18n.t("administrator.flash.banned") } end # POST /admins/unban/:user_uid def unban_user @user.remove_role :denied @user.add_role :user - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.unbanned") } + + redirect_back fallback_location: admins_path, flash: { success: I18n.t("administrator.flash.unbanned") } end # POST /admins/approve/:user_uid @@ -87,7 +102,7 @@ def approve send_user_approved_email(@user) - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.approved") } + redirect_back fallback_location: admins_path, flash: { success: I18n.t("administrator.flash.approved") } end # POST /admins/approve/:user_uid @@ -96,7 +111,7 @@ def undelete @user.undelete! @user.rooms.deleted.each(&:undelete!) - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.restored") } + redirect_back fallback_location: admins_path, flash: { success: I18n.t("administrator.flash.restored") } end # POST /admins/invite @@ -118,8 +133,54 @@ def reset send_password_reset_email(@user) - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.reset_password") } + if session[:prev_url].present? + redirect_path = session[:prev_url] + session.delete(:prev_url) + else + redirect_path = admins_path + end + + 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_back fallback_location: admins_path + end + # SITE SETTINGS # POST /admins/update_settings @@ -158,6 +219,13 @@ def registration_method end end + # POST /admins/clear_auth + def clear_auth + User.include_deleted.where(provider: @user_domain).update_all(social_uid: nil) + + redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } + end + # POST /admins/clear_cache def clear_cache Rails.cache.delete("#{@user_domain}/getUser") @@ -166,6 +234,13 @@ def clear_cache redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } end + # POST /admins/log_level + def log_level + Rails.logger.level = params[:value].to_i + + redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } + end + # ROLES # GET /admins/roles @@ -224,11 +299,11 @@ def delete_role private def find_user - @user = User.where(uid: params[:user_uid]).includes(:roles).first + @user = User.find_by(uid: params[:user_uid]) end def find_deleted_user - @user = User.deleted.where(uid: params[:user_uid]).includes(:roles).first + @user = User.deleted.find_by(uid: params[:user_uid]) end # Verifies that admin is an administrator of the user in the action @@ -237,37 +312,6 @@ def verify_admin_of_user flash: { alert: I18n.t("administrator.flash.unauthorized") } unless current_user.admin_of?(@user) end - # 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 - initial_user.where.not(id: current_user.id) - else - 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, current_role) - .admins_order(@order_column, @order_direction) - else - initial_list.admins_search(@search, current_role) - .admins_order(@order_column, @order_direction) - end - end - # Creates the invite if it doesn't exist, or updates the updated_at time if it does def create_or_update_invite(email) invite = Invitation.find_by(email: email, provider: @user_domain) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 96cd481f32..ddc07cdf02 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -29,7 +29,7 @@ class ApplicationController < ActionController::Base # Retrieves the current user. def current_user - @current_user ||= User.where(id: session[:user_id]).includes(:roles).first + @current_user ||= User.includes(:roles, :main_room).find_by(id: session[:user_id]) if Rails.configuration.loadbalanced_configuration if @current_user && !@current_user.has_role?(:super_admin) && @@ -67,7 +67,7 @@ def set_user_domain # Sets the settinfs variable def set_user_settings - @settings = Setting.find_or_create_by(provider: @user_domain) + @settings = Setting.includes(:features).find_or_create_by(provider: @user_domain) end # Redirects the user to a Maintenance page if turned on @@ -172,6 +172,12 @@ def configured_providers end helper_method :configured_providers + # Indicates whether users are allowed to share rooms + def shared_access_allowed + @settings.get_value("Shared Access") == "true" + end + helper_method :shared_access_allowed + # Parses the url for the user domain def parse_user_domain(hostname) return hostname.split('.').first if Rails.configuration.url_host.empty? @@ -194,7 +200,23 @@ def handle_bigbluebutton_error # Manually deal with 401 errors rescue_from CanCan::AccessDenied do |_exception| - render "errors/greenlight_error" + if current_user + render "errors/greenlight_error" + else + # Store the current url as a cookie to redirect to after sigining in + cookies[:return_to] = request.url + + # Get the correct signin path + path = if allow_greenlight_accounts? + signin_path + elsif Rails.configuration.loadbalanced_configuration + omniauth_login_url(:bn_launcher) + else + signin_path + end + + redirect_to path + end end private @@ -214,6 +236,7 @@ def check_provider_exists logger.error "Error in retrieve provider info: #{e}" # Use the default site settings @user_domain = "greenlight" + @settings = Setting.find_or_create_by(provider: @user_domain) if e.message.eql? "No user with that id exists" render "errors/greenlight_error", locals: { message: I18n.t("errors.not_found.user_not_found.message"), diff --git a/app/controllers/concerns/authenticator.rb b/app/controllers/concerns/authenticator.rb index f4dc3799ef..f935d6b4c9 100644 --- a/app/controllers/concerns/authenticator.rb +++ b/app/controllers/concerns/authenticator.rb @@ -68,6 +68,18 @@ def logout session.delete(:user_id) if current_user end + # Check if the user is using local accounts + def auth_changed_to_local?(user) + Rails.configuration.loadbalanced_configuration && user.social_uid.present? && allow_greenlight_accounts? + end + + # Check if the user exists under the same email with no social uid and that social accounts are allowed + def auth_changed_to_social?(email) + Rails.configuration.loadbalanced_configuration && + User.exists?(email: email, provider: @user_domain, social_uid: nil) && + !allow_greenlight_accounts? + end + private # Migrates all of the twitter users rooms to the new account diff --git a/app/controllers/concerns/bbb_server.rb b/app/controllers/concerns/bbb_server.rb index 02b3fde72c..ee70ed7c76 100644 --- a/app/controllers/concerns/bbb_server.rb +++ b/app/controllers/concerns/bbb_server.rb @@ -29,6 +29,11 @@ def room_running?(bbb_id) bbb_server.is_meeting_running?(bbb_id) end + # Returns a list of all running meetings + def all_running_meetings + bbb_server.get_meetings + end + def get_recordings(meeting_id) bbb_server.get_recordings(meetingID: meeting_id) end diff --git a/app/controllers/concerns/emailer.rb b/app/controllers/concerns/emailer.rb index 39ee1ea390..69a068333e 100644 --- a/app/controllers/concerns/emailer.rb +++ b/app/controllers/concerns/emailer.rb @@ -101,7 +101,8 @@ def send_approval_user_signup_email(user) return unless Rails.configuration.enable_email_verification admin_emails = admin_emails() - UserMailer.approval_user_signup(user, admins_url, admin_emails, @settings).deliver_now unless admin_emails.empty? + UserMailer.approval_user_signup(user, admins_url(tab: "pending"), + admin_emails, @settings).deliver_now unless admin_emails.empty? rescue => e logger.error "Support: Error in email delivery: #{e}" flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error")) @@ -124,7 +125,7 @@ def send_invite_user_signup_email(user) # Returns the link the user needs to click to verify their account def user_verification_link(user) - edit_account_activation_url(token: user.activation_token, email: user.email) + edit_account_activation_url(token: user.activation_token) end def admin_emails @@ -139,7 +140,7 @@ def admin_emails end def reset_link(user) - edit_password_reset_url(user.reset_token, email: user.email) + edit_password_reset_url(user.reset_token) end def invitation_link(token) diff --git a/app/controllers/concerns/populator.rb b/app/controllers/concerns/populator.rb new file mode 100644 index 0000000000..771fa25e2b --- /dev/null +++ b/app/controllers/concerns/populator.rb @@ -0,0 +1,96 @@ +# 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 Populator + extend ActiveSupport::Concern + + # Returns a list of users that are in the same context of the current user + def manage_users_list + current_role = @role + + initial_user = case @tab + when "active" + User.includes(:roles).without_role(:pending).without_role(:denied) + when "deleted" + User.includes(:roles).deleted + else + User.includes(:roles) + 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 + initial_user.where.not(id: current_user.id) + else + 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, current_role) + .admins_order(@order_column, @order_direction) + else + initial_list.admins_search(@search, current_role) + .admins_order(@order_column, @order_direction) + end + end + + # Returns a list of rooms that are in the same context of the current user + def server_rooms_list + if Rails.configuration.loadbalanced_configuration + Room.includes(:owner).where(users: { provider: @user_domain }) + .admins_search(@search) + .admins_order(@order_column, @order_direction) + else + Room.includes(:owner).all.admins_search(@search).admins_order(@order_column, @order_direction) + end + end + + # Returns list of rooms needed to get the recordings on the server + def rooms_list_for_recordings + if Rails.configuration.loadbalanced_configuration + Room.includes(:owner).where(users: { provider: @user_domain }).pluck(:bbb_id) + else + Room.pluck(:bbb_id) + end + end + + # Returns a list of users that are in the same context of the current user + def shared_user_list + roles_can_appear = [] + Role.where(provider: @user_domain).each do |role| + roles_can_appear << role.name if role.get_permission("can_appear_in_share_list") && role.priority >= 0 + end + + 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) + end +end diff --git a/app/controllers/concerns/rolify.rb b/app/controllers/concerns/rolify.rb index 1fab4bc5e0..e2affed99a 100644 --- a/app/controllers/concerns/rolify.rb +++ b/app/controllers/concerns/rolify.rb @@ -119,17 +119,32 @@ def update_priority(role_to_update) return false if role.priority <= current_user_role.priority || role.provider != @user_domain end - # Update the roles priority including the user role - top_priority = 0 + # Get the priority of the current user's role and start with 1 higher + new_priority = [current_user_role.priority, 0].max + 1 - role_to_update.each_with_index do |id, index| - new_priority = index + [current_user_role.priority, 0].max + 1 - top_priority = new_priority - Role.where(id: id).update_all(priority: new_priority) - end + begin + # Save the old priorities incase something fails + old_priority = Role.where(id: role_to_update).select(:id, :priority).index_by(&:id) + + # Set all the priorities to nil to avoid unique column issues + Role.where(id: role_to_update).update_all(priority: nil) + + # Starting at the starting priority, increase by 1 every time + role_to_update.each_with_index do |id, index| + Role.find(id).update_attribute(:priority, new_priority + index) + end - user_role.priority = top_priority + 1 - user_role.save! + true + rescue => e + # Reset to old prorities + role_to_update.each_with_index do |id, _index| + Role.find(id).update_attribute(:priority, old_priority[id.to_i].priority) + end + + logger.error "#{current_user} failed to update role priorities: #{e}" + + false + end end # Update Permissions @@ -141,7 +156,8 @@ def update_permissions(role) role_params = params.require(:role).permit(:name) permission_params = params.require(:role).permit(:can_create_rooms, :send_promoted_email, - :send_demoted_email, :can_edit_site_settings, :can_edit_roles, :can_manage_users, :colour) + :send_demoted_email, :can_edit_site_settings, :can_edit_roles, :can_manage_users, + :can_manage_rooms_recordings, :can_appear_in_share_list, :colour) permission_params.transform_values! do |v| if v == "0" diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb index 3c22a43e6c..715594e010 100644 --- a/app/controllers/password_resets_controller.rb +++ b/app/controllers/password_resets_controller.rb @@ -56,6 +56,8 @@ def update # Password does not match password confirmation flash.now[:alert] = I18n.t("password_different_notice") elsif @user.update_attributes(user_params) + # Clear the user's social uid if they are switching from a social to a local account + @user.update_attribute(:social_uid, nil) if @user.social_uid.present? # Successfully reset password return redirect_to root_path, flash: { success: I18n.t("password_reset_success") } end @@ -66,7 +68,7 @@ def update private def find_user - @user = User.find_by(email: params[:email]) + @user = User.find_by(reset_digest: User.digest(params[:id]), provider: @user_domain) end def user_params diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index f432253757..2f4440d806 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -20,14 +20,18 @@ class RoomsController < ApplicationController include Pagy::Backend include Recorder include Joiner + include Populator before_action :validate_accepted_terms, unless: -> { !Rails.configuration.terms } before_action :validate_verified_email, except: [:show, :join], unless: -> { !Rails.configuration.enable_email_verification } before_action :find_room, except: [:create, :join_specific_room] - before_action :verify_room_ownership, only: [:destroy, :start, :update_settings] + before_action :verify_room_ownership_or_admin_or_shared, only: [:start, :shared_access] + before_action :verify_room_ownership_or_admin, only: [:update_settings, :destroy] + before_action :verify_room_ownership_or_shared, only: [:remove_shared_access] before_action :verify_room_owner_verified, only: [:show, :join], unless: -> { !Rails.configuration.enable_email_verification } + before_action :verify_room_owner_valid, only: [:show, :join] before_action :verify_user_not_admin, only: [:show] # POST / @@ -60,14 +64,17 @@ def create def show @anyone_can_start = JSON.parse(@room[:room_settings])["anyoneCanStart"] @room_running = room_running?(@room.bbb_id) + @shared_room = room_shared_with_user # If its the current user's room - if current_user && @room.owned_by?(current_user) + if current_user && (@room.owned_by?(current_user) || @shared_room) if current_user.highest_priority_role.get_permission("can_create_rooms") # User is allowed to have rooms @search, @order_column, @order_direction, recs = recordings(@room.bbb_id, params.permit(:search, :column, :direction), true) + @user_list = shared_user_list if shared_access_allowed + @pagy, @recordings = pagy_array(recs) else # Render view for users that cant create rooms @@ -112,10 +119,21 @@ def join # DELETE /:room_uid def destroy - # Don't delete the users home room. - @room.destroy if @room.owned_by?(current_user) && @room != current_user.main_room + begin + # Don't delete the users home room. + raise I18n.t("room.delete.home_room") if @room == @room.owner.main_room + @room.destroy + rescue => e + flash[:alert] = I18n.t("room.delete.fail", error: e) + else + flash[:success] = I18n.t("room.delete.success") + end - redirect_to current_user.main_room + # Redirect to home room if the redirect_back location is the deleted room + return redirect_to @current_user.main_room if request.referer == room_url(@room) + + # Redirect to the location that the user deleted the room from + redirect_back fallback_location: current_user.main_room end # POST /room/join @@ -162,7 +180,6 @@ def update_settings begin options = params[:room].nil? ? params : params[:room] raise "Room name can't be blank" if options[:name].blank? - raise "Unauthorized Request" if !@room.owned_by?(current_user) || @room == current_user.main_room # Update the rooms values room_settings_string = create_room_settings_string(options) @@ -179,7 +196,64 @@ def update_settings flash[:alert] = I18n.t("room.update_settings_error") end - redirect_to room_path + redirect_back fallback_location: room_path(@room) + end + + # POST /:room_uid/update_shared_access + def shared_access + begin + current_list = @room.shared_users.pluck(:id) + new_list = User.where(uid: params[:add]).pluck(:id) + + # Get the list of users that used to be in the list but were removed + users_to_remove = current_list - new_list + # Get the list of users that are in the new list but not in the current list + users_to_add = new_list - current_list + + # Remove users that are removed + SharedAccess.where(room_id: @room.id, user_id: users_to_remove).delete_all unless users_to_remove.empty? + + # Add users that are added + users_to_add.each do |id| + SharedAccess.create(room_id: @room.id, user_id: id) + end + + flash[:success] = I18n.t("room.shared_access_success") + rescue => e + logger.error "Support: Error in updating room shared access: #{e}" + flash[:alert] = I18n.t("room.shared_access_error") + end + + redirect_back fallback_location: room_path + end + + # POST /:room_uid/remove_shared_access + def remove_shared_access + begin + SharedAccess.find_by!(room_id: @room.id, user_id: params[:user_id]).destroy + flash[:success] = I18n.t("room.remove_shared_access_success") + rescue => e + logger.error "Support: Error in removing room shared access: #{e}" + flash[:alert] = I18n.t("room.remove_shared_access_error") + end + + redirect_to current_user.main_room + end + + # GET /:room_uid/shared_users + def shared_users + # Respond with JSON object of users that have access to the room + respond_to do |format| + format.json { render body: @room.shared_users.to_json } + end + end + + # GET /:room_uid/room_settings + def room_settings + # Respond with JSON object of the room_settings + respond_to do |format| + format.json { render body: @room.room_settings.to_json } + end end # GET /:room_uid/logout @@ -219,12 +293,24 @@ def room_params # Find the room from the uid. def find_room - @room = Room.find_by!(uid: params[:room_uid]) + @room = Room.includes(:owner).find_by!(uid: params[:room_uid]) + end + + # Ensure the user either owns the room or is an admin of the room owner or the room is shared with him + def verify_room_ownership_or_admin_or_shared + return redirect_to root_path unless @room.owned_by?(current_user) || + room_shared_with_user || + current_user&.admin_of?(@room.owner) + end + + # Ensure the user either owns the room or is an admin of the room owner + def verify_room_ownership_or_admin + return redirect_to root_path if !@room.owned_by?(current_user) && !current_user&.admin_of?(@room.owner) end - # Ensure the user is logged into the room they are accessing. - def verify_room_ownership - return redirect_to root_path unless @room.owned_by?(current_user) + # Ensure the user owns the room or is allowed to start it + def verify_room_ownership_or_shared + return redirect_to root_path unless @room.owned_by?(current_user) || room_shared_with_user end def validate_accepted_terms @@ -236,10 +322,12 @@ def validate_verified_email end def verify_room_owner_verified - unless @room.owner.activated? - flash[:alert] = t("room.unavailable") - redirect_to root_path - end + redirect_to root_path, alert: t("room.unavailable") unless @room.owner.activated? + end + + # Check to make sure the room owner is not pending or banned + def verify_room_owner_valid + redirect_to root_path, alert: t("room.owner_banned") if @room.owner.has_role?(:pending) || @room.owner.has_role?(:denied) end def verify_user_not_admin @@ -250,6 +338,11 @@ def auth_required @settings.get_value("Room Authentication") == "true" && current_user.nil? end + # Checks if the room is shared with the user and room sharing is enabled + def room_shared_with_user + shared_access_allowed ? @room.shared_with?(current_user) : false + end + def room_limit_exceeded limit = @settings.get_value("Room Limit").to_i diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 11e7d0fe18..23eef094af 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -74,6 +74,10 @@ def create # Check user with that email exists return redirect_to(signin_path, alert: I18n.t("invalid_credentials")) unless user + + # Check if authenticators have switched + return switch_account_to_local(user) if !is_super_admin && auth_changed_to_local?(user) + # Check correct password was entered return redirect_to(signin_path, alert: I18n.t("invalid_credentials")) unless user.try(:authenticate, session_params[:password]) @@ -84,7 +88,10 @@ def create # 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? + unless user.activated? + user.create_activation_token + return redirect_to(account_activation_path(token: user.activation_token)) + end end login(user) @@ -199,6 +206,9 @@ def process_signin # 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 + # Switch the user to a social account if they exist under the same email with no social uid + switch_account_to_social if !@user_exists && auth_changed_to_social?(@auth['info']['email']) + user = User.from_omniauth(@auth) logger.info "Support: Auth user #{user.email} is attempting to login." @@ -225,4 +235,28 @@ def process_signin end end end + + # Send the user a password reset email to allow them to set their password + def switch_account_to_local(user) + logger.info "Switching social account to local account for #{user.uid}" + + # Send the user a reset password email + user.create_reset_digest + send_password_reset_email(user) + + # Overwrite the flash with a more descriptive message if successful + flash[:success] = I18n.t("reset_password.auth_change") if flash[:success].present? + + redirect_to signin_path + end + + # Set the user's social id to the new id being passed + def switch_account_to_social + user = User.find_by(email: @auth['info']['email'], provider: @user_domain, social_uid: nil) + + logger.info "Switching account to social account for #{user.uid}" + + # Set the user's social id to the one being returned from auth + user.update_attribute(:social_uid, @auth['uid']) + end end diff --git a/app/controllers/themes_controller.rb b/app/controllers/themes_controller.rb index f5c32eb29b..79132b7816 100644 --- a/app/controllers/themes_controller.rb +++ b/app/controllers/themes_controller.rb @@ -26,7 +26,7 @@ def index lighten_color = @settings.get_value("Primary Color Lighten") || Rails.configuration.primary_color_lighten_default darken_color = @settings.get_value("Primary Color Darken") || Rails.configuration.primary_color_darken_default - file_name = Rails.root.join('app', 'assets', 'stylesheets', 'utilities', '_primary_themes.scss') + file_name = Rails.root.join('lib', 'assets', '_primary_themes.scss') @file_contents = File.read(file_name) # Include the variables and covert scss file to css diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 846c714d6b..3779c55ece 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -58,6 +58,8 @@ def create # Sign in automatically if email verification is disabled or if user is already verified. login(@user) && return if !Rails.configuration.enable_email_verification || @user.email_verified + @user.create_activation_token + send_activation_email(@user) redirect_to root_path @@ -80,7 +82,14 @@ def delete_account # PATCH /u/:user_uid/edit def update profile = params[:setting] == "password" ? change_password_path(@user) : edit_user_path(@user) - redirect_path = current_user.admin_of?(@user) ? admins_path : profile + if session[:prev_url].present? + path = session[:prev_url] + session.delete(:prev_url) + else + path = admins_path + end + + redirect_path = current_user.admin_of?(@user) ? path : profile if params[:setting] == "password" # Update the users password. @@ -123,12 +132,13 @@ def update # DELETE /u/:user_uid def destroy # Include deleted users in the check + admin_path = request.referer.present? ? request.referer : admins_path @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 + redirect_url = self_delete ? root_path : admin_path begin if current_user && (self_delete || current_user.admin_of?(@user)) @@ -183,7 +193,7 @@ def terms private def find_user - @user = User.where(uid: params[:user_uid]).includes(:roles).first + @user = User.find_by(uid: params[:user_uid]) end # Verify that GreenLight is configured to allow user signup. diff --git a/app/helpers/admins_helper.rb b/app/helpers/admins_helper.rb index aff41f2a58..955d188e4b 100644 --- a/app/helpers/admins_helper.rb +++ b/app/helpers/admins_helper.rb @@ -37,6 +37,14 @@ def room_authentication_string end end + def shared_access_string + if @settings.get_value("Shared Access") == "true" + I18n.t("administrator.site_settings.authentication.enabled") + else + I18n.t("administrator.site_settings.authentication.disabled") + end + end + def recording_default_visibility_string if @settings.get_value("Default Recording Visibility") == "public" I18n.t("recording.visibility.public") @@ -56,6 +64,23 @@ def registration_method_string end end + def log_level_string + case Rails.logger.level + when 0 + t("administrator.site_settings.log_level.debug") + when 1 + t("administrator.site_settings.log_level.info") + when 2 + t("administrator.site_settings.log_level.warn") + when 3 + t("administrator.site_settings.log_level.error") + when 4 + t("administrator.site_settings.log_level.fatal") + when 5 + t("administrator.site_settings.log_level.unknown") + end + end + def room_limit_number @settings.get_value("Room Limit").to_i end @@ -63,4 +88,9 @@ def room_limit_number def edit_disabled @edit_disabled ||= @selected_role.priority <= current_user.highest_priority_role.priority end + + # Get the room status to display in the Server Rooms table + def room_is_running(id) + @running_room_bbb_ids.include?(id) + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 155335cf82..eaf90a23c8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -57,7 +57,6 @@ def fallback_translations # 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 diff --git a/app/helpers/recordings_helper.rb b/app/helpers/recordings_helper.rb index 062964575e..2cc2dc8211 100644 --- a/app/helpers/recordings_helper.rb +++ b/app/helpers/recordings_helper.rb @@ -24,18 +24,13 @@ def recording_date(date) # Helper for converting BigBlueButton dates into a nice length string. def recording_length(playbacks) - # Stats format currently doesn't support length. - valid_playbacks = playbacks.reject { |p| p[:type] == "statistics" } - return "0 min" if valid_playbacks.empty? - - len = valid_playbacks.first[:length] - if len > 60 - "#{(len / 60).to_i} h #{len % 60} min" - elsif len.zero? - "< 1 min" - else - "#{len} min" + # Looping through playbacks array and returning first non-zero length value + playbacks.each do |playback| + length = playback[:length] + return recording_length_string(length) unless length.zero? end + # Return '< 1 min' if length values are zero + "< 1 min" end # Prevents single images from erroring when not passed as an array. @@ -51,4 +46,15 @@ def room_uid_from_bbb(bbb_id) def recording_thumbnails? Rails.configuration.recording_thumbnails end + + private + + # Returns length of the recording as a string + def recording_length_string(len) + if len > 60 + "#{(len / 60).to_i} h #{len % 60} min" + else + "#{len} min" + end + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 649bf02e23..541d6940bc 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -27,7 +27,7 @@ def initialize(user) else highest_role = user.highest_priority_role if highest_role.get_permission("can_edit_site_settings") - can [:index, :site_settings, :server_recordings, :update_settings, :coloring, :registration_method], :admin + can [:index, :site_settings, :update_settings, :coloring, :registration_method], :admin end if highest_role.get_permission("can_edit_roles") @@ -36,11 +36,13 @@ def initialize(user) 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") + if !highest_role.get_permission("can_edit_site_settings") && !highest_role.get_permission("can_edit_roles") && - !highest_role.get_permission("can_manage_users") + !highest_role.get_permission("can_manage_users") && !highest_role.get_permission("can_manage_rooms_recordings") cannot :manage, AdminsController end end diff --git a/app/models/concerns/auth_values.rb b/app/models/concerns/auth_values.rb index 06681f9d25..0c35fb2165 100644 --- a/app/models/concerns/auth_values.rb +++ b/app/models/concerns/auth_values.rb @@ -48,6 +48,9 @@ def auth_image(auth) case auth['provider'] when :twitter auth['info']['image'].gsub("http", "https").gsub("_normal", "") + when :ldap + return auth['info']['image'] if auth['info']['image']&.starts_with?("http") + "" else auth['info']['image'] end diff --git a/app/models/role.rb b/app/models/role.rb index a4b1331fd2..124bcd8e30 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -20,7 +20,7 @@ class Role < ApplicationRecord has_and_belongs_to_many :users, join_table: :users_roles has_many :role_permissions - default_scope { order(:priority) } + default_scope { includes(:role_permissions).order(:priority) } scope :by_priority, -> { order(:priority) } scope :editable_roles, ->(provider) { where(provider: provider).where.not(name: %w[super_admin denied pending]) } @@ -35,14 +35,14 @@ def self.create_default_roles(provider) .update_all_role_permissions(can_create_rooms: true) Role.create(name: "admin", provider: provider, priority: 0, colour: "#f1c40f") .update_all_role_permissions(can_create_rooms: true, send_promoted_email: true, - send_demoted_email: true, can_edit_site_settings: true, + send_demoted_email: true, can_edit_site_settings: true, can_manage_rooms_recordings: true, can_edit_roles: true, can_manage_users: true) Role.create(name: "pending", provider: provider, priority: -1, colour: "#17a2b8").update_all_role_permissions - Role.create(name: "denied", provider: provider, priority: -1, colour: "#343a40").update_all_role_permissions - Role.create(name: "super_admin", provider: provider, priority: -2, colour: "#cd201f") + Role.create(name: "denied", provider: provider, priority: -2, colour: "#343a40").update_all_role_permissions + Role.create(name: "super_admin", provider: provider, priority: -3, colour: "#cd201f") .update_all_role_permissions(can_create_rooms: true, send_promoted_email: true, send_demoted_email: true, can_edit_site_settings: true, - can_edit_roles: true, can_manage_users: true) + can_edit_roles: true, can_manage_users: true, can_manage_rooms_recordings: true) end def self.create_new_role(role_name, provider) @@ -55,8 +55,8 @@ def self.create_new_role(role_name, provider) role.priority = user_role.priority user_role.priority += 1 - role.save! user_role.save! + role.save! role end @@ -68,10 +68,15 @@ def update_all_role_permissions(permissions = {}) update_permission("can_edit_site_settings", permissions[:can_edit_site_settings].to_s) update_permission("can_edit_roles", permissions[:can_edit_roles].to_s) update_permission("can_manage_users", permissions[:can_manage_users].to_s) + update_permission("can_manage_rooms_recordings", permissions[:can_manage_rooms_recordings].to_s) + update_permission("can_appear_in_share_list", permissions[:can_appear_in_share_list].to_s) end # Updates the value of the permission and enables it def update_permission(name, value) + # Dont update if it is not explicitly set to a value + return unless value.present? + permission = role_permissions.find_or_create_by!(name: name) permission.update_attributes(value: value, enabled: true) @@ -79,18 +84,36 @@ def update_permission(name, value) # Returns the value if enabled or the default if not enabled def get_permission(name, return_boolean = true) - permission = role_permissions.find_or_create_by!(name: name) + value = nil - value = if permission[:enabled] - permission[:value] - else - "false" + role_permissions.each do |permission| + next if permission.name != name + + value = if permission.enabled + permission.value + else + default_value(name) + end end + # Create the role_permissions since it doesn't exist + role_permissions.create(name: name) if value.nil? + if return_boolean value == "true" else value end end + + private + + def default_value(name) + case name + when "can_appear_in_share_list" + Rails.configuration.shared_access_default.to_s + else + "false" + end + end end diff --git a/app/models/room.rb b/app/models/room.rb index 0e61fa7e24..158fdc23d4 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -26,11 +26,46 @@ class Room < ApplicationRecord validates :name, presence: true belongs_to :owner, class_name: 'User', foreign_key: :user_id + has_many :shared_access + + def self.admins_search(string) + active_database = Rails.configuration.database_configuration[Rails.env]["adapter"] + # Postgres requires created_at to be cast to a string + created_at_query = if active_database == "postgresql" + "created_at::text" + else + "created_at" + end + + search_query = "rooms.name LIKE :search OR rooms.uid LIKE :search OR users.email LIKE :search" \ + " OR users.#{created_at_query} LIKE :search" + + search_param = "%#{string}%" + + where(search_query, search: search_param) + end + + def self.admins_order(column, direction) + # Include the owner of the table + table = joins(:owner) + + return table.order(Arel.sql("rooms.#{column} #{direction}")) if table.column_names.include?(column) || column == "users.name" + + table + end # Determines if a user owns a room. def owned_by?(user) + user_id == user&.id + end + + def shared_users + User.where(id: shared_access.pluck(:user_id)) + end + + def shared_with?(user) return false if user.nil? - user.rooms.include?(self) + shared_users.include?(user) end # Determines the invite path for the room. diff --git a/app/models/setting.rb b/app/models/setting.rb index 82f395f575..d657ad36ac 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -28,22 +28,36 @@ def update_value(name, value) # 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 - when "Registration Method" - Rails.configuration.registration_method_default - when "Room Authentication" - false - when "Room Limit" - Rails.configuration.number_of_rooms_default - end + # Return feature value if already exists + features.each do |feature| + next if feature.name != name + + return feature.value if feature.enabled + return default_value(name) + end + + # Create the feature since it doesn't exist + features.create(name: name) + default_value(name) + end + + private + + def default_value(name) + # return default value + case name + when "Branding Image" + Rails.configuration.branding_image_default + when "Primary Color" + Rails.configuration.primary_color_default + when "Registration Method" + Rails.configuration.registration_method_default + when "Room Authentication" + false + when "Room Limit" + Rails.configuration.number_of_rooms_default + when "Shared Access" + Rails.configuration.shared_access_default end end end diff --git a/app/models/shared_access.rb b/app/models/shared_access.rb new file mode 100644 index 0000000000..eb88a555c0 --- /dev/null +++ b/app/models/shared_access.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class SharedAccess < ApplicationRecord + belongs_to :room +end diff --git a/app/models/user.rb b/app/models/user.rb index 4cae3de96a..d87e6a4b72 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -21,7 +21,7 @@ class User < ApplicationRecord include Deleteable - attr_accessor :reset_token + attr_accessor :reset_token, :activation_token after_create :setup_user before_save { email.try(:downcase!) } @@ -29,9 +29,10 @@ class User < ApplicationRecord before_destroy :destroy_rooms has_many :rooms + has_many :shared_access belongs_to :main_room, class_name: 'Room', foreign_key: :room_id, required: false - has_and_belongs_to_many :roles, -> { includes :role_permissions }, join_table: :users_roles + has_and_belongs_to_many :roles, join_table: :users_roles validates :name, length: { maximum: 256 }, presence: true validates :provider, presence: true @@ -102,6 +103,11 @@ def self.admins_order(column, direction) order(Arel.sql("#{column} #{direction}")) end + # Returns a list of rooms ordered by last session + def ordered_rooms + [main_room] + rooms.where.not(id: main_room.id).order("last_session desc") + end + # Activates an account and initialize a users main room def activate update_attributes(email_verified: true, activated_at: Time.zone.now) @@ -121,7 +127,7 @@ def create_reset_digest def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? - BCrypt::Password.new(digest).is_password?(token) + digest == Digest::SHA256.base64digest(token) end # Return true if password reset link expires @@ -129,10 +135,9 @@ def password_reset_expired? reset_sent_at < 2.hours.ago end - # Retrives a list of all a users rooms that are not the main room, sorted by last session date. - def secondary_rooms - room_list = rooms.where.not(uid: main_room.uid) - room_list.where.not(last_session: nil).order("last_session desc") + room_list.where(last_session: nil) + # Retrieves a list of rooms that are shared with the user + def shared_rooms + Room.where(id: shared_access.pluck(:room_id)) end def name_chunk @@ -153,9 +158,9 @@ def greenlight_account? social_uid.nil? end - def activation_token - # Create the token. - create_reset_activation_digest(User.new_token) + def create_activation_token + self.activation_token = User.new_token + update_attributes(activation_digest: User.digest(activation_token)) end def admin_of?(user) @@ -172,8 +177,7 @@ def admin_of?(user) end def self.digest(string) - cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost - BCrypt::Password.create(string, cost: cost) + Digest::SHA256.base64digest(string) end # Returns a random token. @@ -183,7 +187,7 @@ def self.new_token # role functions def highest_priority_role - roles.by_priority.first + roles.min_by(&:priority) end def add_role(role) @@ -217,7 +221,11 @@ def remove_role(role) # rubocop:disable Naming/PredicateName def has_role?(role) # rubocop:enable Naming/PredicateName - roles.exists?(name: role) + roles.each do |single_role| + return true if single_role.name.eql? role.to_s + end + + false end def self.with_role(role) @@ -228,19 +236,24 @@ def self.without_role(role) User.where.not(id: with_role(role).pluck(:id)) end + def self.with_highest_priority_role(role) + User.all_users_highest_priority_role.where(roles: { name: role }) + end + def self.all_users_with_roles User.joins("INNER JOIN users_roles ON users_roles.user_id = users.id INNER JOIN roles " \ "ON roles.id = users_roles.role_id INNER JOIN role_permissions ON roles.id = role_permissions.role_id").distinct end - private - - def create_reset_activation_digest(token) - # Create the digest and persist it. - update_attribute(:activation_digest, User.digest(token)) - token + def self.all_users_highest_priority_role + User.joins("INNER JOIN (SELECT user_id, min(roles.priority) as role_priority FROM users_roles " \ + "INNER JOIN roles ON users_roles.role_id = roles.id GROUP BY user_id) as a ON " \ + "a.user_id = users.id INNER JOIN roles ON roles.priority = a.role_priority " \ + " INNER JOIN role_permissions ON roles.id = role_permissions.role_id").distinct end + private + # Destory a users rooms when they are removed. def destroy_rooms rooms.destroy_all diff --git a/app/views/account_activations/show.html.erb b/app/views/account_activations/show.html.erb index 1098255f00..c2ab1b521e 100644 --- a/app/views/account_activations/show.html.erb +++ b/app/views/account_activations/show.html.erb @@ -22,7 +22,7 @@

    <%= t("verify.not_verified") %>

    - <%= button_to t("verify.resend"), resend_email_path, params: { email: params['email'], email_verified: false }, class: "btn btn-primary btn-space" %> + <%= button_to t("verify.resend"), resend_email_path, params: { token: params['token'], email_verified: false }, class: "btn btn-primary btn-space", "data-disable": "" %>
    diff --git a/app/views/admins/components/_menu_buttons.html.erb b/app/views/admins/components/_menu_buttons.html.erb index 17bbc46086..3af0953231 100644 --- a/app/views/admins/components/_menu_buttons.html.erb +++ b/app/views/admins/components/_menu_buttons.html.erb @@ -21,17 +21,22 @@ <%= t("administrator.users.title") %> <% end %> <% end %> - <% if highest_role.get_permission("can_edit_site_settings") || highest_role.name == "super_admin" %> + <% if highest_role.get_permission("can_manage_rooms_recordings") || highest_role.name == "super_admin" %> + <%= link_to admin_rooms_path, class: "list-group-item list-group-item-action dropdown-item #{"active" if active_page == "server_rooms"}" do %> + <%= t("administrator.rooms.title") %> + <% end %> <%= link_to admin_recordings_path, class: "list-group-item list-group-item-action dropdown-item #{"active" if active_page == "server_recordings"}" do %> - <%= t("administrator.recordings.title") %> + <%= t("administrator.recordings.title") %> <% end %> + <% end %> + <% if highest_role.get_permission("can_edit_site_settings") || highest_role.name == "super_admin" %> <%= link_to admin_site_settings_path, class: "list-group-item list-group-item-action dropdown-item #{"active" if active_page == "site_settings"}" do %> <%= t("administrator.site_settings.title") %> <% end %> <% end %> <% if highest_role.get_permission("can_edit_roles") || highest_role.name == "super_admin" %> <%= link_to admin_roles_path, class: "list-group-item list-group-item-action dropdown-item #{"active" if active_page == "roles"}" do %> - <%= t("administrator.roles.title") %> + <%= t("administrator.roles.title") %> <% end %> <% end %> \ No newline at end of file diff --git a/app/views/admins/components/_roles.html.erb b/app/views/admins/components/_roles.html.erb index 51f5cf14a9..77a316e1f5 100644 --- a/app/views/admins/components/_roles.html.erb +++ b/app/views/admins/components/_roles.html.erb @@ -53,14 +53,14 @@ <%= f.check_box :can_create_rooms, checked: @selected_role.get_permission("can_create_rooms"), class: "custom-switch-input", disabled: edit_disabled || !current_role.get_permission("can_create_rooms") %> -