Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/bundler/puma-6.3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
jfederico authored Feb 21, 2024
2 parents 70c1fe0 + 7f0cbde commit 0208f2f
Show file tree
Hide file tree
Showing 13 changed files with 329 additions and 20 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ Metrics/PerceivedComplexity:
Exclude:
- app/models/recording.rb
- app/models/server.rb
- app/models/tenant.rb
- lib/server_sync.rb

# Avoid classes longer than 100 lines of code.
Expand Down Expand Up @@ -164,6 +165,7 @@ Metrics/CyclomaticComplexity:
Exclude:
- app/models/recording.rb
- app/models/server.rb
- app/models/tenant.rb
- lib/server_sync.rb

# Checks for method parameter names that contain capital letters, end in numbers, or do not meet a minimal length.
Expand Down
5 changes: 5 additions & 0 deletions app/controllers/bigbluebutton_api_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ def create

params[:voiceBridge] = meeting.voice_bridge

if @tenant&.lrs_endpoint.present?
lrs_payload = LrsPayloadService.new(tenant: @tenant, secret: server.secret).call
params[:'meta_secret-lrs-payload'] = lrs_payload if lrs_payload.present?
end

logger.debug("Creating meeting #{params[:meetingID]} on BigBlueButton server #{server.id}")
params_hash = params

Expand Down
9 changes: 2 additions & 7 deletions app/models/meeting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,8 @@ def self.all(tenant_id = nil)
hash = redis.hgetall(key(id))
next if hash.blank?

if tenant_id.present?
# Only fetch meetings for particular Tenant
next if tenant_id != hash['tenant_id']
elsif hash['tenant_id'].present?
next
end
# Only fetch meetings without Tenant
# If tenant_id given, only fetch meetings for particular Tenant
next if tenant_id.present? && hash['tenant_id'] != tenant_id

hash[:id] = id
meetings << new.init_with_attributes(hash)
Expand Down
19 changes: 18 additions & 1 deletion app/models/tenant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
class Tenant < ApplicationRedisRecord
SECRETS_SEPARATOR = ':'

define_attribute_methods :id, :name, :secrets
define_attribute_methods :id, :name, :secrets, :lrs_endpoint, :lrs_basic_token, :kc_token_url, :kc_client_id, :kc_client_secret, :kc_username,
:kc_password

# Unique ID for this tenant
application_redis_attr :id
Expand All @@ -14,6 +15,15 @@ class Tenant < ApplicationRedisRecord
# Shared secrets for making API requests for this tenant (: separated)
application_redis_attr :secrets

# Custom LRS work
application_redis_attr :lrs_endpoint
application_redis_attr :lrs_basic_token
application_redis_attr :kc_token_url
application_redis_attr :kc_client_id
application_redis_attr :kc_client_secret
application_redis_attr :kc_username
application_redis_attr :kc_password

def save!
with_connection do |redis|
raise RecordNotSaved.new('Cannot update id field', self) if id_changed? && !@new_record
Expand All @@ -34,6 +44,13 @@ def save!
pipeline.del(old_names_key) if !id_changed? && name_changed? # Delete the old name key if it's not a new record and the name was updated
pipeline.hset(id_key, 'name', name) if name_changed?
pipeline.hset(id_key, 'secrets', secrets) if secrets_changed?
pipeline.hset(id_key, 'lrs_endpoint', lrs_endpoint) if lrs_endpoint_changed?
pipeline.hset(id_key, 'lrs_basic_token', lrs_basic_token) if lrs_basic_token_changed?
pipeline.hset(id_key, 'kc_token_url', kc_token_url) if kc_token_url_changed?
pipeline.hset(id_key, 'kc_client_id', kc_client_id) if kc_client_id_changed?
pipeline.hset(id_key, 'kc_client_secret', kc_client_secret) if kc_client_secret_changed?
pipeline.hset(id_key, 'kc_username', kc_username) if kc_username_changed?
pipeline.hset(id_key, 'kc_password', kc_password) if kc_password_changed?
pipeline.sadd?('tenants', id) if id_changed?
end
end
Expand Down
74 changes: 74 additions & 0 deletions app/services/lrs_payload_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# frozen_string_literal: true

class LrsPayloadService
def initialize(tenant:, secret:)
@tenant = tenant
@secret = secret
end

def call
token = @tenant.kc_token_url.present? ? fetch_token_from_keycloak : @tenant.lrs_basic_token

if token.nil?
Rails.logger.warn("LRS Token not found")
return nil
end

lrs_payload = {
lrs_endpoint: @tenant.lrs_endpoint,
lrs_token: token
}

# Generate a random salt
salt = SecureRandom.random_bytes(8)

# Generate a key and initialization vector (IV) using PBKDF2 with SHA-256
key_iv = OpenSSL::PKCS5.pbkdf2_hmac(@secret, salt, 10_000, 48, OpenSSL::Digest.new('SHA256'))
key = key_iv[0, 32] # 32 bytes for the key
iv = key_iv[32, 16] # 16 bytes for the IV

# Encrypt the data using AES-256-CBC
cipher = OpenSSL::Cipher.new('AES-256-CBC')
cipher.encrypt
cipher.key = key
cipher.iv = iv

# Encrypt and Base64 encode the data
Base64.strict_encode64(Random.random_bytes(8) + salt + cipher.update(lrs_payload.to_json) + cipher.final)
rescue StandardError => e
Rails.logger.warn("Error #{e} when trying to compute LRS Payload")

nil
end

private

def fetch_token_from_keycloak
Rails.logger.debug { "Fetching LRS token from #{@tenant.kc_token_url}" }

url = URI.parse(@tenant.kc_token_url)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = (url.scheme == 'https')

payload = {
client_id: @tenant.kc_client_id,
client_secret: @tenant.kc_client_secret,
username: @tenant.kc_username,
password: @tenant.kc_password,
grant_type: 'password'
}

request = Net::HTTP::Post.new(url.path)
request.set_form_data(payload)

response = http.request(request)

if response.code.to_i != 200
Rails.logger.warn("Error #{response.message} when trying to fetch LRS Access Token")
return nil
end

parsed_response = JSON.parse(response.body)
parsed_response['access_token']
end
end
4 changes: 1 addition & 3 deletions bigbluebutton/scalelite_post_publish.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
end

puts("Transferring recording archive to #{spool_dir}")
system('rsync', '--verbose', '--protect-args', *extra_rsync_opts, archive_file, spool_dir) \
system('rsync', '--verbose', '--remove-source-files', '--protect-args', *extra_rsync_opts, archive_file, spool_dir) \
|| raise('Failed to transfer recording archive')

# Delete recording after transfer
Expand All @@ -85,6 +85,4 @@
File.write("#{recording_dir}/status/published/#{meeting_id}-sender.done", "Published #{meeting_id}")

puts('Recording transferring to Scalelite ends')
ensure
FileUtils.rm_f(archive_file)
end
18 changes: 17 additions & 1 deletion lib/tasks/poll.rake
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,26 @@ namespace :poll do
next if server.increment_unhealthy < Rails.configuration.x.server_unhealthy_threshold

Rails.logger.warn("Server id=#{server.id} is unhealthy. Panicking and setting offline...")
Rake::Task['servers:panic'].invoke(server.id, true) # Panic server to clear meetings

meetings = Meeting.all.select { |m| m.server_id == server.id }
meetings.each do |meeting|
puts("Clearing Meeting id=#{meeting.id}")
moderator_pw = meeting.try(:moderator_pw)
meeting.destroy!
get_post_req(encode_bbb_uri('end', server.url, server.secret, meetingID: meeting.id, password: moderator_pw))
rescue ApplicationRedisRecord::RecordNotDestroyed => e
raise("ERROR: Could not destroy meeting id=#{meeting.id}: #{e}")
rescue StandardError => e
puts("WARNING: Could not end meeting id=#{meeting.id}: #{e}")
end

server.reset_counters
server.load = nil
server.online = false
server.meetings = 0
server.users = 0
server.largest_meeting = 0
server.videos = 0
ensure
begin
server.save!
Expand Down
13 changes: 13 additions & 0 deletions lib/tasks/servers.rake
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ namespace :servers do
puts('Error: Please input at least a URL and a secret!')
exit(1)
end

unless args.url.start_with?('http://', 'https://')
puts('Error: Server URL must start with http:// or https://')
exit(1)
end

tmp_load_multiplier = 1.0
unless args.load_multiplier.nil?
tmp_load_multiplier = args.load_multiplier.to_d
Expand Down Expand Up @@ -56,6 +62,7 @@ namespace :servers do
puts('OK')
rescue ApplicationRedisRecord::RecordNotFound
puts("ERROR: No server found with id: #{args.id}")
exit(1)
end

desc 'Remove a BigBlueButton server'
Expand All @@ -65,6 +72,7 @@ namespace :servers do
puts('OK')
rescue ApplicationRedisRecord::RecordNotFound
puts("ERROR: No server found with id: #{args.id}")
exit(1)
end

desc 'Mark a BigBlueButton server as available for scheduling new meetings'
Expand All @@ -75,6 +83,7 @@ namespace :servers do
puts('OK')
rescue ApplicationRedisRecord::RecordNotFound
puts("ERROR: No server found with id: #{args.id}")
exit(1)
end

desc 'Mark a BigBlueButton server as cordoned to stop scheduling new meetings but consider for
Expand All @@ -86,6 +95,7 @@ namespace :servers do
puts('OK')
rescue ApplicationRedisRecord::RecordNotFound
puts("ERROR: No server found with id: #{args.id}")
exit(1)
end

desc 'Mark a BigBlueButton server as unavailable to stop scheduling new meetings'
Expand Down Expand Up @@ -117,6 +127,7 @@ namespace :servers do
puts('OK')
rescue ApplicationRedisRecord::RecordNotFound
puts("ERROR: No server found with id: #{args.id}")
exit(1)
end

desc 'Mark a BigBlueButton server as unavailable, and clear all meetings from it'
Expand All @@ -142,6 +153,7 @@ namespace :servers do
puts('OK')
rescue ApplicationRedisRecord::RecordNotFound
puts("ERROR: No server found with id: #{args.id}")
exit(1)
end

desc 'Set the load-multiplier of a BigBlueButton server'
Expand All @@ -160,6 +172,7 @@ namespace :servers do
puts('OK')
rescue ApplicationRedisRecord::RecordNotFound
puts("ERROR: No server found with id: #{args.id}")
exit(1)
end

desc 'Adds multiple BigBlueButton servers defined in a YAML file passed as an argument'
Expand Down
61 changes: 61 additions & 0 deletions lib/tasks/tenants.rake
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ task tenants: :environment do |_t, _args|
puts("id: #{tenant.id}")
puts("\tname: #{tenant.name}")
puts("\tsecrets: #{tenant.secrets}")
puts("\tlrs_endpoint: #{tenant.lrs_endpoint}") if tenant.lrs_endpoint.present?
puts("\tlrs_basic_token: #{tenant.lrs_basic_token}") if tenant.lrs_basic_token.present?
puts("\tkc_token_url: #{tenant.kc_token_url}") if tenant.kc_token_url.present?
puts("\tkc_client_id: #{tenant.kc_client_id}") if tenant.kc_client_id.present?
puts("\tkc_client_secret: #{tenant.kc_client_secret}") if tenant.kc_client_secret.present?
puts("\tkc_username: #{tenant.kc_username}") if tenant.kc_username.present?
puts("\tkc_password: #{tenant.kc_password}") if tenant.kc_password.present?
end
end

Expand Down Expand Up @@ -53,6 +60,60 @@ namespace :tenants do
tenant = Tenant.find(id)
tenant.name = name if name.present?
tenant.secrets = secrets if secrets.present?

tenant.save!

puts('OK')
puts("Updated Tenant id: #{tenant.id}")
end

desc 'Update an existing Tenants LRS credentials with basic authentication'
task :update_lrs_basic, [:id, :lrs_endpoint, :lrs_basic_token] => :environment do |_t, args|
check_multitenancy
id = args[:id]
lrs_endpoint = args[:lrs_endpoint]
lrs_basic_token = args[:lrs_basic_token]

if id.blank? || lrs_endpoint.blank? || lrs_basic_token.blank?
puts('Error: id, LRS_ENDPOINT, LRS_BASIC_TOKEN are required to update a Tenant')
exit(1)
end

tenant = Tenant.find(id)
tenant.lrs_endpoint = lrs_endpoint
tenant.lrs_basic_token = lrs_basic_token

tenant.save!

puts('OK')
puts("Updated Tenant id: #{tenant.id}")
end

desc 'Update an existing Tenants LRS credentials with Keycloak'
task :update_lrs_kc, [:id, :lrs_endpoint, :kc_token_url, :kc_client_id, :kc_client_secret, :kc_username, :kc_password] => :environment do |_t, args|
check_multitenancy
id = args[:id]
lrs_endpoint = args[:lrs_endpoint]
kc_token_url = args[:kc_token_url]
kc_client_id = args[:kc_client_id]
kc_client_secret = args[:kc_client_secret]
kc_username = args[:kc_username]
kc_password = args[:kc_password]

if id.blank? || lrs_endpoint.blank? || kc_token_url.blank? || kc_client_id.blank? ||
kc_client_secret.blank? || kc_username.blank? || kc_password.blank?
puts('Error: LRS_ENDPOINT, KC_TOKEN_URL, KC_CLIENT_ID, KC_CLIENT_SECRET, KC_USERNAME, KC_PASSWORD are required to update a Tenant')
exit(1)
end

tenant = Tenant.find(id)
tenant.lrs_endpoint = lrs_endpoint
tenant.kc_token_url = kc_token_url
tenant.kc_client_id = kc_client_id
tenant.kc_client_secret = kc_client_secret
tenant.kc_username = kc_username
tenant.kc_password = kc_password

tenant.save!

puts('OK')
Expand Down
7 changes: 7 additions & 0 deletions spec/factories/tenant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,12 @@
factory :tenant do
name { Faker::Creature::Animal.name }
secrets { "#{Faker::Crypto.sha256}:#{Faker::Crypto.sha512}" }
lrs_endpoint { nil }
lrs_basic_token { nil }
kc_token_url { nil }
kc_client_id { nil }
kc_client_secret { nil }
kc_username { nil }
kc_password { nil }
end
end
10 changes: 2 additions & 8 deletions spec/models/meeting_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,8 @@
context 'without tenant_id param' do
let(:meetings) { described_class.all }

it 'fetches correct nb of Meetings' do
expect(meetings.size).to eq 2
end

it 'fetches Meetings with correct tenant_id' do
meetings.each do |meeting|
expect(meeting.tenant_id).to be_nil
end
it 'fetches all Meetings' do
expect(meetings.size).to eq 7
end
end
end
Expand Down
Loading

0 comments on commit 0208f2f

Please sign in to comment.