diff --git a/.gitignore b/.gitignore index 32af8a97..f9bf6839 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ vendor/.bundle !/log/.keep !/tmp/.keep .env +coverage # Ignore Byebug command history file. .byebug_history diff --git a/Gemfile b/Gemfile index 6cf5b618..75554816 100644 --- a/Gemfile +++ b/Gemfile @@ -49,6 +49,11 @@ group :development do gem 'spring-watcher-listen', '~> 2.0.0' end +group :test do + gem 'mocha' + gem 'simplecov', :require => false +end + group :production do # For more condensed logging gem "lograge" diff --git a/Gemfile.lock b/Gemfile.lock index 0a422138..999bd378 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -56,6 +56,7 @@ GEM coffee-script-source (1.10.0) concurrent-ruby (1.0.2) debug_inspector (0.0.2) + docile (1.1.5) dotenv (2.1.1) dotenv-rails (2.1.1) dotenv (= 2.1.1) @@ -99,12 +100,15 @@ GEM nokogiri (>= 1.5.9) mail (2.6.4) mime-types (>= 1.16, < 4) + metaclass (0.0.4) method_source (0.8.2) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) mini_portile2 (2.1.0) minitest (5.9.1) + mocha (1.2.1) + metaclass (~> 0.0.1) multi_json (1.12.1) multi_xml (0.5.5) multipart-post (2.0.0) @@ -177,6 +181,11 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) + simplecov (0.13.0) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) spring (2.0.0) activesupport (>= 4.2) spring-watcher-listen (2.0.1) @@ -228,6 +237,7 @@ DEPENDENCIES jquery-ui-rails listen (~> 3.0.5) lograge + mocha omniauth (= 1.3.1) omniauth-google-oauth2 (= 0.4.1) omniauth-twitter (= 1.2.1) @@ -236,6 +246,7 @@ DEPENDENCIES rails (~> 5.0.0, >= 5.0.0.1) rails-timeago (~> 2.0) sass-rails (~> 5.0) + simplecov spring spring-watcher-listen (~> 2.0.0) sqlite3 diff --git a/app/controllers/bbb_controller.rb b/app/controllers/bbb_controller.rb index 6f8735c4..e1d67eaf 100644 --- a/app/controllers/bbb_controller.rb +++ b/app/controllers/bbb_controller.rb @@ -21,7 +21,6 @@ class BbbController < ApplicationController before_action :load_and_authorize_room_owner!, only: [:end] skip_before_action :verify_authenticity_token, only: :callback - before_action :validate_checksum, only: :callback # GET /:resource/:id/join # GET /:resource/:room_id/:id/join @@ -104,9 +103,13 @@ class BbbController < ApplicationController end end - # POST /:resource/:id/callback + # POST /:resource/:room_id/:id/callback # Endpoint for webhook calls from BigBlueButton def callback + # respond with 200 anyway so BigBlueButton knows the hook call was ok + # but abort execution + head(:ok) && return unless validate_checksum + begin data = JSON.parse(read_body(request)) treat_callback_event(data["event"]) @@ -114,11 +117,10 @@ class BbbController < ApplicationController logger.error "Error parsing webhook data. Data: #{data}, exception: #{e.inspect}" # respond with 200 anyway so BigBlueButton knows the hook call was ok - render head(:ok) + head(:ok) && return end end - # DELETE /rooms/:id/end # DELETE /rooms/:room_id/:id/end def end load_and_authorize_room_owner! @@ -130,7 +132,7 @@ class BbbController < ApplicationController render_bbb_response bbb_res end - # GET /rooms/:id/recordings + # GET /rooms/:room_id/recordings # GET /rooms/:room_id/:id/recordings def recordings load_room! @@ -144,7 +146,7 @@ class BbbController < ApplicationController render_bbb_response bbb_res, bbb_res[:recordings] end - # PATCH /rooms/:id/recordings/:record_id + # PATCH /rooms/:room_id/recordings/:record_id # PATCH /rooms/:room_id/:id/recordings/:record_id def update_recordings published = params[:published] == 'true' @@ -156,7 +158,7 @@ class BbbController < ApplicationController render_bbb_response bbb_res end - # DELETE /rooms/:id/recordings/:record_id + # DELETE /rooms/:room_id/recordings/:record_id # DELETE /rooms/:room_id/:id/recordings/:record_id def delete_recordings recording = bbb_get_recordings({recordID: params[:record_id]})[:recordings].first @@ -259,10 +261,7 @@ class BbbController < ApplicationController if calculated_checksum != checksum logger.error "Checksum did not match. Calculated: #{calculated_checksum}, received: #{checksum}" - - # respond with 200 anyway so BigBlueButton knows the hook call was ok - # but abort execution - render head(:ok) && return + false end end diff --git a/app/controllers/landing_controller.rb b/app/controllers/landing_controller.rb index 93062bc2..b02ed8aa 100644 --- a/app/controllers/landing_controller.rb +++ b/app/controllers/landing_controller.rb @@ -48,10 +48,6 @@ class LandingController < ApplicationController render layout: false end - def auth_failure - redirect_to '/' - end - def admin? @user && @user == current_user end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index cac3140b..01f1f979 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -34,4 +34,8 @@ class SessionsController < ApplicationController end redirect_to root_path end + + def auth_failure + redirect_to '/' + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2e15f26e..fc92ad54 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -46,9 +46,4 @@ module ApplicationHelper def omniauth_login_url(provider) "#{relative_root}/auth/#{provider}" end - - # Whether the current page is the page of a room/meeting or not - def on_room_or_meeting_page? - params[:id].present? - end end diff --git a/app/helpers/landing_helper.rb b/app/helpers/landing_helper.rb index a351c30a..b2b3e74a 100644 --- a/app/helpers/landing_helper.rb +++ b/app/helpers/landing_helper.rb @@ -15,7 +15,5 @@ # with BigBlueButton; if not, see . module LandingHelper - def new_meeting_token - rand.to_s[2..10] - end + end diff --git a/app/views/landing/index.html.erb b/app/views/landing/index.html.erb index ae3a4825..38e90ba4 100644 --- a/app/views/landing/index.html.erb +++ b/app/views/landing/index.html.erb @@ -39,9 +39,11 @@ <% end %> -
- <%= render 'shared/signup' %> -
+ <% if omniauth_providers_configured.present? %> +
+ <%= render 'shared/signup' %> +
+ <% end %> diff --git a/config/routes.rb b/config/routes.rb index b4f7501a..f9e764ad 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -23,7 +23,7 @@ Rails.application.routes.draw do get '/users/logout', to: 'sessions#destroy', as: :user_logout match '/auth/:provider/callback', to: 'sessions#create', via: [:get, :post] - get '/auth/failure', to: 'landing#auth_failure' + get '/auth/failure', to: 'sessions#auth_failure' # There are two resources [meetings|rooms] # meetings offer a landing page for NON authenticated users to create and join session in BigBlueButton diff --git a/test/controllers/bbb_controller_test.rb b/test/controllers/bbb_controller_test.rb index 241d31d6..c7b2e4a3 100644 --- a/test/controllers/bbb_controller_test.rb +++ b/test/controllers/bbb_controller_test.rb @@ -17,13 +17,158 @@ require 'test_helper' class BbbControllerTest < ActionController::TestCase - # test "should get join" do - # get :join - # assert_response :success - # end + include BbbApi + + setup do + @meeting_id = 'test_id' + @user = users :user1 + @name = 'test_name' + @recording = 'test_recording' + end + + test "should get join URL from join for meeting" do + BbbController.any_instance.expects(:bbb_join_url) + .with() do |token, full_name, opts| + token == @meeting_id && full_name == @name && opts[:user_is_moderator] + end.returns(success_join_res('correct_url')).once + + get :join, params: { id: @meeting_id, resource: 'meetings', name: @name } + assert_response :success + + result = JSON.parse(response.body).deep_symbolize_keys + assert_equal 'correct_url', result[:response][:join_url] + end + + test "should get join URL from join for authenticated meeting" do + login @user + + BbbController.any_instance.expects(:bbb_join_url) + .with() do |token, full_name, opts| + token == meeting_token(@user, @meeting_id) && opts[:wait_for_moderator] && opts[:user_is_moderator] && opts[:meeting_recorded] + end.returns(success_join_res('correct_url')).once + + get :join, params: { room_id: @user.encrypted_id, id: @meeting_id, resource: 'rooms', name: @name } + assert_response :success + end + + test "should wati for moderator on join for authenticated meeting when not room owner" do + BbbController.any_instance.expects(:bbb_join_url) + .with() do |token, full_name, opts| + opts[:wait_for_moderator] && !opts[:user_is_moderator] + end.returns(success_join_res('correct_url')).once + + get :join, params: { room_id: @user.encrypted_id, id: @meeting_id, resource: 'rooms', name: @name } + assert_response :success + end + + test "should end meeting" do + login @user + + BbbController.any_instance.expects(:bbb_end_meeting) + .with() do |token| + token == meeting_token(@user, @meeting_id) + end.returns({status: :ok}).once + + get :end, params: { room_id: @user.encrypted_id, id: @meeting_id, resource: 'rooms' } + assert_response :success + end + + test "should not end meeting for unauthorized user" do + login users :user2 + + get :end, params: { room_id: @user.encrypted_id, id: @meeting_id, resource: 'rooms' } + assert_response :unauthorized + end + + test "should get recordings" do + + BbbController.any_instance.expects(:bbb_get_recordings) + .returns({status: :ok, recordings: []}).once + + get :recordings, params: { room_id: @user.encrypted_id, resource: 'rooms' } + assert_response :success + end + + test "should update recording" do + login @user + + BbbController.any_instance.expects(:bbb_get_recordings) + .returns({status: :ok, recordings: [{recordID: @recording}]}).once + + BbbController.any_instance.expects(:bbb_update_recordings) + .returns({status: :ok}).once + + patch :update_recordings, params: { room_id: @user.encrypted_id, resource: 'rooms', record_id: @recording } + assert_response :success + end + + test "should delete recording" do + login @user + + BbbController.any_instance.expects(:bbb_get_recordings) + .returns({status: :ok, recordings: [{recordID: @recording}]}).at_least_once + + BbbController.any_instance.expects(:bbb_delete_recordings) + .returns({status: :ok}).once + + delete :delete_recordings, params: { room_id: @user.encrypted_id, resource: 'rooms', record_id: @recording } + assert_response :success + end + + test "should not delete recording if unauthorized" do + login users :user2 + + BbbController.any_instance.expects(:bbb_get_recordings) + .returns({status: :ok, recordings: [{recordID: @recording}]}).at_least_once + + BbbController.any_instance.expects(:bbb_delete_recordings) + .returns({status: :ok}).once + + delete :delete_recordings, params: { room_id: @user.encrypted_id, resource: 'rooms', record_id: @recording } + assert_response :unauthorized + end + + test "should not delete recording if not owner" do + login @user + + BbbController.any_instance.expects(:bbb_get_recordings) + .returns({status: :ok, recordings: []}).once + + BbbController.any_instance.expects(:bbb_update_recordings) + .returns({status: :ok}).once + + patch :delete_recordings, params: { room_id: @user.encrypted_id, resource: 'rooms', record_id: @recording } + assert_response :not_found + end + + test "should return success on invalid checksum" do + + BbbController.any_instance.expects(:treat_callback_event).never + + post :callback, params: { room_id: @user.encrypted_id, resource: 'rooms', id: @meeting_id, event: {} } + assert_response :success + end + + # TODO fix this test + # test "should send notification on valid callback" do # - # test "should get end" do - # get :close + # BbbController.any_instance.expects(:treat_callback_event).once + # + # BbbController.any_instance.expects(:validate_checksum) + # .returns(true).once + # + # post :callback, params: { room_id: @user.encrypted_id, resource: 'rooms', id: @meeting_id, event: {} } # assert_response :success # end + + private + + def meeting_token(user, id) + "#{user.encrypted_id}-#{id}" + end + + def login(user) + session[:user_id] = user.id + @current_user = user + end end diff --git a/test/controllers/landing_controller_test.rb b/test/controllers/landing_controller_test.rb index 4a698af5..648921b9 100644 --- a/test/controllers/landing_controller_test.rb +++ b/test/controllers/landing_controller_test.rb @@ -19,7 +19,7 @@ require 'test_helper' class LandingControllerTest < ActionController::TestCase setup do - @meeting_id = rand 100000000..999999999 + @meeting_id = 'test_id' @user = users :user1 end @@ -38,14 +38,27 @@ class LandingControllerTest < ActionController::TestCase assert_response :success end + test "should get meeting room" do + get :resource, params: { room_id: @user.encrypted_id, id: @meeting_id, resource: 'rooms' } + assert_response :success + end + test "should get wait for moderator" do - get :wait_for_moderator, params: { room_id: @user.encrypted_id, id: 'room1', resource: 'rooms' } + get :wait_for_moderator, params: { room_id: @user.encrypted_id, id: @meeting_id, resource: 'rooms' } assert_response :success end test "should get session status refresh" do - get :wait_for_moderator, params: { room_id: @user.encrypted_id, id: 'room1', resource: 'rooms' } + get :session_status_refresh, params: { room_id: @user.encrypted_id, id: @meeting_id, resource: 'rooms' } assert_response :success end + test "should fallback to en-US locale if locale is en" do + request.headers["Accept-Language"] = 'en' + get :index, params: {resource: 'meetings'} + assert_response :success + + assert css_select('html').attribute('lang').value, 'en' + end + end diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb new file mode 100644 index 00000000..544c2b77 --- /dev/null +++ b/test/controllers/sessions_controller_test.rb @@ -0,0 +1,45 @@ +require 'test_helper' + +class SessionsControllerTest < ActionController::TestCase + + setup do + @user = users :user1 + end + + test "should get new" do + get :new + assert_response :success + end + + test "should redirect to home on auth failture" do + get "auth_failure" + assert_redirected_to root_path + end + + test "should not create session without omniauth env set" do + post :create, params: {provider: 'google'} + assert_redirected_to root_path + end + + test "should create session and user" do + provider = 'google' + email = 'new_user@email.com' + request.env['omniauth.auth'] = {'uid' => 'uid', 'provider' => provider, + 'info' => {'name' => 'name', 'email' => email}} + + post :create, params: {provider: provider} + + new_user = User.find_by email: email + + assert_not_nil new_user + assert_redirected_to meeting_room_path(id: new_user.encrypted_id, resource: 'rooms') + assert_equal new_user.id, session[:user_id] + end + + test "should destroy current session" do + session[:user_id] = @user.id + get :destroy + assert_redirected_to root_path + assert_nil session[:user_id] + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index da9519e1..61a1b471 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -15,8 +15,20 @@ # with BigBlueButton; if not, see . ENV['RAILS_ENV'] ||= 'test' + +require 'simplecov' +SimpleCov.start do + add_group 'Models', 'app/models' + add_group 'Controllers', 'app/controllers' + add_group 'Helpers', 'app/helpers' + add_group 'Config', 'config/' + add_group 'Libraries', 'lib/' + add_group 'Tests', 'test/' +end + require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' +require "mocha/test_unit" class ActiveSupport::TestCase # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.