Skip to content

Commit 0208f2f

Browse files
authored
Merge branch 'master' into dependabot/bundler/puma-6.3.1
2 parents 70c1fe0 + 7f0cbde commit 0208f2f

File tree

13 files changed

+329
-20
lines changed

13 files changed

+329
-20
lines changed

.rubocop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ Metrics/PerceivedComplexity:
135135
Exclude:
136136
- app/models/recording.rb
137137
- app/models/server.rb
138+
- app/models/tenant.rb
138139
- lib/server_sync.rb
139140

140141
# Avoid classes longer than 100 lines of code.
@@ -164,6 +165,7 @@ Metrics/CyclomaticComplexity:
164165
Exclude:
165166
- app/models/recording.rb
166167
- app/models/server.rb
168+
- app/models/tenant.rb
167169
- lib/server_sync.rb
168170

169171
# Checks for method parameter names that contain capital letters, end in numbers, or do not meet a minimal length.

app/controllers/bigbluebutton_api_controller.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ def create
198198

199199
params[:voiceBridge] = meeting.voice_bridge
200200

201+
if @tenant&.lrs_endpoint.present?
202+
lrs_payload = LrsPayloadService.new(tenant: @tenant, secret: server.secret).call
203+
params[:'meta_secret-lrs-payload'] = lrs_payload if lrs_payload.present?
204+
end
205+
201206
logger.debug("Creating meeting #{params[:meetingID]} on BigBlueButton server #{server.id}")
202207
params_hash = params
203208

app/models/meeting.rb

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,8 @@ def self.all(tenant_id = nil)
277277
hash = redis.hgetall(key(id))
278278
next if hash.blank?
279279

280-
if tenant_id.present?
281-
# Only fetch meetings for particular Tenant
282-
next if tenant_id != hash['tenant_id']
283-
elsif hash['tenant_id'].present?
284-
next
285-
end
286-
# Only fetch meetings without Tenant
280+
# If tenant_id given, only fetch meetings for particular Tenant
281+
next if tenant_id.present? && hash['tenant_id'] != tenant_id
287282

288283
hash[:id] = id
289284
meetings << new.init_with_attributes(hash)

app/models/tenant.rb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
class Tenant < ApplicationRedisRecord
44
SECRETS_SEPARATOR = ':'
55

6-
define_attribute_methods :id, :name, :secrets
6+
define_attribute_methods :id, :name, :secrets, :lrs_endpoint, :lrs_basic_token, :kc_token_url, :kc_client_id, :kc_client_secret, :kc_username,
7+
:kc_password
78

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

18+
# Custom LRS work
19+
application_redis_attr :lrs_endpoint
20+
application_redis_attr :lrs_basic_token
21+
application_redis_attr :kc_token_url
22+
application_redis_attr :kc_client_id
23+
application_redis_attr :kc_client_secret
24+
application_redis_attr :kc_username
25+
application_redis_attr :kc_password
26+
1727
def save!
1828
with_connection do |redis|
1929
raise RecordNotSaved.new('Cannot update id field', self) if id_changed? && !@new_record
@@ -34,6 +44,13 @@ def save!
3444
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
3545
pipeline.hset(id_key, 'name', name) if name_changed?
3646
pipeline.hset(id_key, 'secrets', secrets) if secrets_changed?
47+
pipeline.hset(id_key, 'lrs_endpoint', lrs_endpoint) if lrs_endpoint_changed?
48+
pipeline.hset(id_key, 'lrs_basic_token', lrs_basic_token) if lrs_basic_token_changed?
49+
pipeline.hset(id_key, 'kc_token_url', kc_token_url) if kc_token_url_changed?
50+
pipeline.hset(id_key, 'kc_client_id', kc_client_id) if kc_client_id_changed?
51+
pipeline.hset(id_key, 'kc_client_secret', kc_client_secret) if kc_client_secret_changed?
52+
pipeline.hset(id_key, 'kc_username', kc_username) if kc_username_changed?
53+
pipeline.hset(id_key, 'kc_password', kc_password) if kc_password_changed?
3754
pipeline.sadd?('tenants', id) if id_changed?
3855
end
3956
end

app/services/lrs_payload_service.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
3+
class LrsPayloadService
4+
def initialize(tenant:, secret:)
5+
@tenant = tenant
6+
@secret = secret
7+
end
8+
9+
def call
10+
token = @tenant.kc_token_url.present? ? fetch_token_from_keycloak : @tenant.lrs_basic_token
11+
12+
if token.nil?
13+
Rails.logger.warn("LRS Token not found")
14+
return nil
15+
end
16+
17+
lrs_payload = {
18+
lrs_endpoint: @tenant.lrs_endpoint,
19+
lrs_token: token
20+
}
21+
22+
# Generate a random salt
23+
salt = SecureRandom.random_bytes(8)
24+
25+
# Generate a key and initialization vector (IV) using PBKDF2 with SHA-256
26+
key_iv = OpenSSL::PKCS5.pbkdf2_hmac(@secret, salt, 10_000, 48, OpenSSL::Digest.new('SHA256'))
27+
key = key_iv[0, 32] # 32 bytes for the key
28+
iv = key_iv[32, 16] # 16 bytes for the IV
29+
30+
# Encrypt the data using AES-256-CBC
31+
cipher = OpenSSL::Cipher.new('AES-256-CBC')
32+
cipher.encrypt
33+
cipher.key = key
34+
cipher.iv = iv
35+
36+
# Encrypt and Base64 encode the data
37+
Base64.strict_encode64(Random.random_bytes(8) + salt + cipher.update(lrs_payload.to_json) + cipher.final)
38+
rescue StandardError => e
39+
Rails.logger.warn("Error #{e} when trying to compute LRS Payload")
40+
41+
nil
42+
end
43+
44+
private
45+
46+
def fetch_token_from_keycloak
47+
Rails.logger.debug { "Fetching LRS token from #{@tenant.kc_token_url}" }
48+
49+
url = URI.parse(@tenant.kc_token_url)
50+
http = Net::HTTP.new(url.host, url.port)
51+
http.use_ssl = (url.scheme == 'https')
52+
53+
payload = {
54+
client_id: @tenant.kc_client_id,
55+
client_secret: @tenant.kc_client_secret,
56+
username: @tenant.kc_username,
57+
password: @tenant.kc_password,
58+
grant_type: 'password'
59+
}
60+
61+
request = Net::HTTP::Post.new(url.path)
62+
request.set_form_data(payload)
63+
64+
response = http.request(request)
65+
66+
if response.code.to_i != 200
67+
Rails.logger.warn("Error #{response.message} when trying to fetch LRS Access Token")
68+
return nil
69+
end
70+
71+
parsed_response = JSON.parse(response.body)
72+
parsed_response['access_token']
73+
end
74+
end

bigbluebutton/scalelite_post_publish.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
end
7373

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

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

8787
puts('Recording transferring to Scalelite ends')
88-
ensure
89-
FileUtils.rm_f(archive_file)
9088
end

lib/tasks/poll.rake

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,26 @@ namespace :poll do
9696
next if server.increment_unhealthy < Rails.configuration.x.server_unhealthy_threshold
9797

9898
Rails.logger.warn("Server id=#{server.id} is unhealthy. Panicking and setting offline...")
99-
Rake::Task['servers:panic'].invoke(server.id, true) # Panic server to clear meetings
99+
100+
meetings = Meeting.all.select { |m| m.server_id == server.id }
101+
meetings.each do |meeting|
102+
puts("Clearing Meeting id=#{meeting.id}")
103+
moderator_pw = meeting.try(:moderator_pw)
104+
meeting.destroy!
105+
get_post_req(encode_bbb_uri('end', server.url, server.secret, meetingID: meeting.id, password: moderator_pw))
106+
rescue ApplicationRedisRecord::RecordNotDestroyed => e
107+
raise("ERROR: Could not destroy meeting id=#{meeting.id}: #{e}")
108+
rescue StandardError => e
109+
puts("WARNING: Could not end meeting id=#{meeting.id}: #{e}")
110+
end
111+
100112
server.reset_counters
101113
server.load = nil
102114
server.online = false
115+
server.meetings = 0
116+
server.users = 0
117+
server.largest_meeting = 0
118+
server.videos = 0
103119
ensure
104120
begin
105121
server.save!

lib/tasks/servers.rake

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ namespace :servers do
2626
puts('Error: Please input at least a URL and a secret!')
2727
exit(1)
2828
end
29+
30+
unless args.url.start_with?('http://', 'https://')
31+
puts('Error: Server URL must start with http:// or https://')
32+
exit(1)
33+
end
34+
2935
tmp_load_multiplier = 1.0
3036
unless args.load_multiplier.nil?
3137
tmp_load_multiplier = args.load_multiplier.to_d
@@ -56,6 +62,7 @@ namespace :servers do
5662
puts('OK')
5763
rescue ApplicationRedisRecord::RecordNotFound
5864
puts("ERROR: No server found with id: #{args.id}")
65+
exit(1)
5966
end
6067

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

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

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

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

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

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

165178
desc 'Adds multiple BigBlueButton servers defined in a YAML file passed as an argument'

lib/tasks/tenants.rake

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ task tenants: :environment do |_t, _args|
1414
puts("id: #{tenant.id}")
1515
puts("\tname: #{tenant.name}")
1616
puts("\tsecrets: #{tenant.secrets}")
17+
puts("\tlrs_endpoint: #{tenant.lrs_endpoint}") if tenant.lrs_endpoint.present?
18+
puts("\tlrs_basic_token: #{tenant.lrs_basic_token}") if tenant.lrs_basic_token.present?
19+
puts("\tkc_token_url: #{tenant.kc_token_url}") if tenant.kc_token_url.present?
20+
puts("\tkc_client_id: #{tenant.kc_client_id}") if tenant.kc_client_id.present?
21+
puts("\tkc_client_secret: #{tenant.kc_client_secret}") if tenant.kc_client_secret.present?
22+
puts("\tkc_username: #{tenant.kc_username}") if tenant.kc_username.present?
23+
puts("\tkc_password: #{tenant.kc_password}") if tenant.kc_password.present?
1724
end
1825
end
1926

@@ -53,6 +60,60 @@ namespace :tenants do
5360
tenant = Tenant.find(id)
5461
tenant.name = name if name.present?
5562
tenant.secrets = secrets if secrets.present?
63+
64+
tenant.save!
65+
66+
puts('OK')
67+
puts("Updated Tenant id: #{tenant.id}")
68+
end
69+
70+
desc 'Update an existing Tenants LRS credentials with basic authentication'
71+
task :update_lrs_basic, [:id, :lrs_endpoint, :lrs_basic_token] => :environment do |_t, args|
72+
check_multitenancy
73+
id = args[:id]
74+
lrs_endpoint = args[:lrs_endpoint]
75+
lrs_basic_token = args[:lrs_basic_token]
76+
77+
if id.blank? || lrs_endpoint.blank? || lrs_basic_token.blank?
78+
puts('Error: id, LRS_ENDPOINT, LRS_BASIC_TOKEN are required to update a Tenant')
79+
exit(1)
80+
end
81+
82+
tenant = Tenant.find(id)
83+
tenant.lrs_endpoint = lrs_endpoint
84+
tenant.lrs_basic_token = lrs_basic_token
85+
86+
tenant.save!
87+
88+
puts('OK')
89+
puts("Updated Tenant id: #{tenant.id}")
90+
end
91+
92+
desc 'Update an existing Tenants LRS credentials with Keycloak'
93+
task :update_lrs_kc, [:id, :lrs_endpoint, :kc_token_url, :kc_client_id, :kc_client_secret, :kc_username, :kc_password] => :environment do |_t, args|
94+
check_multitenancy
95+
id = args[:id]
96+
lrs_endpoint = args[:lrs_endpoint]
97+
kc_token_url = args[:kc_token_url]
98+
kc_client_id = args[:kc_client_id]
99+
kc_client_secret = args[:kc_client_secret]
100+
kc_username = args[:kc_username]
101+
kc_password = args[:kc_password]
102+
103+
if id.blank? || lrs_endpoint.blank? || kc_token_url.blank? || kc_client_id.blank? ||
104+
kc_client_secret.blank? || kc_username.blank? || kc_password.blank?
105+
puts('Error: LRS_ENDPOINT, KC_TOKEN_URL, KC_CLIENT_ID, KC_CLIENT_SECRET, KC_USERNAME, KC_PASSWORD are required to update a Tenant')
106+
exit(1)
107+
end
108+
109+
tenant = Tenant.find(id)
110+
tenant.lrs_endpoint = lrs_endpoint
111+
tenant.kc_token_url = kc_token_url
112+
tenant.kc_client_id = kc_client_id
113+
tenant.kc_client_secret = kc_client_secret
114+
tenant.kc_username = kc_username
115+
tenant.kc_password = kc_password
116+
56117
tenant.save!
57118

58119
puts('OK')

spec/factories/tenant.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,12 @@
44
factory :tenant do
55
name { Faker::Creature::Animal.name }
66
secrets { "#{Faker::Crypto.sha256}:#{Faker::Crypto.sha512}" }
7+
lrs_endpoint { nil }
8+
lrs_basic_token { nil }
9+
kc_token_url { nil }
10+
kc_client_id { nil }
11+
kc_client_secret { nil }
12+
kc_username { nil }
13+
kc_password { nil }
714
end
815
end

spec/models/meeting_spec.rb

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,8 @@
167167
context 'without tenant_id param' do
168168
let(:meetings) { described_class.all }
169169

170-
it 'fetches correct nb of Meetings' do
171-
expect(meetings.size).to eq 2
172-
end
173-
174-
it 'fetches Meetings with correct tenant_id' do
175-
meetings.each do |meeting|
176-
expect(meeting.tenant_id).to be_nil
177-
end
170+
it 'fetches all Meetings' do
171+
expect(meetings.size).to eq 7
178172
end
179173
end
180174
end

0 commit comments

Comments
 (0)