From 244bd2ab78dd104456370bbad7dbd3cd02c8c449 Mon Sep 17 00:00:00 2001 From: Niraj Raut <84171890+nirajkumar999@users.noreply.github.com> Date: Tue, 6 Aug 2024 20:04:32 +0530 Subject: [PATCH 1/9] Refactored LinksForm component. Prevent unnecessary form submission on button click. (#5891) We can extend the performance optimization by preventing unnecessary onSubmit of Form in case of tnc, privacy policy and help center fields. So PATCH request will only be sent if there is actual change in the value. I have introduced a useRef variable formText which initially stores the actual value of these fields and further tracks any changes to the value. In handleSubmit function we call mutation only when this formText variable shows any changes. Hence this way we prevent any unnecessary PATCH request when user continuously clicks the Change URL function. --- .../administration/LinksForm.jsx | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/javascript/components/admin/site_settings/administration/LinksForm.jsx b/app/javascript/components/admin/site_settings/administration/LinksForm.jsx index b92aa3680c..ce500ede70 100644 --- a/app/javascript/components/admin/site_settings/administration/LinksForm.jsx +++ b/app/javascript/components/admin/site_settings/administration/LinksForm.jsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License along // with Greenlight; if not, see . -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import { Button } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; @@ -29,13 +29,28 @@ export default function LinksForm({ id, value, mutation: useUpdateSiteSettingsAP const { methods, fields } = useLinksForm({ defaultValues: { value } }); + const formText = useRef(''); + useEffect(() => { - if (!methods) { return; } - methods.reset({ value }); + if (methods) { + methods.reset({ value }); + formText.current = value; + } }, [methods, value]); + const handleSubmit = useCallback( + (formData) => { + if (formText.current !== formData[`${fields.value.hookForm.id}`]) { + formText.current = formData[`${fields.value.hookForm.id}`]; + return updateSiteSettingsAPI.mutate(formData); + } + return null; + }, + [updateSiteSettingsAPI.mutate], + ); + return ( -
+ Date: Tue, 6 Aug 2024 15:05:26 -0400 Subject: [PATCH 2/9] Translate config/locales/en.yml in tr (#5911) 100% translated source file: 'config/locales/en.yml' on 'tr'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- config/locales/tr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/tr.yml b/config/locales/tr.yml index bd3ea5d111..4ea3d385cd 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -63,7 +63,7 @@ tr: invitation_to_join: BigBlueButton çağrısı you_have_been_invited: "%{name} tarafından bir BigBlueButton hesabı açmaya çağrıldınız." get_started: Hesap açmak için aşağıdaki düğmeye tıklayın ve yönergeleri izleyin. - valid_invitation: Çağrı 24 saat boyunca geçerlidir. + valid_invitation: Çağrı 7 gün boyunca geçerlidir. sign_up: Hesap aç new_user_signup: new_user: Yeni BigBlueButton kullanıcı hesabı açılışı From 92c6cf3779015809660d137cbee51d6c2ebcbfe1 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:05:42 -0400 Subject: [PATCH 3/9] Translate app/assets/locales/en.json in tr (#5910) 100% translated source file: 'app/assets/locales/en.json' on 'tr'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- app/assets/locales/tr.json | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/app/assets/locales/tr.json b/app/assets/locales/tr.json index c6188d597a..e55ac85b1c 100644 --- a/app/assets/locales/tr.json +++ b/app/assets/locales/tr.json @@ -14,6 +14,7 @@ "report": "Bildir", "share": "Paylaş", "cancel": "İptal", + "reset": "Sıfırla", "close": "Kapat", "delete": "Sil", "copy": "Kopyala", @@ -163,6 +164,10 @@ "wrong_access_code": "Erişim kodu yanlış", "generate_viewers_access_code": "İzleyiciler için erişim kodu oluştur", "generate_mods_access_code": "Sorumlular için erişim kodu oluştur", + "server_tag": "Bu oda için bir sunucu türü seçin", + "default_tag_name": "Varsayılan", + "server_tag_desired": "İstenilen", + "server_tag_required": "Zorunlu", "are_you_sure_delete_room": "Bu odayı silmek istediğinize emin misiniz?" } }, @@ -229,7 +234,8 @@ "empty_invited_users_subtext": "Durumu çağrılmış olarak değiştirilen kullanıcılar burada görüntülenir.", "invited": { "time_sent": "Gönderilme zamanı", - "valid": "Geçerli" + "valid": "Geçerli", + "revoke": "Geçersiz kıl" } }, "server_rooms": { @@ -272,8 +278,8 @@ "administration": { "administration": "Yönetim", "terms": "Hüküm ve koşullar", - "privacy": "Gizlilik ilkesi", - "privacy_policy": "Gizlilik ilkesi", + "privacy": "Gizlilik bildirimi", + "privacy_policy": "Gizlilik bildirimi", "change_term_links": "Sayfanın altında görüntülenecek koşullar bağlantısını değiştir", "change_privacy_link": "Sayfanın altında görüntülenecek gizlilik bağlantısını değiştir", "helpcenter": "Yardım merkezi", @@ -301,7 +307,7 @@ "registration": { "registration": "Hesap açma", "role_mapping_by_email": "Roller e-postaya göre eşleştirilsin", - "role_mapping_by_email_description": "Kullanıcı rollerini e-postalarına göre belirler. rol1?e-posta1, rol2=e-posta2 biçiminde olmalıdır", + "role_mapping_by_email_description": "Kullanıcı rollerini e-postalarına göre belirler. rol1?e-posta1,rol2=e-posta2 biçiminde olmalıdır", "enter_role_mapping_rule": "Bir rol eşleştirme kuralı yazın", "resync_on_login": "Kullanıcı verileri her oturum açıldığında eşitlensin", "resync_on_login_description": "Bir kullanıcının bilgilerini her oturum açtığından yeniden eşitleyerek, Greenlight üzerindeki bilgilerin her zaman dış kimlik doğrulama hizmeti sağlayıcısındakiler ile aynı olmasını sağlar", @@ -411,7 +417,7 @@ "brand_color_updated": "Marka rengi güncellendi.", "brand_image_updated": "Marka görseli güncellendi.", "brand_image_deleted": "Marka görseli silindi.", - "privacy_policy_updated": "Gizlilik ilkesi güncellendi.", + "privacy_policy_updated": "Gizlilik bildirimi güncellendi.", "helpcenter_updated": "Yardım merkezi bağlantısı güncellendi.", "terms_of_service_updated": "Hizmet koşulları güncellendi.", "maintenance_updated": "Bakım duyurusu güncellendi." @@ -429,11 +435,13 @@ "role_permission_updated": "Rol yetkileri güncellendi." }, "invitations": { - "invitation_sent": "Çağrı gönderildi" + "invitation_sent": "Çağrı gönderildi", + "invitation_revoked": "Bir çağrı geçersiz kılındı" } }, "error": { "problem_completing_action": "Bu işlem yapılırken bir sorun çıktı.\nLütfen yeniden deneyin.", + "server_type_unavailable": "İstenilen sunucu türü kullanılamıyor. Lütfen oda ayarlarında başka bir tür seçin.", "file_type_not_supported": "Dosya türü desteklenmiyor.", "file_size_too_large": "Dosya boyutu çok büyük.", "file_upload_error": "Dosya yüklenemedi.", @@ -530,6 +538,11 @@ }, "url": { "invalid": "Adres geçersiz" + }, + "text_form": { + "value": { + "required": "Lütfen bir ileti yazın" + } } }, "room": { From 3b282ff2ec62ca42bd58e51902c05fbbf54b82ea Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:09:30 -0400 Subject: [PATCH 4/9] Translate config/locales/en.yml in tr (#5909) 100% translated source file: 'config/locales/en.yml' on 'tr'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> Co-authored-by: Ahmad Farhat --- config/locales/tr.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 4ea3d385cd..939861b928 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -47,18 +47,18 @@ tr: opengraph: - description: "Sorunsuz sanal işbirliği ve çevrim içi öğrenme deneyimi sağlayan, güvenilir açık kaynaklı İnternet konferansı çözümü BigBlueButton uygulamasını kullanarak öğrenin." + description: "Sorunsuz sanal işbirliği ve çevrim içi öğrenme deneyimleri sağlayan, güvenilir ve açık kaynaklı İnternet üzerinden görüşme çözümü BigBlueButton uygulamasını kullanarak öğrenin." meeting: - moderator_message: "Toplantıya çağırmak istediğiniz kişilere bu bağlantıyı gönderin:" + moderator_message: "Toplantıya katılmasını istediğiniz kişilere bu bağlantıyı gönderin: " access_code: "Erişim kodu: %{code}" email: activation: account_activation: Hesap etkinleştirme welcome_to_bbb: BigBlueButton uygulamasına hoş geldiniz! - get_started: Lütfen başlamak için aşağıdaki düğmeye tıklayarak hesabınızı etkinleştirin. + get_started: "Başlamak için, lütfen aşağıdaki düğmeye tıklayarak hesabınızı etkinleştirin." activate_account: Hesabı etkinleştir - link_expires: Bu bağlantı 24 saat sonra geçersiz olacak. - if_link_expires: "Bağlantının süresi geçerse, yeni bir etkinleştirme e-postası almak için oturum açın." + link_expires: Bağlantı 24 saat sonra geçersiz olacak. + if_link_expires: Bağlantının süresi geçerse oturum açarak yeni bir etkinleştirme e-postası alın. invitation: invitation_to_join: BigBlueButton çağrısı you_have_been_invited: "%{name} tarafından bir BigBlueButton hesabı açmaya çağrıldınız." @@ -74,10 +74,10 @@ tr: take_action: "Yeni kullanıcıyı görüntülemek ya da gerekli işlemi yapmak için yönetim panosuna gidin" reset: password_reset: Parolayı sıfırla - password_reset_requested: "%{email} e-posta adresi için parola sıfırlama isteğinde bulunuldu." + password_reset_requested: "%{email} için bir parola sıfırlama isteğinde bulunuldu." password_reset_confirmation: Parolanızı sıfırlamak için aşağıdaki düğmeye tıklayın. reset_password: Parolayı sıfırla link_expires: Bağlantı 1 saat sonra geçersiz olacak. - ignore_request: Parolanızı değiştirme isteğinde bulunmadıysanız bu e-postayı yok sayabilirsiniz. + ignore_request: Parola değiştirme isteğinde bulunmadıysanız bu e-postayı yok sayabilirsiniz. room: new_room_name: "%{username} kullanıcısının odası" From 339c399561377076eb698af769e564c880bafe04 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:09:43 -0400 Subject: [PATCH 5/9] Translate app/assets/locales/en.json in ja (#5906) 100% translated source file: 'app/assets/locales/en.json' on 'ja'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- app/assets/locales/ja.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/assets/locales/ja.json b/app/assets/locales/ja.json index 62d3c7dbed..c33dfe0a3a 100644 --- a/app/assets/locales/ja.json +++ b/app/assets/locales/ja.json @@ -164,6 +164,10 @@ "wrong_access_code": "誤った接続コード", "generate_viewers_access_code": "視聴者用接続コードを作成する", "generate_mods_access_code": "司会者用接続コードを作成する", + "server_tag": "この会議室のサーバタイプを選択", + "default_tag_name": "通常", + "server_tag_desired": "最適", + "server_tag_required": "最小", "are_you_sure_delete_room": "本当にこの会議室を削除しますか?" } }, @@ -274,8 +278,8 @@ "administration": { "administration": "法的根拠", "terms": "利用規約", - "privacy": "プライバシーポリシー", - "privacy_policy": "プライバシーポリシー", + "privacy": "プライバシーに関する告知", + "privacy_policy": "プライバシーに関する告知", "change_term_links": "ページ下方の利用規約へのリンクを変更する", "change_privacy_link": "ページ下方のプライバシーポリシーへのリンクを変更する", "helpcenter": "ヘルプセンター", @@ -413,7 +417,7 @@ "brand_color_updated": "サイトの基調色がアップデートされました。", "brand_image_updated": "ブランド画像がアップデートされました。", "brand_image_deleted": "ブランド画像が消去されました。", - "privacy_policy_updated": "プライバシーポリシーがアップデートされました。", + "privacy_policy_updated": "プライバシーに関する告知がアップデートされました。", "helpcenter_updated": "ヘルプセンターのリンクが更新されました。", "terms_of_service_updated": "利用規約が更新されました。", "maintenance_updated": "メンテナンスバナーが更新されました。" @@ -437,6 +441,7 @@ }, "error": { "problem_completing_action": "操作をうまく完了できませんでした。\nもう一度やってみてください。", + "server_type_unavailable": "希望されたサーバタイプは利用できません。別のタイプを部屋の設定から選んでください。", "file_type_not_supported": "ファイルの形式がサポートされていません。", "file_size_too_large": "ファイルが大きすぎます。", "file_upload_error": "ファイルがアップロードできません。", @@ -533,6 +538,11 @@ }, "url": { "invalid": "無効なURL" + }, + "text_form": { + "value": { + "required": "何かメッセージを入力してください" + } } }, "room": { From 7d248f2bc7a61430c91bb922f403b325601a1e9b Mon Sep 17 00:00:00 2001 From: Ahmad Farhat Date: Fri, 9 Aug 2024 16:11:51 -0400 Subject: [PATCH 6/9] Pass through user_id to BBB meeting (#5915) --- app/controllers/api/v1/meetings_controller.rb | 17 +++++++ app/services/big_blue_button_api.rb | 3 +- app/services/meeting_starter.rb | 6 ++- spec/controllers/meetings_controller_spec.rb | 46 ++++++++++++------- spec/services/meeting_starter_spec.rb | 2 + 5 files changed, 55 insertions(+), 19 deletions(-) diff --git a/app/controllers/api/v1/meetings_controller.rb b/app/controllers/api/v1/meetings_controller.rb index f0a68b5819..a0dddd6657 100644 --- a/app/controllers/api/v1/meetings_controller.rb +++ b/app/controllers/api/v1/meetings_controller.rb @@ -37,6 +37,7 @@ def start render_data data: BigBlueButtonApi.new(provider: current_provider).join_meeting( room: @room, name: current_user.name, + user_id: fetch_bbb_user_id, avatar_url: current_user.avatar.attached? ? url_for(current_user.avatar) : nil, role: 'Moderator' ), status: :created @@ -81,6 +82,7 @@ def status data[:joinUrl] = BigBlueButtonApi.new(provider: current_provider).join_meeting( room: @room, name: current_user ? current_user.name : params[:name], + user_id: fetch_bbb_user_id, avatar_url: current_user&.avatar&.attached? ? url_for(current_user.avatar) : nil, role: bbb_role ) @@ -145,6 +147,21 @@ def infer_bbb_role(mod_code:, viewer_code:, anyone_join_as_mod:) 'Viewer' end end + + def fetch_bbb_user_id + return "gl-#{current_user.id}" if current_user + + return cookies[:guest_id] if cookies[:guest_id].present? + + guest_id = "gl-guest-#{SecureRandom.hex(12)}" + + cookies[:guest_id] = { + value: guest_id, + expires: 1.day.from_now + } + + guest_id + end end end end diff --git a/app/services/big_blue_button_api.rb b/app/services/big_blue_button_api.rb index 21493fdbb6..495e1dacdf 100644 --- a/app/services/big_blue_button_api.rb +++ b/app/services/big_blue_button_api.rb @@ -44,12 +44,13 @@ def start_meeting(room:, options: {}, presentation_url: nil) end end - def join_meeting(room:, role:, name: nil, avatar_url: nil) + def join_meeting(room:, role:, user_id:, name: nil, avatar_url: nil) bbb_server.join_meeting_url( room.meeting_id, name, '', # empty password -> use the role passed ing { + userId: user_id, role:, avatarURL: avatar_url, createTime: room.last_session&.to_datetime&.strftime('%Q') diff --git a/app/services/meeting_starter.rb b/app/services/meeting_starter.rb index 99d95e3ffc..63009ffd2a 100644 --- a/app/services/meeting_starter.rb +++ b/app/services/meeting_starter.rb @@ -69,9 +69,11 @@ def computed_options(access_code:) logoutURL: room_url, meta_endCallbackUrl: meeting_ended_url(host: @base_url), 'meta_bbb-recording-ready-url': recording_ready_url(host: @base_url), - 'meta_bbb-origin-version': ENV.fetch('VERSION_TAG', 'v3'), 'meta_bbb-origin': 'greenlight', - 'meta_bbb-origin-server-name': URI(@base_url).host + 'meta_bbb-origin-server-name': URI(@base_url).host, + 'meta_bbb-origin-version': ENV.fetch('VERSION_TAG', 'v3'), + 'meta_bbb-context-name': @room.name, + 'meta_bbb-context-id': @room.friendly_id } end diff --git a/spec/controllers/meetings_controller_spec.rb b/spec/controllers/meetings_controller_spec.rb index 48aeec11d8..a4d1c6f67a 100644 --- a/spec/controllers/meetings_controller_spec.rb +++ b/spec/controllers/meetings_controller_spec.rb @@ -25,6 +25,8 @@ let(:test_user) { create(:user) } let(:test_room) { create(:room, user: test_user) } let(:user_with_manage_rooms_permission) { create(:user, :with_manage_rooms_permission) } + let(:user_id) { "gl-#{user.id}" } + let(:guest_user_id) { "gl-#{guest_user.id}" } before do request.headers['ACCEPT'] = 'application/json' @@ -43,7 +45,7 @@ it 'makes a call to the MeetingStarter service with the right values and returns the join url' do expect(MeetingStarter).to receive(:new).with(room:, base_url: request.base_url, current_user: user, provider: 'greenlight').and_call_original expect_any_instance_of(MeetingStarter).to receive(:call) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: user.name, avatar_url: nil, role: 'Moderator') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: user.name, user_id:, avatar_url: nil, role: 'Moderator') post :start, params: { friendly_id: room.friendly_id } @@ -61,7 +63,7 @@ it 'makes a call to the BigBlueButtonApi to get the join url' do expect_any_instance_of(BigBlueButtonApi) .to receive(:join_meeting) - .with(room:, name: user.name, avatar_url: nil, role: 'Moderator') + .with(room:, name: user.name, user_id:, avatar_url: nil, role: 'Moderator') post :start, params: { friendly_id: room.friendly_id } end @@ -72,7 +74,7 @@ expect_any_instance_of(BigBlueButtonApi) .to receive(:join_meeting) - .with(room:, name: user.name, avatar_url:, role: 'Moderator') + .with(room:, name: user.name, user_id:, avatar_url:, role: 'Moderator') post :start, params: { friendly_id: room.friendly_id } end @@ -129,7 +131,8 @@ it 'allows a user who the room is shared with to start the meeting' do expect_any_instance_of(MeetingStarter).to receive(:call) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: guest_user.name, avatar_url: nil, role: 'Moderator') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: guest_user.name, user_id: guest_user_id, + avatar_url: nil, role: 'Moderator') post :start, params: { friendly_id: room.friendly_id } @@ -142,7 +145,8 @@ describe '#status' do it 'gets the joinUrl if the meeting is running' do allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room: test_room, name: user.name, avatar_url: nil, role: 'Viewer') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room: test_room, name: user.name, user_id:, avatar_url: nil, + role: 'Viewer') post :status, params: { friendly_id: test_room.friendly_id, name: user.name } expect(response).to have_http_status(:ok) @@ -161,14 +165,15 @@ it 'joins as viewer if no access code is required nor provided' do allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room: test_room, name: user.name, avatar_url: nil, role: 'Viewer') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room: test_room, name: user.name, user_id:, avatar_url: nil, + role: 'Viewer') post :status, params: { friendly_id: test_room.friendly_id, name: user.name } expect(response).to have_http_status(:ok) end it 'joins as moderator if user is joining his own room' do allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: user.name, avatar_url: nil, role: 'Moderator') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: user.name, user_id:, avatar_url: nil, role: 'Moderator') post :status, params: { friendly_id: room.friendly_id, name: user.name } expect(response).to have_http_status(:ok) end @@ -180,7 +185,7 @@ expect_any_instance_of(BigBlueButtonApi) .to receive(:join_meeting) - .with(room: test_room, name: user.name, avatar_url:, role: 'Viewer') + .with(room: test_room, name: user.name, user_id:, avatar_url:, role: 'Viewer') post :status, params: { friendly_id: test_room.friendly_id, name: user.name } end @@ -203,7 +208,8 @@ it 'joins as moderator' do allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: guest_user.name, avatar_url: nil, role: 'Moderator') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: guest_user.name, user_id: guest_user_id, + avatar_url: nil, role: 'Moderator') post :status, params: { friendly_id: room.friendly_id, name: guest_user.name } end end @@ -219,7 +225,8 @@ it 'joins as viewer if access code correspond to the viewer access code' do allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room: test_room, name: user.name, avatar_url: nil, role: 'Viewer') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room: test_room, name: user.name, user_id:, avatar_url: nil, + role: 'Viewer') expect(RoomSettingsGetter).to receive(:new).with( room_id: test_room.id, provider: 'greenlight', show_codes: true, current_user: user, settings: %w[glRequireAuthentication glViewerAccessCode glModeratorAccessCode glAnyoneCanStart glAnyoneJoinAsModerator] @@ -234,7 +241,7 @@ it 'joins as moderator if access code correspond to moderator access code' do allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: user.name, avatar_url: nil, role: 'Moderator') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: user.name, user_id:, avatar_url: nil, role: 'Moderator') expect(RoomSettingsGetter).to receive(:new).with( room_id: room.id, provider: 'greenlight', show_codes: true, current_user: user, settings: %w[glRequireAuthentication glViewerAccessCode glModeratorAccessCode glAnyoneCanStart glAnyoneJoinAsModerator] @@ -273,7 +280,8 @@ it 'user joins as a moderator' do allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: guest_user.name, avatar_url: nil, role: 'Moderator') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: guest_user.name, user_id: guest_user_id, + avatar_url: nil, role: 'Moderator') expect(RoomSettingsGetter).to receive(:new).with( room_id: room.id, provider: 'greenlight', show_codes: true, current_user: guest_user, settings: %w[glRequireAuthentication glViewerAccessCode glModeratorAccessCode glAnyoneCanStart glAnyoneJoinAsModerator] @@ -299,7 +307,8 @@ it 'user joins as moderator if an access code is needed and the input correspond the viewer access code' do allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: guest_user.name, avatar_url: nil, role: 'Moderator') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: guest_user.name, user_id: guest_user_id, + avatar_url: nil, role: 'Moderator') expect(RoomSettingsGetter).to receive(:new).with( room_id: room.id, provider: 'greenlight', show_codes: true, current_user: guest_user, settings: %w[glRequireAuthentication glViewerAccessCode glModeratorAccessCode glAnyoneCanStart glAnyoneJoinAsModerator] @@ -314,7 +323,8 @@ it 'user joins as moderator if an access code is needed and the input correspond the moderator access code' do allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: guest_user.name, avatar_url: nil, role: 'Moderator') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: guest_user.name, user_id: guest_user_id, + avatar_url: nil, role: 'Moderator') expect(RoomSettingsGetter).to receive(:new).with( room_id: room.id, provider: 'greenlight', show_codes: true, current_user: guest_user, settings: %w[glRequireAuthentication glViewerAccessCode glModeratorAccessCode glAnyoneCanStart glAnyoneJoinAsModerator] @@ -387,7 +397,8 @@ it 'allows the user to join if they are signed in' do allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room: test_room, name: user.name, avatar_url: nil, role: 'Viewer') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room: test_room, name: user.name, user_id:, avatar_url: nil, + role: 'Viewer') post :status, params: { friendly_id: test_room.friendly_id, name: user.name } @@ -406,10 +417,13 @@ end it 'allows access to an unauthenticated user' do + allow(SecureRandom).to receive(:hex).and_return('abc123456789') + session[:session_token] = nil allow_any_instance_of(BigBlueButtonApi).to receive(:meeting_running?).and_return(true) - expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: user.name, avatar_url: nil, role: 'Viewer') + expect_any_instance_of(BigBlueButtonApi).to receive(:join_meeting).with(room:, name: user.name, user_id: 'gl-guest-abc123456789', + avatar_url: nil, role: 'Viewer') post :status, params: { friendly_id: room.friendly_id, name: user.name } diff --git a/spec/services/meeting_starter_spec.rb b/spec/services/meeting_starter_spec.rb index 254c77e5ef..4fb0464bb0 100644 --- a/spec/services/meeting_starter_spec.rb +++ b/spec/services/meeting_starter_spec.rb @@ -46,6 +46,8 @@ 'meta_bbb-origin-version': 'v3', 'meta_bbb-origin': 'greenlight', 'meta_bbb-origin-server-name': URI(base_url).host, + 'meta_bbb-context-name': room.name, + 'meta_bbb-context-id': room.friendly_id, setting: 'value' } end From 8ea184816ec4ecd5d5d9cfd3264d5187016ca628 Mon Sep 17 00:00:00 2001 From: Ali Hadi Mazeh <91922430+alihadimazeh@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:03:57 -0400 Subject: [PATCH 7/9] 5815 allowed email domains (#5919) * Front end changes - added a row for the specific email domain sign up - added corresponding locales * Created specific email domain sign up site setting - added specific email domain sign up to site settings in `tenant.rb` * Rspec tests and data migration * external controller tests + rubocop fixes * rubocop fixes * Rspec test fix for tenants controller spec - added the setting SpecificEmailDomainSignUp * rubocop fix * test fixes * Changed Specific Email Domain Sign Up to Allowed Domains - changed all instances of previous name to AllowedDomains * Delete db/data/20240806205559_add_domain_specific_email_signup_to_site_settings.rb * gemfile change * schema change * locale changes --------- Co-authored-by: Ahmad Farhat --- app/assets/locales/en.json | 8 +- app/controllers/api/v1/users_controller.rb | 14 ++ app/controllers/external_controller.rb | 13 ++ .../registration/Registration.jsx | 21 ++- .../site_settings/useUpdateSiteSetting.jsx | 3 + app/services/tenant_setup.rb | 3 +- ...36_add_allowed_domains_to_site_settings.rb | 23 +++ db/data_schema.rb | 2 +- .../admin/tenants_controller_spec.rb | 3 +- spec/controllers/external_controller_spec.rb | 135 ++++++++++++++---- spec/controllers/users_controller_spec.rb | 60 ++++++++ 11 files changed, 254 insertions(+), 31 deletions(-) create mode 100644 db/data/20240812210436_add_allowed_domains_to_site_settings.rb diff --git a/app/assets/locales/en.json b/app/assets/locales/en.json index d3ddd9c7ab..665d511926 100644 --- a/app/assets/locales/en.json +++ b/app/assets/locales/en.json @@ -319,7 +319,10 @@ "open": "Open Registration", "invite": "Join by Invitation", "approval": "Approve/Decline" - } + }, + "allowed_domains": "Allowed Email Domains", + "allowed_domains_signup_description": "Allow specific email domains to sign up. Format must be: @test.com,domain.com", + "enter_allowed_domains_rule" : "Enter the allowed domains" } }, "room_configuration": { @@ -420,7 +423,8 @@ "privacy_policy_updated": "The privacy notice has been updated.", "helpcenter_updated": "The help center link has been updated.", "terms_of_service_updated": "The terms of service have been updated.", - "maintenance_updated": "The maintenance banner has been updated." + "maintenance_updated": "The maintenance banner has been updated.", + "allowed_domains_signup_updated": "The allowed email domains have been updated." }, "recording": { "recording_visibility_updated": "The recording visibility has been updated.", diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb index d28c0834a3..6661d71a91 100644 --- a/app/controllers/api/v1/users_controller.rb +++ b/app/controllers/api/v1/users_controller.rb @@ -61,6 +61,9 @@ def create # Users created by a user will have the creator language by default with a fallback to the server configured default_locale. create_user_params[:language] = current_user&.language || I18n.default_locale if create_user_params[:language].blank? + # renders an error if the user is signing up with an invalid domain based off site settings + return render_error errors: Rails.configuration.custom_error_msgs[:unauthorized], status: :forbidden unless valid_domain? + user = UserCreator.new(user_params: create_user_params.except(:invite_token), provider: current_provider, role: default_role).call smtp_enabled = ENV['SMTP_SERVER'].present? @@ -184,6 +187,17 @@ def valid_invite_token Invitation.destroy_by(email: create_user_params[:email].downcase, provider: current_provider, token: create_user_params[:invite_token]).present? end + + def valid_domain? + allowed_domains_emails = SettingGetter.new(setting_name: 'AllowedDomains', provider: current_provider).call + return true if allowed_domains_emails.blank? + + domains = allowed_domains_emails.split(',') + domains.each do |domain| + return true if create_user_params[:email].end_with?(domain) + end + false + end end end end diff --git a/app/controllers/external_controller.rb b/app/controllers/external_controller.rb index 085805aa19..ec57eb1006 100644 --- a/app/controllers/external_controller.rb +++ b/app/controllers/external_controller.rb @@ -41,6 +41,8 @@ def create_user return redirect_to root_path(error: Rails.configuration.custom_error_msgs[:invite_token_invalid]) end + return render_error status: :forbidden unless valid_domain?(user_info[:email]) + # Create the user if they dont exist if new_user user = UserCreator.new(user_params: user_info, provider: current_provider, role: default_role).call @@ -164,4 +166,15 @@ def build_user_info(credentials) verified: true } end + + def valid_domain?(email) + allowed_domain_emails = SettingGetter.new(setting_name: 'AllowedDomains', provider: current_provider).call + return true if allowed_domain_emails.blank? + + domains = allowed_domain_emails.split(',') + domains.each do |domain| + return true if email.end_with?(domain) + end + false + end end diff --git a/app/javascript/components/admin/site_settings/registration/Registration.jsx b/app/javascript/components/admin/site_settings/registration/Registration.jsx index 6b75f888bc..7d9029666f 100644 --- a/app/javascript/components/admin/site_settings/registration/Registration.jsx +++ b/app/javascript/components/admin/site_settings/registration/Registration.jsx @@ -28,11 +28,12 @@ import useRoles from '../../../../hooks/queries/admin/roles/useRoles'; export default function Registration() { const { t } = useTranslation(); const { data: env } = useEnv(); - const { data: siteSettings } = useSiteSettings(['RoleMapping', 'DefaultRole', 'ResyncOnLogin', 'RegistrationMethod']); + const { data: siteSettings } = useSiteSettings(['RoleMapping', 'DefaultRole', 'ResyncOnLogin', 'RegistrationMethod', 'AllowedDomains']); const { data: roles } = useRoles(); const updateRegistrationMethod = useUpdateSiteSetting('RegistrationMethod'); const updateDefaultRole = useUpdateSiteSetting('DefaultRole'); const updateRoleMapping = useUpdateSiteSetting('RoleMapping'); + const updateDomainSignUp = useUpdateSiteSetting('AllowedDomains'); return ( <> @@ -99,6 +100,24 @@ export default function Registration() { + + + {t('admin.site_settings.registration.allowed_domains')} +

{t('admin.site_settings.registration.allowed_domains_signup_description')}

+ + + + +
); } diff --git a/app/javascript/hooks/mutations/admin/site_settings/useUpdateSiteSetting.jsx b/app/javascript/hooks/mutations/admin/site_settings/useUpdateSiteSetting.jsx index 9b3725952f..7e4f4e3c0c 100644 --- a/app/javascript/hooks/mutations/admin/site_settings/useUpdateSiteSetting.jsx +++ b/app/javascript/hooks/mutations/admin/site_settings/useUpdateSiteSetting.jsx @@ -63,6 +63,9 @@ export default function useUpdateSiteSetting(name) { case 'Maintenance': toast.success(t('toast.success.site_settings.maintenance_updated')); break; + case 'AllowedDomains': + toast.success(t('toast.success.site_settings.allowed_domains_signup_updated')); + break; default: toast.success(t('toast.success.site_settings.site_setting_updated')); } diff --git a/app/services/tenant_setup.rb b/app/services/tenant_setup.rb index 47a8b8651f..2bad739f9c 100644 --- a/app/services/tenant_setup.rb +++ b/app/services/tenant_setup.rb @@ -56,7 +56,8 @@ def create_site_settings { setting: Setting.find_by(name: 'DefaultRole'), provider: @provider, value: 'User' }, { setting: Setting.find_by(name: 'DefaultRecordingVisibility'), provider: @provider, value: 'Published' }, { setting: Setting.find_by(name: 'Maintenance'), provider: @provider, value: '' }, - { setting: Setting.find_by(name: 'SessionTimeout'), provider: @provider, value: '1' } + { setting: Setting.find_by(name: 'SessionTimeout'), provider: @provider, value: '1' }, + { setting: Setting.find_by(name: 'AllowedDomains'), value: '', provider: @provider } ] end diff --git a/db/data/20240812210436_add_allowed_domains_to_site_settings.rb b/db/data/20240812210436_add_allowed_domains_to_site_settings.rb new file mode 100644 index 0000000000..5c1aa3f5bb --- /dev/null +++ b/db/data/20240812210436_add_allowed_domains_to_site_settings.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class AddAllowedDomainsToSiteSettings < ActiveRecord::Migration[7.1] + def up + setting = Setting.find_or_create_by(name: 'AllowedDomains') + + SiteSetting.create!(setting:, value: '', provider: 'greenlight') unless SiteSetting.exists?(setting:, provider: 'greenlight') + + Tenant.find_each do |tenant| + SiteSetting.create!(setting:, value: '', provider: tenant.name) unless SiteSetting.exists?(setting:, provider: tenant.name) + end + end + + def down + Tenant.find_each do |tenant| + SiteSetting.find_by(setting: Setting.find_by(name: 'Maintenance'), provider: tenant.name)&.destroy + end + + SiteSetting.find_by(setting: Setting.find_by(name: 'Maintenance'), provider: 'greenlight')&.destroy + + Setting.find_by(name: 'AllowedDomains')&.destroy + end +end diff --git a/db/data_schema.rb b/db/data_schema.rb index dd44530cd2..5f04068525 100644 --- a/db/data_schema.rb +++ b/db/data_schema.rb @@ -1 +1 @@ -DataMigrate::Data.define(version: 20240423162700) +DataMigrate::Data.define(version: 20240812210436) diff --git a/spec/controllers/admin/tenants_controller_spec.rb b/spec/controllers/admin/tenants_controller_spec.rb index dfd13e8e93..a54876a60d 100644 --- a/spec/controllers/admin/tenants_controller_spec.rb +++ b/spec/controllers/admin/tenants_controller_spec.rb @@ -18,7 +18,7 @@ require 'rails_helper' -RSpec.describe Api::V1::Admin::TenantsController, type: :controller do +RSpec.describe Api::V1::Admin::TenantsController do let(:user) { create(:user, :with_super_admin) } let(:valid_tenant_params) do { @@ -146,6 +146,7 @@ def create_settings_permissions_meetingoptions Setting.find_or_create_by(name: 'HelpCenter') Setting.find_or_create_by(name: 'Maintenance') Setting.find_or_create_by(name: 'SessionTimeout') + Setting.find_or_create_by(name: 'AllowedDomains') Permission.find_or_create_by(name: 'CreateRoom') Permission.find_or_create_by(name: 'ManageUsers') diff --git a/spec/controllers/external_controller_spec.rb b/spec/controllers/external_controller_spec.rb index 607dac434f..764690dbc1 100644 --- a/spec/controllers/external_controller_spec.rb +++ b/spec/controllers/external_controller_spec.rb @@ -18,7 +18,7 @@ require 'rails_helper' -RSpec.describe ExternalController, type: :controller do +RSpec.describe ExternalController do let(:fake_setting_getter) { instance_double(SettingGetter) } describe '#create_user' do @@ -80,7 +80,7 @@ expect do get :create_user, params: { provider: 'openid_connect' } - end.to change(User, :count).by(0) + end.not_to change(User, :count) end it 'looks the user up based on email' do @@ -90,7 +90,7 @@ expect do get :create_user, params: { provider: 'openid_connect' } - end.to change(User, :count).by(0) + end.not_to change(User, :count) end context 'redirect' do @@ -212,40 +212,52 @@ email: 'email@example.com') end - it 'overwrites the saved values with the values from the authentication provider if true' do - allow_any_instance_of(SettingGetter).to receive(:call).and_return(true) + context 'value is true' do + before do + reg_method = instance_double(SettingGetter) + allow(SettingGetter).to receive(:new).with(setting_name: 'ResyncOnLogin', provider: 'greenlight').and_return(reg_method) + allow(reg_method).to receive(:call).and_return(true) + end - request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] + it 'overwrites the saved values with the values from the authentication provider if true' do + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] - get :create_user, params: { provider: 'openid_connect' } + get :create_user, params: { provider: 'openid_connect' } - user.reload - expect(user.name).to eq(OmniAuth.config.mock_auth[:openid_connect]['info']['name']) - expect(user.email).to eq(OmniAuth.config.mock_auth[:openid_connect]['info']['email']) - end + user.reload + expect(user.name).to eq(OmniAuth.config.mock_auth[:openid_connect]['info']['name']) + expect(user.email).to eq(OmniAuth.config.mock_auth[:openid_connect]['info']['email']) + end - it 'does not overwrite the saved values with the values from the authentication provider if false' do - allow_any_instance_of(SettingGetter).to receive(:call).and_return(false) + it 'does not overwrite the role even if true' do + allow_any_instance_of(SettingGetter).to receive(:call).and_return(true) + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] - request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] + new_role = create(:role) + user.update(role: new_role) - get :create_user, params: { provider: 'openid_connect' } + get :create_user, params: { provider: 'openid_connect' } - user.reload - expect(user.name).to eq('Example Name') - expect(user.email).to eq('email@example.com') + expect(user.reload.role).to eq(new_role) + end end - it 'does not overwrite the role even if true' do - allow_any_instance_of(SettingGetter).to receive(:call).and_return(true) - request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] + context 'value is false' do + before do + reg_method = instance_double(SettingGetter) + allow(SettingGetter).to receive(:new).with(setting_name: 'ResyncOnLogin', provider: 'greenlight').and_return(reg_method) + allow(reg_method).to receive(:call).and_return(false) + end - new_role = create(:role) - user.update(role: new_role) + it 'does not overwrite the saved values with the values from the authentication provider if false' do + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] - get :create_user, params: { provider: 'openid_connect' } + get :create_user, params: { provider: 'openid_connect' } - expect(user.reload.role).to eq(new_role) + user.reload + expect(user.name).to eq('Example Name') + expect(user.email).to eq('email@example.com') + end end end @@ -325,6 +337,79 @@ end end + context 'Allowed Domains' do + context 'restricted domain not set' do + before do + site_settings = instance_double(SettingGetter) + allow(SettingGetter).to receive(:new).with(setting_name: 'AllowedDomains', provider: 'greenlight').and_return(site_settings) + allow(site_settings).to receive(:call).and_return('') + end + + it 'creates the user' do + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] + + expect { get :create_user, params: { provider: 'openid_connect' } }.to change(User, :count).from(0).to(1) + end + end + + context 'restricted domain set to 1 domain' do + before do + site_settings = instance_double(SettingGetter) + allow(SettingGetter).to receive(:new).with(setting_name: 'AllowedDomains', provider: 'greenlight').and_return(site_settings) + allow(site_settings).to receive(:call).and_return('@domain.com') + end + + it 'creates the user if the domain is allowed' do + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] + request.env['omniauth.auth'][:info][:email] = 'email@domain.com' + + expect { get :create_user, params: { provider: 'openid_connect' } }.to change(User, :count).from(0).to(1) + end + + it 'does not create if the domain is not allowed' do + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] + + expect { get :create_user, params: { provider: 'openid_connect' } }.not_to change(User, :count) + end + end + + context 'restricted domain set to multiple domain' do + before do + site_settings = instance_double(SettingGetter) + allow(SettingGetter).to receive(:new).with(setting_name: 'AllowedDomains', provider: 'greenlight').and_return(site_settings) + allow(site_settings).to receive(:call).and_return('@example.com,@test.com,@domain.com') + end + + it 'creates the user if the domain is allowed 1' do + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] + request.env['omniauth.auth'][:info][:email] = 'email@example.com' + + expect { get :create_user, params: { provider: 'openid_connect' } }.to change(User, :count).from(0).to(1) + end + + it 'creates the user if the domain is allowed 2' do + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] + request.env['omniauth.auth'][:info][:email] = 'email@test.com' + + expect { get :create_user, params: { provider: 'openid_connect' } }.to change(User, :count).from(0).to(1) + end + + it 'creates the user if the domain is allowed 3' do + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] + request.env['omniauth.auth'][:info][:email] = 'email@domain.com' + + expect { get :create_user, params: { provider: 'openid_connect' } }.to change(User, :count).from(0).to(1) + end + + it 'does not create if the domain is not allowed' do + request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect] + request.env['omniauth.auth'][:info][:email] = 'test@invaliddomain.com' + + expect { get :create_user, params: { provider: 'openid_connect' } }.not_to change(User, :count) + end + end + end + context 'Role mapping' do let!(:role1) { create(:role, name: 'role1') } diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 81a9d594ec..d24017fc3b 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -301,6 +301,66 @@ expect(response.parsed_body['errors']).not_to be_nil end end + + context 'Allowed Domains' do + context 'restricted domain not set' do + before do + site_settings = instance_double(SettingGetter) + allow(SettingGetter).to receive(:new).with(setting_name: 'AllowedDomains', provider: 'greenlight').and_return(site_settings) + allow(site_settings).to receive(:call).and_return('') + end + + it 'creates the user' do + expect { post :create, params: user_params }.to change(User, :count).from(0).to(1) + end + end + + context 'restricted domain set to 1 domain' do + before do + site_settings = instance_double(SettingGetter) + allow(SettingGetter).to receive(:new).with(setting_name: 'AllowedDomains', provider: 'greenlight').and_return(site_settings) + allow(site_settings).to receive(:call).and_return('@domain.com') + end + + it 'creates the user if the domain is allowed' do + user_params[:user][:email] = 'test@domain.com' + expect { post :create, params: user_params }.to change(User, :count).from(0).to(1) + end + + it 'does not create if the domain is not allowed' do + user_params[:user][:email] = 'test@invaliddomain.com' + expect { post :create, params: user_params }.not_to change(User, :count) + end + end + + context 'restricted domain set to multiple domain' do + before do + site_settings = instance_double(SettingGetter) + allow(SettingGetter).to receive(:new).with(setting_name: 'AllowedDomains', provider: 'greenlight').and_return(site_settings) + allow(site_settings).to receive(:call).and_return('@example.com,@test.com,@domain.com') + end + + it 'creates the user if the domain is allowed 1' do + user_params[:user][:email] = 'test@example.com' + expect { post :create, params: user_params }.to change(User, :count).from(0).to(1) + end + + it 'creates the user if the domain is allowed 2' do + user_params[:user][:email] = 'test@test.com' + expect { post :create, params: user_params }.to change(User, :count).from(0).to(1) + end + + it 'creates the user if the domain is allowed 3' do + user_params[:user][:email] = 'test@domain.com' + expect { post :create, params: user_params }.to change(User, :count).from(0).to(1) + end + + it 'does not create if the domain is not allowed' do + user_params[:user][:email] = 'test@invaliddomain.com' + expect { post :create, params: user_params }.not_to change(User, :count) + end + end + end end describe '#show' do From a4adf08c6377681dbad098ecbae87e890e7ab43d Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:15:57 -0400 Subject: [PATCH 8/9] Translate app/assets/locales/en.json in el (#5921) 100% translated source file: 'app/assets/locales/en.json' on 'el'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- app/assets/locales/el.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/locales/el.json b/app/assets/locales/el.json index c78670ced6..93cd8e9f7e 100644 --- a/app/assets/locales/el.json +++ b/app/assets/locales/el.json @@ -319,7 +319,10 @@ "open": "Ενεργοποίηση εγγραφών", "invite": "Συμμετοχή με πρόσκληση", "approval": "Έγκριση/Απόρριψη" - } + }, + "allowed_domains": "Επιτρέπονται τα email ονομάτων τομεα", + "allowed_domains_signup_description": "Επιτρέπονται συγκεκριμένα email ονομάτων τομεα για εγγραφή. Η μορφή πρέπει να είναι @test.com,domain.com", + "enter_allowed_domains_rule" : "Προσθηκη επιτρεπόμενου ονόματος τομέα " } }, "room_configuration": { @@ -420,7 +423,8 @@ "privacy_policy_updated": "Η πολιτική απορρήτου ενημερώθηκε.", "helpcenter_updated": "Ο σύνδεσμος για το Κέντρο βοήθειας ενημερώθηκε. ", "terms_of_service_updated": "Οι όριο χρήσης ενημερώθηκαν.", - "maintenance_updated": "Το μήνυμα της λειτουργίας συντήρησης ενημερώθηκε." + "maintenance_updated": "Το μήνυμα της λειτουργίας συντήρησης ενημερώθηκε.", + "allowed_domains_signup_updated": "Τα επιτρεπόμενα ονόματα τομέα ενημερώθηκαν" }, "recording": { "recording_visibility_updated": "Η εμφάνιση καταγραφής ενημερώθηκε.", From a1b9e5b6b1d8572815aa55d01b07e8c972b159a6 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:16:05 -0400 Subject: [PATCH 9/9] Translate app/assets/locales/en.json in ja (#5920) 100% translated source file: 'app/assets/locales/en.json' on 'ja'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- app/assets/locales/ja.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/locales/ja.json b/app/assets/locales/ja.json index c33dfe0a3a..41f1604f09 100644 --- a/app/assets/locales/ja.json +++ b/app/assets/locales/ja.json @@ -319,7 +319,10 @@ "open": "誰でも自由に登録", "invite": "招待制", "approval": "承認制" - } + }, + "allowed_domains": "許可するEmailのドメイン", + "allowed_domains_signup_description": "特定のEmailドメインのみからのサインアップを許可します。@test.com や domain.com のような形式で記入してください。", + "enter_allowed_domains_rule" : "許可するドメインを入力" } }, "room_configuration": { @@ -420,7 +423,8 @@ "privacy_policy_updated": "プライバシーに関する告知がアップデートされました。", "helpcenter_updated": "ヘルプセンターのリンクが更新されました。", "terms_of_service_updated": "利用規約が更新されました。", - "maintenance_updated": "メンテナンスバナーが更新されました。" + "maintenance_updated": "メンテナンスバナーが更新されました。", + "allowed_domains_signup_updated": "許可するEmailのドメインが更新されました。" }, "recording": { "recording_visibility_updated": "録画の公開度が更新されました。",