diff --git a/.env.sample b/.env.sample
index c506d1a..905a375 100644
--- a/.env.sample
+++ b/.env.sample
@@ -28,3 +28,11 @@ MUX_TOKEN_ID=
MUX_TOKEN_SECRET=
MUX_BROADCAST_SERVER_URL=rtmp://global-live.mux.com:5222/app
MUX_TEST_MODE_DISABLED=
+
+
+# Required for Microsoft Azure AD Auth
+AZURE_CLIENT_ID = Client App ID
+AZURE_CLIENT_SECRET = Client App Secret
+AZURE_TENANT_ID = Client App Tenant ID
+AZURE_REDIRECT_URI = where microsoft will redirect after login, eg. http://localhost:3000/auth/azure_ad/callback
+AZURE_SCOPES = Scopes required for Application, eg. 'openid email profile User.Read offline_access OnlineMeetings.ReadWrite'
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
index 098be61..425a27b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -139,9 +139,14 @@ gem 'rack-cors'
# Ruby wrappers for the HubSpot REST API
gem "hubspot-ruby"
+# OAuth
+gem 'omniauth-oauth2', '~> 1.6'
+# OmniAuth CSRF protection
+gem 'omniauth-rails_csrf_protection', '~> 0.1.2'
+
# authenticate via Microsoft
# gem 'omniauth-microsoft_graph', git: 'https://github.com/m4c3/omniauth-microsoft_graph'
-gem 'omniauth-microsoft_graph'
+# gem 'omniauth-microsoft_graph'
group :development, :test, :review do
# Call "byebug" anywhere in the code to stop execution and get a debugger console
diff --git a/Gemfile.lock b/Gemfile.lock
index 15ae75b..34bba69 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -319,12 +319,12 @@ GEM
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
- omniauth-microsoft_graph (0.3.3)
- omniauth (~> 1.1, >= 1.1.1)
- omniauth-oauth2 (~> 1.6)
omniauth-oauth2 (1.6.0)
oauth2 (~> 1.1)
omniauth (~> 1.9)
+ omniauth-rails_csrf_protection (0.1.2)
+ actionpack (>= 4.2)
+ omniauth (>= 1.3.1)
parallel (1.19.1)
parity (3.2.0)
parser (2.6.5.0)
@@ -569,7 +569,8 @@ DEPENDENCIES
mux_ruby!
oath (~> 1.1.0)
oath-generators (~> 1.0.1)
- omniauth-microsoft_graph
+ omniauth-oauth2 (~> 1.6)
+ omniauth-rails_csrf_protection (~> 0.1.2)
parity (~> 3.2.0)
pdf-reader (~> 2.1.0)
pdfkit (~> 0.8.2)
diff --git a/app/controllers/broadcasts_controller.rb b/app/controllers/broadcasts_controller.rb
index 072498b..af38f6d 100644
--- a/app/controllers/broadcasts_controller.rb
+++ b/app/controllers/broadcasts_controller.rb
@@ -26,7 +26,7 @@ class BroadcastsController < ApplicationController
end
def show
- @conference_url = url_for [@broadcast.project, @broadcast, :zoom_meeting]
+ @conference_url = url_for [@broadcast.project, @broadcast, :conference_meeting]
@recordings = @broadcast.broadcast_recordings.order_by_recent.paginate(page: params[:page])
@files = @broadcast.files.order("created_at DESC").paginate(page: params[:files_page])
render layout: 'application'
@@ -72,7 +72,7 @@ class BroadcastsController < ApplicationController
end
def broadcast_params
- params.require(:broadcast).permit(:name, :shoot_location_time_zone, files: [])
+ params.require(:broadcast).permit(:name, :shoot_location_time_zone, :conference_option, files: [])
end
def set_project
diff --git a/app/controllers/callbacks_controller.rb b/app/controllers/callbacks_controller.rb
index 2f727ae..c08d9a4 100644
--- a/app/controllers/callbacks_controller.rb
+++ b/app/controllers/callbacks_controller.rb
@@ -5,6 +5,18 @@ class CallbacksController < ApplicationController
skip_before_action :verify_authenticity_token
def create
- render plain: params.inspect
+ uid = request.env['omniauth.auth'][:uid]
+ token_data = request.env['omniauth.auth'][:credentials]
+
+ current_user&.tap do |user|
+ user.microsoft_user_id = uid
+ user.microsoft_access_token = token_data.token
+ user.microsoft_refresh_token = token_data.refresh_token
+ user.microsoft_token_expires_at = token_data.expires_at # Expiration time is returned in seconds
+ user.save
+ end
+
+ redirect_to profile_path
end
+
end
diff --git a/app/controllers/conference_meetings_controller.rb b/app/controllers/conference_meetings_controller.rb
new file mode 100644
index 0000000..40b85dc
--- /dev/null
+++ b/app/controllers/conference_meetings_controller.rb
@@ -0,0 +1,45 @@
+class ConferenceMeetingsController < ApplicationController
+ require 'microsoft_graph'
+
+ def show
+ authorize broadcast = Broadcast.find(params[:broadcast_id])
+ case broadcast.conference_option
+ when 'zoom'
+ redirect_to broadcast.zoom_meeting_url
+ when 'ms_teams'
+ if broadcast.conference_join_url.nil?
+ begin
+ graph_api = MicrosoftGraph.new(
+ current_user,
+ ENV['AZURE_CLIENT_ID'],
+ ENV['AZURE_CLIENT_SECRET'],
+ ENV['AZURE_TENANT_ID'],
+ ENV['AZURE_SCOPES']
+ )
+
+ subject = "#{broadcast.name} Online Meeting"
+ teams_meeting = graph_api.create_teams_meeting(subject)
+ join_url = teams_meeting['joinUrl']
+
+ if join_url.present?
+ broadcast.conference_join_url = join_url
+ broadcast.save
+ else
+ raise StandardError, 'Failed to read teams meeting join URL'
+ end
+ rescue ActionController::InvalidAuthenticityToken => e
+ Rails.logger.error(e.message)
+ redirect_to project_broadcast_url(broadcast.project, broadcast), alert: t('.alerts.not_authenticated')
+ return
+ rescue StandardError => e
+ Rails.logger.error(e.message)
+ redirect_to project_broadcast_url(broadcast.project, broadcast), alert: t('.alerts.failed_to_join')
+ return
+ end
+ end
+ redirect_to broadcast.conference_join_url
+ else
+ redirect_to project_broadcast_url(broadcast.project, broadcast), alert: t('.alerts.unknown_conference_option')
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/helpers/broadcast_conferences_helper.rb b/app/helpers/broadcast_conferences_helper.rb
new file mode 100644
index 0000000..05ab097
--- /dev/null
+++ b/app/helpers/broadcast_conferences_helper.rb
@@ -0,0 +1,17 @@
+module BroadcastConferencesHelper
+ def options_for_conference_select
+ [
+ ['Zoom', 'zoom'],
+ ['MS Teams', 'ms_teams']
+ ]
+ end
+
+ def conference_option_name_from_key(key)
+ option = options_for_conference_select.find { |option| option[1] == key }
+ if option.present?
+ option.first
+ else
+ 'Unknown conference option'
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/views/broadcasts/_form.html.erb b/app/views/broadcasts/_form.html.erb
index 2efc4bd..b60665d 100644
--- a/app/views/broadcasts/_form.html.erb
+++ b/app/views/broadcasts/_form.html.erb
@@ -2,6 +2,7 @@
<%= bootstrap_form_with model: model, local: true do |form| %>
<%= form.text_field :name %>
+ <%= form.select :conference_option, options_for_conference_select, { label: t('.labels.conference_option') }, class: "form-control custom-select" %>
<%= form.time_zone_select(:shoot_location_time_zone, nil, label: "Time zone of shoot location") %>
diff --git a/app/views/broadcasts/show.html.erb b/app/views/broadcasts/show.html.erb
index d36fccc..c6a94c3 100644
--- a/app/views/broadcasts/show.html.erb
+++ b/app/views/broadcasts/show.html.erb
@@ -118,8 +118,8 @@
If you want to join the ZOOM meeting dedicated to this broadcast, follow the link below.
- <%= link_to 'Video Conference', @conference_url, class: 'btn btn-primary btn-block', target: '_blank' %>
+ <%= "If you want to join the #{conference_option_name_from_key(@broadcast.conference_option)} meeting dedicated to this broadcast, follow the link below." %>
+ <%= link_to 'Video Conference', @conference_url, class: "btn btn-primary btn-block", target: '_blank' %>
params[:active_tab] == 'recordings') %>" id="recordings">
diff --git a/app/views/profiles/show.html.erb b/app/views/profiles/show.html.erb
index 2f4775b..d677cab 100644
--- a/app/views/profiles/show.html.erb
+++ b/app/views/profiles/show.html.erb
@@ -17,7 +17,7 @@
<%= @user.role_for(Current.account).to_s.titleize %>
<% end %>
- <%= link_to 'Auth to Microsoft', '/auth/microsoft_graph', class: "btn btn-primary" %>
+ <%= link_to 'Auth to Microsoft', '/auth/azure_ad', method: :post, class: "btn btn-primary" %>
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
index d85c6e7..07e6abc 100644
--- a/config/initializers/omniauth.rb
+++ b/config/initializers/omniauth.rb
@@ -1,20 +1,17 @@
-ENV['AZURE_CLIENT_ID'] = 'c45b93ae-ef07-415d-b13a-ab566b877c1c'
-ENV['AZURE_CLIENT_SECRET'] = 'XVboF2sRaS_H2oK6I9R56.A_exnRhiv~Xt'
-ENV['AZURE_TENANT_ID'] = '1e33d1c7-dfb4-4df1-86da-62770313bcb0'
-ENV['AZURE_EXTENSIONS'] = ''
-
-# Rails.application.config.middleware.use OmniAuth::Builder do
-# provider :microsoft_graph,{
-# client_id: ENV['AZURE_CLIENT_ID'],
-# client_secret: ENV['AZURE_CLIENT_SECRET'],
-# tenant_id: ENV['AZURE_TENANT_ID'],
-# extensions: ENV['AZURE_EXTENSIONS'],
-# redirect_uri: 'https://517e57c6cd6c.ngrok.io/auth/microsoft_graph/callback',
-# scope: 'openid email profile User.Read'
-# }
-# end
+require 'azure_ad'
Rails.application.config.middleware.use OmniAuth::Builder do
- provider :microsoft_graph, ENV['AZURE_CLIENT_ID'], ENV['AZURE_CLIENT_SECRET'], scope: 'openid email profile User.Read'
+ provider :azure_ad,
+ client_id: ENV['AZURE_CLIENT_ID'],
+ client_secret: ENV['AZURE_CLIENT_SECRET'],
+ redirect_uri: ENV['AZURE_REDIRECT_URI'],
+ client_options: {
+ token_url: "#{ENV['AZURE_TENANT_ID']}/oauth2/v2.0/token",
+ authorize_url: "#{ENV['AZURE_TENANT_ID']}/oauth2/v2.0/authorize"
+ },
+ scope: ENV['AZURE_SCOPES']
end
+# Rails.application.config.middleware.use OmniAuth::Builder do
+# provider :microsoft_graph, ENV['AZURE_CLIENT_ID'], ENV['AZURE_CLIENT_SECRET'], scope: 'openid email profile User.Read'
+# end
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 46f0db3..af6396a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -262,6 +262,9 @@ en:
stream_multiple_cameras: Stream multiple cameras at one time
update:
reset_notice: The Share URL has been reset, and the previous URL will no longer work. Please click "Copy URL" and share it again with those who you want to have access to this live stream
+ form:
+ labels:
+ conference_option: Conference Option
bulk_taggings:
new_bulk_tag_modal:
submit: Add
@@ -1554,3 +1557,9 @@ en:
edit: Edit
report: Report
generating: Generating...
+ conference_meetings:
+ show:
+ alerts:
+ not_authenticated: You are not authenticated via Microsoft, please authenticate and try again
+ failed_to_join: Failed to join conference
+ unknown_conference_option: Unknown conference option
diff --git a/config/locales/es.yml b/config/locales/es.yml
index db3048b..9f9c267 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -120,6 +120,9 @@ es:
stream_multiple_cameras: Stream multiple cameras at one time
update:
reset_notice: The Share URL has been reset, and the previous URL will no longer work. Please click "Copy URL" and share it again with those who you want to have access to this live stream
+ form:
+ labels:
+ conference_option: Conference Option (ES)
contract_templates:
blank_contracts:
create:
@@ -630,3 +633,11 @@ es:
production_elements_logs: Production Elements Logs, and more (ES)
reduces_labor_cost: Reduces labor costs (ES)
simplifies_cue_sheets: Simplifies Music Cue Sheets, Graphic Cue Sheets (ES)
+ conference_meetings:
+ show:
+ alert:
+ not_authenticated: ""
+ alerts:
+ not_authenticated: You are not authenticated via Microsoft, please authenticate and try again (ES)
+ failed_to_join: Failed to join conference (ES)
+ unknown_conference_option: Unknown conference option (ES)
diff --git a/config/routes.rb b/config/routes.rb
index e18fa00..7dcfb5e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -4,7 +4,7 @@ require 'sidekiq/web'
Rails.application.routes.draw do
AVAILABLE_LOCALES_REGEX = /#{I18n.available_locales.join("|")}/.freeze
- get 'auth/microsoft_graph/callback', to: 'callbacks#create'
+ get 'auth/azure_ad/callback', to: 'callbacks#create'
concern :confirmable do
resources :video_release_confirmations, only: [:new, :create, :destroy]
@@ -101,7 +101,7 @@ Rails.application.routes.draw do
member do
delete :destroy_file
end
- resource :zoom_meeting, only: [:show]
+ resource :conference_meeting, only: [:show]
end
resources :directories, except: [:index] do
member do
diff --git a/db/migrate/20200810140331_add_microsoft_tokens_to_users.rb b/db/migrate/20200810140331_add_microsoft_tokens_to_users.rb
new file mode 100644
index 0000000..c58be13
--- /dev/null
+++ b/db/migrate/20200810140331_add_microsoft_tokens_to_users.rb
@@ -0,0 +1,7 @@
+class AddMicrosoftTokensToUsers < ActiveRecord::Migration[6.0]
+ def change
+ add_column :users, :microsoft_access_token, :string
+ add_column :users, :microsoft_refresh_token, :string
+ add_column :users, :microsoft_token_expires_at, :integer
+ end
+end
\ No newline at end of file
diff --git a/db/migrate/20200812161119_add_microsoft_user_id_to_users.rb b/db/migrate/20200812161119_add_microsoft_user_id_to_users.rb
new file mode 100644
index 0000000..1ae14f6
--- /dev/null
+++ b/db/migrate/20200812161119_add_microsoft_user_id_to_users.rb
@@ -0,0 +1,5 @@
+class AddMicrosoftUserIdToUsers < ActiveRecord::Migration[6.0]
+ def change
+ add_column :users, :microsoft_user_id, :string
+ end
+end
\ No newline at end of file
diff --git a/db/migrate/20200817233053_add_conference_details_to_broadcasts.rb b/db/migrate/20200817233053_add_conference_details_to_broadcasts.rb
new file mode 100644
index 0000000..72a5d9e
--- /dev/null
+++ b/db/migrate/20200817233053_add_conference_details_to_broadcasts.rb
@@ -0,0 +1,6 @@
+class AddConferenceDetailsToBroadcasts < ActiveRecord::Migration[6.0]
+ def change
+ add_column :broadcasts, :conference_option, :string
+ add_column :broadcasts, :conference_join_url, :string
+ end
+end
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index fce0e15..866d2fa 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -562,7 +562,9 @@ CREATE TABLE public.broadcasts (
token character varying,
streamer_status integer DEFAULT 0,
shoot_location_time_zone character varying DEFAULT 'UTC'::character varying,
- full_live_stream_playback_uid character varying
+ full_live_stream_playback_uid character varying,
+ conference_option character varying,
+ conference_join_url character varying
);
@@ -1755,7 +1757,11 @@ CREATE TABLE public.users (
remember_created_at timestamp without time zone,
first_name character varying,
last_name character varying,
- time_zone character varying DEFAULT 'UTC'::character varying NOT NULL
+ time_zone character varying DEFAULT 'UTC'::character varying NOT NULL,
+ microsoft_access_token character varying,
+ microsoft_refresh_token character varying,
+ microsoft_token_expires_at integer,
+ microsoft_user_id character varying
);
@@ -3966,6 +3972,9 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200720051634'),
('20200720131309'),
('20200721140821'),
-('20200725231419');
+('20200725231419'),
+('20200810140331'),
+('20200812161119'),
+('20200817233053');
diff --git a/lib/azure_ad.rb b/lib/azure_ad.rb
new file mode 100644
index 0000000..b79a410
--- /dev/null
+++ b/lib/azure_ad.rb
@@ -0,0 +1,124 @@
+require 'omniauth-oauth2'
+
+# This file is from omniauth-microsoft_graph lib (not installed)
+# It is modified to make auth work
+
+module OmniAuth
+ module Strategies
+ class AzureAd < OmniAuth::Strategies::OAuth2
+ BASE_SCOPE_URL = 'https://graph.microsoft.com/'
+ BASE_SCOPES = %w[offline_access openid email profile].freeze
+ DEFAULT_SCOPE = 'offline_access openid email profile User.Read'.freeze
+
+ option :name, :azure_ad
+
+ option :client_options,
+ site: 'https://login.microsoftonline.com/'
+
+ option :authorize_options, %i[state callback_url scope response_mode]
+
+ option :token_params, {}
+
+ option :scope, DEFAULT_SCOPE
+ option :authorized_client_ids, []
+
+ uid { raw_info["id"] }
+
+ info do
+ {
+ # 'email' => raw_info["mail"],
+ # 'first_name' => raw_info["givenName"],
+ # 'last_name' => raw_info["surname"],
+ # 'name' => [raw_info["givenName"], raw_info["surname"]].join(' '),
+ # 'nickname' => raw_info["displayName"],
+ }
+ end
+
+ extra do
+ {
+ # 'raw_info' => raw_info,
+ # 'params' => access_token.params,
+ # 'aud' => options.client_id
+ }
+ end
+
+ def authorize_params
+ super.tap do |params|
+ options[:authorize_options].each do |k|
+ params[k] = request.params[k.to_s] unless [nil, ''].include?(request.params[k.to_s])
+ end
+
+ params[:scope] = get_scope(params)
+
+ session['omniauth.state'] = params[:state] if params[:state]
+ end
+ end
+
+ def raw_info
+ @raw_info ||= access_token.get('https://graph.microsoft.com/v1.0/me').parsed
+ end
+
+ def callback_url
+ options[:callback_url] || full_host + script_name + callback_path
+ end
+
+ def custom_build_access_token
+ token_response = get_access_token(request)
+ session[:microsoft_graph_api_token] = token_response.token
+ token_response
+ end
+
+ alias build_access_token custom_build_access_token
+
+ private
+
+ def get_access_token(request)
+ verifier = request.params['code']
+ redirect_uri = request.params['redirect_uri'] || request.params['callback_url']
+ if verifier && request.xhr?
+ client_get_token(verifier, redirect_uri || '/auth/azure_ad/callback')
+ elsif verifier
+ client_get_token(verifier, redirect_uri || callback_url)
+ elsif verify_token(request.params['access_token'])
+ ::OAuth2::AccessToken.from_hash(client, request.params.dup)
+ elsif request.content_type =~ /json/i
+ begin
+ body = JSON.parse(request.body.read)
+ request.body.rewind # rewind request body for downstream middlewares
+ verifier = body && body['code']
+ client_get_token(verifier, '/auth/azure_ad/callback') if verifier
+ rescue JSON::ParserError => e
+ warn "[omniauth google-oauth2] JSON parse error=#{e}"
+ end
+ end
+ end
+
+ def client_get_token(verifier, redirect_uri)
+ client.auth_code.get_token(verifier, get_token_options(redirect_uri), get_token_params)
+ end
+
+ def get_token_params
+ deep_symbolize(options.auth_token_params || {})
+ end
+
+ def get_token_options(redirect_uri = '')
+ { redirect_uri: redirect_uri }.merge(token_params.to_hash(symbolize_keys: true))
+ end
+
+ def get_scope(params)
+ raw_scope = params[:scope] || DEFAULT_SCOPE
+ scope_list = raw_scope.split(' ').map { |item| item.split(',') }.flatten
+ scope_list.map! { |s| s =~ %r{^https?://} || BASE_SCOPES.include?(s) ? s : "#{BASE_SCOPE_URL}#{s}" }
+ scope_list.join(' ')
+ end
+
+ def verify_token(access_token)
+ return false unless access_token
+ # access_token.get('https://graph.microsoft.com/v1.0/me').parsed
+ raw_response = client.request(:get, 'https://graph.microsoft.com/v1.0/me',
+ params: { access_token: access_token }).parsed
+ (raw_response['aud'] == options.client_id) || options.authorized_client_ids.include?(raw_response['aud'])
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/microsoft_graph.rb b/lib/microsoft_graph.rb
new file mode 100644
index 0000000..27531cc
--- /dev/null
+++ b/lib/microsoft_graph.rb
@@ -0,0 +1,107 @@
+require 'httparty'
+
+class MicrosoftGraph
+ BASE_URL = 'https://graph.microsoft.com/v1.0'.freeze
+
+ def initialize(current_user, client_id, client_secret, tenant_id, scopes)
+ @current_user = current_user
+ @uid = current_user.microsoft_user_id
+ @token = current_user.microsoft_access_token
+ @refresh_token = current_user.microsoft_refresh_token
+ @token_expires_at = current_user.microsoft_token_expires_at
+
+ @client_id = client_id
+ @client_secret = client_secret
+ @tenant_id = tenant_id
+ @scopes = scopes
+ end
+
+ def create_teams_meeting(subject)
+ if @refresh_token.nil? || @token_expires_at.nil?
+ raise ActionController::InvalidAuthenticityToken, 'Missing refresh token / token expiration'
+ return
+ end
+
+ # Obtain new token if token is expired or will expire in less than 5 minutes
+ if 5.minutes.from_now.to_i > @token_expires_at.seconds
+ refresh_access_token
+ end
+
+ if @token.nil?
+ raise ActionController::InvalidAuthenticityToken, 'Missing access token'
+ return
+ end
+
+ response = HTTParty.post(
+ "#{BASE_URL}/me/onlineMeetings",
+ body: {
+ subject: subject,
+ participants: {
+ organizer: {
+ identity: {
+ user: {
+ id: @uid
+ }
+ }
+ }
+ }
+ }.to_json,
+ headers: {
+ Authorization: "Bearer #{@token}",
+ 'Content-Type': 'application/json'
+ }
+ )
+
+ raise StandardError, 'Authenticated user does not have a permission to create Teams Online Meeting' if response.code == 403
+
+ if response.code != 201
+ Rails.logger.error('[Microsoft Graph Error]')
+ Rails.logger.error(response.inspect)
+ raise StandardError, "Failed to create teams meeting [#{response.code}]"
+ else
+ JSON.parse(response.body)
+ end
+ end
+
+ private
+
+ def refresh_token_url
+ "https://login.microsoftonline.com/#{@tenant_id}/oauth2/v2.0/token"
+ end
+
+ def refresh_access_token
+ response = HTTParty.post(refresh_token_url,
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: {
+ client_id: @client_id,
+ client_secret: @client_secret,
+ refresh_token: @refresh_token,
+ grant_type: 'refresh_token',
+ scope: @scopes
+ })
+
+ if response.code != 200
+ Rails.logger.error '[Microsoft Graph Error] Failed to obtain new access token using refresh token'
+ Rails.logger.error(response.inspect)
+ raise StandardError, 'Failed to obtain new access token'
+ end
+
+ parsed_response = JSON.parse(response.body)
+
+ new_access_token = parsed_response['access_token']
+ new_refresh_token = parsed_response['refresh_token']
+ token_expires_in = parsed_response['expires_in'] # For how long access token is valid (in seconds)
+ token_new_expiration_time = Time.now.to_i + token_expires_in
+
+ @current_user.microsoft_access_token = new_access_token
+ @current_user.microsoft_refresh_token = new_refresh_token
+ @current_user.microsoft_token_expires_at = token_new_expiration_time
+ @current_user.save!
+
+ @token = new_access_token
+ @refresh_token = new_refresh_token
+ @token_expires_at = token_new_expiration_time
+ end
+end
\ No newline at end of file
diff --git a/spec/controllers/broadcasts_controller_spec.rb b/spec/controllers/broadcasts_controller_spec.rb
index ed37ad2..072ad00 100644
--- a/spec/controllers/broadcasts_controller_spec.rb
+++ b/spec/controllers/broadcasts_controller_spec.rb
@@ -131,16 +131,25 @@ RSpec.describe BroadcastsController, type: :controller do
expect(response.body).to have_xpath "//input[@readonly][@value='#{broadcast_url(broadcast.token)}']"
end
- it "displays zoom meeting button" do
+ it "displays zoom meeting button for zoom conference option" do
get :show, params: { project_id: project.id, id: broadcast.id }
- expect(response.body).to have_link("Video Conference", href: project_broadcast_zoom_meeting_url(project, broadcast))
+ expect(response.body).to have_content 'Zoom'
+ expect(response.body).to have_link("Video Conference", href: project_broadcast_conference_meeting_url(project, broadcast))
+ end
+
+ it "displays microsoft teams meeting button for MS Teams conference option" do
+ ms_teams_broadcast = create(:broadcast, :ms_teams_conference, project: project )
+ get :show, params: { project_id: project.id, id: ms_teams_broadcast.id }
+
+ expect(response.body).to have_content 'MS Teams'
+ expect(response.body).to have_link 'Video Conference', href: project_broadcast_conference_meeting_url(project, ms_teams_broadcast)
end
it "assigns required variables" do
get :show, params: { project_id: project.id, id: broadcast.id }
- expect(assigns(:conference_url)).to eq project_broadcast_zoom_meeting_url(project, broadcast)
+ expect(assigns(:conference_url)).to eq project_broadcast_conference_meeting_url(project, broadcast)
expect(assigns(:broadcast)).to eq broadcast
end
diff --git a/spec/controllers/conference_meetings_controller_spec.rb b/spec/controllers/conference_meetings_controller_spec.rb
new file mode 100644
index 0000000..9b9167a
--- /dev/null
+++ b/spec/controllers/conference_meetings_controller_spec.rb
@@ -0,0 +1,76 @@
+require 'rails_helper'
+
+RSpec.describe ConferenceMeetingsController, type: :controller do
+ let(:user) { create(:user) }
+ let(:account) { user.primary_account }
+ let(:project) { create(:project, account: user.primary_account) }
+ let(:broadcast) { create(:broadcast, name: "Broadcast", project: project) }
+ let(:ms_teams_broadcast) { create(:broadcast, :ms_teams_conference, project: project) }
+ let(:unknown_option_broadcast) { create(:broadcast, project: project, conference_option: 'google') }
+
+ let(:meeting_start_url) { "http://meeting_start_url" }
+ let(:meeting_hash) { HashWithIndifferentAccess.new(start_url: meeting_start_url) }
+ let(:user_create_response) { {"id" => "new_host_id"} }
+ let(:roles_assign_response) { {"ids" => ["new_host_id"]} }
+ let(:roles_list_response) { {"roles" => [{"name" => "directme-host"}]} }
+
+ before :each do
+ allow_any_instance_of(ZoomGateway).to receive(:find_meeting).and_return(meeting_hash)
+ allow_any_instance_of(ZoomGateway).to receive(:create_meeting).and_return("meeting_id")
+ allow_any_instance_of(ZoomGateway).to receive(:create_host).and_return("host_id")
+ allow(MuxLiveStream).to receive(:new).and_return OpenStruct.new(id: 'id', key: 'key', playback_id: 'playback_id')
+ end
+
+ describe "#show" do
+ before { sign_in user }
+
+ it "redirects to meeting start url with Zoom conference option" do
+ get :show, params: { project_id: project.id, broadcast_id: broadcast.id }
+ expect(response).to redirect_to(meeting_start_url)
+ end
+
+ it "redirects to the broadcast show page with alert if user is not authenticated via microsoft and tries to create MS Teams meeting" do
+ get :show, params: { project_id: project.id, broadcast_id: ms_teams_broadcast.id }
+ expect(response).to redirect_to project_broadcast_path(project, ms_teams_broadcast)
+ expect(flash.alert).to eq not_authenticated_alert
+ end
+
+ it "redirects to the broadcast show page with alert if user is authenticated via microsoft and tries to create MS Teams meeting but Graph API fails to create meeting" do
+ allow_any_instance_of(MicrosoftGraph).to receive(:create_teams_meeting).and_return(nil)
+ get :show, params: { project_id: project.id, broadcast_id: ms_teams_broadcast.id }
+ expect(response).to redirect_to project_broadcast_path(project, ms_teams_broadcast)
+ expect(flash.alert).to eq failed_to_join_alert
+ end
+
+ it "redirects to the broadcast show page with alert if conference option is not reckognized" do
+ get :show, params: { project_id: project.id, broadcast_id: unknown_option_broadcast.id }
+ expect(response).to redirect_to project_broadcast_path(project, unknown_option_broadcast)
+ expect(flash.alert).to eq unknown_conference_option_alert
+ end
+
+ it "redirects to meeting start url with MS Teams conference option" do
+ new_ms_teams_meeting = JSON.parse({
+ joinUrl: meeting_start_url
+ }.to_json)
+
+ allow_any_instance_of(MicrosoftGraph).to receive(:create_teams_meeting).and_return(new_ms_teams_meeting)
+
+ get :show, params: { project_id: project.id, broadcast_id: ms_teams_broadcast.id }
+ expect(response).to redirect_to(meeting_start_url)
+ end
+ end
+
+ private
+
+ def not_authenticated_alert
+ t 'conference_meetings.show.alerts.not_authenticated'
+ end
+
+ def failed_to_join_alert
+ t 'conference_meetings.show.alerts.failed_to_join'
+ end
+
+ def unknown_conference_option_alert
+ t 'conference_meetings.show.alerts.unknown_conference_option'
+ end
+end
diff --git a/spec/controllers/zoom_meetings_controller_spec.rb b/spec/controllers/zoom_meetings_controller_spec.rb
deleted file mode 100644
index ca1ab04..0000000
--- a/spec/controllers/zoom_meetings_controller_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe ZoomMeetingsController, type: :controller do
- let(:user) { create(:user) }
- let(:account) { user.primary_account }
- let(:project) { create(:project, account: user.primary_account) }
- let(:broadcast) { create(:broadcast, name: "Broadcast", project: project) }
-
- let(:meeting_start_url) { "http://meeting_start_url" }
- let(:meeting_hash) { HashWithIndifferentAccess.new(start_url: meeting_start_url) }
- let(:user_create_response) { {"id" => "new_host_id"} }
- let(:roles_assign_response) { {"ids" => ["new_host_id"]} }
- let(:roles_list_response) { {"roles" => [{"name" => "directme-host"}]} }
-
- before :each do
- allow_any_instance_of(ZoomGateway).to receive(:find_meeting).and_return(meeting_hash)
- allow_any_instance_of(ZoomGateway).to receive(:create_meeting).and_return("meeting_id")
- allow_any_instance_of(ZoomGateway).to receive(:create_host).and_return("host_id")
- allow(MuxLiveStream).to receive(:new).and_return OpenStruct.new(id: 'id', key: 'key', playback_id: 'playback_id')
- end
-
- describe "#show" do
- before { sign_in user }
-
- it "redirects to meeting start url" do
- get :show, params: { project_id: project.id, broadcast_id: broadcast.id }
- expect(response).to redirect_to(meeting_start_url)
- end
- end
-end
diff --git a/spec/factories/broadcasts.rb b/spec/factories/broadcasts.rb
index 4148242..33da96c 100644
--- a/spec/factories/broadcasts.rb
+++ b/spec/factories/broadcasts.rb
@@ -2,11 +2,16 @@ FactoryBot.define do
factory :broadcast do
association :project
name "My Live Stream"
+ conference_option "zoom"
transient do
skip_create_callback false
end
+ trait :ms_teams_conference do
+ conference_option "ms_teams"
+ end
+
trait :with_stream do
stream_uid "mux_stream"
stream_key "mux_key"
diff --git a/spec/features/user_managing_broadcasts_spec.rb b/spec/features/user_managing_broadcasts_spec.rb
index b4acf4e..6e2fa6a 100644
--- a/spec/features/user_managing_broadcasts_spec.rb
+++ b/spec/features/user_managing_broadcasts_spec.rb
@@ -24,6 +24,7 @@ feature 'User managing broadcasts' do
by 'filling out the form' do
fill_in broadcast_name_field, with: 'My Broadcast'
+ select_conference_option('Zoom')
select_time_zone("New Delhi")
end
@@ -267,6 +268,12 @@ feature 'User managing broadcasts' do
end
end
+ def select_conference_option(value)
+ if value.present?
+ select value, from: "broadcast[conference_option]"
+ end
+ end
+
def click_checkboxes
all('input[type="checkbox"]')[0].click
all('input[type="checkbox"]')[1].click