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") %>
-