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/app/controllers/broadcasts_controller.rb b/app/controllers/broadcasts_controller.rb index 072498b..3a6a1ba 100644 --- a/app/controllers/broadcasts_controller.rb +++ b/app/controllers/broadcasts_controller.rb @@ -26,7 +26,8 @@ class BroadcastsController < ApplicationController end def show - @conference_url = url_for [@broadcast.project, @broadcast, :zoom_meeting] + # @conference_url = url_for [@broadcast.project, @broadcast, :zoom_meeting] + @conference_url = url_for [@broadcast.project, @broadcast, :microsoft_teams_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' diff --git a/app/controllers/callbacks_controller.rb b/app/controllers/callbacks_controller.rb index b3c4989..c08d9a4 100644 --- a/app/controllers/callbacks_controller.rb +++ b/app/controllers/callbacks_controller.rb @@ -5,9 +5,11 @@ class CallbacksController < ApplicationController skip_before_action :verify_authenticity_token def create + 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 diff --git a/app/controllers/microsoft_teams_meetings_controller.rb b/app/controllers/microsoft_teams_meetings_controller.rb new file mode 100644 index 0000000..c86bfc2 --- /dev/null +++ b/app/controllers/microsoft_teams_meetings_controller.rb @@ -0,0 +1,17 @@ +class MicrosoftTeamsMeetingsController < ApplicationController + require 'microsoft_graph' + def show + authorize broadcast = Broadcast.find(params[:broadcast_id]) + + graph_api = MicrosoftGraph.new(current_user, ENV['AZURE_CLIENT_ID'], ENV['AZURE_CLIENT_SECRET'], ENV['AZURE_SCOPES']) + + meeting_start = DateTime.now + meeting_end = DateTime.now + 1.hour + subject = "Broadcast Meeting" + + + r = graph_api.create_teams_meeting(meeting_start, meeting_end, subject) + + render plain: r + end +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 32bf489..327a924 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -101,7 +101,8 @@ Rails.application.routes.draw do member do delete :destroy_file end - resource :zoom_meeting, only: [:show] + # resource :zoom_meeting, only: [:show] + resource :microsoft_teams_meeting, only: [:show] end resources :directories, except: [:index] do member do 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/structure.sql b/db/structure.sql index d5a9301..34371f4 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1758,7 +1758,8 @@ CREATE TABLE public.users ( 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_token_expires_at integer, + microsoft_user_id character varying ); @@ -3970,6 +3971,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20200720131309'), ('20200721140821'), ('20200725231419'), -('20200810140331'); +('20200810140331'), +('20200812161119'); diff --git a/lib/microsoft_graph.rb b/lib/microsoft_graph.rb new file mode 100644 index 0000000..040d22c --- /dev/null +++ b/lib/microsoft_graph.rb @@ -0,0 +1,83 @@ +require 'httparty' + +class MicrosoftGraph + BASE_URL = 'https://graph.microsoft.com/v1.0'.freeze + BETA_BASE_URL = 'https://graph.microsoft.com/beta'.freeze + # Documentation says that "common" can be used (instead of tenantID) to refresh access token + REFRESH_TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'.freeze + + def initialize(current_user, client_id, client_secret, scopes) + @current_user = current_user + @uid = current_user.microsoft_user_id + @token = current_user.microsoft_access_token + @refresh_token = current_user.microsoft_refresh_token + @expires_at = current_user.microsoft_token_expires_at + + @client_id = client_id + @client_secret = client_secret + @scopes = scopes + end + + def request(resource) + return false unless @token + + response = HTTParty.get( + "#{BASE_URL}/#{resource}", + headers: { + Authorization: "Bearer #{@token}" + } + ) + + if response.code != 200 + p '[Microsoft Graph API Error]' + p response.inspect + false + else + JSON.parse(response.body) + end + end + + def create_teams_meeting(start_date_time, end_date_time, subject) + # check if token expired and obtain new access_token using refresh token + + response = HTTParty.post( + "#{BETA_BASE_URL}/me/onlineMeetings", + body: { + startDateTime: start_date_time, + endDateTime: end_date_time, + subject: subject, + participants: { + organizer: { + identity: { + user: { + id: @uid + } + } + } + } + }, + headers: { + Authorization: "Bearer #{@token}" + } + ) + + if response.code != 201 + p '[Microsoft Graph API Error] Failed to create online meeting' + p response.inspect + else + JSON.parse(response.body) + end + end + + def refresh_token + response = HTTParty.post(REFRESH_TOKEN_URL, + body: { + client_id: @client_id, + client_secret: @client_secret, + refresh_token: @refresh_token, + grant_type: 'refresh_token', + scope: @scopes + }) + + end +end \ No newline at end of file