Add optional moderator codes (#2413)

* add column for moderator code

* add interface for moderator access code

* add support for write and update moderator access

* check if correct moderator_code in session

* move access code form into own component

* add support for moderator access code

* add support for moderator access code

* add copy code button for moderator code

* freeze all the things

* add tests for moderator access code

* add helpfer for moderator_access setting

* add setting for moderator access code

* show setting for moderator access code

* add checks for moderator code setting

* use method from room controller for moderator password check

* add tests for login with moderator access code

* add check for moderator code setting

* check if moderator codes are enabled in settings

* only display form for moderator code if enabled in settings

* add newline at end of file

* make check for moderator code available as helper

* align style of join button and access code button

* add localization for moderator codes

* add field for moderator codes

* add field for moderator access code to rooms

* fixes for rubocop

* fix LineLenghts for rubocop

* fix double space

Co-authored-by: Ahmad Farhat <ahmad.af.farhat@gmail.com>
master
zechmeister 2 years ago committed by GitHub
parent 4cd41f5aa8
commit 9dc59b1211
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 44
      app/assets/javascripts/room.js
  2. 4
      app/assets/stylesheets/rooms.scss
  3. 6
      app/controllers/application_controller.rb
  4. 5
      app/controllers/concerns/joiner.rb
  5. 30
      app/controllers/rooms_controller.rb
  6. 8
      app/helpers/admins_helper.rb
  7. 2
      app/models/setting.rb
  8. 21
      app/views/admins/components/site_settings/_settings.html.erb
  9. 29
      app/views/rooms/components/_enter_access_code_form.html.erb
  10. 2
      app/views/rooms/components/_room_block.html.erb
  11. 29
      app/views/rooms/join.html.erb
  12. 287
      app/views/rooms/show.html.erb
  13. 13
      app/views/shared/modals/_create_room_modal.html.erb
  14. 3
      config/application.rb
  15. 10
      config/locales/de_DE.yml
  16. 10
      config/locales/en.yml
  17. 7
      db/migrate/20210108032132_add_moderator_access_code_to_rooms.rb
  18. 3
      db/schema.rb
  19. 108
      spec/controllers/rooms_controller_spec.rb

@ -184,17 +184,19 @@ function copyInvite() {
}
}
function copyAccess() {
$('#copy-code').attr("type", "text")
$('#copy-code').select()
function copyAccess(target) {
input = target ? $("#copy-" + target + "-code") : $("#copy-code")
input.attr("type", "text")
input.select()
if (document.execCommand("copy")) {
$('#copy-code').attr("type", "hidden")
copy = $("#copy-access")
input.attr("type", "hidden")
copy = target ? $("#copy-" + target + "-access") : $("#copy-access")
copy.addClass('btn-success');
copy.html("<i class='fas fa-check mr-1'></i>" + getLocalizedString("copied"))
setTimeout(function(){
copy.removeClass('btn-success');
copy.html("<i class='fas fa-copy mr-1'></i>" + getLocalizedString("room.copy_access"))
originalString = target ? getLocalizedString("room.copy_" + target + "_access") : getLocalizedString("room.copy_access")
copy.html("<i class='fas fa-copy mr-1'></i>" + originalString)
}, 1000)
}
}
@ -202,7 +204,9 @@ function copyAccess() {
function showCreateRoom(target) {
$("#create-room-name").val("")
$("#create-room-access-code").text(getLocalizedString("modal.create_room.access_code_placeholder"))
$("#create-room-moderator-access-code").text(getLocalizedString("modal.create_room.moderator_access_code_placeholder"))
$("#room_access_code").val(null)
$("#room_moderator_access_code").val(null)
$("#createRoomModal form").attr("action", $("body").data('relative-root'))
$("#room_mute_on_join").prop("checked", $("#room_mute_on_join").data("default"))
@ -254,6 +258,16 @@ function showUpdateRoom(target) {
$("#create-room-access-code").text(getLocalizedString("modal.create_room.access_code_placeholder"))
$("#room_access_code").val(null)
}
var moderatorAccessCode = modal.closest(".room-block").data("room-moderator-access-code")
if(moderatorAccessCode){
$("#create-room-moderator-access-code").text(getLocalizedString("modal.create_room.moderator_access_code") + ": " + moderatorAccessCode)
$("#room_moderator_access_code").val(moderatorAccessCode)
} else {
$("#create-room-moderator-access-code").text(getLocalizedString("modal.create_room.moderator_access_code_placeholder"))
$("#room_moderator_access_code").val(null)
}
}
function showDeleteRoom(target) {
@ -291,6 +305,24 @@ function ResetAccessCode(){
$("#room_access_code").val(null)
}
function generateModeratorAccessCode(){
const accessCodeLength = 6
var validCharacters = "abcdefghijklmopqrstuvwxyz"
var accessCode = ""
for( var i = 0; i < accessCodeLength; i++){
accessCode += validCharacters.charAt(Math.floor(Math.random() * validCharacters.length));
}
$("#create-room-moderator-access-code").text(getLocalizedString("modal.create_room.moderator_access_code") + ": " + accessCode)
$("#room_moderator_access_code").val(accessCode)
}
function ResetModeratorAccessCode(){
$("#create-room-moderator-access-code").text(getLocalizedString("modal.create_room.moderator_access_code_placeholder"))
$("#room_moderator_access_code").val(null)
}
function saveAccessChanges() {
let listItemsToAdd = $("#user-list li:not(.remove-shared)").toArray().map(user => $(user).data("uid"))

@ -32,8 +32,8 @@
font-size: 20px !important;
}
.join-input {
height: 48px;
.moderator-code-label {
margin-top: 150px !important;
}
.home-indicator {

@ -186,6 +186,12 @@ class ApplicationController < ActionController::Base
end
helper_method :recording_consent_required?
# Indicates whether users are allowed to add moderator access codes to rooms
def moderator_code_allowed?
@settings.get_value("Moderator Access Codes") == "true"
end
helper_method :moderator_code_allowed?
# Returns a list of allowed file types
def allowed_file_types
Rails.configuration.allowed_file_types

@ -50,10 +50,11 @@ module Joiner
def join_room(opts)
@room_settings = JSON.parse(@room[:room_settings])
if room_running?(@room.bbb_id) || @room.owned_by?(current_user) || room_setting_with_config("anyoneCanStart")
moderator_privileges = @room.owned_by?(current_user) || valid_moderator_access_code(session[:moderator_access_code])
if room_running?(@room.bbb_id) || room_setting_with_config("anyoneCanStart") || moderator_privileges
# Determine if the user needs to join as a moderator.
opts[:user_is_moderator] = @room.owned_by?(current_user) || room_setting_with_config("joinModerator") || @shared_room
opts[:user_is_moderator] = room_setting_with_config("joinModerator") || @shared_room || moderator_privileges
opts[:record] = record_meeting
opts[:require_moderator_approval] = room_setting_with_config("requireModeratorApproval")
opts[:mute_on_start] = room_setting_with_config("muteOnStart")

@ -44,7 +44,9 @@ class RoomsController < ApplicationController
return redirect_to current_user.main_room, flash: { alert: I18n.t("room.room_limit") } if room_limit_exceeded
# Create room
@room = Room.new(name: room_params[:name], access_code: room_params[:access_code])
@room = Room.new(name: room_params[:name],
access_code: room_params[:access_code],
moderator_access_code: room_params[:moderator_access_code])
@room.owner = current_user
@room.room_settings = create_room_settings_string(room_params)
@ -109,8 +111,9 @@ class RoomsController < ApplicationController
@shared_room = room_shared_with_user
unless @room.owned_by?(current_user) || @shared_room
# Don't allow users to join unless they have a valid access code or the room doesn't have an access code
if @room.access_code && !@room.access_code.empty? && @room.access_code != session[:access_code]
# Don't allow users to join unless they have a valid access code or the room doesn't have an access codes
valid_access_code = !@room.access_code.present? || @room.access_code == session[:access_code]
if !valid_access_code && !valid_moderator_access_code(session[:moderator_access_code])
return redirect_to room_path(room_uid: params[:room_uid]), flash: { alert: I18n.t("room.access_code_required") }
end
@ -203,7 +206,8 @@ class RoomsController < ApplicationController
@room.update_attributes(
name: options[:name],
room_settings: room_settings_string,
access_code: options[:access_code]
access_code: options[:access_code],
moderator_access_code: options[:moderator_access_code]
)
flash[:success] = I18n.t("room.update_settings_success")
@ -321,9 +325,16 @@ class RoomsController < ApplicationController
# POST /:room_uid/login
def login
session[:access_code] = room_params[:access_code]
# use same form for access_code and moderator_access_code
if valid_moderator_access_code(room_params[:access_code])
session[:moderator_access_code] = room_params[:access_code]
else
session[:access_code] = room_params[:access_code]
end
flash[:alert] = I18n.t("room.access_code_required") if session[:access_code] != @room.access_code
if session[:access_code] != @room.access_code && !valid_moderator_access_code(session[:moderator_access_code])
flash[:alert] = I18n.t("room.access_code_required")
end
redirect_to room_path(@room.uid)
end
@ -345,7 +356,7 @@ class RoomsController < ApplicationController
def room_params
params.require(:room).permit(:name, :auto_join, :mute_on_join, :access_code,
:require_moderator_approval, :anyone_can_start, :all_join_moderator,
:recording, :presentation)
:recording, :presentation, :moderator_access_code)
end
# Find the room from the uid.
@ -412,6 +423,11 @@ class RoomsController < ApplicationController
end
helper_method :room_limit_exceeded
def valid_moderator_access_code(code)
code == @room.moderator_access_code && !@room.moderator_access_code.blank? && moderator_code_allowed?
end
helper_method :valid_moderator_access_code
def record_meeting
# If the require consent setting is checked, then check the room setting, else, set to true
if recording_consent_required?

@ -89,6 +89,14 @@ module AdminsHelper
end
end
def moderator_codes_string
if @settings.get_value("Moderator Access Codes") == "true"
I18n.t("administrator.site_settings.moderator_codes.enabled")
else
I18n.t("administrator.site_settings.moderator_codes.disabled")
end
end
def log_level_string
case Rails.logger.level
when 0

@ -66,6 +66,8 @@ class Setting < ApplicationRecord
Rails.configuration.shared_access_default
when "Preupload Presentation"
Rails.configuration.preupload_presentation_default
when "Moderator Access Codes"
Rails.configuration.moderator_codes_default
when "Room Configuration Mute On Join"
room_config_setting("mute-on-join")
when "Room Configuration Require Moderator"

@ -143,6 +143,27 @@
</div>
</div>
</div>
<div class="row mb-2">
<div class="col-12">
<div class="form-group">
<label class="form-label"><%= t("administrator.site_settings.moderator_codes.title") %></label>
<label class="form-label text-muted"><%= t("administrator.site_settings.moderator_codes.info") %></label>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<%= moderator_codes_string %>
</button>
<div class="dropdown-menu" aria-labelledby="room-auth">
<%= button_to admin_update_settings_path(setting: "Moderator Access Codes", value: "true"), class: "dropdown-item", "data-disable": "" do %>
<%= t("administrator.site_settings.moderator_codes.enabled") %>
<% end %>
<%= button_to admin_update_settings_path(setting: "Moderator Access Codes", value: "false"), class: "dropdown-item", "data-disable": "" do %>
<%= t("administrator.site_settings.moderator_codes.disabled") %>
<% end %>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="form-group">

@ -0,0 +1,29 @@
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<%= form_for :room, url: login_room_path(@room.uid) do |f| %>
<div class="input-group join-input">
<%= f.text_field :access_code,
required: true,
class: "form-control join-form",
placeholder: access_code_type == 'moderator' ? t("room.enter_the_moderator_access_code") : t("room.enter_the_access_code"),
value: "" ,
autofocus: true,
maxlength: 26 %>
<span class="input-group-append">
<%= f.button t("room.login"), type: :submit, class: "btn btn-primary btn-sm px-7 form-control join-form" %>
</span>
</div>
<% end %>

@ -13,7 +13,7 @@
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<div data-path="<%= update_settings_path(room) %>" data-room-access-code="<%= room.access_code %>" class="card room-block">
<div data-path="<%= update_settings_path(room) %>" data-room-access-code="<%= room.access_code %>" data-room-moderator-access-code="<%= room.moderator_access_code %>" class="card room-block">
<div class="card-body p-1">
<table class="table table-hover table-vcenter text-wrap table-no-border">
<tbody class="no-border-top">

@ -15,23 +15,15 @@
<% content_for(:page_desc) { t("room.invitation_description", name: @room.name) } %>
<% valid_access_code = @room.access_code.nil? || @room.access_code.empty? || @room.access_code == session[:access_code] %>
<%= render 'rooms/components/room_event', render_recordings: valid_access_code do %>
<% access_code_set = @room.access_code.present? %>
<% valid_access_code = access_code_set && @room.access_code == session[:access_code] %>
<% moderator_access_code_set = @room.moderator_access_code.present? && moderator_code_allowed? %>
<% valid_moderator_access_code = valid_moderator_access_code(session[:moderator_access_code]) %>
<%= render 'rooms/components/room_event', render_recordings: valid_access_code || valid_moderator_access_code do %>
<% if room_authentication_required %>
<h2><%= t("administrator.site_settings.authentication.user-info") %></h2>
<% elsif !valid_access_code %>
<%= form_for :room, url: login_room_path(@room.uid) do |f| %>
<div class="input-group join-input">
<%= f.text_field :access_code,
required: true,
class: "form-control join-form",
placeholder: t("room.enter_the_access_code"),
value: "" ,
autofocus: true %>
<%= f.submit t("room.login"), class: "btn btn-primary btn-sm col-sm-3 form-control join-form" %>
</div>
<% end %>
<% else %>
<% elsif valid_moderator_access_code || valid_access_code || !access_code_set %>
<%= form_for room_path(@room), method: :post do |f| %>
<div class="input-group">
<%= f.hidden_field(:search, :value => params[:search])%>
@ -59,5 +51,12 @@
</label>
<% end %>
<% end %>
<% if moderator_access_code_set && !valid_moderator_access_code %>
<!-- <hr class="mt-2 float-right w-100 moderator-code-hr"> -->
<label class="moderator-code-label form-label"><%= t("room.optional_moderator_access_code") %></label>
<%= render "rooms/components/enter_access_code_form", access_code_type: 'moderator' %>
<% end %>
<% else %>
<%= render "rooms/components/enter_access_code_form", access_code_type: 'standard_access' %>
<% end %>
<% end %>

@ -1,140 +1,147 @@
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<% exceeds_limit = current_room_exceeds_limit(@room)%>
<% if exceeds_limit%>
<div class="alert alert-danger alert-dismissible text-center mb-0">
<%= t("room.room_limit_exceeded", difference: @diff) %>
</div>
<% end %>
<div class="background pb-1">
<div class="container">
<div class="row pt-7 pt-sm-9 mb-7">
<div class="col-lg-9 col-sm-12">
<div id="room-title" class="display-3 form-inline <%= 'edit_hover_class' if current_user.main_room != @room %>" data-path="<%= update_settings_path(@room) %>">
<h1 contenteditable=false id="user-text" class="display-3 text-left mb-3 font-weight-400 text-break"><%= title(@room.name) %></h1>
<% if current_user.main_room == @room %>
<a class="disable-click"><i class="fas fa-home align-top home-indicator ml-2"></i></a>
<% else %>
<a><i id="edit-room" class="fa fa-edit align-top home-indicator ml-2" data-edit-room="<%= @room.uid %>"></i></a>
<% end %>
</div>
<h4 class="text-left mb-6"><%= @room.sessions %> <%= t("room.sessions") %><% unless hide_recording_tables %> | <%= @recordings.length %> <%= t("room.recordings") %><% end %></h4>
<% unless exceeds_limit %>
<label class="form-label"><%= t("room.invite_participants") %></label>
<div class="row">
<div class="col-lg-6 col-md-12 mt-2">
<div class="input-icon invite-link-input">
<span class="input-icon-addon">
<i class="fas fa-link"></i>
</span>
<input id="invite-url" type="text" class="form-control w-100" value="<%= request.base_url + @room.invite_path %>" readonly="">
</div>
</div>
<div class="col-lg-6 pr-lg-7 col-md-12 pl-lg-0">
<div class="row">
<div class="col-sm-6 mt-2">
<button id="copy-invite" class="btn btn-primary btn-block" onclick="copyInvite()">
<i class="fas fa-copy mr-1"></i>
<%= t("copy") %>
</button>
</div>
<div class="col-sm-6 pl-0 mt-2">
<% if @room.access_code.present? %>
<input id="copy-code" value="<%= @room.access_code %>" type="hidden">
<button id="copy-access" class="btn btn-secondary btn-block" onclick="copyAccess()">
<i class="fas fa-copy mr-1"></i>
<%= t("room.copy_access") %>
</button>
<% end %>
<% if Rails.configuration.enable_google_calendar_button %>
<a href="<%= google_calendar_path %>" target="__blank" id="schedule" class="btn btn-primary btn-block mt-2">
<i class="fas fa-calendar-plus"></i>
<%= t("add_to_google_calendar") %>
</a>
<% end %>
</div>
</div>
</div>
</div>
<% end %>
</div>
<div class="col-lg-3 col-sm-12 force-bottom mt-5">
<% if @room_running %>
<%= button_to t("room.join"), room_path(@room), class: "btn btn-primary btn-block px-7 start-button float-right", "data-disable": "" %>
<% else %>
<% unless exceeds_limit %>
<%= button_to t("room.start"), start_room_path(@room), class: "btn btn-primary btn-block px-7 start-button float-right", "data-disable": "" %>
<% end %>
<% end %>
</div>
</div>
<% if total_room_count(current_user) > 5 %>
<div class="input-icon invite-link-input mb-3">
<span class="input-icon-addon">
<i class="fas fa-search"></i>
</span>
<input id="room-search" type="text" placeholder="<%= t("room.search") %>" class="form-control w-100" onChange="filterRooms()" onKeyUp="filterRooms()">
<span id="clear-room-search" class="text-primary" onclick="clearRoomSearch()">
<i class="fas fa-times"></i>
</span>
</div>
<% end %>
<div id="room_block_container" class="row mb-5">
<% if current_user.role.get_permission("can_create_rooms") %>
<% current_user.ordered_rooms.each do |room| %>
<div class="col-lg-4 col-md-6 col-sm-12">
<%= link_to room do %>
<%= render "rooms/components/room_block", room: room %>
<% end %>
</div>
<% end %>
<% end %>
<% if shared_access_allowed %>
<% current_user.shared_rooms.each do |room| %>
<div class="col-lg-4 col-md-6 col-sm-12">
<%= link_to room do %>
<%= render "rooms/components/shared_room_block", room: room %>
<% end %>
</div>
<% end %>
<% end %>
<% if current_user.role.get_permission("can_create_rooms") && !room_limit_exceeded %>
<%= render "rooms/components/create_room_block"%>
<% end %>
</div>
</div>
</div>
<% unless hide_recording_tables %>
<%= render "shared/sessions", recordings: @recordings, pagy: @pagy, only_public: false, shared_room: @shared_room, user_recordings: false, title: t("room.recordings")%>
<% end %>
<%= render "shared/modals/delete_room_modal" %>
<%= render "shared/modals/create_room_modal" %>
<% if preupload_allowed? %>
<%= render "shared/modals/preupload_presentation_modal" %>
<% end %>
<% if shared_access_allowed %>
<%= render "shared/modals/share_room_modal" %>
<%= render "shared/modals/remove_access_modal" %>
<% end %>
<%
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
%>
<% exceeds_limit = current_room_exceeds_limit(@room)%>
<% if exceeds_limit%>
<div class="alert alert-danger alert-dismissible text-center mb-0">
<%= t("room.room_limit_exceeded", difference: @diff) %>
</div>
<% end %>
<div class="background pb-1">
<div class="container">
<div class="row pt-7 pt-sm-9 mb-7">
<div class="col-lg-9 col-sm-12">
<div id="room-title" class="display-3 form-inline <%= 'edit_hover_class' if current_user.main_room != @room %>" data-path="<%= update_settings_path(@room) %>">
<h1 contenteditable=false id="user-text" class="display-3 text-left mb-3 font-weight-400 text-break"><%= title(@room.name) %></h1>
<% if current_user.main_room == @room %>
<a class="disable-click"><i class="fas fa-home align-top home-indicator ml-2"></i></a>
<% else %>
<a><i id="edit-room" class="fa fa-edit align-top home-indicator ml-2" data-edit-room="<%= @room.uid %>"></i></a>
<% end %>
</div>
<h4 class="text-left mb-6"><%= @room.sessions %> <%= t("room.sessions") %><% unless hide_recording_tables %> | <%= @recordings.length %> <%= t("room.recordings") %><% end %></h4>
<% unless exceeds_limit %>
<label class="form-label"><%= t("room.invite_participants") %></label>
<div class="row">
<div class="col-lg-6 col-md-12 mt-2">
<div class="input-icon invite-link-input">
<span class="input-icon-addon">
<i class="fas fa-link"></i>
</span>
<input id="invite-url" type="text" class="form-control w-100" value="<%= request.base_url + @room.invite_path %>" readonly="">
</div>
</div>
<div class="col-lg-6 pr-lg-7 col-md-12 pl-lg-0">
<div class="row">
<div class="col-sm-6 mt-2">
<button id="copy-invite" class="btn btn-primary btn-block" onclick="copyInvite()">
<i class="fas fa-copy mr-1"></i>
<%= t("copy") %>
</button>
</div>
<div class="btn-group-vertical col-sm-6 pl-0 mt-2">
<% if @room.access_code.present? %>
<input id="copy-code" value="<%= @room.access_code %>" type="hidden">
<button id="copy-access" class="btn btn-secondary btn-block" onclick="copyAccess()">
<i class="fas fa-copy mr-1"></i>
<%= t("room.copy_access") %>
</button>
<% end %>
<% if moderator_code_allowed? && @room.moderator_access_code.present? %>
<input id="copy-moderator-code" value="<%= @room.moderator_access_code %>" type="hidden">
<button id="copy-moderator-access" class="btn btn-secondary btn-block" onclick="copyAccess('moderator')">
<i class="fas fa-copy mr-1"></i>
<%= t("room.copy_moderator_access") %>
</button>
<% end %>
<% if Rails.configuration.enable_google_calendar_button %>
<a href="<%= google_calendar_path %>" target="__blank" id="schedule" class="btn btn-primary btn-block mt-2">
<i class="fas fa-calendar-plus"></i>
<%= t("add_to_google_calendar") %>
</a>
<% end %>
</div>
</div>
</div>
</div>
<% end %>
</div>
<div class="col-lg-3 col-sm-12 force-bottom mt-5">
<% if @room_running %>
<%= button_to t("room.join"), room_path(@room), class: "btn btn-primary btn-block px-7 start-button float-right", "data-disable": "" %>
<% else %>
<% unless exceeds_limit %>
<%= button_to t("room.start"), start_room_path(@room), class: "btn btn-primary btn-block px-7 start-button float-right", "data-disable": "" %>
<% end %>
<% end %>
</div>
</div>
<% if total_room_count(current_user) > 5 %>
<div class="input-icon invite-link-input mb-3">
<span class="input-icon-addon">
<i class="fas fa-search"></i>
</span>
<input id="room-search" type="text" placeholder="<%= t("room.search") %>" class="form-control w-100" onChange="filterRooms()" onKeyUp="filterRooms()">
<span id="clear-room-search" class="text-primary" onclick="clearRoomSearch()">
<i class="fas fa-times"></i>
</span>
</div>
<% end %>
<div id="room_block_container" class="row mb-5">
<% if current_user.role.get_permission("can_create_rooms") %>
<% current_user.ordered_rooms.each do |room| %>
<div class="col-lg-4 col-md-6 col-sm-12">
<%= link_to room do %>
<%= render "rooms/components/room_block", room: room %>
<% end %>
</div>
<% end %>
<% end %>
<% if shared_access_allowed %>
<% current_user.shared_rooms.each do |room| %>
<div class="col-lg-4 col-md-6 col-sm-12">
<%= link_to room do %>
<%= render "rooms/components/shared_room_block", room: room %>
<% end %>
</div>
<% end %>
<% end %>
<% if current_user.role.get_permission("can_create_rooms") && !room_limit_exceeded %>
<%= render "rooms/components/create_room_block"%>
<% end %>
</div>
</div>
</div>
<% unless hide_recording_tables %>
<%= render "shared/sessions", recordings: @recordings, pagy: @pagy, only_public: false, shared_room: @shared_room, user_recordings: false, title: t("room.recordings")%>
<% end %>
<%= render "shared/modals/delete_room_modal" %>
<%= render "shared/modals/create_room_modal" %>
<% if preupload_allowed? %>
<%= render "shared/modals/preupload_presentation_modal" %>
<% end %>
<% if shared_access_allowed %>
<%= render "shared/modals/share_room_modal" %>
<%= render "shared/modals/remove_access_modal" %>
<% end %>

@ -43,6 +43,19 @@
</span>
</div>
<% if moderator_code_allowed? %>
<div class="input-icon mb-2">
<span onclick="generateModeratorAccessCode()" class="input-icon-addon allow-icon-click cursor-pointer">
<i class="fas fa-dice"></i>
</span>
<%= f.label :moderator_access_code, t("modal.create_room.moderator_access_code_placeholder"), id: "create-room-moderator-access-code", class: "form-control" %>
<%= f.hidden_field :moderator_access_code %>
<span onclick="ResetModeratorAccessCode()" class="input-icon-addon allow-icon-click cursor-pointer">
<i class="far fa-trash-alt"></i>
</span>
</div>
<% end %>
<% mute = room_configuration("Room Configuration Mute On Join") %>
<% if mute != "disabled" %>
<label class="custom-switch pl-0 mt-3 mb-3 w-100 text-left d-inline-block <%= "enabled-setting" if mute == "enabled" %>">

@ -175,6 +175,9 @@ module Greenlight
# Don't allow users to preupload presentations by default
config.preupload_presentation_default = "false"
# Don't show option to generate moderator access codes
config.moderator_codes_default = "false"
# Default admin password
config.admin_password_default = ENV['ADMIN_PASSWORD'] || 'administrator'
end

@ -105,6 +105,11 @@ de_DE:
approval: Zulassen/Ablehnen
invite: Teilnahme durch Einladung
open: Offene Registrierung
moderator_codes:
info: "Mit gültigen Moderatorencodes können Nutzerinnen einem Raum als Moderator beitreten, ohne sich vorher einen Account anzulegen und einzeln Zugriff zu erhalten. Die Einstellung kann für jeden Raum seperat getätigt werden."
title: Ermöglicht die Erstellung von Moderatorencodes
enabled: Aktiviert
disabled: Deaktiviert
rooms:
info: "Limitiert die Anzahl der Räume, die Nutzer einrichten können (inklusive des Startraums). Diese Einstellung wirkt sich nicht auf Administratoren aus."
title: Anzahl der Räume pro Nutzer
@ -390,7 +395,9 @@ de_DE:
title: Neue Rolle erstellen
create_room:
access_code: Zugangscode
moderator_access_code: Moderatorencode
access_code_placeholder: Generieren eines optionalen Raumzugangscodes
moderator_access_code_placeholder: Generieren eines optionalen Moderatorencodes
auto_join: Automatisch dem Raum beitreten
create: Raum erstellen
free_delete: Sie können den Raum jederzeit wieder löschen.
@ -543,6 +550,7 @@ de_DE:
access_code_required: "Bitte geben Sie einen gültigen Zugangscode ein, um den Raum zu betreten"
add_presentation: Präsentation hinzufügen
copy_access: Zugangscode kopieren
copy_moderator_access: Moderatorcode kopieren
create_room: Raum erstellen
create_room_error: Bei der Erstellung des Raums ist ein Fehler aufgetreten
create_room_success: Raum erfolgreich erstellt
@ -551,6 +559,8 @@ de_DE:
success: Raum erfolgreich gelöscht
fail: "Raum konnte nicht gelöscht werden (%{error})"
enter_the_access_code: Raumzugangscode bitte eingeben
enter_the_moderator_access_code: "Moderatorencode bitte eingeben!"
optional_moderator_access_code: "Optionaler Moderatorencode:"
invalid_provider: "Sie haben eine ungültige URL eingegeben, bitte überprüfen Sie die URL und versuchen Sie es erneut."
invitation_description: "Sie wurden zu %{name} über BigBlueButton zur Teilnahme eingeladen. Um beizutreten, klicken Sie auf den obigen Link und geben Sie Ihren Namen ein."
invited: Sie wurden zur Teilnahme eingeladen

@ -111,6 +111,11 @@ en:
shared_access:
info: Setting to disabled will remove the button from the Room options dropdown, preventing users from sharing rooms
title: Allow Users to Share Rooms
moderator_codes:
info: "With valid moderator codes, users can join a room as a moderator without having to create an account and receive access individually. The setting can be made separately for each room."
title: Enables the generation of moderator codes
enabled: Enabled
disabled: Disabled
subtitle: Customize Greenlight
tabs:
appearance: Appearance
@ -391,7 +396,9 @@ en:
title: Create New Role
create_room:
access_code: Access Code
moderator_access_code: Moderator Code
access_code_placeholder: Generate an optional room access code
moderator_access_code_placeholder: Generate an optional code for moderators
auto_join: Automatically join me into the room
create: Create Room
free_delete: You will be free to delete this room at any time.
@ -544,6 +551,7 @@ en:
access_code_required: Please enter a valid access code to join the room
add_presentation: Add Presentation
copy_access: Copy Access Code
copy_moderator_access: Copy Moderator Code
create_room: Create a Room
create_room_error: There was an error creating the room
create_room_success: Room created successfully
@ -552,6 +560,8 @@ en:
success: Room deleted successfully
fail: Failed to delete room (%{error})
enter_the_access_code: Enter the room's access code
enter_the_moderator_access_code: Enter the room's moderator code!
optional_moderator_access_code: "Optional Moderator Code:"
invalid_provider: You have entered an invalid url. Please check the url and try again.
invitation_description: You have been invited to join %{name} using BigBlueButton. To join, click the link above and enter your name.
invited: You have been invited to join

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddModeratorAccessCodeToRooms < ActiveRecord::Migration[5.2]
def change
add_column :rooms, :moderator_access_code, :string, null: true, default: nil
end
end

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_12_14_232153) do
ActiveRecord::Schema.define(version: 2021_01_08_032132) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
@ -96,6 +96,7 @@ ActiveRecord::Schema.define(version: 2020_12_14_232153) do
t.string "attendee_pw"
t.string "access_code"
t.boolean "deleted", default: false, null: false
t.string "moderator_access_code"
t.index ["bbb_id"], name: "index_rooms_on_bbb_id"
t.index ["deleted"], name: "index_rooms_on_deleted"
t.index ["last_session"], name: "index_rooms_on_last_session"

@ -273,6 +273,21 @@ describe RoomsController, type: :controller do
expect(response).to redirect_to(join_path(@owner.main_room, "Join Name", {}, response.cookies["guest_id"]))
end
it "should use join name if user is not logged in and meeting running and moderator access code is enabled and set" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(true)
allow_any_instance_of(Setting).to receive(:get_value).and_call_original
allow_any_instance_of(Setting).to receive(:get_value).with("Moderator Access Codes").and_return("true")
room = Room.new(name: "test", moderator_access_code: "abcdef")
room.room_settings = "{ }"
room.owner = @owner
room.save
post :join, params: { room_uid: room, join_name: "Join Name" }, session: { moderator_access_code: "abcdef" }
expect(response).to redirect_to(join_path(room, "Join Name", { user_is_moderator: true }, response.cookies["guest_id"]))
end
it "should render wait if meeting isn't running" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(false)
@ -398,6 +413,62 @@ describe RoomsController, type: :controller do
expect(response).to redirect_to room_path(protected_room.uid)
end
it "should join the room as moderator if the user has the moderator_access code (and regular access code is not set)" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(true)
allow_any_instance_of(Setting).to receive(:get_value).and_call_original
allow_any_instance_of(Setting).to receive(:get_value).with("Moderator Access Codes").and_return("true")
room = Room.new(name: "test", moderator_access_code: "abcdef")
room.room_settings = "{ }"
room.owner = @owner
room.save
post :join, params: { room_uid: room, join_name: "Join Name" }, session: { moderator_access_code: "abcdef" }
expect(response).to redirect_to(join_path(room, "Join Name", { user_is_moderator: true }, response.cookies["guest_id"]))
end
it "should join the room as moderator if the user has the moderator_access code (and regular access code is set)" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(true)
allow_any_instance_of(Setting).to receive(:get_value).and_call_original
allow_any_instance_of(Setting).to receive(:get_value).with("Moderator Access Codes").and_return("true")
room = Room.new(name: "test", access_code: "123456", moderator_access_code: "abcdef")
room.room_settings = "{ }"
room.owner = @owner
room.save
post :join, params: { room_uid: room, join_name: "Join Name" }, session: { moderator_access_code: "abcdef" }
expect(response).to redirect_to(join_path(room, "Join Name", { user_is_moderator: true }, response.cookies["guest_id"]))
end
it "should redirect to login if a wrong moderator access code is supplied" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(true)
room = Room.new(name: "test", access_code: "123456", moderator_access_code: "abcdef")
room.room_settings = "{ }"
room.owner = @owner
room.save
post :join, params: { room_uid: room, join_name: "Join Name" }, session: { moderator_access_code: "abcdee" }
expect(response).to redirect_to room_path(room.uid)
end
it "should redirect to login if a 'empty' moderator access code is supplied and moderator code is not set" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(true)
room = Room.new(name: "test", access_code: "123456")
room.room_settings = "{ }"
room.owner = @owner
room.save
post :join, params: { room_uid: room, join_name: "Join Name" }, session: { moderator_access_code: nil }
expect(response).to redirect_to room_path(room.uid)
end
it "should join owner as moderator if meeting running" do
allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:is_meeting_running?).and_return(true)
@ -671,12 +742,49 @@ describe RoomsController, type: :controller do
expect(flash[:alert]).to be_nil
end
it "should redirect to show with valid moderator_access_code as regular access_code" do
allow_any_instance_of(Setting).to receive(:get_value).and_call_original
allow_any_instance_of(Setting).to receive(:get_value).with("Moderator Access Codes").and_return("true")
@room.moderator_access_code = "abcdef"
@room.save
post :login, params: { room_uid: @room.uid, room: { access_code: "abcdef" } }
expect(response).to redirect_to room_path(@room.uid)
expect(flash[:alert]).to be_nil
expect(session[:moderator_access_code]).to eq("abcdef")
end
it "should redirect to show with and notify user of invalid access code" do
post :login, params: { room_uid: @room.uid, room: { access_code: "123455" } }
expect(response).to redirect_to room_path(@room.uid)
expect(flash[:alert]).to eq(I18n.t("room.access_code_required"))
end
it "should redirect to show and notify user of invalid moderator access code" do
@room.moderator_access_code = "abcdef"
@room.save
post :login, params: { room_uid: @room.uid, room: { moderator_access_code: "abcdee" } }
expect(response).to redirect_to room_path(@room.uid)
expect(flash[:alert]).to eq(I18n.t("room.access_code_required"))
end
it "it should redirect to show with valid moderator access code and disabled moderator codes setting" do
allow_any_instance_of(Setting).to receive(:get_value).and_call_original
allow_any_instance_of(Setting).to receive(:get_value).with("Moderator Access Codes").and_return("false")
@room.moderator_access_code = "abcdef"
@room.save
post :join, params: { room_uid: @room, join_name: "Join Name" }, session: { moderator_access_code: "abcdef" }
expect(response).to redirect_to room_path(@room.uid)
expect(flash[:alert]).to eq(I18n.t("room.access_code_required"))
end
end
describe "POST join_specific_room" do