From c410c0b8ef345b4e8d1daf7fa0c7f4b0670e2107 Mon Sep 17 00:00:00 2001 From: shawn-higgins1 <23224097+shawn-higgins1@users.noreply.github.com> Date: Fri, 12 Jul 2019 16:34:47 -0400 Subject: [PATCH 01/16] Change default recording sort order to descending (#630) --- app/models/concerns/api_concern.rb | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/app/models/concerns/api_concern.rb b/app/models/concerns/api_concern.rb index dd766d25..2b25f586 100644 --- a/app/models/concerns/api_concern.rb +++ b/app/models/concerns/api_concern.rb @@ -23,7 +23,7 @@ module APIConcern def format_recordings(api_res, search_params, ret_search_params) search = search_params[:search] || "" order_col = search_params[:column] && search_params[:direction] != "none" ? search_params[:column] : "end_time" - order_dir = search_params[:column] && search_params[:direction] != "none" ? search_params[:direction] : "asc" + order_dir = search_params[:column] && search_params[:direction] != "none" ? search_params[:direction] : "desc" search = search.downcase @@ -66,26 +66,26 @@ module APIConcern def sort_recordings(recs, order_col, order_dir) recs = case order_col when "end_time" - recs.sort_by { |r| r[:endTime] } + recs.sort_by { |r| r[:endTime] } when "name" - recs.sort_by do |r| - if !r[:metadata].nil? && !r[:metadata][:name].nil? - r[:metadata][:name].downcase - else - r[:name].downcase - end - end + recs.sort_by do |r| + if !r[:metadata].nil? && !r[:metadata][:name].nil? + r[:metadata][:name].downcase + else + r[:name].downcase + end + end when "length" - recs.sort_by { |r| r[:playbacks].reject { |p| p[:type] == "statistics" }.first[:length] } + recs.sort_by { |r| r[:playbacks].reject { |p| p[:type] == "statistics" }.first[:length] } when "users" - recs.sort_by { |r| r[:participants] } + recs.sort_by { |r| r[:participants] } when "visibility" - recs.sort_by { |r| r[:metadata][:"gl-listed"] } + recs.sort_by { |r| r[:metadata][:"gl-listed"] } when "formats" - recs.sort_by { |r| r[:playbacks].first[:type].downcase } - else - recs.sort_by { |r| r[:endTime] } - end + recs.sort_by { |r| r[:playbacks].first[:type].downcase } + else + recs.sort_by { |r| r[:endTime] } + end if order_dir == 'asc' recs From ad2321e3e98f288e04935d593c9ae10afed60bf6 Mon Sep 17 00:00:00 2001 From: farhatahmad <35435341+farhatahmad@users.noreply.github.com> Date: Fri, 12 Jul 2019 16:55:11 -0400 Subject: [PATCH 02/16] Update sample.env with the correct links (#637) --- sample.env | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/sample.env b/sample.env index 57b6ba2a..c5d9525f 100644 --- a/sample.env +++ b/sample.env @@ -1,7 +1,7 @@ # Create a Secret Key for Rails # # You can generate a secure one through the Greenlight docker image -# with with the command. +# with the command. # # docker run --rm bigbluebutton/greenlight:v2 bundle exec rake secret # @@ -20,7 +20,7 @@ BIGBLUEBUTTON_SECRET= # # For in-depth steps on setting up a Google Login Provider, see: # -# http://docs.bigbluebutton.org/install/greenlight-v2.html#google-oauth2 +# https://docs.bigbluebutton.org/greenlight/gl-customize.html#google-oauth2 # # The GOOGLE_OAUTH2_HD variable is used to limit sign-ins to a particular set of Google Apps hosted # domains. This can be a string with separating commas such as, 'domain.com, example.com' or @@ -32,18 +32,13 @@ GOOGLE_OAUTH2_HD= # Twitter Login Provider (optional) # -# For in-depth steps on setting up a Twitter Login Provider, see: -# -# http://docs.bigbluebutton.org/install/greenlight-v2.html#twitter-oauth2 -# -TWITTER_ID= -TWITTER_SECRET= +# Twitter Authentication is deprecated and will be phased out in a future release. # Microsoft Office365 Login Provider (optional) # # For in-depth steps on setting up a Office 365 Login Provider, see: # -# http://docs.bigbluebutton.org/install/greenlight-v2.html#office365-oauth2 +# https://docs.bigbluebutton.org/greenlight/gl-customize.html#office365-oauth2 # OFFICE365_KEY= OFFICE365_SECRET= @@ -55,7 +50,7 @@ OFFICE365_HD= # Configuring LDAP authentication will take precedence over all other providers. # For information about setting up LDAP, see: # -# http://docs.bigbluebutton.org/install/greenlight-v2.html#ldap-auth +# https://docs.bigbluebutton.org/greenlight/gl-customize.html#ldap-auth # # LDAP_SERVER=ldap.example.com # LDAP_PORT=389 @@ -75,7 +70,7 @@ LDAP_PASSWORD= # Set this to true if you want GreenLight to support user signup and login without # Omniauth. For more information, see: # -# http://docs.bigbluebutton.org/install/greenlight-v2.html#in-application-greenlight +# https://docs.bigbluebutton.org/greenlight/gl-overview.html#accounts-and-profile # ALLOW_GREENLIGHT_ACCOUNTS=true From 5a3ad3159c1f51c297807e7f10bafc3dd776310f Mon Sep 17 00:00:00 2001 From: farhatahmad <35435341+farhatahmad@users.noreply.github.com> Date: Mon, 15 Jul 2019 10:13:41 -0400 Subject: [PATCH 03/16] GRN2-45, GRN2-197: Upgrade Rails to 5.2.3 and removed sass-rails gem (#636) * Removed deprecate sass-rails and replace with sassc-rails * Upgraded to Rails 5.2.3 * Fixed travis * Added back Spring code * Fixed travis again --- Gemfile | 9 +- Gemfile.lock | 188 +++++++++--------- app/controllers/concerns/themer.rb | 4 +- app/controllers/themes_controller.rb | 2 +- bin/bundle | 2 +- bin/setup | 6 +- bin/update | 6 +- bin/yarn | 13 ++ config/application.rb | 3 + config/boot.rb | 1 + config/environments/development.rb | 11 +- config/environments/production.rb | 7 + config/environments/test.rb | 6 +- config/initializers/assets.rb | 9 +- .../initializers/content_security_policy.rb | 27 +++ config/initializers/new_framework_defaults.rb | 28 --- config/puma.rb | 23 +-- config/spring.rb | 4 +- config/storage.yml | 34 ++++ 19 files changed, 228 insertions(+), 155 deletions(-) create mode 100755 bin/yarn create mode 100644 config/initializers/content_security_policy.rb delete mode 100644 config/initializers/new_framework_defaults.rb create mode 100644 config/storage.yml diff --git a/Gemfile b/Gemfile index 2e77d0e3..72aaf145 100644 --- a/Gemfile +++ b/Gemfile @@ -8,13 +8,13 @@ git_source(:github) do |repo_name| end # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '~> 5.0.7' +gem 'rails', '~> 5.2.3' # Use Puma as the app server gem 'puma', '~> 3.0' # Use SCSS for stylesheets -gem 'sass-rails', '~> 5.0' +gem 'sassc-rails' # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '>= 1.3.0' @@ -40,6 +40,9 @@ gem 'redis', '~> 3.0' # Use ActiveModel has_secure_password gem 'bcrypt', '~> 3.1.7' +# Reduces boot times through caching; required in config/boot.rb +gem 'bootsnap', '>= 1.1.0', require: false + # Authentication. gem 'omniauth' gem 'omniauth-twitter' @@ -54,7 +57,7 @@ gem 'bigbluebutton-api-ruby' # Front-end. gem 'bootstrap', '~> 4.3.1' -gem 'tabler-rubygem' +gem 'tabler-rubygem', git: 'https://github.com/vbalazs/tabler-rubygem.git', branch: 'fix-sass' gem 'pagy' # For detecting the users preferred language. diff --git a/Gemfile.lock b/Gemfile.lock index 9bc6b0b0..bb53ab03 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,58 +23,73 @@ GIT omniauth (>= 1.3.2) omniauth-oauth2 (>= 1.5.0) +GIT + remote: https://github.com/vbalazs/tabler-rubygem.git + revision: 1360795a174f6e6317eb6c81461597ddc965c2ce + branch: fix-sass + specs: + tabler-rubygem (0.1.4) + autoprefixer-rails (>= 6.0.3) + bootstrap (~> 4.3.1) + GEM remote: https://rubygems.org/ specs: action-cable-testing (0.5.0) actioncable (>= 5.0) - actioncable (5.0.7.2) - actionpack (= 5.0.7.2) - nio4r (>= 1.2, < 3.0) - websocket-driver (~> 0.6.1) - actionmailer (5.0.7.2) - actionpack (= 5.0.7.2) - actionview (= 5.0.7.2) - activejob (= 5.0.7.2) + actioncable (5.2.3) + actionpack (= 5.2.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.0.7.2) - actionview (= 5.0.7.2) - activesupport (= 5.0.7.2) + actionpack (5.2.3) + actionview (= 5.2.3) + activesupport (= 5.2.3) rack (~> 2.0) - rack-test (~> 0.6.3) + rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.7.2) - activesupport (= 5.0.7.2) + actionview (5.2.3) + activesupport (= 5.2.3) builder (~> 3.1) - erubis (~> 2.7.0) + erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.0.7.2) - activesupport (= 5.0.7.2) + activejob (5.2.3) + activesupport (= 5.2.3) globalid (>= 0.3.6) - activemodel (5.0.7.2) - activesupport (= 5.0.7.2) - activerecord (5.0.7.2) - activemodel (= 5.0.7.2) - activesupport (= 5.0.7.2) - arel (~> 7.0) - activesupport (5.0.7.2) + activemodel (5.2.3) + activesupport (= 5.2.3) + activerecord (5.2.3) + activemodel (= 5.2.3) + activesupport (= 5.2.3) + arel (>= 9.0) + activestorage (5.2.3) + actionpack (= 5.2.3) + activerecord (= 5.2.3) + marcel (~> 0.3.1) + activesupport (5.2.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) addressable (2.6.0) public_suffix (>= 2.0.2, < 4.0) - arel (7.1.4) + arel (9.0.0) ast (2.4.0) - autoprefixer-rails (9.5.1.1) + autoprefixer-rails (9.6.1) execjs - bcrypt (3.1.12) + bcrypt (3.1.13) bigbluebutton-api-ruby (1.7.0) xml-simple (~> 1.1) - bindex (0.7.0) + bindex (0.8.1) + bootsnap (1.4.4) + msgpack (~> 1.0) bootstrap (4.3.1) autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) @@ -100,26 +115,26 @@ GEM safe_yaml (~> 1.0.0) crass (1.0.4) diff-lcs (1.3) - docile (1.3.1) - dotenv (2.7.2) - dotenv-rails (2.7.2) - dotenv (= 2.7.2) + docile (1.3.2) + dotenv (2.7.4) + dotenv-rails (2.7.4) + dotenv (= 2.7.4) railties (>= 3.2, < 6.1) - erubis (2.7.0) + erubi (1.8.0) execjs (2.7.0) factory_bot (5.0.2) activesupport (>= 4.2.0) factory_bot_rails (5.0.2) factory_bot (~> 5.0.2) railties (>= 4.2.0) - faker (1.9.3) + faker (1.9.6) i18n (>= 0.7) faraday (0.15.4) multipart-post (>= 1.2, < 3) ffi (1.11.1) globalid (0.4.2) activesupport (>= 4.2.0) - hashdiff (0.3.9) + hashdiff (0.4.0) hashie (3.6.0) health_check (3.0.0) railties (>= 5.0) @@ -127,15 +142,15 @@ GEM i18n (1.6.0) concurrent-ruby (~> 1.0) i18n-language-mapping (0.1.0) - jaro_winkler (1.5.2) + jaro_winkler (1.5.3) jbuilder (2.9.1) activesupport (>= 4.2.0) - jquery-rails (4.3.3) + jquery-rails (4.3.5) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (2.2.0) - jwt (2.1.0) + jwt (2.2.1) libv8 (7.3.492.27.1) listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) @@ -145,17 +160,21 @@ GEM nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) method_source (0.9.2) - mini_mime (1.0.1) + 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) multi_xml (0.6.0) multipart-post (2.1.1) net-ldap (0.16.1) - nio4r (2.3.1) + nio4r (2.4.0) nokogiri (1.10.3) mini_portile2 (~> 2.4.0) oauth (0.5.4) @@ -168,7 +187,7 @@ GEM omniauth (1.9.0) hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) - omniauth-google-oauth2 (0.6.1) + omniauth-google-oauth2 (0.7.0) jwt (>= 2.0) omniauth (>= 1.1.1) omniauth-oauth2 (>= 1.5) @@ -181,28 +200,29 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - pagy (3.2.0) + pagy (3.3.2) parallel (1.17.0) parser (2.6.3.0) ast (~> 2.4.0) pg (0.21.0) popper_js (1.14.5) - public_suffix (3.0.3) + public_suffix (3.1.1) puma (3.12.1) rack (2.0.7) - rack-test (0.6.3) - rack (>= 1.0) - rails (5.0.7.2) - actioncable (= 5.0.7.2) - actionmailer (= 5.0.7.2) - actionpack (= 5.0.7.2) - actionview (= 5.0.7.2) - activejob (= 5.0.7.2) - activemodel (= 5.0.7.2) - activerecord (= 5.0.7.2) - activesupport (= 5.0.7.2) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.3) + actioncable (= 5.2.3) + actionmailer (= 5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) + activemodel (= 5.2.3) + activerecord (= 5.2.3) + activestorage (= 5.2.3) + activesupport (= 5.2.3) bundler (>= 1.3.0) - railties (= 5.0.7.2) + railties (= 5.2.3) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.4) actionpack (>= 5.0.1.x) @@ -213,31 +233,31 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.0.4) loofah (~> 2.2, >= 2.2.2) - railties (5.0.7.2) - actionpack (= 5.0.7.2) - activesupport (= 5.0.7.2) + railties (5.2.3) + actionpack (= 5.2.3) + activesupport (= 5.2.3) method_source rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) + thor (>= 0.19.0, < 2.0) rainbow (3.0.0) rake (12.3.2) random_password (0.1.1) rb-fsevent (0.10.3) rb-inotify (0.10.0) ffi (~> 1.0) - recaptcha (4.14.0) + recaptcha (5.0.0) json redcarpet (3.4.0) redis (3.3.5) remote_syslog_logger (1.0.4) syslog_protocol rolify (5.2.0) - rspec-core (3.8.0) + rspec-core (3.8.2) rspec-support (~> 3.8.0) - rspec-expectations (3.8.3) + rspec-expectations (3.8.4) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) + rspec-mocks (3.8.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) rspec-rails (3.8.2) @@ -248,31 +268,20 @@ GEM rspec-expectations (~> 3.8.0) rspec-mocks (~> 3.8.0) rspec-support (~> 3.8.0) - rspec-support (3.8.0) - rubocop (0.70.0) + rspec-support (3.8.2) + rubocop (0.72.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.6) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) - ruby-progressbar (1.10.0) + ruby-progressbar (1.10.1) safe_yaml (1.0.5) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.7) - railties (>= 4.0.0, < 6) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) sassc (2.0.1) ffi (~> 1.9) rake - sassc-rails (2.1.1) + sassc-rails (2.1.2) railties (>= 4.0.0) sassc (>= 2.0) sprockets (> 3.0) @@ -285,8 +294,7 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) - spring (2.0.2) - activesupport (>= 4.2) + spring (2.1.0) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) @@ -299,14 +307,12 @@ GEM sprockets (>= 3.0.0) sqlite3 (1.3.13) syslog_protocol (0.9.2) - tabler-rubygem (0.1.4) - autoprefixer-rails (>= 6.0.3) term-ansicolor (1.7.1) tins (~> 1.0) thor (0.20.3) thread_safe (0.3.6) tilt (2.0.9) - tins (1.20.2) + tins (1.21.0) turbolinks (5.2.0) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) @@ -320,13 +326,13 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) - webmock (3.5.1) + webmock (3.6.0) addressable (>= 2.3.6) crack (>= 0.3.2) - hashdiff - websocket-driver (0.6.5) + hashdiff (>= 0.4.0, < 2.0.0) + websocket-driver (0.7.1) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) + websocket-extensions (0.1.4) xml-simple (1.1.5) PLATFORMS @@ -337,6 +343,7 @@ DEPENDENCIES bcrypt (~> 3.1.7) bigbluebutton-api-ruby bn-ldap-authentication! + bootsnap (>= 1.1.0) bootstrap (~> 4.3.1) byebug cancancan (~> 2.0) @@ -361,7 +368,7 @@ DEPENDENCIES pagy pg (~> 0.18) puma (~> 3.0) - rails (~> 5.0.7) + rails (~> 5.2.3) rails-controller-testing random_password recaptcha @@ -371,14 +378,15 @@ DEPENDENCIES rolify rspec-rails (~> 3.7) rubocop - sass-rails (~> 5.0) + sassc-rails shoulda-matchers (~> 3.1) spring spring-watcher-listen (~> 2.0.0) sqlite3 (~> 1.3.6) - tabler-rubygem + tabler-rubygem! turbolinks (~> 5) tzinfo-data uglifier (>= 1.3.0) web-console (>= 3.3.0) webmock + diff --git a/app/controllers/concerns/themer.rb b/app/controllers/concerns/themer.rb index 97eb6b80..2893ea27 100644 --- a/app/controllers/concerns/themer.rb +++ b/app/controllers/concerns/themer.rb @@ -24,7 +24,7 @@ module Themer # Uses the built in Sass Engine to lighten the color dummy_scss = "h1 { color: $lighten; }" - compiled = Sass::Engine.new("$lighten:lighten(#{color}, 40%);" + dummy_scss, syntax: :scss).render + compiled = SassC::Engine.new("$lighten:lighten(#{color}, 40%);" + dummy_scss, syntax: :scss).render string_locater = 'color: ' color_start = compiled.index(string_locater) + string_locater.length @@ -37,7 +37,7 @@ module Themer # Uses the built in Sass Engine to darken the color dummy_scss = "h1 { color: $darken; }" - compiled = Sass::Engine.new("$darken:darken(#{color}, 10%);" + dummy_scss, syntax: :scss).render + compiled = SassC::Engine.new("$darken:darken(#{color}, 10%);" + dummy_scss, syntax: :scss).render string_locater = 'color: ' color_start = compiled.index(string_locater) + string_locater.length diff --git a/app/controllers/themes_controller.rb b/app/controllers/themes_controller.rb index 2f26c7f3..d5fe8b06 100644 --- a/app/controllers/themes_controller.rb +++ b/app/controllers/themes_controller.rb @@ -29,7 +29,7 @@ class ThemesController < ApplicationController @file_contents = File.read(file_name) # Include the variables and covert scss file to css - @compiled = Sass::Engine.new("$primary-color:#{color};" \ + @compiled = SassC::Engine.new("$primary-color:#{color};" \ "$primary-color-lighten:#{lighten_color};" \ "$primary-color-darken:#{darken_color};" + @file_contents, syntax: :scss).render diff --git a/bin/bundle b/bin/bundle index 58115ecf..2dbb7176 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,5 +1,5 @@ #!/usr/bin/env ruby # frozen_string_literal: true -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) load Gem.bin_path('bundler', 'bundle') diff --git a/bin/setup b/bin/setup index 629e3584..c2e43ceb 100755 --- a/bin/setup +++ b/bin/setup @@ -1,12 +1,11 @@ #!/usr/bin/env ruby # frozen_string_literal: true -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -20,6 +19,9 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') # cp 'config/database.yml.sample', 'config/database.yml' diff --git a/bin/update b/bin/update index fdac831b..313c74b3 100755 --- a/bin/update +++ b/bin/update @@ -1,12 +1,11 @@ #!/usr/bin/env ruby # frozen_string_literal: true -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -20,6 +19,9 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + puts "\n== Updating database ==" system! 'bin/rails db:migrate' diff --git a/bin/yarn b/bin/yarn new file mode 100755 index 00000000..5f754ba9 --- /dev/null +++ b/bin/yarn @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do + begin + exec "yarnpkg", *ARGV + rescue Errno::ENOENT + warn "Yarn executable was not detected in the system." + warn "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/config/application.rb b/config/application.rb index 7e6f8915..839f8dda 100644 --- a/config/application.rb +++ b/config/application.rb @@ -26,6 +26,9 @@ Bundler.require(*Rails.groups) module Greenlight class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.2 + # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. diff --git a/config/boot.rb b/config/boot.rb index 30e594e2..c04863fa 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -3,3 +3,4 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/config/environments/development.rb b/config/environments/development.rb index 0eeb0fb0..25309d5a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -15,12 +15,13 @@ Rails.application.configure do config.consider_all_requests_local = false # Enable/disable caching. By default caching is disabled. - if Rails.root.join('tmp/caching-dev.txt').exist? + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :memory_store config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=172800', + 'Cache-Control' => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false @@ -28,6 +29,9 @@ Rails.application.configure do config.cache_store = :null_store end + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + # Don't wrap form components in field_with_error divs ActionView::Base.field_error_proc = proc do |html_tag| html_tag.html_safe @@ -57,6 +61,9 @@ Rails.application.configure do # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. diff --git a/config/environments/production.rb b/config/environments/production.rb index a34cc596..eed2b5da 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -16,6 +16,10 @@ Rails.application.configure do config.consider_all_requests_local = false config.action_controller.perform_caching = true + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].blank? @@ -36,6 +40,9 @@ Rails.application.configure do # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + # Mount Action Cable outside main process or domain # config.action_cable.mount_path = nil # config.action_cable.url = 'wss://example.com/cable' diff --git a/config/environments/test.rb b/config/environments/test.rb index f4dd5186..42285acc 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -17,7 +17,7 @@ Rails.application.configure do # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=3600', + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. @@ -29,6 +29,10 @@ Rails.application.configure do # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory + config.active_storage.service = :test + config.action_mailer.perform_caching = false # Tell Action Mailer not to deliver emails to the real world. diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 3d3fd61d..a9b0d0f1 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -5,9 +5,12 @@ # Version of your assets, change this if you want to expire all your assets. Rails.application.config.assets.version = '1.0' -# Add additional assets to the asset load path +# Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') # Precompile additional assets. -# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. -# Rails.application.config.assets.precompile += %w() +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..e3c96496 --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +# Rails.application.config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https + +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end + +# If you are using UJS then enable automatic nonce generation +# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb deleted file mode 100644 index 377b6532..00000000 --- a/config/initializers/new_framework_defaults.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -# Be sure to restart your server when you modify this file. -# -# This file contains migration options to ease your Rails 5.0 upgrade. -# -# Read the Guide for Upgrading Ruby on Rails for more info on each option. - -Rails.application.config.action_controller.raise_on_unfiltered_parameters = true - -# Enable per-form CSRF tokens. Previous versions had false. -Rails.application.config.action_controller.per_form_csrf_tokens = true - -# Enable origin-checking CSRF mitigation. Previous versions had false. -Rails.application.config.action_controller.forgery_protection_origin_check = true - -# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. -# Previous versions had false. -ActiveSupport.to_time_preserves_timezone = true - -# Require `belongs_to` associations by default. Previous versions had false. -Rails.application.config.active_record.belongs_to_required_by_default = true - -# Do not halt callback chains when a callback returns false. Previous versions had true. -ActiveSupport.halt_callback_chains_on_return_false = false - -# Configure SSL options to enable HSTS with subdomains. Previous versions had false. -Rails.application.config.ssl_options = { hsts: { subdomains: true } } diff --git a/config/puma.rb b/config/puma.rb index 2ef031d5..fda772af 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true # Puma can serve each request in a thread from an internal thread pool. -# The `threads` method setting takes two numbers a minimum and maximum. +# The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum -# and maximum, this matches the default thread size of Active Record. +# and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } threads threads_count, threads_count -# Specifies the `port` that Puma will listen on to receive requests, default is 3000. +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. # port ENV.fetch("PORT") { 80 } @@ -28,22 +28,9 @@ environment ENV.fetch("RAILS_ENV") { "development" } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write -# process behavior so workers use less memory. If you use this option -# you need to make sure to reconnect any threads in the `on_worker_boot` -# block. +# process behavior so workers use less memory. # # preload_app! -# The code in the `on_worker_boot` will be called if you are using -# clustered mode by specifying a number of `workers`. After each worker -# process is booted this block will be run, if you are using `preload_app!` -# option you will want to use this block to reconnect to any threads -# or connections that may have been created at application boot, Ruby -# cannot share connections between processes. -# -# on_worker_boot do -# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) -# end - # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/config/spring.rb b/config/spring.rb index ff5ba06b..c5933e49 100644 --- a/config/spring.rb +++ b/config/spring.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -%w( +%w[ .ruby-version .rbenv-vars tmp/restart.txt tmp/caching-dev.txt -).each { |path| Spring.watch(path) } +].each { |path| Spring.watch(path) } diff --git a/config/storage.yml b/config/storage.yml new file mode 100644 index 00000000..d32f76e8 --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] From 8c63f793a559587cfeda221bd046061d0afdd7a6 Mon Sep 17 00:00:00 2001 From: shawn-higgins1 <23224097+shawn-higgins1@users.noreply.github.com> Date: Mon, 15 Jul 2019 14:45:06 -0400 Subject: [PATCH 04/16] Reduce number of roles queries for the admin controller (#631) --- app/controllers/admins_controller.rb | 8 ++++---- app/controllers/application_controller.rb | 6 +++--- app/controllers/rooms_controller.rb | 4 ++-- app/controllers/users_controller.rb | 2 +- app/helpers/application_helper.rb | 2 +- app/helpers/rooms_helper.rb | 4 ++-- app/helpers/sessions_helper.rb | 2 +- app/helpers/theming_helper.rb | 2 +- app/models/user.rb | 10 +++++++--- app/views/shared/_header.html.erb | 4 ++-- 10 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb index 00ab91bc..aa4977d3 100644 --- a/app/controllers/admins_controller.rb +++ b/app/controllers/admins_controller.rb @@ -162,7 +162,7 @@ class AdminsController < ApplicationController private def find_user - @user = User.find_by!(uid: params[:user_uid]) + @user = User.where(uid: params[:user_uid]).includes(:roles).first end def find_setting @@ -176,10 +176,10 @@ class AdminsController < ApplicationController # Gets the list of users based on your configuration def user_list - initial_list = if current_user.has_role? :super_admin - User.where.not(id: current_user.id) + initial_list = if current_user.has_cached_role? :super_admin + User.where.not(id: current_user.id).includes(:roles) else - User.without_role(:super_admin).where.not(id: current_user.id) + User.without_role(:super_admin).where.not(id: current_user.id).includes(:roles) end list = @role.present? ? initial_list.with_role(@role.to_sym) : initial_list diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 70df16a3..fc86a331 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -116,7 +116,7 @@ class ApplicationController < ActionController::Base # Checks to make sure that the admin has changed his password from the default def check_admin_password - if current_user&.has_role?(:admin) && current_user&.greenlight_account? && + if current_user&.has_cached_role?(:admin) && current_user&.greenlight_account? && current_user&.authenticate(Rails.configuration.admin_password_default) flash.now[:alert] = I18n.t("default_admin", @@ -156,10 +156,10 @@ class ApplicationController < ActionController::Base # Checks if the user is banned and logs him out if he is def check_user_role - if current_user&.has_role? :denied + if current_user&.has_cached_role? :denied session.delete(:user_id) redirect_to root_path, flash: { alert: I18n.t("registration.banned.fail") } - elsif current_user&.has_role? :pending + elsif current_user&.has_cached_role? :pending session.delete(:user_id) redirect_to root_path, flash: { alert: I18n.t("registration.approval.fail") } end diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index fde621b1..e8e23540 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -274,7 +274,7 @@ class RoomsController < ApplicationController end def verify_user_not_admin - redirect_to admins_path if current_user && current_user&.has_role?(:super_admin) + redirect_to admins_path if current_user && current_user&.has_cached_role?(:super_admin) end def auth_required @@ -287,7 +287,7 @@ class RoomsController < ApplicationController # Does not apply to admin # 15+ option is used as unlimited - return false if current_user&.has_role?(:admin) || limit == 15 + return false if current_user&.has_cached_role?(:admin) || limit == 15 current_user.rooms.count >= limit end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index dd89d633..0224d905 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -185,7 +185,7 @@ class UsersController < ApplicationController private def find_user - @user = User.find_by!(uid: params[:user_uid]) + @user = User.where(uid: params[:user_uid]).includes(:roles).first end def ensure_unauthenticated diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d947f54b..2f64206f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -106,7 +106,7 @@ module ApplicationHelper # 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 + return admins_path if current_user.has_cached_role? :super_admin current_user.main_room end end diff --git a/app/helpers/rooms_helper.rb b/app/helpers/rooms_helper.rb index 009fd77d..26bfaf65 100644 --- a/app/helpers/rooms_helper.rb +++ b/app/helpers/rooms_helper.rb @@ -37,7 +37,7 @@ module RoomsHelper # Does not apply to admin or users that aren't signed in # 15+ option is used as unlimited - return false if current_user&.has_role?(:admin) || limit == 15 + return false if current_user&.has_cached_role?(:admin) || limit == 15 current_user.rooms.length >= limit end @@ -46,7 +46,7 @@ module RoomsHelper # Get how many rooms need to be deleted to reach allowed room number limit = Setting.find_or_create_by!(provider: user_settings_provider).get_value("Room Limit").to_i - return false if current_user&.has_role?(:admin) || limit == 15 + return false if current_user&.has_cached_role?(:admin) || limit == 15 @diff = current_user.rooms.count - limit @diff.positive? && current_user.rooms.pluck(:id).index(room.id) + 1 > limit diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index c9ab3525..60652d4c 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -61,7 +61,7 @@ module SessionsHelper # Retrieves the current user. def current_user - @current_user ||= User.find_by(id: session[:user_id]) + @current_user ||= User.where(id: session[:user_id]).includes(:roles).first end def generate_checksum(user_domain, redirect_url, secret) diff --git a/app/helpers/theming_helper.rb b/app/helpers/theming_helper.rb index e3f073fb..05479980 100644 --- a/app/helpers/theming_helper.rb +++ b/app/helpers/theming_helper.rb @@ -31,7 +31,7 @@ module ThemingHelper # Returns the user's provider in the settings context def user_settings_provider - if Rails.configuration.loadbalanced_configuration && current_user && !current_user&.has_role?(:super_admin) + if Rails.configuration.loadbalanced_configuration && current_user && !current_user&.has_cached_role?(:super_admin) current_user.provider elsif Rails.configuration.loadbalanced_configuration @user_domain diff --git a/app/models/user.rb b/app/models/user.rb index f7af9a35..ba914f9b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -214,13 +214,17 @@ class User < ApplicationRecord def admin_of?(user) if Rails.configuration.loadbalanced_configuration - if has_role? :super_admin + # Pulls in the user roles if they weren't request in the original request + # So the has_cached_role? doesn't always return false + user.roles + if has_cached_role? :super_admin id != user.id else - (has_role? :admin) && (id != user.id) && (provider == user.provider) && (!user.has_role? :super_admin) + (has_cached_role? :admin) && (id != user.id) && (provider == user.provider) && + (!user.has_cached_role? :super_admin) end else - ((has_role? :admin) || (has_role? :super_admin)) && (id != user.id) + ((has_cached_role? :admin) || (has_cached_role? :super_admin)) && (id != user.id) end end diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 1228e2b4..3182142f 100755 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -23,7 +23,7 @@
<% if current_user %> - <% if current_user.has_role? :super_admin %> + <% if current_user.has_cached_role? :super_admin %> <% admins_page = params[:controller] == "admins" && params[:action] == "index" ? "active" : "" %> <%= link_to admins_path, class: "px-3 mx-1 mt-1 header-nav #{admins_page}" do %> <%= t("header.dropdown.home") %> @@ -56,7 +56,7 @@ <%= link_to edit_user_path(current_user), class: "dropdown-item" do %> <%= t("header.dropdown.settings") %> <% end %> - <% if current_user.has_role? :admin %> + <% if current_user.has_cached_role? :admin %> <%= link_to admins_path, class: "dropdown-item" do %> <%= t("header.dropdown.account_settings") %> <% end %> From a055b88eb7bb71b6630b0b7832074e961ac466aa Mon Sep 17 00:00:00 2001 From: farhatahmad <35435341+farhatahmad@users.noreply.github.com> Date: Mon, 22 Jul 2019 12:46:48 -0400 Subject: [PATCH 05/16] GRN2-129: Added server recordings and refactored adminsitrator panel (#662) * Added server recordings and refactored adminsitrator panel * Fixed some issues * Fixed issue with owner email search * Fixed issue with edit user --- app/assets/javascripts/admins.js | 4 +- app/assets/javascripts/recording.js | 41 ++ app/assets/javascripts/rename.js | 5 +- app/assets/javascripts/room.js.erb | 15 - app/assets/javascripts/search.js | 3 +- app/assets/javascripts/settings.js | 2 +- app/assets/javascripts/sort.js | 5 +- .../utilities/_primary_themes.scss | 2 + app/controllers/admins_controller.rb | 35 +- .../concerns/recorder.rb} | 50 +- app/controllers/recordings_controller.rb | 7 +- app/controllers/rooms_controller.rb | 7 +- app/controllers/users_controller.rb | 12 +- app/helpers/admins_helper.rb | 12 + app/models/room.rb | 32 +- app/models/user.rb | 28 +- .../admins/components/_menu_buttons.html.erb | 26 + .../admins/components/_recordings.html.erb | 91 +++ .../components/_server_recording_row.html.erb | 81 +++ .../admins/components/_setting_view.html.erb | 26 + .../components/_settings.html.erb} | 0 .../components}/_users.html.erb | 16 + app/views/admins/edit_user.html.erb | 27 + app/views/admins/index.html.erb | 22 +- app/views/admins/server_recordings.html.erb | 27 + app/views/admins/site_settings.html.erb | 27 + app/views/rooms/show.html.erb | 2 +- .../shared/modals/_delete_room_modal.html.erb | 4 +- app/views/users/edit.html.erb | 6 +- config/locales/en.yml | 3 + config/routes.rb | 2 + lib/bbb_api.rb | 10 +- spec/concerns/recorder_spec.rb | 567 ++++++++++++++++++ spec/controllers/admins_controller_spec.rb | 18 +- spec/controllers/rooms_controller_spec.rb | 4 +- spec/controllers/users_controller_spec.rb | 2 + spec/models/room_spec.rb | 414 ------------- spec/models/user_spec.rb | 93 --- 38 files changed, 1088 insertions(+), 640 deletions(-) create mode 100644 app/assets/javascripts/recording.js rename app/{models/concerns/api_concern.rb => controllers/concerns/recorder.rb} (65%) create mode 100644 app/views/admins/components/_menu_buttons.html.erb create mode 100644 app/views/admins/components/_recordings.html.erb create mode 100644 app/views/admins/components/_server_recording_row.html.erb create mode 100644 app/views/admins/components/_setting_view.html.erb rename app/views/{shared/admin_settings/_site_settings.html.erb => admins/components/_settings.html.erb} (100%) rename app/views/{shared/admin_settings => admins/components}/_users.html.erb (90%) create mode 100644 app/views/admins/edit_user.html.erb create mode 100644 app/views/admins/server_recordings.html.erb create mode 100644 app/views/admins/site_settings.html.erb create mode 100644 spec/concerns/recorder_spec.rb diff --git a/app/assets/javascripts/admins.js b/app/assets/javascripts/admins.js index 55fa3a43..0b27efe0 100644 --- a/app/assets/javascripts/admins.js +++ b/app/assets/javascripts/admins.js @@ -43,9 +43,9 @@ $(document).on('turbolinks:load', function(){ window.location.replace(url); }) - - /* COLOR SELECTORS */ + } + if (controller == "admins" && action == "site_settings") { loadColourSelectors() } diff --git a/app/assets/javascripts/recording.js b/app/assets/javascripts/recording.js new file mode 100644 index 00000000..213ca957 --- /dev/null +++ b/app/assets/javascripts/recording.js @@ -0,0 +1,41 @@ +// 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 . + +// Handle changing of settings tabs. +$(document).on('turbolinks:load', function(){ + var controller = $("body").data('controller'); + var action = $("body").data('action'); + + if (controller == "rooms" && action == "show" + || controller == "rooms" && action == "update" + || controller == "users" && action == "recordings" + || controller == "admins" && action == "server_recordings"){ + // Handle recording emails. + $('.email-link').each(function(){ + $(this).click(function(){ + var subject = $(".username").text() + " " + t('room.mailer.subject'); + var body = t('room.mailer.body') + "\n\n" + $(this).attr("data-pres-link"); + var autogenerated = "\n\n" + t('room.mailer.autogenerated') + "\n"; + var footer = t('room.mailer.footer'); + + var url = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body) + encodeURIComponent(autogenerated) + encodeURIComponent(footer); + var win = window.open(url, '_blank'); + + win.focus(); + }); + }); + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/rename.js b/app/assets/javascripts/rename.js index 20570498..a8f32c6a 100644 --- a/app/assets/javascripts/rename.js +++ b/app/assets/javascripts/rename.js @@ -18,7 +18,10 @@ $(document).on('turbolinks:load', function(){ var controller = $("body").data('controller'); var action = $("body").data('action'); - if(controller == "rooms" && action == "show" || controller == "rooms" && action == "update" || controller == "users" && action == "recordings"){ + if(controller == "rooms" && action == "show" + || controller == "rooms" && action == "update" + || controller == "users" && action == "recordings" + || controller == "admins" && action == "server_recordings"){ // Set a room header rename event var configure_room_header = function(room_title){ diff --git a/app/assets/javascripts/room.js.erb b/app/assets/javascripts/room.js.erb index f5be24f0..1dc13675 100644 --- a/app/assets/javascripts/room.js.erb +++ b/app/assets/javascripts/room.js.erb @@ -39,21 +39,6 @@ $(document).on('turbolinks:load', function(){ }, 2000) } }); - - // Handle recording emails. - $('.email-link').each(function(){ - $(this).click(function(){ - var subject = $(".username").text() + " " + t('room.mailer.subject'); - var body = t('room.mailer.body') + "\n\n" + $(this).attr("data-pres-link"); - var autogenerated = "\n\n" + t('room.mailer.autogenerated') + "\n"; - var footer = t('room.mailer.footer'); - - var url = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body) + encodeURIComponent(autogenerated) + encodeURIComponent(footer); - var win = window.open(url, '_blank'); - - win.focus(); - }); - }); } // Display and update all fields related to creating a room in the createRoomModal diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js index de2c0f78..6869ebb5 100644 --- a/app/assets/javascripts/search.js +++ b/app/assets/javascripts/search.js @@ -22,7 +22,8 @@ $(document).on('turbolinks:load', function(){ (controller == "rooms" && action == "show") || (controller == "rooms" && action == "update") || (controller == "rooms" && action == "join") || - (controller == "users" && action == "recordings")) { + (controller == "users" && action == "recordings") || + (controller == "admins" && action == "server_recordings")) { // Submit search if the user hits enter $("#search-input").keypress(function(key) { var keyPressed = key.which diff --git a/app/assets/javascripts/settings.js b/app/assets/javascripts/settings.js index bacd22b4..42b7418e 100644 --- a/app/assets/javascripts/settings.js +++ b/app/assets/javascripts/settings.js @@ -20,7 +20,7 @@ $(document).on('turbolinks:load', function(){ var action = $("body").data('action'); // Only run on the settings page. - if ((controller == "users" && action == "edit") || (controller == "users" && action == "update") || (controller == "admins" && action == "index")){ + if ((controller == "users" && action == "edit") || (controller == "users" && action == "update")){ var settingsButtons = $('.setting-btn'); var settingsViews = $('.setting-view'); diff --git a/app/assets/javascripts/sort.js b/app/assets/javascripts/sort.js index a7903de6..effa8ea6 100644 --- a/app/assets/javascripts/sort.js +++ b/app/assets/javascripts/sort.js @@ -18,7 +18,10 @@ $(document).on('turbolinks:load', function(){ var controller = $("body").data('controller'); var action = $("body").data('action'); - if(controller == "rooms" && action == "show" || controller == "rooms" && action == "update" || controller == "users" && action == "recordings"){ + if(controller == "rooms" && action == "show" + || controller == "rooms" && action == "update" + || controller == "users" && action == "recordings" + || controller == "admins" && action == "server_recordings"){ // Choose active header // (Name, Length or Users) diff --git a/app/assets/stylesheets/utilities/_primary_themes.scss b/app/assets/stylesheets/utilities/_primary_themes.scss index 33d48d31..c8a37695 100644 --- a/app/assets/stylesheets/utilities/_primary_themes.scss +++ b/app/assets/stylesheets/utilities/_primary_themes.scss @@ -84,6 +84,7 @@ a { color: #6e7687 !important; &:hover { color: $primary-color !important; + background-color: $primary-color-lighten !important; } &:active { background-color: $primary-color-lighten !important; @@ -130,6 +131,7 @@ input:focus, select:focus { } & a { + color: $primary-color !important; border-color: $primary-color !important; } diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb index aa4977d3..8a128b23 100644 --- a/app/controllers/admins_controller.rb +++ b/app/controllers/admins_controller.rb @@ -20,6 +20,7 @@ class AdminsController < ApplicationController include Pagy::Backend include Themer include Emailer + include Recorder manage_users = [:edit_user, :promote, :demote, :ban_user, :unban_user, :approve] site_settings = [:branding, :coloring, :coloring_lighten, :coloring_darken, @@ -40,11 +41,27 @@ class AdminsController < ApplicationController @pagy, @users = pagy(user_list) end + # GET /admins/site_settings + def site_settings + end + + # GET /admins/server_recordings + def server_recordings + server_rooms = if Rails.configuration.loadbalanced_configuration + Room.includes(:owner).where(users: { provider: user_settings_provider }).pluck(:bbb_id) + else + Room.pluck(:bbb_id) + end + + @search, @order_column, @order_direction, recs = + all_recordings(server_rooms, @user_domain, params.permit(:search, :column, :direction), true, true) + @pagy, @recordings = pagy_array(recs) + end + # MANAGE USERS # GET /admins/edit/:user_uid def edit_user - render "admins/index", locals: { setting_id: "account" } end # POST /admins/promote/:user_uid @@ -111,7 +128,7 @@ class AdminsController < ApplicationController # POST /admins/branding def branding @settings.update_value("Branding Image", params[:url]) - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } + redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } end # POST /admins/color @@ -119,23 +136,23 @@ class AdminsController < ApplicationController @settings.update_value("Primary Color", params[:color]) @settings.update_value("Primary Color Lighten", color_lighten(params[:color])) @settings.update_value("Primary Color Darken", color_darken(params[:color])) - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } + redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } end def coloring_lighten @settings.update_value("Primary Color Lighten", params[:color]) - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } + redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } end def coloring_darken @settings.update_value("Primary Color Darken", params[:color]) - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } + redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } end # POST /admins/room_authentication def room_authentication @settings.update_value("Room Authentication", params[:value]) - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } + redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } end # POST /admins/registration_method/:method @@ -144,11 +161,11 @@ class AdminsController < ApplicationController # Only allow change to Join by Invitation if user has emails enabled if !Rails.configuration.enable_email_verification && new_method == Rails.configuration.registration_methods[:invite] - redirect_to admins_path, + redirect_to admin_site_settings_path, flash: { alert: I18n.t("administrator.flash.invite_email_verification") } else @settings.update_value("Registration Method", new_method) - redirect_to admins_path, + redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.registration_method_updated") } end end @@ -156,7 +173,7 @@ class AdminsController < ApplicationController # POST /admins/room_limit def room_limit @settings.update_value("Room Limit", params[:limit]) - redirect_to admins_path, flash: { success: I18n.t("administrator.flash.settings") } + redirect_to admin_site_settings_path, flash: { success: I18n.t("administrator.flash.settings") } end private diff --git a/app/models/concerns/api_concern.rb b/app/controllers/concerns/recorder.rb similarity index 65% rename from app/models/concerns/api_concern.rb rename to app/controllers/concerns/recorder.rb index 2b25f586..0833e5f8 100644 --- a/app/models/concerns/api_concern.rb +++ b/app/controllers/concerns/recorder.rb @@ -16,11 +16,50 @@ # You should have received a copy of the GNU Lesser General Public License along # with BigBlueButton; if not, see . -module APIConcern +module Recorder extend ActiveSupport::Concern + include ::BbbApi + + # Fetches all recordings for a room. + def recordings(room_bbb_id, provider, search_params = {}, ret_search_params = false) + res = bbb(provider).get_recordings(meetingID: room_bbb_id) + + format_recordings(res, search_params, ret_search_params) + end + + # Fetches a rooms public recordings. + def public_recordings(room_bbb_id, provider, search_params = {}, ret_search_params = false) + search, order_col, order_dir, recs = recordings(room_bbb_id, provider, search_params, ret_search_params) + [search, order_col, order_dir, recs.select { |r| r[:metadata][:"gl-listed"] == "true" }] + end + + # Makes paginated API calls to get recordings + def all_recordings(room_bbb_ids, provider, search_params = {}, ret_search_params = false, search_name = false) + pag_num = Rails.configuration.pagination_number + + pag_loops = room_bbb_ids.length / pag_num - 1 + + res = { recordings: [] } + + (0..pag_loops).each do |i| + pag_rooms = room_bbb_ids[pag_num * i, pag_num] + + # bbb.get_recordings returns an object + # take only the array portion of the object that is returned + full_res = bbb(provider).get_recordings(meetingID: pag_rooms) + res[:recordings].push(*full_res[:recordings]) + end + + last_pag_room = room_bbb_ids[pag_num * (pag_loops + 1), room_bbb_ids.length % pag_num] + + full_res = bbb(provider).get_recordings(meetingID: last_pag_room) + res[:recordings].push(*full_res[:recordings]) + + format_recordings(res, search_params, ret_search_params, search_name) + end # Format, filter, and sort recordings to match their current use in the app - def format_recordings(api_res, search_params, ret_search_params) + def format_recordings(api_res, search_params, ret_search_params, search_name = false) search = search_params[:search] || "" order_col = search_params[:column] && search_params[:direction] != "none" ? search_params[:column] : "end_time" order_dir = search_params[:column] && search_params[:direction] != "none" ? search_params[:direction] : "desc" @@ -40,7 +79,7 @@ module APIConcern r.delete(:playback) end - recs = filter_recordings(api_res, search) + recs = filter_recordings(api_res, search, search_name) recs = sort_recordings(recs, order_col, order_dir) if ret_search_params @@ -50,7 +89,7 @@ module APIConcern end end - def filter_recordings(api_res, search) + def filter_recordings(api_res, search, search_name = false) api_res[:recordings].select do |r| (!r[:metadata].nil? && ((!r[:metadata][:name].nil? && r[:metadata][:name].downcase.include?(search)) || @@ -59,7 +98,8 @@ module APIConcern ((r[:metadata].nil? || r[:metadata][:name].nil?) && r[:name].downcase.include?(search)) || r[:participants].include?(search) || - !r[:playbacks].select { |p| p[:type].downcase.include?(search) }.empty? + !r[:playbacks].select { |p| p[:type].downcase.include?(search) }.empty? || + (search_name && Room.find_by(bbb_id: r[:meetingID]).owner.email.downcase.include?(search)) end end diff --git a/app/controllers/recordings_controller.rb b/app/controllers/recordings_controller.rb index e1313f89..2c4446ae 100644 --- a/app/controllers/recordings_controller.rb +++ b/app/controllers/recordings_controller.rb @@ -50,6 +50,11 @@ class RecordingsController < ApplicationController # Ensure the user is logged into the room they are accessing. def verify_room_ownership - redirect_to root_path unless @room.owned_by?(current_user) + if !current_user || + !@room.owned_by?(current_user) || + !current_user.has_cached_role?(:admin) || + !current_user.has_cached_role?(:super_admin) + redirect_to root_path + end end end diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index e8e23540..1e633d0d 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -19,6 +19,7 @@ class RoomsController < ApplicationController include RecordingsHelper include Pagy::Backend + include Recorder before_action :validate_accepted_terms, unless: -> { !Rails.configuration.terms } before_action :validate_verified_email, except: [:show, :join], @@ -56,7 +57,7 @@ class RoomsController < ApplicationController def show if current_user && @room.owned_by?(current_user) @search, @order_column, @order_direction, recs = - @room.recordings(params.permit(:search, :column, :direction), true) + recordings(@room.bbb_id, @user_domain, params.permit(:search, :column, :direction), true) @pagy, @recordings = pagy_array(recs) @@ -72,7 +73,7 @@ class RoomsController < ApplicationController end @search, @order_column, @order_direction, pub_recs = - @room.public_recordings(params.permit(:search, :column, :direction), true) + public_recordings(@room.bbb_id, @user_domain, params.permit(:search, :column, :direction), true) @pagy, @public_recordings = pagy_array(pub_recs) @@ -135,7 +136,7 @@ class RoomsController < ApplicationController search_params = params[@room.invite_path] || params @search, @order_column, @order_direction, pub_recs = - @room.public_recordings(search_params.permit(:search, :column, :direction), true) + public_recordings(@room.bbb_id, @user_domain, search_params.permit(:search, :column, :direction), true) @pagy, @public_recordings = pagy_array(pub_recs) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0224d905..c28d1c0f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -21,6 +21,7 @@ class UsersController < ApplicationController include Pagy::Backend include Emailer include Registrar + include Recorder before_action :find_user, only: [:edit, :update, :destroy] before_action :ensure_unauthenticated, only: [:new, :create] @@ -103,6 +104,8 @@ class UsersController < ApplicationController # PATCH /u/:user_uid/edit def update + redirect_path = current_user.admin_of?(@user) ? admins_path : edit_user_path(@user) + if params[:setting] == "password" # Update the users password. errors = {} @@ -123,7 +126,7 @@ class UsersController < ApplicationController if errors.empty? && @user.save # Notify the user that their account has been updated. flash[:success] = I18n.t("info_update_success") - redirect_to edit_user_path(@user) + redirect_to redirect_path else # Append custom errors. errors.each { |k, v| @user.errors.add(k, v) } @@ -132,11 +135,11 @@ class UsersController < ApplicationController elsif user_params[:email] != @user.email && @user.update_attributes(user_params) @user.update_attributes(email_verified: false) flash[:success] = I18n.t("info_update_success") - redirect_to edit_user_path(@user) + redirect_to redirect_path elsif @user.update_attributes(user_params) update_locale(@user) flash[:success] = I18n.t("info_update_success") - redirect_to edit_user_path(@user) + redirect_to redirect_path else render :edit, params: { settings: params[:settings] } end @@ -165,7 +168,8 @@ class UsersController < ApplicationController def recordings if current_user && current_user.uid == params[:user_uid] @search, @order_column, @order_direction, recs = - current_user.all_recordings(params.permit(:search, :column, :direction), true) + all_recordings(current_user.rooms.pluck(:bbb_id), current_user.provider, + params.permit(:search, :column, :direction), true) @pagy, @recordings = pagy_array(recs) else redirect_to root_path diff --git a/app/helpers/admins_helper.rb b/app/helpers/admins_helper.rb index af23f731..40193283 100644 --- a/app/helpers/admins_helper.rb +++ b/app/helpers/admins_helper.rb @@ -19,6 +19,18 @@ module AdminsHelper include Pagy::Frontend + # Returns the action method of the current page + def active_page + route = Rails.application.routes.recognize_path(request.env['PATH_INFO']) + + route[:action] + end + + # Gets the email of the room owner to which the recording belongs to + def recording_owner_email(room_id) + Room.find_by(bbb_id: room_id).owner.email + end + def display_invite current_page?(admins_path) && invite_registration end diff --git a/app/models/room.rb b/app/models/room.rb index dc0b5af2..728278fd 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -19,7 +19,6 @@ require 'bbb_api' class Room < ApplicationRecord - include ::APIConcern include ::BbbApi before_create :setup @@ -40,7 +39,7 @@ class Room < ApplicationRecord # Checks if a room is running on the BigBlueButton server. def running? - bbb.is_meeting_running?(bbb_id) + bbb(owner.provider).is_meeting_running?(bbb_id) end # Determines the invite path for the room. @@ -62,7 +61,7 @@ class Room < ApplicationRecord # Send the create request. begin - meeting = bbb.create_meeting(name, bbb_id, create_options) + meeting = bbb(owner.provider).create_meeting(name, bbb_id, create_options) # Update session info. unless meeting[:messageKey] == 'duplicateWarning' update_attributes(sessions: sessions + 1, last_session: DateTime.now) @@ -84,10 +83,10 @@ class Room < ApplicationRecord options[:user_is_moderator] ||= false options[:meeting_recorded] ||= false - return call_invalid_res unless bbb + return call_invalid_res unless bbb(owner.provider) # Get the meeting info. - meeting_info = bbb.get_meeting_info(bbb_id, nil) + meeting_info = bbb(owner.provider).get_meeting_info(bbb_id, nil) # Determine the password to use when joining. password = if options[:user_is_moderator] @@ -101,7 +100,7 @@ class Room < ApplicationRecord join_opts[:userID] = uid if uid join_opts[:joinViaHtml5] = options[:join_via_html5] if options[:join_via_html5] - bbb.join_meeting_url(bbb_id, name, password, join_opts) + bbb(owner.provider).join_meeting_url(bbb_id, name, password, join_opts) end # Notify waiting users that a meeting has started. @@ -111,7 +110,7 @@ class Room < ApplicationRecord # Retrieves all the users in a room. def participants - res = bbb.get_meeting_info(bbb_id, nil) + res = bbb(owner.provider).get_meeting_info(bbb_id, nil) res[:attendees].map do |att| User.find_by(uid: att[:userID], name: att[:fullName]) end @@ -120,27 +119,18 @@ class Room < ApplicationRecord [] end - # Fetches all recordings for a room. - def recordings(search_params = {}, ret_search_params = false) - res = bbb.get_recordings(meetingID: bbb_id) - - format_recordings(res, search_params, ret_search_params) - end - - # Fetches a rooms public recordings. - def public_recordings(search_params = {}, ret_search_params = false) - search, order_col, order_dir, recs = recordings(search_params, ret_search_params) - [search, order_col, order_dir, recs.select { |r| r[:metadata][:"gl-listed"] == "true" }] + def recording_count + bbb(owner.provider).get_recordings(meetingID: bbb_id)[:recordings].length end def update_recording(record_id, meta) meta[:recordID] = record_id - bbb.send_api_request("updateRecordings", meta) + bbb(owner.provider).send_api_request("updateRecordings", meta) end # Deletes a recording from a room. def delete_recording(record_id) - bbb.delete_recordings(record_id) + bbb(owner.provider).delete_recordings(record_id) end private @@ -155,7 +145,7 @@ class Room < ApplicationRecord # Deletes all recordings associated with the room. def delete_all_recordings - record_ids = recordings.map { |r| r[:recordID] } + record_ids = bbb(owner.provider).get_recordings(meetingID: bbb_id)[:recordings].pluck(:recordID) delete_recording(record_ids) unless record_ids.empty? end diff --git a/app/models/user.rb b/app/models/user.rb index ba914f9b..6ed05a76 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -20,7 +20,6 @@ require 'bbb_api' class User < ApplicationRecord rolify - include ::APIConcern include ::BbbApi attr_accessor :reset_token @@ -118,31 +117,8 @@ class User < ApplicationRecord end def self.admins_order(column, direction) - order("#{column} #{direction}") - end - - def all_recordings(search_params = {}, ret_search_params = false) - pag_num = Rails.configuration.pagination_number - - pag_loops = rooms.length / pag_num - 1 - - res = { recordings: [] } - - (0..pag_loops).each do |i| - pag_rooms = rooms[pag_num * i, pag_num] - - # bbb.get_recordings returns an object - # take only the array portion of the object that is returned - full_res = bbb.get_recordings(meetingID: pag_rooms.pluck(:bbb_id)) - res[:recordings].push(*full_res[:recordings]) - end - - last_pag_room = rooms[pag_num * (pag_loops + 1), rooms.length % pag_num] - - full_res = bbb.get_recordings(meetingID: last_pag_room.pluck(:bbb_id)) - res[:recordings].push(*full_res[:recordings]) - - format_recordings(res, search_params, ret_search_params) + # Arel.sql to avoid sql injection + order(Arel.sql("#{column} #{direction}")) end # Activates an account and initialize a users main room diff --git a/app/views/admins/components/_menu_buttons.html.erb b/app/views/admins/components/_menu_buttons.html.erb new file mode 100644 index 00000000..7dfcc8ff --- /dev/null +++ b/app/views/admins/components/_menu_buttons.html.erb @@ -0,0 +1,26 @@ +<% +# 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 . +%> + +
+ <%= link_to admins_path, class: "list-group-item list-group-item-action dropdown-item #{"active" if active_page == "index"}" do %> + <%= t("administrator.users.title") %> + <% end %> + <%= 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 %> + <%= 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") %> + <% end %> +
\ No newline at end of file diff --git a/app/views/admins/components/_recordings.html.erb b/app/views/admins/components/_recordings.html.erb new file mode 100644 index 00000000..2ffa9ee9 --- /dev/null +++ b/app/views/admins/components/_recordings.html.erb @@ -0,0 +1,91 @@ +<% +# 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 . +%> + +
+
+
+ + + + + + + + + + + + + + + + <% if @recordings.empty? %> + + + + <% else %> + <% @recordings.each do |recording| %> + <%= render "admins/components/server_recording_row", recording: recording %> + <% end %> + <% end %> + +
"> + <%= t("recording.table.name") %> + <% if @order_column == "name" && @order_direction == "desc" %> + ↓ + <% elsif @order_column == "name" && @order_direction == "asc" %> + ↑ + <% end %> + "> + <%= t("recording.table.length") %> + <% if @order_column == "length" && @order_direction == "desc" %> + ↓ + <% elsif @order_column == "length" && @order_direction == "asc" %> + ↑ + <% end %> + "> + <%= t("recording.table.users") %> + <% if @order_column == "users" && @order_direction == "desc" %> + ↓ + <% elsif @order_column == "users" && @order_direction == "asc" %> + ↑ + <% end %> + "> + <%= t("recording.table.visibility") %> + <% if @order_column == "visibility" && @order_direction == "desc" %> + ↓ + <% elsif @order_column == "visibility" && @order_direction == "asc" %> + ↑ + <% end %> + "> + <%= t("recording.table.formats") %> + <% if @order_column == "formats" && @order_direction == "desc" %> + ↓ + <% elsif @order_column == "formats" && @order_direction == "asc" %> + ↑ + <% end %> +
+ <%= t("administrator.recordings.no_recordings") %> +
+ <% if !@recordings.empty?%> +
+ <%== pagy_bootstrap_nav(@pagy) %> +
+ <% end %> +
+
+
\ No newline at end of file diff --git a/app/views/admins/components/_server_recording_row.html.erb b/app/views/admins/components/_server_recording_row.html.erb new file mode 100644 index 00000000..c6234755 --- /dev/null +++ b/app/views/admins/components/_server_recording_row.html.erb @@ -0,0 +1,81 @@ +<% +# 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 . +%> + + + +
+ + <% if recording[:metadata][:name] %> + <%= recording[:metadata][:name] %> + <% else %> + <%= recording[:name] %> + <% end %> + + +
+
+ <%= t("recording.recorded_on", date: recording_date(recording[:startTime])) %> +
+
+ <%= recording_owner_email(recording[:meetingID]) %> +
+ + + <%= recording_length(recording[:playbacks]) %> + + + <%= recording[:participants] || "-" %> + + + + + + <% sorted_formats = recording[:playbacks].sort_by! { |p| p[:type] } %> + <% sorted_formats.each do |p| %> + <%= link_to t("recording.format.#{p[:type]}"), p[:url], class: "btn btn-sm btn-primary", target: "_blank" %> + <% end %> + + + + + diff --git a/app/views/admins/components/_setting_view.html.erb b/app/views/admins/components/_setting_view.html.erb new file mode 100644 index 00000000..1b2eb6ec --- /dev/null +++ b/app/views/admins/components/_setting_view.html.erb @@ -0,0 +1,26 @@ +<% +# 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 . +%> + +<%= content_tag(:div, id: setting_id, class: "setting-view card") do %> +
+
+
+ <%= render "shared/components/subtitle", subtitle: setting_title, search: search %> +
+
+ + <%= render "admins/components/#{setting_id}" %> +
+<% end %> diff --git a/app/views/shared/admin_settings/_site_settings.html.erb b/app/views/admins/components/_settings.html.erb similarity index 100% rename from app/views/shared/admin_settings/_site_settings.html.erb rename to app/views/admins/components/_settings.html.erb diff --git a/app/views/shared/admin_settings/_users.html.erb b/app/views/admins/components/_users.html.erb similarity index 90% rename from app/views/shared/admin_settings/_users.html.erb rename to app/views/admins/components/_users.html.erb index df8422fa..5d1ad1bd 100644 --- a/app/views/shared/admin_settings/_users.html.erb +++ b/app/views/admins/components/_users.html.erb @@ -13,6 +13,21 @@ # with BigBlueButton; if not, see . %> +<% +# 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 . +%> + <% if @role.present? %> <%= render "shared/components/admins_tags" %> <% end %> @@ -146,3 +161,4 @@
<%= render "shared/modals/invite_user_modal" %> + diff --git a/app/views/admins/edit_user.html.erb b/app/views/admins/edit_user.html.erb new file mode 100644 index 00000000..54beeb8e --- /dev/null +++ b/app/views/admins/edit_user.html.erb @@ -0,0 +1,27 @@ +<% +# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/. +# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below). +# This program is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free Software +# Foundation; either version 3.0 of the License, or (at your option) any later +# version. +# +# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public License along +# with BigBlueButton; if not, see . +%> + +
+ <%= render "shared/components/subtitle", subtitle: t("administrator.title"), search: false %> + +
+
+ <%= render "admins/components/menu_buttons" %> +
+
+ <%= render "shared/settings/setting_view", setting_id: "account", setting_title: t("settings.account.subtitle") %> +
+
+
diff --git a/app/views/admins/index.html.erb b/app/views/admins/index.html.erb index 1903f14c..d366aa23 100644 --- a/app/views/admins/index.html.erb +++ b/app/views/admins/index.html.erb @@ -18,26 +18,10 @@
-
- - -
+ <%= render "admins/components/menu_buttons" %>
- - -
- <% if defined?(setting_id) && setting_id == "account" %> - <%= render "shared/settings/setting_view", setting_id: "account", setting_title: t("administrator.users.edit.title") %> - <% else %> - <%= render "shared/settings/setting_view", admin_view: true, setting_id: "users", setting_title: t("administrator.users.title") %> - <%= render "shared/settings/setting_view", admin_view: true, setting_id: "site_settings", setting_title: t("administrator.site_settings.subtitle") %> - <% end %> - - <%= render "shared/modals/delete_account_modal", delete_location: relative_root %> +
+ <%= render "admins/components/setting_view", setting_id: "users", setting_title: t("administrator.users.title"), search: true %>
diff --git a/app/views/admins/server_recordings.html.erb b/app/views/admins/server_recordings.html.erb new file mode 100644 index 00000000..a07b19cb --- /dev/null +++ b/app/views/admins/server_recordings.html.erb @@ -0,0 +1,27 @@ +<% +# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/. +# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below). +# This program is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free Software +# Foundation; either version 3.0 of the License, or (at your option) any later +# version. +# +# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public License along +# with BigBlueButton; if not, see . +%> + +
+ <%= render "shared/components/subtitle", subtitle: t("administrator.title"), search: false %> + +
+
+ <%= render "admins/components/menu_buttons" %> +
+
+ <%= render "admins/components/setting_view", setting_id: "recordings", setting_title: t("administrator.recordings.title"), search: true %> +
+
+
diff --git a/app/views/admins/site_settings.html.erb b/app/views/admins/site_settings.html.erb new file mode 100644 index 00000000..c246ce71 --- /dev/null +++ b/app/views/admins/site_settings.html.erb @@ -0,0 +1,27 @@ +<% +# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/. +# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below). +# This program is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free Software +# Foundation; either version 3.0 of the License, or (at your option) any later +# version. +# +# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public License along +# with BigBlueButton; if not, see . +%> + +
+ <%= render "shared/components/subtitle", subtitle: t("administrator.title"), search: false %> + +
+
+ <%= render "admins/components/menu_buttons" %> +
+
+ <%= render "admins/components/setting_view", setting_id: "settings", setting_title: t("administrator.site_settings.subtitle"), search: false %> +
+
+
\ No newline at end of file diff --git a/app/views/rooms/show.html.erb b/app/views/rooms/show.html.erb index d527be81..40725512 100644 --- a/app/views/rooms/show.html.erb +++ b/app/views/rooms/show.html.erb @@ -89,7 +89,7 @@ <%= render "shared/components/room_block", room: room %> <% end %> - <%= render "shared/modals/delete_room_modal", room: room %> + <%= render "shared/modals/delete_room_modal", recording_count: room.recording_count, room: room %> <% end %> <% end %> <% unless room_limit_exceeded %> diff --git a/app/views/shared/modals/_delete_room_modal.html.erb b/app/views/shared/modals/_delete_room_modal.html.erb index 27f92853..3b7a1d8d 100644 --- a/app/views/shared/modals/_delete_room_modal.html.erb +++ b/app/views/shared/modals/_delete_room_modal.html.erb @@ -34,8 +34,8 @@ diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index bee0c169..8ee6186d 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -20,17 +20,17 @@
- <% if @user.social_uid.nil? %> - <% end %> -
diff --git a/config/locales/en.yml b/config/locales/en.yml index e04ed564..fdbec16a 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -73,6 +73,9 @@ en: registration_method_updated: Registration method successfully updated settings: Site Settings successfully changed unauthorized: You are not authorized to perform actions on this user + recordings: + title: Server Recordings + no_recordings: This server has no recordings. title: Organization Settings users: invite: Invite User diff --git a/config/routes.rb b/config/routes.rb index a1dc1eaa..56dc67ae 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -37,6 +37,8 @@ Rails.application.routes.draw do resources :admins, only: [:index] scope '/admins' do + get '/site_settings', to: 'admins#site_settings', as: :admin_site_settings + get '/recordings', to: 'admins#server_recordings', as: :admin_recordings post '/branding', to: 'admins#branding', as: :admin_branding post '/coloring', to: 'admins#coloring', as: :admin_coloring post '/room_authentication', to: 'admins#room_authentication', as: :admin_room_authentication diff --git a/lib/bbb_api.rb b/lib/bbb_api.rb index 46f9bdfd..b76b3c81 100644 --- a/lib/bbb_api.rb +++ b/lib/bbb_api.rb @@ -12,15 +12,9 @@ module BbbApi end # Sets a BigBlueButtonApi object for interacting with the API. - def bbb + def bbb(user_provider) if Rails.configuration.loadbalanced_configuration - if instance_of? Room - # currently in the Room Model - user_domain = retrieve_provider_info(owner.provider) - elsif instance_of? User - # currently in the User Model - user_domain = retrieve_provider_info(provider) - end + user_domain = retrieve_provider_info(user_provider) BigBlueButton::BigBlueButtonApi.new(remove_slash(user_domain["apiURL"]), user_domain["secret"], "0.8") else diff --git a/spec/concerns/recorder_spec.rb b/spec/concerns/recorder_spec.rb new file mode 100644 index 00000000..7e082e1f --- /dev/null +++ b/spec/concerns/recorder_spec.rb @@ -0,0 +1,567 @@ +# 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 . + +require "rails_helper" +require 'bigbluebutton_api' + +shared_examples_for "recorder" do + let(:controller) { described_class } # the class that includes the concern + + before do + @user = create(:user) + @room = @user.main_room + + allow_any_instance_of(Room).to receive(:owner).and_return(@user) + end + + it "should properly find meeting recordings" do + allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( + recordings: [ + { + name: "Example", + playback: { + format: + { + type: "presentation" + } + } + } + ] + ) + + expect(recordings(@room.bbb_id, @room.owner.provider)).to contain_exactly( + name: "Example", + playbacks: + [ + { + type: "presentation" + } + ] + ) + end + + it "gets all filtered and sorted recordings for the user" do + allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( + recordings: [ + { + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playback: { + format: + { + type: "presentation" + } + }, + metadata: { + "gl-listed": "true", + } + }, + { + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "5", + playback: { + format: + { + type: "other" + } + }, + metadata: { + "gl-listed": "false", + } + }, + { + meetingID: @room.bbb_id, + name: "test", + participants: "1", + playback: { + format: + { + type: "presentation" + } + }, + metadata: { + "gl-listed": "true", + } + }, + { + meetingID: @room.bbb_id, + name: "Exam", + participants: "1", + playback: { + format: + { + type: "other" + } + }, + metadata: { + "gl-listed": "false", + name: "z", + } + } + ] + ) + + expect(all_recordings(@user.rooms.pluck(:bbb_id), @user.provider, search: "Exam", column: "name", + direction: "desc")).to eq( + [ + { + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playbacks: + [ + { + type: "presentation" + } + ], + metadata: { + "gl-listed": "true", + } + }, + { + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "5", + playbacks: + [ + { + type: "other" + } + ], + metadata: { + "gl-listed": "false", + } + } + ] + ) + end + + context '#filtering' do + before do + allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( + recordings: [ + { + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playback: { + format: + { + type: "presentation" + } + }, + metadata: { + "gl-listed": "true", + } + }, + { + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "5", + playback: { + format: + { + type: "other" + } + }, + metadata: { + "gl-listed": "false", + } + }, + { + meetingID: @room.bbb_id, + name: "test", + participants: "1", + playback: { + format: + { + type: "presentation" + } + }, + metadata: { + "gl-listed": "true", + } + }, + { + meetingID: @room.bbb_id, + name: "Exam", + participants: "1", + playback: { + format: + { + type: "other" + } + }, + metadata: { + "gl-listed": "false", + name: "metadata", + } + } + ] + ) + end + + it "should filter recordings on name" do + expect(recordings(@room.bbb_id, @room.owner.provider, search: "Exam")).to contain_exactly( + { + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "5", + playbacks: + [ + { + type: "other" + } + ], + metadata: { + "gl-listed": "false", + } + }, + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playbacks: + [ + { + type: "presentation" + } + ], + metadata: { + "gl-listed": "true", + } + ) + end + + it "should filter recordings on participants" do + expect(recordings(@room.bbb_id, @room.owner.provider, search: "5")).to contain_exactly( + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "5", + playbacks: + [ + { + type: "other" + } + ], + metadata: { + "gl-listed": "false", + } + ) + end + + it "should filter recordings on format" do + expect(recordings(@room.bbb_id, @room.owner.provider, search: "presentation")).to contain_exactly( + { + meetingID: @room.bbb_id, + name: "test", + participants: "1", + playbacks: + [ + { + type: "presentation" + } + ], + metadata: { + "gl-listed": "true", + } + }, + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playbacks: + [ + { + type: "presentation" + } + ], + metadata: { + "gl-listed": "true", + } + ) + end + + it "should filter recordings on visibility" do + expect(recordings(@room.bbb_id, @room.owner.provider, search: "public")).to contain_exactly( + { + meetingID: @room.bbb_id, + name: "test", + participants: "1", + playbacks: + [ + { + type: "presentation" + } + ], + metadata: { + "gl-listed": "true", + }, + }, + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playbacks: + [ + { + type: "presentation" + } + ], + metadata: { + "gl-listed": "true", + } + ) + end + + it "should filter recordings on metadata name by default" do + expect(recordings(@room.bbb_id, @room.owner.provider, search: "metadata")).to contain_exactly( + meetingID: @room.bbb_id, + name: "Exam", + participants: "1", + playbacks: + [ + { + type: "other" + } + ], + metadata: { + "gl-listed": "false", + name: "metadata", + } + ) + end + end + + context '#sorting' do + before do + allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( + recordings: [ + { + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playback: { + format: { + type: "presentation", + length: "4" + } + }, + metadata: { + "gl-listed": "true", + } + }, + { + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "1", + playback: { + format: { + type: "other", + length: "3" + } + }, + metadata: { + name: "Z", + "gl-listed": "false" + } + } + ] + ) + end + + it "should sort recordings on name" do + expect(recordings(@room.bbb_id, @room.owner.provider, column: "name", direction: "asc")).to eq( + [ + { + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playbacks: [ + { + type: "presentation", + length: "4" + } + ], + metadata: { + "gl-listed": "true", + } + }, + { + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "1", + playbacks: [ + { + type: "other", + length: "3" + } + ], + metadata: { + name: "Z", + "gl-listed": "false" + } + } + ] + ) + end + + it "should sort recordings on participants" do + expect(recordings(@room.bbb_id, @room.owner.provider, column: "users", direction: "desc")).to eq( + [ + { + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playbacks: [ + { + type: "presentation", + length: "4" + } + ], + metadata: { + "gl-listed": "true", + } + }, + { + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "1", + playbacks: [ + { + type: "other", + length: "3" + } + ], + metadata: { + name: "Z", + "gl-listed": "false" + } + } + ] + ) + end + + it "should sort recordings on visibility" do + expect(recordings(@room.bbb_id, @room.owner.provider, column: "visibility", direction: "desc")).to eq( + [ + { + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playbacks: [ + { + type: "presentation", + length: "4" + } + ], + metadata: { + "gl-listed": "true", + } + }, + { + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "1", + playbacks: [ + { + type: "other", + length: "3" + } + ], + metadata: { + name: "Z", + "gl-listed": "false" + } + } + ] + ) + end + + it "should sort recordings on length" do + expect(recordings(@room.bbb_id, @room.owner.provider, column: "length", direction: "asc")).to eq( + [ + { + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "1", + playbacks: [ + { + type: "other", + length: "3" + } + ], + metadata: { + name: "Z", + "gl-listed": "false" + } + }, + { + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playbacks: [ + { + type: "presentation", + length: "4" + } + ], + metadata: { + "gl-listed": "true", + } + } + ] + ) + end + + it "should sort recordings on format" do + expect(recordings(@room.bbb_id, @room.owner.provider, column: "formats", direction: "desc")).to eq( + [ + { + meetingID: @room.bbb_id, + name: "Example", + participants: "3", + playbacks: [ + { + type: "presentation", + length: "4" + } + ], + metadata: { + "gl-listed": "true", + } + }, + { + meetingID: @room.bbb_id, + name: "aExamaaa", + participants: "1", + playbacks: [ + { + type: "other", + length: "3" + } + ], + metadata: { + name: "Z", + "gl-listed": "false" + } + } + ] + ) + end + end +end diff --git a/spec/controllers/admins_controller_spec.rb b/spec/controllers/admins_controller_spec.rb index 3f55a2c0..d03576ca 100644 --- a/spec/controllers/admins_controller_spec.rb +++ b/spec/controllers/admins_controller_spec.rb @@ -52,7 +52,7 @@ describe AdminsController, type: :controller do get :edit_user, params: { user_uid: @user.uid } - expect(response).to render_template(:index) + expect(response).to render_template(:edit_user) end end @@ -197,7 +197,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Branding Image") expect(feature[:value]).to eq(fake_image_url) - expect(response).to redirect_to(admins_path) + expect(response).to redirect_to(admin_site_settings_path) end end @@ -214,7 +214,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color") expect(feature[:value]).to eq(primary_color) - expect(response).to redirect_to(admins_path) + expect(response).to redirect_to(admin_site_settings_path) end it "changes the primary-lighten on the page" do @@ -229,7 +229,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color Lighten") expect(feature[:value]).to eq(primary_color) - expect(response).to redirect_to(admins_path) + expect(response).to redirect_to(admin_site_settings_path) end it "changes the primary-darken on the page" do @@ -244,7 +244,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Primary Color Darken") expect(feature[:value]).to eq(primary_color) - expect(response).to redirect_to(admins_path) + expect(response).to redirect_to(admin_site_settings_path) end end end @@ -264,7 +264,7 @@ describe AdminsController, type: :controller do expect(feature[:value]).to eq(Rails.configuration.registration_methods[:invite]) expect(flash[:success]).to be_present - expect(response).to redirect_to(admins_path) + expect(response).to redirect_to(admin_site_settings_path) end it "does not allow the user to change to invite if emails are off" do @@ -277,7 +277,7 @@ describe AdminsController, type: :controller do post :registration_method, params: { method: "invite" } expect(flash[:alert]).to be_present - expect(response).to redirect_to(admins_path) + expect(response).to redirect_to(admin_site_settings_path) end end @@ -293,7 +293,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Room Authentication") expect(feature[:value]).to eq("true") - expect(response).to redirect_to(admins_path) + expect(response).to redirect_to(admin_site_settings_path) end end @@ -309,7 +309,7 @@ describe AdminsController, type: :controller do feature = Setting.find_by(provider: "provider1").features.find_by(name: "Room Limit") expect(feature[:value]).to eq("5") - expect(response).to redirect_to(admins_path) + expect(response).to redirect_to(admin_site_settings_path) end end end diff --git a/spec/controllers/rooms_controller_spec.rb b/spec/controllers/rooms_controller_spec.rb index b9899079..7fe3b6b3 100644 --- a/spec/controllers/rooms_controller_spec.rb +++ b/spec/controllers/rooms_controller_spec.rb @@ -28,6 +28,8 @@ def random_valid_room_params end describe RoomsController, type: :controller do + it_behaves_like "recorder" + include Recorder describe "GET #show" do before do @user = create(:user) @@ -39,7 +41,7 @@ describe RoomsController, type: :controller do get :show, params: { room_uid: @owner.main_room } - expect(assigns(:recordings)).to eql(@owner.main_room.recordings) + expect(assigns(:recordings)).to eql(recordings(@owner.main_room.bbb_id, @owner.provider)) expect(assigns(:is_running)).to eql(@owner.main_room.running?) end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 42bc6103..23d40e8d 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -302,6 +302,7 @@ describe UsersController, type: :controller do describe "PATCH #update" do it "properly updates user attributes" do user = create(:user) + @request.session[:user_id] = user.id params = random_valid_user_params patch :update, params: params.merge!(user_uid: user) @@ -315,6 +316,7 @@ describe UsersController, type: :controller do it "renders #edit on unsuccessful save" do @user = create(:user) + @request.session[:user_id] = @user.id patch :update, params: invalid_params.merge!(user_uid: @user) expect(response).to render_template(:edit) diff --git a/spec/models/room_spec.rb b/spec/models/room_spec.rb index a95a9684..2745aaa5 100644 --- a/spec/models/room_spec.rb +++ b/spec/models/room_spec.rb @@ -133,420 +133,6 @@ describe Room, type: :model do end context "#recordings" do - it "should properly find meeting recordings" do - allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( - recordings: [ - { - name: "Example", - playback: { - format: - { - type: "presentation" - } - } - } - ] - ) - - expect(@room.recordings).to contain_exactly( - name: "Example", - playbacks: - [ - { - type: "presentation" - } - ] - ) - end - - context '#filtering' do - before do - allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( - recordings: [ - { - name: "Example", - participants: "3", - playback: { - format: - { - type: "presentation" - } - }, - metadata: { - "gl-listed": "true", - } - }, - { - name: "aExamaaa", - participants: "5", - playback: { - format: - { - type: "other" - } - }, - metadata: { - "gl-listed": "false", - } - }, - { - name: "test", - participants: "1", - playback: { - format: - { - type: "presentation" - } - }, - metadata: { - "gl-listed": "true", - } - }, - { - name: "Exam", - participants: "1", - playback: { - format: - { - type: "other" - } - }, - metadata: { - "gl-listed": "false", - name: "z", - } - } - ] - ) - end - - it "should filter recordings on name" do - expect(@room.recordings(search: "Exam")).to contain_exactly( - { - name: "aExamaaa", - participants: "5", - playbacks: - [ - { - type: "other" - } - ], - metadata: { - "gl-listed": "false", - } - }, - name: "Example", - participants: "3", - playbacks: - [ - { - type: "presentation" - } - ], - metadata: { - "gl-listed": "true", - } - ) - end - - it "should filter recordings on participants" do - expect(@room.recordings(search: "5")).to contain_exactly( - name: "aExamaaa", - participants: "5", - playbacks: - [ - { - type: "other" - } - ], - metadata: { - "gl-listed": "false", - } - ) - end - - it "should filter recordings on format" do - expect(@room.recordings(search: "presentation")).to contain_exactly( - { - name: "test", - participants: "1", - playbacks: - [ - { - type: "presentation" - } - ], - metadata: { - "gl-listed": "true", - } - }, - name: "Example", - participants: "3", - playbacks: - [ - { - type: "presentation" - } - ], - metadata: { - "gl-listed": "true", - } - ) - end - - it "should filter recordings on visibility" do - expect(@room.recordings(search: "public")).to contain_exactly( - { - name: "test", - participants: "1", - playbacks: - [ - { - type: "presentation" - } - ], - metadata: { - "gl-listed": "true", - }, - }, - name: "Example", - participants: "3", - playbacks: - [ - { - type: "presentation" - } - ], - metadata: { - "gl-listed": "true", - } - ) - end - - it "should filter recordings on metadata name by default" do - expect(@room.recordings(search: "z")).to contain_exactly( - name: "Exam", - participants: "1", - playbacks: - [ - { - type: "other" - } - ], - metadata: { - "gl-listed": "false", - name: "z", - } - ) - end - end - - context '#sorting' do - before do - allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( - recordings: [ - { - name: "Example", - participants: "3", - playback: { - format: { - type: "presentation", - length: "4" - } - }, - metadata: { - "gl-listed": "true", - } - }, - { - name: "aExamaaa", - participants: "1", - playback: { - format: { - type: "other", - length: "3" - } - }, - metadata: { - name: "Z", - "gl-listed": "false" - } - } - ] - ) - end - - it "should sort recordings on name" do - expect(@room.recordings(column: "name", direction: "asc")).to eq( - [ - { - name: "Example", - participants: "3", - playbacks: [ - { - type: "presentation", - length: "4" - } - ], - metadata: { - "gl-listed": "true", - } - }, - { - name: "aExamaaa", - participants: "1", - playbacks: [ - { - type: "other", - length: "3" - } - ], - metadata: { - name: "Z", - "gl-listed": "false" - } - } - ] - ) - end - - it "should sort recordings on participants" do - expect(@room.recordings(column: "users", direction: "desc")).to eq( - [ - { - name: "Example", - participants: "3", - playbacks: [ - { - type: "presentation", - length: "4" - } - ], - metadata: { - "gl-listed": "true", - } - }, - { - name: "aExamaaa", - participants: "1", - playbacks: [ - { - type: "other", - length: "3" - } - ], - metadata: { - name: "Z", - "gl-listed": "false" - } - } - ] - ) - end - - it "should sort recordings on visibility" do - expect(@room.recordings(column: "visibility", direction: "desc")).to eq( - [ - { - name: "Example", - participants: "3", - playbacks: [ - { - type: "presentation", - length: "4" - } - ], - metadata: { - "gl-listed": "true", - } - }, - { - name: "aExamaaa", - participants: "1", - playbacks: [ - { - type: "other", - length: "3" - } - ], - metadata: { - name: "Z", - "gl-listed": "false" - } - } - ] - ) - end - - it "should sort recordings on length" do - expect(@room.recordings(column: "length", direction: "asc")).to eq( - [ - { - name: "aExamaaa", - participants: "1", - playbacks: [ - { - type: "other", - length: "3" - } - ], - metadata: { - name: "Z", - "gl-listed": "false" - } - }, - { - name: "Example", - participants: "3", - playbacks: [ - { - type: "presentation", - length: "4" - } - ], - metadata: { - "gl-listed": "true", - } - } - ] - ) - end - - it "should sort recordings on format" do - expect(@room.recordings(column: "formats", direction: "desc")).to eq( - [ - { - name: "Example", - participants: "3", - playbacks: [ - { - type: "presentation", - length: "4" - } - ], - metadata: { - "gl-listed": "true", - } - }, - { - name: "aExamaaa", - participants: "1", - playbacks: [ - { - type: "other", - length: "3" - } - ], - metadata: { - name: "Z", - "gl-listed": "false" - } - } - ] - ) - end - end - it "deletes the recording" do allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:delete_recordings).and_return( returncode: true, deleted: true diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index dc38dc11..fefb0b20 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -173,97 +173,4 @@ describe User, type: :model do .to raise_exception(ActiveRecord::RecordInvalid, "Validation failed: Email can't be blank") end end - - context '#recordings' do - it "gets all filtered and sorted recordings for the user" do - allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( - recordings: [ - { - name: "Example", - participants: "3", - playback: { - format: - { - type: "presentation" - } - }, - metadata: { - "gl-listed": "true", - } - }, - { - name: "aExamaaa", - participants: "5", - playback: { - format: - { - type: "other" - } - }, - metadata: { - "gl-listed": "false", - } - }, - { - name: "test", - participants: "1", - playback: { - format: - { - type: "presentation" - } - }, - metadata: { - "gl-listed": "true", - } - }, - { - name: "Exam", - participants: "1", - playback: { - format: - { - type: "other" - } - }, - metadata: { - "gl-listed": "false", - name: "z", - } - } - ] - ) - - expect(@user.all_recordings(search: "Exam", column: "name", direction: "desc")).to eq( - [ - { - name: "Example", - participants: "3", - playbacks: - [ - { - type: "presentation" - } - ], - metadata: { - "gl-listed": "true", - } - }, - { - name: "aExamaaa", - participants: "5", - playbacks: - [ - { - type: "other" - } - ], - metadata: { - "gl-listed": "false", - } - } - ] - ) - end - end end From 7d1c9e87a95b742fe1d8d79d4af8c965640fb030 Mon Sep 17 00:00:00 2001 From: shawn-higgins1 <23224097+shawn-higgins1@users.noreply.github.com> Date: Mon, 22 Jul 2019 13:12:44 -0400 Subject: [PATCH 06/16] Allow rooms to have an optional access code (#646) --- app/assets/javascripts/room.js.erb | 31 ++++++++ app/assets/stylesheets/rooms.scss | 4 + app/controllers/rooms_controller.rb | 78 ++++++++++++------- app/views/rooms/join.html.erb | 14 +++- app/views/rooms/wait.html.erb | 2 +- app/views/shared/_room_event.html.erb | 4 +- .../shared/components/_room_block.html.erb | 2 +- .../shared/modals/_create_room_modal.html.erb | 13 +++- config/locales/en.yml | 5 ++ config/routes.rb | 1 + ...0711192033_add_password_digest_to_rooms.rb | 7 ++ db/schema.rb | 3 +- spec/controllers/rooms_controller_spec.rb | 49 ++++++++++++ 13 files changed, 179 insertions(+), 34 deletions(-) create mode 100644 db/migrate/20190711192033_add_password_digest_to_rooms.rb diff --git a/app/assets/javascripts/room.js.erb b/app/assets/javascripts/room.js.erb index 1dc13675..15893e68 100644 --- a/app/assets/javascripts/room.js.erb +++ b/app/assets/javascripts/room.js.erb @@ -44,6 +44,9 @@ $(document).on('turbolinks:load', function(){ // Display and update all fields related to creating a room in the createRoomModal $("#create-room-block").click(function(){ $("#create-room-name").val("") + $("#create-room-access-code").text("<%= I18n.t("modal.create_room.access_code_placeholder") %>") + $("#room_access_code").val(null) + $("#createRoomModal form").attr("action", $("body").data('relative-root')) updateDropdown($(".dropdown-item[value='default']")) $("#room_mute_on_join").prop("checked", false) @@ -80,6 +83,16 @@ $(document).on('turbolinks:load', function(){ }) updateCurrentSettings($(this).closest("#room-block").data("room-settings")) + + accessCode = $(this).closest("#room-block").data("room-access-code") + + if(accessCode){ + $("#create-room-access-code").text("<%= I18n.t("modal.create_room.access_code") %>: " + accessCode) + $("#room_access_code").val(accessCode) + } else{ + $("#create-room-access-code").text("<%= I18n.t("modal.create_room.access_code_placeholder") %>") + $("#room_access_code").val(null) + } }) //Update the createRoomModal to show the correct current settings @@ -107,3 +120,21 @@ function updateDropdown(element) { $("#dropdown-trigger").text(element.text()) $("#room_client").val(element.val()) } + +function generateAccessCode(){ + const accessCodeLength = 6 + var validCharacters = "0123456789" + var accessCode = "" + + for( var i = 0; i < accessCodeLength; i++){ + accessCode += validCharacters.charAt(Math.floor(Math.random() * validCharacters.length)); + } + + $("#create-room-access-code").text("<%= I18n.t("modal.create_room.access_code") %>: " + accessCode) + $("#room_access_code").val(accessCode) +} + +function ResetAccessCode(){ + $("#create-room-access-code").text("<%= I18n.t("modal.create_room.access_code_placeholder") %>") + $("#room_access_code").val(null) +} diff --git a/app/assets/stylesheets/rooms.scss b/app/assets/stylesheets/rooms.scss index e971c0bb..1d037930 100644 --- a/app/assets/stylesheets/rooms.scss +++ b/app/assets/stylesheets/rooms.scss @@ -65,3 +65,7 @@ background-color: rgba(0, 0, 0, 0.04); } } + +.allow-icon-click{ + pointer-events: auto; +} diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index 1e633d0d..5a92f114 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -25,7 +25,7 @@ class RoomsController < ApplicationController before_action :validate_verified_email, except: [:show, :join], unless: -> { !Rails.configuration.enable_email_verification } before_action :find_room, except: :create - before_action :verify_room_ownership, except: [:create, :show, :join, :logout] + before_action :verify_room_ownership, except: [:create, :show, :join, :logout, :login] before_action :verify_room_owner_verified, only: [:show, :join], unless: -> { !Rails.configuration.enable_email_verification } before_action :verify_user_not_admin, only: [:show] @@ -36,7 +36,7 @@ class RoomsController < ApplicationController return redirect_to current_user.main_room, flash: { alert: I18n.t("room.room_limit") } if room_limit_exceeded - @room = Room.new(name: room_params[:name]) + @room = Room.new(name: room_params[:name], access_code: room_params[:access_code]) @room.owner = current_user @room.room_settings = create_room_settings_string(room_params[:mute_on_join], room_params[:client]) @@ -106,6 +106,12 @@ class RoomsController < ApplicationController opts = default_meeting_options unless @room.owned_by?(current_user) + # 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] + return redirect_to room_path(room_uid: params[:room_uid]), flash: { alert: I18n.t("room.access_code_required") } + end + # Assign join name if passed. if params[@room.invite_path] @join_name = params[@room.invite_path][:join_name] @@ -118,31 +124,7 @@ class RoomsController < ApplicationController # create or update cookie with join name cookies.encrypted[:greenlight_name] = @join_name unless cookies.encrypted[:greenlight_name] == @join_name - if @room.running? || @room.owned_by?(current_user) - # Determine if the user needs to join as a moderator. - opts[:user_is_moderator] = @room.owned_by?(current_user) - - # Check if the user has specified which client to use - room_settings = JSON.parse(@room[:room_settings]) - opts[:join_via_html5] = room_settings["joinViaHtml5"] if room_settings["joinViaHtml5"] - - if current_user - redirect_to @room.join_path(current_user.name, opts, current_user.uid) - else - join_name = params[:join_name] || params[@room.invite_path][:join_name] - redirect_to @room.join_path(join_name, opts) - end - else - - search_params = params[@room.invite_path] || params - @search, @order_column, @order_direction, pub_recs = - public_recordings(@room.bbb_id, @user_domain, search_params.permit(:search, :column, :direction), true) - - @pagy, @public_recordings = pagy_array(pub_recs) - - # They need to wait until the meeting begins. - render :wait - end + join_room(opts) end # DELETE /:room_uid @@ -185,6 +167,8 @@ class RoomsController < ApplicationController update_room_attributes("settings") # Update the rooms name if it has been changed update_room_attributes("name") if @room.name != room_params[:name] + # Update the room's access code if it has changed + update_room_attributes("access_code") if @room.access_code != room_params[:access_code] rescue StandardError flash[:alert] = I18n.t("room.update_settings_error") else @@ -199,6 +183,15 @@ class RoomsController < ApplicationController redirect_to @room end + # POST /:room_uid/login + def login + session[:access_code] = room_params[:access_code] + + flash[:alert] = I18n.t("room.access_code_required") if session[:access_code] != @room.access_code + + redirect_to room_path(@room.uid) + end + private def update_room_attributes(update_type) @@ -208,6 +201,8 @@ class RoomsController < ApplicationController elsif update_type.eql? "settings" room_settings_string = create_room_settings_string(room_params[:mute_on_join], room_params[:client]) @room.update_attributes(room_settings: room_settings_string) + elsif update_type.eql? "access_code" + @room.update_attributes(access_code: room_params[:access_code]) end end end @@ -226,7 +221,7 @@ class RoomsController < ApplicationController end def room_params - params.require(:room).permit(:name, :auto_join, :mute_on_join, :client) + params.require(:room).permit(:name, :auto_join, :mute_on_join, :client, :access_code) end # Find the room from the uid. @@ -292,4 +287,31 @@ class RoomsController < ApplicationController current_user.rooms.count >= limit end + + def join_room(opts) + if @room.running? || @room.owned_by?(current_user) + # Determine if the user needs to join as a moderator. + opts[:user_is_moderator] = @room.owned_by?(current_user) + + # Check if the user has specified which client to use + room_settings = JSON.parse(@room[:room_settings]) + opts[:join_via_html5] = room_settings["joinViaHtml5"] if room_settings["joinViaHtml5"] + + if current_user + redirect_to @room.join_path(current_user.name, opts, current_user.uid) + else + join_name = params[:join_name] || params[@room.invite_path][:join_name] + redirect_to @room.join_path(join_name, opts) + end + else + search_params = params[@room.invite_path] || params + @search, @order_column, @order_direction, pub_recs = + public_recordings(@room.bbb_id, @user_domain, search_params.permit(:search, :column, :direction), true) + + @pagy, @public_recordings = pagy_array(pub_recs) + + # They need to wait until the meeting begins. + render :wait + end + end end diff --git a/app/views/rooms/join.html.erb b/app/views/rooms/join.html.erb index 5e718e15..9cd55d5d 100644 --- a/app/views/rooms/join.html.erb +++ b/app/views/rooms/join.html.erb @@ -13,9 +13,21 @@ # with BigBlueButton; if not, see . %> -<%= render 'shared/room_event' do %> +<% valid_access_code = @room.access_code.nil? || @room.access_code.empty? || @room.access_code == session[:access_code] %> +<%= render 'shared/room_event', render_recordings: valid_access_code do %> <% if room_authentication_required %>

<%= t("administrator.site_settings.authentication.user-info") %>

+ <% elsif !valid_access_code %> + <%= form_for :room, url: login_room_path(@room.uid) do |f| %> +
+ <%= f.text_field :access_code, + required: true, + class: "form-control join-form", + placeholder: t("room.enter_the_access_code"), + value: "" %> + <%= f.submit t("room.login"), class: "btn btn-primary btn-sm col-sm-3 form-control join-form" %> +
+ <% end %> <% else %> <%= form_for room_path(@room), method: :post do |f| %>
diff --git a/app/views/rooms/wait.html.erb b/app/views/rooms/wait.html.erb index 2afad065..e93f153c 100644 --- a/app/views/rooms/wait.html.erb +++ b/app/views/rooms/wait.html.erb @@ -13,7 +13,7 @@ # with BigBlueButton; if not, see . %> -<%= render 'shared/room_event' do %> +<%= render 'shared/room_event', render_recordings: true do %>

<%= t("room.wait.message") %>

diff --git a/app/views/shared/_room_event.html.erb b/app/views/shared/_room_event.html.erb index 2dbcda0e..88a29f1c 100644 --- a/app/views/shared/_room_event.html.erb +++ b/app/views/shared/_room_event.html.erb @@ -40,4 +40,6 @@
-<%= render "shared/sessions", recordings: @public_recordings, pagy: @pagy, only_public: true, user_recordings: false, title: t("room.recordings") %> +<% if render_recordings %> + <%= render "shared/sessions", recordings: @public_recordings, pagy: @pagy, only_public: true, user_recordings: false, title: t("room.recordings") %> +<% end %> diff --git a/app/views/shared/components/_room_block.html.erb b/app/views/shared/components/_room_block.html.erb index 9e8c6fc4..28b81b6f 100644 --- a/app/views/shared/components/_room_block.html.erb +++ b/app/views/shared/components/_room_block.html.erb @@ -13,7 +13,7 @@ # with BigBlueButton; if not, see . %> -
class="card"> +
data-room-access-code="<%= room.access_code %>" class="card">
diff --git a/app/views/shared/modals/_create_room_modal.html.erb b/app/views/shared/modals/_create_room_modal.html.erb index 12028de2..658f6f9e 100644 --- a/app/views/shared/modals/_create_room_modal.html.erb +++ b/app/views/shared/modals/_create_room_modal.html.erb @@ -28,10 +28,21 @@ - <%= f.text_field :name, id: "create-room-name", class: "form-control", value: "", placeholder: t("modal.create_room.name_placeholder"), autocomplete: :off %> + <%= f.text_field :name, id: "create-room-name", class: "form-control text-center", value: "", placeholder: t("modal.create_room.name_placeholder"), autocomplete: :off %>
<%= t("modal.create_room.not_blank") %>
+
+ + + + <%= f.label :access_code, t("modal.create_room.access_code_placeholder"), id: "create-room-access-code", class: "form-control" %> + <%= f.hidden_field :access_code %> + + + +
+ <% if Rails.configuration.room_features.include? "default-client" %>