From a58dbd454c600749a8a0703d7af1721062e81c9a Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Thu, 29 Jun 2023 14:23:24 +0200 Subject: [PATCH 01/11] parameters: update missing parameters --- b3lb/rest/models.py | 343 +++++++++++++++++++------------------------- 1 file changed, 151 insertions(+), 192 deletions(-) diff --git a/b3lb/rest/models.py b/b3lb/rest/models.py index 7906995..2d5ea88 100644 --- a/b3lb/rest/models.py +++ b/b3lb/rest/models.py @@ -915,22 +915,20 @@ class MetricAdmin(admin.ModelAdmin): class Parameter(models.Model): # Create Parameters - WELCOME = "welcome" - MAX_PARTICIPANTS = "maxParticipants" - LOGOUT_URL = "logoutURL" - RECORD = "record" - DISABLED_FEATURES = "disabledFeatures" - DURATION = "duration" - MODERATOR_ONLY_MESSAGE = "moderatorOnlyMessage" - AUTO_START_RECORDING = "autoStartRecording" + ALLOW_MODS_TO_UNMUTE_USERS = "allowModsToUnmuteUsers" ALLOW_START_STOP_RECORDING = "allowStartStopRecording" - WEBCAMS_ONLY_FOR_MODERATOR = "webcamsOnlyForModerator" - LOGO = "logo" - BANNER_TEXT = "bannerText" + AUTO_START_RECORDING = "autoStartRecording" BANNER_COLOR = "bannerColor" + BANNER_TEXT = "bannerText" COPYRIGHT = "copyright" - MUTE_ON_START = "muteOnStart" - ALLOW_MODS_TO_UNMUTE_USERS = "allowModsToUnmuteUsers" + DISABLED_FEATURES = "disabledFeatures" + DISABLED_FEATURES_EXCLUDED = "disabledFeaturesExclude" + DURATION = "duration" + END_WHEN_NO_MODERATOR = "endWhenNoModerator" + END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES = "endWhenNoModeratorDelayInMinutes" + GROUPS = "groups" + GUEST_POLICY = "guestPolicy" + LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES = "learningDashboardCleanupDelayInMinutes" LOCK_SETTINGS_DISABLE_CAM = "lockSettingsDisableCam" LOCK_SETTINGS_DISABLE_MIC = "lockSettingsDisableMic" LOCK_SETTINGS_DISABLE_PRIVATE_CHAT = "lockSettingsDisablePrivateChat" @@ -940,25 +938,25 @@ class Parameter(models.Model): LOCK_SETTINGS_LOCKED_LAYOUT = "lockSettingsLockedLayout" LOCK_SETTINGS_LOCK_ON_JOIN = "lockSettingsLockOnJoin" LOCK_SETTINGS_LOCK_ON_JOIN_CONFIGURABLE = "lockSettingsLockOnJoinConfigurable" - GUEST_POLICY = "guestPolicy" - GROUPS = "groups" - META_FULLAUDIO_BRIDGE = "meta_fullaudio-bridge" + LOGO = "logo" + LOGOUT_URL = "logoutURL" + MAX_PARTICIPANTS = "maxParticipants" MEETING_CAMERA_CAP = "meetingCameraCap" MEETING_EXPIRE_IF_NO_USER_JOINED_IN_MINUTES = "meetingExpireIfNoUserJoinedInMinutes" MEETING_EXPIRE_WHEN_LAST_USER_LEFT_IN_MINUTES = "meetingExpireWhenLastUserLeftInMinutes" MEETING_KEEP_EVENT = "meetingKeepEvents" + MEETING_LAYOUT = "meetingLayout" + META_FULLAUDIO_BRIDGE = "meta_fullaudio-bridge" + MODERATOR_ONLY_MESSAGE = "moderatorOnlyMessage" + MUTE_ON_START = "muteOnStart" NOTIFY_RECORDING_IS_ON = "notifyRecordingIsOn" + PRE_UPLOADED_PRESENTATION_OVERRIDE_DEFAULT = "preUploadedPresentationOverrideDefault" PRESENTATION_UPLOAD_EXTERNAL_URL = "presentationUploadExternalUrl" PRESENTATION_UPLOAD_EXTERNAL_DESCRIPTION = "presentationUploadExternalDescription" - END_WHEN_NO_MODERATOR = "endWhenNoModerator" - END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES = "endWhenNoModeratorDelayInMinutes" - MEETING_LAYOUT = "meetingLayout" - PRE_UPLOADED_PRESENTATION_OVERRIDE_DEFAULT = "preUploadedPresentationOverrideDefault" - LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES = "learningDashboardCleanupDelayInMinutes" - - # Join - ROLE = "role" - EXCLUDE_FROM_DASHBOARD = "excludeFromDashboard" + RECORD = "record" + RECORD_FULL_DURATION_MEDIA = "recordFullDurationMedia" + WEBCAMS_ONLY_FOR_MODERATOR = "webcamsOnlyForModerator" + WELCOME = "welcome" # Join Parameters # see https://docs.bigbluebutton.org/admin/customize.html#passing-custom-parameters-to-the-client-on-join for documentation @@ -966,54 +964,39 @@ class Parameter(models.Model): # some join parameters needs settings.yml defined inputs, see # https://github.com/bigbluebutton/bigbluebutton/blob/develop/bigbluebutton-html5/private/config/settings.yml # for possible options - - # Join Parameters - Application + EXCLUDE_FROM_DASHBOARD = "excludeFromDashboard" + ROLE = "role" USERDATA_BBB_ASK_FOR_FEEDBACK_ON_LOGOUT = "userdata-bbb_ask_for_feedback_on_logout" USERDATA_BBB_AUTO_JOIN_AUDIO = "userdata-bbb_auto_join_audio" + USERDATA_BBB_AUTO_SHARE_WEBCAM = "userdata-bbb_auto_share_webcam" + USERDATA_BBB_AUTO_SWAP_LAYOUT = "userdata-bbb_auto_swap_layout" USERDATA_BBB_CLIENT_TITLE = "userdata-bbb_client_title" - USERDATA_BBB_FORCE_LISTEN_ONLY = "userdata-bbb_force_listen_only" - USERDATA_BBB_LISTEN_ONLY_MODE = "userdata-bbb_listen_only_mode" - USERDATA_BBB_SKIP_CHECK_AUDIO = "userdata-bbb_skip_check_audio" - USERDATA_BBB_SKIP_CHECK_AUDIO_ON_FIRST_JOIN = "userdata-bbb_skip_check_audio_on_first_join" - USERDATA_BBB_OVERRIDE_DEFAULT_LOCALE = "userdata-bbb_override_default_locale" - - # Join Parameters - Branding + USERDATA_BBB_CUSTOM_STYLE = "userdata-bbb_custom_style" + USERDATA_BBB_CUSTOM_STYLE_URL = "userdata-bbb_custom_style_url" USERDATA_BBB_DISPLAY_BRANDING_AREA = "userdata-bbb_display_branding_area" - - # Join Parameters - Shortcut - USERDATA_BBB_SHORTCUTS = "userdata-bbb_shortcuts" - - # Join Parameters - Kurento - USERDATA_BBB_AUTO_SHARE_WEBCAM = "userdata-bbb_auto_share_webcam" - USERDATA_BBB_PREFFERED_CAMERA_PROFILE = "userdata-bbb_preferred_camera_profile" USERDATA_BBB_ENABLE_SCREEN_SHARING = "userdata-bbb_enable_screen_sharing" USERDATA_BBB_ENABLE_VIDEO = "userdata-bbb_enable_video" - USERDATA_BBB_RECORD_VIDEO = "userdata-bbb_record_video" # currently useless, because record is forbidden in b3lb - USERDATA_BBB_SKIP_VIDEO_PREVIEW = "userdata-bbb_skip_video_preview" - USERDATA_BBB_SKIP_VIDEO_PREVIEW_ON_FIRST_JOIN = "userdata-bbb_skip_video_preview_on_first_join" - USERDATA_BBB_MIRROR_OWN_WEBCAM = "userdata-bbb_mirror_own_webcam" - - # Join Parameter - Presentation USERDATA_BBB_FORCE_RESTORE_PRESENTATION_ON_NEW_EVENTS = "userdata-bbb_force_restore_presentation_on_new_events" - - # Join Parameter - Whiteboard + USERDATA_BBB_FORCE_LISTEN_ONLY = "userdata-bbb_force_listen_only" + USERDATA_BBB_HIDE_PRESENTATION = "userdata-bbb_hide_presentation" + USERDATA_BBB_HIDE_PRESENTATION_ON_JOIN = "userdata-bbb_hide_presentation_on_join" + USERDATA_BBB_LISTEN_ONLY_MODE = "userdata-bbb_listen_only_mode" + USERDATA_BBB_MIRROR_OWN_WEBCAM = "userdata-bbb_mirror_own_webcam" USERDATA_BBB_MULTI_USER_PEN_ONLY = "userdata-bbb_multi_user_pen_only" - USERDATA_BBB_PRESENTER_TOOLS = "userdata-bbb_presenter_tools" USERDATA_BBB_MULTI_USER_TOOLS = "userdata-bbb_multi_user_tools" - - # Join Parameter - Styling - USERDATA_BBB_CUSTOM_STYLE = "userdata-bbb_custom_style" - USERDATA_BBB_CUSTOM_STYLE_URL = "userdata-bbb_custom_style_url" - - # Join Parameter - Layout - USERDATA_BBB_AUTO_SWAP_LAYOUT = "userdata-bbb_auto_swap_layout" - USERDATA_BBB_HIDE_PRESENTATION = "userdata-bbb_hide_presentation" - USERDATA_BBB_SHOW_PARTICIPIANTS_ON_LOGIN = "userdata-bbb_show_participants_on_login" + USERDATA_BBB_OVERRIDE_DEFAULT_LOCALE = "userdata-bbb_override_default_locale" + USERDATA_BBB_PREFERRED_CAMERA_PROFILE = "userdata-bbb_preferred_camera_profile" + USERDATA_BBB_PRESENTER_TOOLS = "userdata-bbb_presenter_tools" + USERDATA_BBB_RECORD_VIDEO = "userdata-bbb_record_video" + USERDATA_BBB_SHORTCUTS = "userdata-bbb_shortcuts" + USERDATA_BBB_SHOW_PARTICIPANTS_ON_LOGIN = "userdata-bbb_show_participants_on_login" USERDATA_BBB_SHOW_PUBLIC_CHAT_ON_LOGIN = "userdata-bbb_show_public_chat_on_login" - - # Join Parameter - External + USERDATA_BBB_SKIP_CHECK_AUDIO = "userdata-bbb_skip_check_audio" + USERDATA_BBB_SKIP_CHECK_AUDIO_ON_FIRST_JOIN = "userdata-bbb_skip_check_audio_on_first_join" + USERDATA_BBB_SKIP_VIDEO_PREVIEW = "userdata-bbb_skip_video_preview" + USERDATA_BBB_SKIP_VIDEO_PREVIEW_ON_FIRST_JOIN = "userdata-bbb_skip_video_preview_on_first_join" + USERDATA_BBB_OUTSIDE_TOGGLE_RECORDING = "userdata-bbb_outside_toggle_recording" USERDATA_BBB_OUTSIDE_TOGGLE_SELF_VOICE = "userdata-bbb_outside_toggle_self_voice" - USERDATA_BBB_OUTSIDE_TOGGLE_RECORDING = "userdata-bbb_outside_toggle_recording" # currently useless, because of forbidden recording # Modes BLOCK = "BLOCK" @@ -1029,11 +1012,13 @@ class Parameter(models.Model): (BANNER_TEXT, BANNER_TEXT), (COPYRIGHT, COPYRIGHT), (DISABLED_FEATURES, DISABLED_FEATURES), + (DISABLED_FEATURES_EXCLUDED, DISABLED_FEATURES_EXCLUDED), (DURATION, DURATION), (END_WHEN_NO_MODERATOR, END_WHEN_NO_MODERATOR), (END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES, END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES), (GROUPS, GROUPS), (GUEST_POLICY, GUEST_POLICY), + (LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES, LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES), (LOCK_SETTINGS_DISABLE_CAM, LOCK_SETTINGS_DISABLE_CAM), (LOCK_SETTINGS_DISABLE_MIC, LOCK_SETTINGS_DISABLE_MIC), (LOCK_SETTINGS_DISABLE_PRIVATE_CHAT, LOCK_SETTINGS_DISABLE_PRIVATE_CHAT), @@ -1046,74 +1031,57 @@ class Parameter(models.Model): (LOGO, LOGO), (LOGOUT_URL, LOGOUT_URL), (MAX_PARTICIPANTS, MAX_PARTICIPANTS), - (META_FULLAUDIO_BRIDGE, META_FULLAUDIO_BRIDGE), (MEETING_CAMERA_CAP, MEETING_CAMERA_CAP), (MEETING_EXPIRE_IF_NO_USER_JOINED_IN_MINUTES, MEETING_EXPIRE_IF_NO_USER_JOINED_IN_MINUTES), (MEETING_EXPIRE_WHEN_LAST_USER_LEFT_IN_MINUTES, MEETING_EXPIRE_WHEN_LAST_USER_LEFT_IN_MINUTES), (MEETING_KEEP_EVENT, MEETING_KEEP_EVENT), + (MEETING_LAYOUT, MEETING_LAYOUT), + (META_FULLAUDIO_BRIDGE, META_FULLAUDIO_BRIDGE), (MODERATOR_ONLY_MESSAGE, MODERATOR_ONLY_MESSAGE), (MUTE_ON_START, MUTE_ON_START), (NOTIFY_RECORDING_IS_ON, NOTIFY_RECORDING_IS_ON), + (PRE_UPLOADED_PRESENTATION_OVERRIDE_DEFAULT, PRE_UPLOADED_PRESENTATION_OVERRIDE_DEFAULT), (PRESENTATION_UPLOAD_EXTERNAL_DESCRIPTION, PRESENTATION_UPLOAD_EXTERNAL_DESCRIPTION), (PRESENTATION_UPLOAD_EXTERNAL_URL, PRESENTATION_UPLOAD_EXTERNAL_URL), (RECORD, RECORD), + (RECORD_FULL_DURATION_MEDIA, RECORD_FULL_DURATION_MEDIA), (WEBCAMS_ONLY_FOR_MODERATOR, WEBCAMS_ONLY_FOR_MODERATOR), (WELCOME, WELCOME), - (MEETING_LAYOUT, MEETING_LAYOUT), - (PRE_UPLOADED_PRESENTATION_OVERRIDE_DEFAULT, PRE_UPLOADED_PRESENTATION_OVERRIDE_DEFAULT), - (LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES, LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES), # Join - (ROLE, ROLE), (EXCLUDE_FROM_DASHBOARD, EXCLUDE_FROM_DASHBOARD), - - # Join - Application + (ROLE, ROLE), (USERDATA_BBB_ASK_FOR_FEEDBACK_ON_LOGOUT, USERDATA_BBB_ASK_FOR_FEEDBACK_ON_LOGOUT), (USERDATA_BBB_AUTO_JOIN_AUDIO, USERDATA_BBB_AUTO_JOIN_AUDIO), + (USERDATA_BBB_AUTO_SHARE_WEBCAM, USERDATA_BBB_AUTO_SHARE_WEBCAM), + (USERDATA_BBB_AUTO_SWAP_LAYOUT, USERDATA_BBB_AUTO_SWAP_LAYOUT), (USERDATA_BBB_CLIENT_TITLE, USERDATA_BBB_CLIENT_TITLE), - (USERDATA_BBB_FORCE_LISTEN_ONLY, USERDATA_BBB_FORCE_LISTEN_ONLY), - (USERDATA_BBB_LISTEN_ONLY_MODE, USERDATA_BBB_LISTEN_ONLY_MODE), - (USERDATA_BBB_SKIP_CHECK_AUDIO, USERDATA_BBB_SKIP_CHECK_AUDIO), - (USERDATA_BBB_SKIP_CHECK_AUDIO_ON_FIRST_JOIN, USERDATA_BBB_SKIP_CHECK_AUDIO_ON_FIRST_JOIN), - (USERDATA_BBB_OVERRIDE_DEFAULT_LOCALE, USERDATA_BBB_OVERRIDE_DEFAULT_LOCALE), - - # Join - Branding + (USERDATA_BBB_CUSTOM_STYLE, USERDATA_BBB_CUSTOM_STYLE), + (USERDATA_BBB_CUSTOM_STYLE_URL, USERDATA_BBB_CUSTOM_STYLE_URL), (USERDATA_BBB_DISPLAY_BRANDING_AREA, USERDATA_BBB_DISPLAY_BRANDING_AREA), - - # Join - Shortcut - (USERDATA_BBB_SHORTCUTS, USERDATA_BBB_SHORTCUTS), - - # Join - Kurento - (USERDATA_BBB_AUTO_SHARE_WEBCAM, USERDATA_BBB_AUTO_SHARE_WEBCAM), - (USERDATA_BBB_PREFFERED_CAMERA_PROFILE, USERDATA_BBB_PREFFERED_CAMERA_PROFILE), (USERDATA_BBB_ENABLE_SCREEN_SHARING, USERDATA_BBB_ENABLE_SCREEN_SHARING), (USERDATA_BBB_ENABLE_VIDEO, USERDATA_BBB_ENABLE_VIDEO), - (USERDATA_BBB_RECORD_VIDEO, USERDATA_BBB_RECORD_VIDEO), - (USERDATA_BBB_SKIP_VIDEO_PREVIEW, USERDATA_BBB_SKIP_VIDEO_PREVIEW), - (USERDATA_BBB_SKIP_VIDEO_PREVIEW_ON_FIRST_JOIN, USERDATA_BBB_SKIP_VIDEO_PREVIEW_ON_FIRST_JOIN), - (USERDATA_BBB_MIRROR_OWN_WEBCAM, USERDATA_BBB_MIRROR_OWN_WEBCAM), - - # Join - Presentation + (USERDATA_BBB_FORCE_LISTEN_ONLY, USERDATA_BBB_FORCE_LISTEN_ONLY), (USERDATA_BBB_FORCE_RESTORE_PRESENTATION_ON_NEW_EVENTS, USERDATA_BBB_FORCE_RESTORE_PRESENTATION_ON_NEW_EVENTS), - - # Join - Whiteboard + (USERDATA_BBB_HIDE_PRESENTATION, USERDATA_BBB_HIDE_PRESENTATION), + (USERDATA_BBB_HIDE_PRESENTATION_ON_JOIN, USERDATA_BBB_HIDE_PRESENTATION_ON_JOIN), + (USERDATA_BBB_LISTEN_ONLY_MODE, USERDATA_BBB_LISTEN_ONLY_MODE), + (USERDATA_BBB_MIRROR_OWN_WEBCAM, USERDATA_BBB_MIRROR_OWN_WEBCAM), (USERDATA_BBB_MULTI_USER_PEN_ONLY, USERDATA_BBB_MULTI_USER_PEN_ONLY), - (USERDATA_BBB_PRESENTER_TOOLS, USERDATA_BBB_PRESENTER_TOOLS), (USERDATA_BBB_MULTI_USER_TOOLS, USERDATA_BBB_MULTI_USER_TOOLS), - - # Join - Styling - (USERDATA_BBB_CUSTOM_STYLE, USERDATA_BBB_CUSTOM_STYLE), - (USERDATA_BBB_CUSTOM_STYLE_URL, USERDATA_BBB_CUSTOM_STYLE_URL), - - # Join - Layout - (USERDATA_BBB_AUTO_SWAP_LAYOUT, USERDATA_BBB_AUTO_SWAP_LAYOUT), - (USERDATA_BBB_HIDE_PRESENTATION, USERDATA_BBB_HIDE_PRESENTATION), - (USERDATA_BBB_SHOW_PARTICIPIANTS_ON_LOGIN, USERDATA_BBB_SHOW_PARTICIPIANTS_ON_LOGIN), - (USERDATA_BBB_SHOW_PUBLIC_CHAT_ON_LOGIN, USERDATA_BBB_SHOW_PUBLIC_CHAT_ON_LOGIN), - - # Join - External - (USERDATA_BBB_OUTSIDE_TOGGLE_SELF_VOICE, USERDATA_BBB_OUTSIDE_TOGGLE_SELF_VOICE), (USERDATA_BBB_OUTSIDE_TOGGLE_RECORDING, USERDATA_BBB_OUTSIDE_TOGGLE_RECORDING), + (USERDATA_BBB_OUTSIDE_TOGGLE_SELF_VOICE, USERDATA_BBB_OUTSIDE_TOGGLE_SELF_VOICE), + (USERDATA_BBB_OVERRIDE_DEFAULT_LOCALE, USERDATA_BBB_OVERRIDE_DEFAULT_LOCALE), + (USERDATA_BBB_PREFERRED_CAMERA_PROFILE, USERDATA_BBB_PREFERRED_CAMERA_PROFILE), + (USERDATA_BBB_PRESENTER_TOOLS, USERDATA_BBB_PRESENTER_TOOLS), + (USERDATA_BBB_RECORD_VIDEO, USERDATA_BBB_RECORD_VIDEO), + (USERDATA_BBB_SHORTCUTS, USERDATA_BBB_SHORTCUTS), + (USERDATA_BBB_SHOW_PARTICIPANTS_ON_LOGIN, USERDATA_BBB_SHOW_PARTICIPANTS_ON_LOGIN), + (USERDATA_BBB_SHOW_PUBLIC_CHAT_ON_LOGIN, USERDATA_BBB_SHOW_PUBLIC_CHAT_ON_LOGIN), + (USERDATA_BBB_SKIP_CHECK_AUDIO, USERDATA_BBB_SKIP_CHECK_AUDIO), + (USERDATA_BBB_SKIP_CHECK_AUDIO_ON_FIRST_JOIN, USERDATA_BBB_SKIP_CHECK_AUDIO_ON_FIRST_JOIN), + (USERDATA_BBB_SKIP_VIDEO_PREVIEW, USERDATA_BBB_SKIP_VIDEO_PREVIEW), + (USERDATA_BBB_SKIP_VIDEO_PREVIEW_ON_FIRST_JOIN, USERDATA_BBB_SKIP_VIDEO_PREVIEW_ON_FIRST_JOIN) ) MODE_CHOICES = [ @@ -1136,19 +1104,20 @@ class Parameter(models.Model): PARAMETER_REGEXES = { # Create - WELCOME: ANY_REGEX, - MAX_PARTICIPANTS: NUMBER_REGEX, - LOGOUT_URL: URL_REGEX, - RECORD: BOOLEAN_REGEX, - DISABLED_FEATURES: ANY_REGEX, - DURATION: NUMBER_REGEX, - MODERATOR_ONLY_MESSAGE: ANY_REGEX, - WEBCAMS_ONLY_FOR_MODERATOR: BOOLEAN_REGEX, - BANNER_TEXT: ANY_REGEX, + ALLOW_MODS_TO_UNMUTE_USERS: BOOLEAN_REGEX, + ALLOW_START_STOP_RECORDING: BOOLEAN_REGEX, + AUTO_START_RECORDING: BOOLEAN_REGEX, BANNER_COLOR: COLOR_REGEX, + BANNER_TEXT: ANY_REGEX, COPYRIGHT: ANY_REGEX, - MUTE_ON_START: BOOLEAN_REGEX, - ALLOW_MODS_TO_UNMUTE_USERS: BOOLEAN_REGEX, + DISABLED_FEATURES: ANY_REGEX, + DISABLED_FEATURES_EXCLUDED: ANY_REGEX, + DURATION: NUMBER_REGEX, + END_WHEN_NO_MODERATOR: BOOLEAN_REGEX, + END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES: NUMBER_REGEX, + GROUPS: ANY_REGEX, + GUEST_POLICY: POLICY_REGEX, + LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES: NUMBER_REGEX, LOCK_SETTINGS_DISABLE_CAM: BOOLEAN_REGEX, LOCK_SETTINGS_DISABLE_MIC: BOOLEAN_REGEX, LOCK_SETTINGS_DISABLE_PRIVATE_CHAT: BOOLEAN_REGEX, @@ -1158,77 +1127,60 @@ class Parameter(models.Model): LOCK_SETTINGS_LOCKED_LAYOUT: BOOLEAN_REGEX, LOCK_SETTINGS_LOCK_ON_JOIN: BOOLEAN_REGEX, LOCK_SETTINGS_LOCK_ON_JOIN_CONFIGURABLE: BOOLEAN_REGEX, - AUTO_START_RECORDING: BOOLEAN_REGEX, - ALLOW_START_STOP_RECORDING: BOOLEAN_REGEX, LOGO: URL_REGEX, - GUEST_POLICY: POLICY_REGEX, - GROUPS: ANY_REGEX, - META_FULLAUDIO_BRIDGE: AUDIO_BRIDGE_REGEX, + LOGOUT_URL: URL_REGEX, + MAX_PARTICIPANTS: NUMBER_REGEX, MEETING_CAMERA_CAP: NUMBER_REGEX, MEETING_EXPIRE_IF_NO_USER_JOINED_IN_MINUTES: NUMBER_REGEX, MEETING_EXPIRE_WHEN_LAST_USER_LEFT_IN_MINUTES: NUMBER_REGEX, MEETING_KEEP_EVENT: BOOLEAN_REGEX, + MEETING_LAYOUT: MEETING_LAYOUT_REGEX, + META_FULLAUDIO_BRIDGE: AUDIO_BRIDGE_REGEX, + MODERATOR_ONLY_MESSAGE: ANY_REGEX, + MUTE_ON_START: BOOLEAN_REGEX, NOTIFY_RECORDING_IS_ON: BOOLEAN_REGEX, + PRE_UPLOADED_PRESENTATION_OVERRIDE_DEFAULT: BOOLEAN_REGEX, PRESENTATION_UPLOAD_EXTERNAL_DESCRIPTION: ANY_REGEX, PRESENTATION_UPLOAD_EXTERNAL_URL: URL_REGEX, - END_WHEN_NO_MODERATOR: BOOLEAN_REGEX, - END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES: NUMBER_REGEX, - MEETING_LAYOUT: MEETING_LAYOUT_REGEX, - PRE_UPLOADED_PRESENTATION_OVERRIDE_DEFAULT: BOOLEAN_REGEX, - LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES: NUMBER_REGEX, + RECORD: BOOLEAN_REGEX, + RECORD_FULL_DURATION_MEDIA: BOOLEAN_REGEX, + WEBCAMS_ONLY_FOR_MODERATOR: BOOLEAN_REGEX, + WELCOME: ANY_REGEX, # Join - ROLE: ROLE_REGEX, EXCLUDE_FROM_DASHBOARD: BOOLEAN_REGEX, - - # Join - Application + ROLE: ROLE_REGEX, USERDATA_BBB_ASK_FOR_FEEDBACK_ON_LOGOUT: BOOLEAN_REGEX, USERDATA_BBB_AUTO_JOIN_AUDIO: BOOLEAN_REGEX, + USERDATA_BBB_AUTO_SHARE_WEBCAM: BOOLEAN_REGEX, + USERDATA_BBB_AUTO_SWAP_LAYOUT: BOOLEAN_REGEX, USERDATA_BBB_CLIENT_TITLE: ANY_REGEX, - USERDATA_BBB_FORCE_LISTEN_ONLY: BOOLEAN_REGEX, - USERDATA_BBB_LISTEN_ONLY_MODE: BOOLEAN_REGEX, - USERDATA_BBB_SKIP_CHECK_AUDIO: BOOLEAN_REGEX, - USERDATA_BBB_SKIP_CHECK_AUDIO_ON_FIRST_JOIN: BOOLEAN_REGEX, - USERDATA_BBB_OVERRIDE_DEFAULT_LOCALE: LOCALE_REGEX, - - # Join - Branding + USERDATA_BBB_CUSTOM_STYLE: ANY_REGEX, + USERDATA_BBB_CUSTOM_STYLE_URL: URL_REGEX, USERDATA_BBB_DISPLAY_BRANDING_AREA: BOOLEAN_REGEX, - - # Join - Shortcut - USERDATA_BBB_SHORTCUTS: ANY_REGEX, - - # Join - Kurento - USERDATA_BBB_AUTO_SHARE_WEBCAM: BOOLEAN_REGEX, - USERDATA_BBB_PREFFERED_CAMERA_PROFILE: CAMERA_REGEX, USERDATA_BBB_ENABLE_SCREEN_SHARING: BOOLEAN_REGEX, USERDATA_BBB_ENABLE_VIDEO: BOOLEAN_REGEX, - USERDATA_BBB_RECORD_VIDEO: BOOLEAN_REGEX, - USERDATA_BBB_SKIP_VIDEO_PREVIEW: BOOLEAN_REGEX, - USERDATA_BBB_SKIP_VIDEO_PREVIEW_ON_FIRST_JOIN: BOOLEAN_REGEX, - USERDATA_BBB_MIRROR_OWN_WEBCAM: BOOLEAN_REGEX, - - # Join - Presentation + USERDATA_BBB_FORCE_LISTEN_ONLY: BOOLEAN_REGEX, USERDATA_BBB_FORCE_RESTORE_PRESENTATION_ON_NEW_EVENTS: BOOLEAN_REGEX, - - # Join - Whiteboard + USERDATA_BBB_HIDE_PRESENTATION: BOOLEAN_REGEX, + USERDATA_BBB_HIDE_PRESENTATION_ON_JOIN: BOOLEAN_REGEX, + USERDATA_BBB_LISTEN_ONLY_MODE: BOOLEAN_REGEX, + USERDATA_BBB_MIRROR_OWN_WEBCAM: BOOLEAN_REGEX, USERDATA_BBB_MULTI_USER_PEN_ONLY: BOOLEAN_REGEX, - USERDATA_BBB_PRESENTER_TOOLS: ANY_REGEX, USERDATA_BBB_MULTI_USER_TOOLS: ANY_REGEX, - - # Join - Styling - USERDATA_BBB_CUSTOM_STYLE: ANY_REGEX, - USERDATA_BBB_CUSTOM_STYLE_URL: URL_REGEX, - - # Join - Layout - USERDATA_BBB_AUTO_SWAP_LAYOUT: BOOLEAN_REGEX, - USERDATA_BBB_HIDE_PRESENTATION: BOOLEAN_REGEX, - USERDATA_BBB_SHOW_PARTICIPIANTS_ON_LOGIN: BOOLEAN_REGEX, - USERDATA_BBB_SHOW_PUBLIC_CHAT_ON_LOGIN: BOOLEAN_REGEX, - - # Join - External + USERDATA_BBB_OUTSIDE_TOGGLE_RECORDING: BOOLEAN_REGEX, USERDATA_BBB_OUTSIDE_TOGGLE_SELF_VOICE: BOOLEAN_REGEX, - USERDATA_BBB_OUTSIDE_TOGGLE_RECORDING: BOOLEAN_REGEX - + USERDATA_BBB_OVERRIDE_DEFAULT_LOCALE: LOCALE_REGEX, + USERDATA_BBB_PREFERRED_CAMERA_PROFILE: CAMERA_REGEX, + USERDATA_BBB_PRESENTER_TOOLS: ANY_REGEX, + USERDATA_BBB_RECORD_VIDEO: BOOLEAN_REGEX, + USERDATA_BBB_SHORTCUTS: ANY_REGEX, + USERDATA_BBB_SHOW_PARTICIPANTS_ON_LOGIN: BOOLEAN_REGEX, + USERDATA_BBB_SHOW_PUBLIC_CHAT_ON_LOGIN: BOOLEAN_REGEX, + USERDATA_BBB_SKIP_CHECK_AUDIO: BOOLEAN_REGEX, + USERDATA_BBB_SKIP_CHECK_AUDIO_ON_FIRST_JOIN: BOOLEAN_REGEX, + USERDATA_BBB_SKIP_VIDEO_PREVIEW: BOOLEAN_REGEX, + USERDATA_BBB_SKIP_VIDEO_PREVIEW_ON_FIRST_JOIN: BOOLEAN_REGEX } PARAMETERS_CREATE = [ @@ -1238,10 +1190,14 @@ class Parameter(models.Model): BANNER_COLOR, BANNER_TEXT, COPYRIGHT, + DISABLED_FEATURES, + DISABLED_FEATURES_EXCLUDED, DURATION, END_WHEN_NO_MODERATOR, END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES, + GROUPS, GUEST_POLICY, + LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES, LOCK_SETTINGS_DISABLE_CAM, LOCK_SETTINGS_DISABLE_MIC, LOCK_SETTINGS_DISABLE_PRIVATE_CHAT, @@ -1251,58 +1207,61 @@ class Parameter(models.Model): LOCK_SETTINGS_LOCK_ON_JOIN, LOCK_SETTINGS_LOCK_ON_JOIN_CONFIGURABLE, LOCK_SETTINGS_LOCKED_LAYOUT, - LOGO, LOGOUT_URL, + LOGO, + LOGOUT_URL, MAX_PARTICIPANTS, META_FULLAUDIO_BRIDGE, MEETING_CAMERA_CAP, MEETING_EXPIRE_IF_NO_USER_JOINED_IN_MINUTES, MEETING_EXPIRE_WHEN_LAST_USER_LEFT_IN_MINUTES, MEETING_KEEP_EVENT, + MEETING_LAYOUT, MODERATOR_ONLY_MESSAGE, MUTE_ON_START, NOTIFY_RECORDING_IS_ON, + PRE_UPLOADED_PRESENTATION_OVERRIDE_DEFAULT, PRESENTATION_UPLOAD_EXTERNAL_DESCRIPTION, PRESENTATION_UPLOAD_EXTERNAL_URL, + RECORD, + RECORD_FULL_DURATION_MEDIA, WEBCAMS_ONLY_FOR_MODERATOR, - WELCOME, - MEETING_LAYOUT, - LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES, - PRE_UPLOADED_PRESENTATION_OVERRIDE_DEFAULT + WELCOME ] PARAMETERS_JOIN = [ + EXCLUDE_FROM_DASHBOARD, + ROLE, USERDATA_BBB_ASK_FOR_FEEDBACK_ON_LOGOUT, USERDATA_BBB_AUTO_JOIN_AUDIO, + USERDATA_BBB_AUTO_SHARE_WEBCAM, + USERDATA_BBB_AUTO_SWAP_LAYOUT, USERDATA_BBB_CLIENT_TITLE, - USERDATA_BBB_FORCE_LISTEN_ONLY, - USERDATA_BBB_LISTEN_ONLY_MODE, - USERDATA_BBB_SKIP_CHECK_AUDIO, - USERDATA_BBB_SKIP_CHECK_AUDIO_ON_FIRST_JOIN, - USERDATA_BBB_OVERRIDE_DEFAULT_LOCALE, + USERDATA_BBB_CUSTOM_STYLE, + USERDATA_BBB_CUSTOM_STYLE_URL, USERDATA_BBB_DISPLAY_BRANDING_AREA, - USERDATA_BBB_SHORTCUTS, - USERDATA_BBB_AUTO_SHARE_WEBCAM, - USERDATA_BBB_PREFFERED_CAMERA_PROFILE, USERDATA_BBB_ENABLE_SCREEN_SHARING, USERDATA_BBB_ENABLE_VIDEO, - USERDATA_BBB_RECORD_VIDEO, - USERDATA_BBB_SKIP_VIDEO_PREVIEW, - USERDATA_BBB_SKIP_VIDEO_PREVIEW_ON_FIRST_JOIN, - USERDATA_BBB_MIRROR_OWN_WEBCAM, + USERDATA_BBB_FORCE_LISTEN_ONLY, USERDATA_BBB_FORCE_RESTORE_PRESENTATION_ON_NEW_EVENTS, + USERDATA_BBB_HIDE_PRESENTATION, + USERDATA_BBB_HIDE_PRESENTATION_ON_JOIN, + USERDATA_BBB_MIRROR_OWN_WEBCAM, USERDATA_BBB_MULTI_USER_PEN_ONLY, - USERDATA_BBB_PRESENTER_TOOLS, USERDATA_BBB_MULTI_USER_TOOLS, - USERDATA_BBB_CUSTOM_STYLE, - USERDATA_BBB_CUSTOM_STYLE_URL, - USERDATA_BBB_AUTO_SWAP_LAYOUT, - USERDATA_BBB_HIDE_PRESENTATION, - USERDATA_BBB_SHOW_PARTICIPIANTS_ON_LOGIN, - USERDATA_BBB_SHOW_PUBLIC_CHAT_ON_LOGIN, USERDATA_BBB_OUTSIDE_TOGGLE_SELF_VOICE, USERDATA_BBB_OUTSIDE_TOGGLE_RECORDING, - ROLE, - EXCLUDE_FROM_DASHBOARD + USERDATA_BBB_OVERRIDE_DEFAULT_LOCALE, + USERDATA_BBB_PREFERRED_CAMERA_PROFILE, + USERDATA_BBB_PRESENTER_TOOLS, + USERDATA_BBB_LISTEN_ONLY_MODE, + USERDATA_BBB_RECORD_VIDEO, + USERDATA_BBB_SHORTCUTS, + USERDATA_BBB_SHOW_PARTICIPANTS_ON_LOGIN, + USERDATA_BBB_SHOW_PUBLIC_CHAT_ON_LOGIN, + USERDATA_BBB_SKIP_CHECK_AUDIO, + USERDATA_BBB_SKIP_CHECK_AUDIO_ON_FIRST_JOIN, + USERDATA_BBB_SKIP_VIDEO_PREVIEW, + USERDATA_BBB_SKIP_VIDEO_PREVIEW_ON_FIRST_JOIN ] mode = models.CharField(max_length=10, choices=MODE_CHOICES) From c2b05745fc0b50c688287c01d8181ee1a4fc7b4d Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Thu, 29 Jun 2023 14:23:53 +0200 Subject: [PATCH 02/11] modules: bump versions of dependencies --- b3lb/requirements.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/b3lb/requirements.txt b/b3lb/requirements.txt index 8847dc2..9d465a8 100644 --- a/b3lb/requirements.txt +++ b/b3lb/requirements.txt @@ -1,21 +1,21 @@ # b3lb v3.1 - Delay update to Django 4.x because of dependencies -Django==3.2.18 +Django==3.2.19 PyGObject==3.44.1 aiohttp==3.8.4 -boto3==1.26.119 -django-extensions==3.2.1 -celery==5.2.7 +boto3==1.26.163 +django-extensions==3.2.3 +celery==5.3.1 celery-singleton==0.3.1 -django-cacheops==7.0 +django-cacheops==7.0.1 django-db-file-storage==0.5.5 django-celery-admin==0.1 django-celery-beat==2.5.0 -django-celery-results==2.5.0 +django-celery-results==2.5.1 django-environ==0.10.0 -django-redis==5.2.0 +django-redis==5.3.0 django-storages==1.13.2 intervaltree==3.1.0 psycopg2cffi==2.9.0 -requests==2.28.2 -uvicorn[standard]==0.21.1 +requests==2.31.0 +uvicorn[standard]==0.22.0 xmltodict==0.13.0 \ No newline at end of file From b3ebe258d64838ab18f7853e97676ff2b0ee43ec Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Thu, 29 Jun 2023 16:19:00 +0200 Subject: [PATCH 03/11] sha: move sha (checksum) configuration from cluster_group to clusters --- b3lb/rest/b3lb/utils.py | 5 ++ b3lb/rest/classes/api.py | 41 ++++++----------- b3lb/rest/classes/checks.py | 8 ++-- .../0013_update_sha_and_parameters.py | 29 ++++++++++++ .../0014_remove_clustergroup_sha_function.py | 17 +++++++ b3lb/rest/models.py | 46 ++++++++++--------- 6 files changed, 93 insertions(+), 53 deletions(-) create mode 100644 b3lb/rest/migrations/0013_update_sha_and_parameters.py create mode 100644 b3lb/rest/migrations/0014_remove_clustergroup_sha_function.py diff --git a/b3lb/rest/b3lb/utils.py b/b3lb/rest/b3lb/utils.py index 46e49ed..2bd59b8 100644 --- a/b3lb/rest/b3lb/utils.py +++ b/b3lb/rest/b3lb/utils.py @@ -15,9 +15,14 @@ # along with this program. If not, see . # This utils file contains functions without import of b3lb files to prevent circular imports +from _hashlib import HASH from xml.sax.saxutils import escape +def get_checksum(sha: HASH, url_string: str) -> str: + sha.update(url_string.encode()) + return sha.hexdigest() + def xml_escape(string: str) -> str: if isinstance(string, str): return escape(string) diff --git a/b3lb/rest/classes/api.py b/b3lb/rest/classes/api.py index ac1db64..febaa6d 100644 --- a/b3lb/rest/classes/api.py +++ b/b3lb/rest/classes/api.py @@ -27,24 +27,20 @@ from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, HttpResponseBadRequest, HttpResponseForbidden from django.template.loader import render_to_string from json import dumps -from hashlib import sha1, sha256, sha384, sha512 +from _hashlib import HASH from random import randint from re import compile, escape from requests import get from rest.b3lb.metrics import incr_metric, update_create_metrics -from rest.models import ClusterGroup, ClusterGroupRelation, Meeting, Metric, Node, Parameter, Record, RecordSet, Secret, SecretMeetingList, SecretMetricsList, Stats, RECORD_PROFILE_DESCRIPTION_LENGTH, MEETING_NAME_LENGTH +from rest.b3lb.utils import get_checksum +from rest.models import Cluster, ClusterGroupRelation, Meeting, Metric, Node, Parameter, Record, RecordSet, Secret, SecretMeetingList, SecretMetricsList, Stats, RECORD_PROFILE_DESCRIPTION_LENGTH, MEETING_NAME_LENGTH from typing import Any, Dict, List, Literal, Union from urllib.parse import urlencode from xmltodict import parse -HOST_REGEX = compile(r'([^:]+)(:\d+)?$') -SLUG_REGEX = compile(r'^([a-z]{2,10})(-(\d{3}))?\.' + escape(settings.B3LB_API_BASE_DOMAIN) + '$') CONTENT_TYPE = "text/xml" - -SHA_ALGORITHMS_BY_LENGTH = {40: ClusterGroup.SHA1, 64: ClusterGroup.SHA256, 96: ClusterGroup.SHA384, 128: ClusterGroup.SHA512} -SHA_ALGORITHMS_BY_STRING = {ClusterGroup.SHA1: sha1, ClusterGroup.SHA256: sha256, ClusterGroup.SHA384: sha384, ClusterGroup.SHA512: sha512} - +HOST_REGEX = compile(r'([^:]+)(:\d+)?$') RETURN_STRING_GET_MEETINGS_NO_MEETINGS = '\r\nSUCCESS\r\n\r\nnoMeetings\r\nno meetings were found on this server\r\n' RETURN_STRING_VERSION = '\r\nSUCCESS\r\n2.0\r\n2.0\r\n\r\n' RETURN_STRING_CREATE_LIMIT_REACHED = '\r\nFAILED\r\nMeeting/Attendee limit reached.\r\n' @@ -59,6 +55,7 @@ RETURN_STRING_RECORD_PUBLISHED = '\r\nSUCCESS\r\n{}\r\n' RETURN_STRING_RECORD_DELETED = '\r\nSUCCESS\r\ntrue\r\n' RETURN_STRING_RECORD_UPDATED = '\r\nSUCCESS\r\ntrue\r\n' +SLUG_REGEX = compile(r'^([a-z]{2,10})(-(\d{3}))?\.' + escape(settings.B3LB_API_BASE_DOMAIN) + '$') class ClientB3lbRequest: @@ -328,12 +325,10 @@ def check_checksum(self) -> bool: if not algorithm: return False - sha = algorithm() endpoint_string = f"{self.endpoint}{self.get_query_string()}" for secret in [self.secret.secret, self.secret.secret2]: - sha.update(f"{endpoint_string}{secret}".encode()) - if sha.hexdigest() == self.checksum: + if get_checksum(algorithm, f"{endpoint_string}{secret}") == self.checksum: return True return False @@ -445,13 +440,11 @@ def get_meeting_defaults(self) -> Dict[str, Any]: return {"id": self.meeting_id, "secret": self.secret, "node": self.node, "room_name": self.parameters.get("name", "Unknown"), "end_callback_url": self.parameters.get("meta_endCallbackUrl", "")} def get_node_endpoint_url(self) -> str: - sha = self.get_sha_algorithm_by_cluster_group()() parameter_str = "" if self.parameters: parameter_str = urlencode(self.parameters, safe='*') - - sha.update(f"{self.endpoint}{parameter_str}{self.node.secret}".encode()) - return f"{self.node.api_base_url}{self.endpoint}?{parameter_str}&checksum={sha.hexdigest()}" + print(get_checksum(self.node.cluster.get_sha(), f'{self.endpoint}{parameter_str}{self.node.secret}')) + return f"{self.node.api_base_url}{self.endpoint}?{parameter_str}&checksum={get_checksum(self.node.cluster.get_sha(), f'{self.endpoint}{parameter_str}{self.node.secret}')}" def get_node_endpoint_url_encoded(self) -> URL: return URL(self.get_node_endpoint_url(), encoded=True) @@ -476,22 +469,18 @@ def get_secret_metrics(self) -> str: return SecretMetricsList.objects.get(secret=self.secret).metrics @staticmethod - def get_sha_algorithm(sha: str) -> Any: - if sha not in settings.B3LB_ALLOWED_SHA_ALGORITHMS or sha not in SHA_ALGORITHMS_BY_STRING: + def get_sha_algorithm(sha: str) -> Union[HASH, None]: + if sha not in settings.B3LB_ALLOWED_SHA_ALGORITHMS or sha not in Cluster.SHA_BY_STRING: return None - return SHA_ALGORITHMS_BY_STRING[sha] - - def get_sha_algorithm_by_cluster_group(self) -> Any: - cluster_group = ClusterGroupRelation.objects.filter(cluster=self.node.cluster) - return self.get_sha_algorithm(cluster_group[0].cluster_group.sha_function) + return Cluster.SHA_BY_STRING.get(sha)() - def get_sha_algorithm_by_checksum_length(self) -> Any: + def get_sha_algorithm_by_checksum_length(self) -> Union[HASH, None]: check_length = len(self.checksum) - if check_length not in SHA_ALGORITHMS_BY_LENGTH: + if check_length not in Cluster.SHA_BY_LENGTH: return self.get_sha_algorithm("") - return self.get_sha_algorithm(SHA_ALGORITHMS_BY_LENGTH[check_length]) + return Cluster.SHA_BY_LENGTH.get(check_length)() - def get_sha_algorithm_by_parameter(self) -> Any: + def get_sha_algorithm_by_parameter(self) -> Union[HASH, None]: return self.get_sha_algorithm(self.parameters.get("checksumHash", "")) def get_tenant_statistic(self) -> str: diff --git a/b3lb/rest/classes/checks.py b/b3lb/rest/classes/checks.py index eb4c923..a7614f3 100644 --- a/b3lb/rest/classes/checks.py +++ b/b3lb/rest/classes/checks.py @@ -1,5 +1,5 @@ -from rest.classes.api import SHA_ALGORITHMS_BY_STRING -from rest.models import Node, ClusterGroupRelation +from rest.b3lb.utils import get_checksum +from rest.models import Node from typing import Any, Dict, List @@ -21,9 +21,7 @@ def add_meeting_to_stats(self, meeting_id: str): self.meeting_stats[meeting_id][param] = "" def get_meetings_url(self) -> str: - sha = SHA_ALGORITHMS_BY_STRING[ClusterGroupRelation.objects.filter(cluster=self.node.cluster)[0].cluster_group.sha_function]() - sha.update(f"getMeetings{self.node.secret}".encode()) - return f"{self.node.api_base_url}getMeetings?checksum={sha.hexdigest()}" + return f"{self.node.api_base_url}getMeetings?checksum={get_checksum(self.node.cluster.get_sha(), f'getMeetings{self.node.secret}')}" def __init__(self, node: Node): self.node = node diff --git a/b3lb/rest/migrations/0013_update_sha_and_parameters.py b/b3lb/rest/migrations/0013_update_sha_and_parameters.py new file mode 100644 index 0000000..c56d168 --- /dev/null +++ b/b3lb/rest/migrations/0013_update_sha_and_parameters.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.19 on 2023-06-29 13:16 + +from django.db import migrations, models + +def preserve_sha_configuration(apps, schema_editor): + ClusterGroupRelation = apps.get_model("rest", "ClusterGroupRelation") + for cluster_group_relation in ClusterGroupRelation.objects.all(): + cluster_group_relation.cluster.sha_function = cluster_group_relation.cluster_group.sha_function + cluster_group_relation.cluster.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('rest', '0012_add_recordings_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='cluster', + name='sha_function', + field=models.CharField(choices=[('sha1', 'sha1'), ('sha256', 'sha256'), ('sha384', 'sha384'), ('sha512', 'sha512')], default='sha256', max_length=6), + ), + migrations.RunPython(preserve_sha_configuration, migrations.RunPython.noop), + migrations.AlterField( + model_name='parameter', + name='parameter', + field=models.CharField(choices=[('allowModsToUnmuteUsers', 'allowModsToUnmuteUsers'), ('allowStartStopRecording', 'allowStartStopRecording'), ('autoStartRecording', 'autoStartRecording'), ('bannerColor', 'bannerColor'), ('bannerText', 'bannerText'), ('copyright', 'copyright'), ('disabledFeatures', 'disabledFeatures'), ('disabledFeaturesExclude', 'disabledFeaturesExclude'), ('duration', 'duration'), ('endWhenNoModerator', 'endWhenNoModerator'), ('endWhenNoModeratorDelayInMinutes', 'endWhenNoModeratorDelayInMinutes'), ('groups', 'groups'), ('guestPolicy', 'guestPolicy'), ('learningDashboardCleanupDelayInMinutes', 'learningDashboardCleanupDelayInMinutes'), ('lockSettingsDisableCam', 'lockSettingsDisableCam'), ('lockSettingsDisableMic', 'lockSettingsDisableMic'), ('lockSettingsDisablePrivateChat', 'lockSettingsDisablePrivateChat'), ('lockSettingsDisablePublicChat', 'lockSettingsDisablePublicChat'), ('lockSettingsDisableNote', 'lockSettingsDisableNote'), ('lockSettingsHideViewersCursor', 'lockSettingsHideViewersCursor'), ('lockSettingsLockOnJoin', 'lockSettingsLockOnJoin'), ('lockSettingsLockOnJoinConfigurable', 'lockSettingsLockOnJoinConfigurable'), ('lockSettingsLockedLayout', 'lockSettingsLockedLayout'), ('logo', 'logo'), ('logoutURL', 'logoutURL'), ('maxParticipants', 'maxParticipants'), ('meetingCameraCap', 'meetingCameraCap'), ('meetingExpireIfNoUserJoinedInMinutes', 'meetingExpireIfNoUserJoinedInMinutes'), ('meetingExpireWhenLastUserLeftInMinutes', 'meetingExpireWhenLastUserLeftInMinutes'), ('meetingKeepEvents', 'meetingKeepEvents'), ('meetingLayout', 'meetingLayout'), ('meta_fullaudio-bridge', 'meta_fullaudio-bridge'), ('moderatorOnlyMessage', 'moderatorOnlyMessage'), ('muteOnStart', 'muteOnStart'), ('notifyRecordingIsOn', 'notifyRecordingIsOn'), ('preUploadedPresentationOverrideDefault', 'preUploadedPresentationOverrideDefault'), ('presentationUploadExternalDescription', 'presentationUploadExternalDescription'), ('presentationUploadExternalUrl', 'presentationUploadExternalUrl'), ('record', 'record'), ('recordFullDurationMedia', 'recordFullDurationMedia'), ('webcamsOnlyForModerator', 'webcamsOnlyForModerator'), ('welcome', 'welcome'), ('excludeFromDashboard', 'excludeFromDashboard'), ('role', 'role'), ('userdata-bbb_ask_for_feedback_on_logout', 'userdata-bbb_ask_for_feedback_on_logout'), ('userdata-bbb_auto_join_audio', 'userdata-bbb_auto_join_audio'), ('userdata-bbb_auto_share_webcam', 'userdata-bbb_auto_share_webcam'), ('userdata-bbb_auto_swap_layout', 'userdata-bbb_auto_swap_layout'), ('userdata-bbb_client_title', 'userdata-bbb_client_title'), ('userdata-bbb_custom_style', 'userdata-bbb_custom_style'), ('userdata-bbb_custom_style_url', 'userdata-bbb_custom_style_url'), ('userdata-bbb_display_branding_area', 'userdata-bbb_display_branding_area'), ('userdata-bbb_enable_screen_sharing', 'userdata-bbb_enable_screen_sharing'), ('userdata-bbb_enable_video', 'userdata-bbb_enable_video'), ('userdata-bbb_force_listen_only', 'userdata-bbb_force_listen_only'), ('userdata-bbb_force_restore_presentation_on_new_events', 'userdata-bbb_force_restore_presentation_on_new_events'), ('userdata-bbb_hide_presentation', 'userdata-bbb_hide_presentation'), ('userdata-bbb_hide_presentation_on_join', 'userdata-bbb_hide_presentation_on_join'), ('userdata-bbb_listen_only_mode', 'userdata-bbb_listen_only_mode'), ('userdata-bbb_mirror_own_webcam', 'userdata-bbb_mirror_own_webcam'), ('userdata-bbb_multi_user_pen_only', 'userdata-bbb_multi_user_pen_only'), ('userdata-bbb_multi_user_tools', 'userdata-bbb_multi_user_tools'), ('userdata-bbb_outside_toggle_recording', 'userdata-bbb_outside_toggle_recording'), ('userdata-bbb_outside_toggle_self_voice', 'userdata-bbb_outside_toggle_self_voice'), ('userdata-bbb_override_default_locale', 'userdata-bbb_override_default_locale'), ('userdata-bbb_preferred_camera_profile', 'userdata-bbb_preferred_camera_profile'), ('userdata-bbb_presenter_tools', 'userdata-bbb_presenter_tools'), ('userdata-bbb_record_video', 'userdata-bbb_record_video'), ('userdata-bbb_shortcuts', 'userdata-bbb_shortcuts'), ('userdata-bbb_show_participants_on_login', 'userdata-bbb_show_participants_on_login'), ('userdata-bbb_show_public_chat_on_login', 'userdata-bbb_show_public_chat_on_login'), ('userdata-bbb_skip_check_audio', 'userdata-bbb_skip_check_audio'), ('userdata-bbb_skip_check_audio_on_first_join', 'userdata-bbb_skip_check_audio_on_first_join'), ('userdata-bbb_skip_video_preview', 'userdata-bbb_skip_video_preview'), ('userdata-bbb_skip_video_preview_on_first_join', 'userdata-bbb_skip_video_preview_on_first_join')], max_length=64), + ), + ] diff --git a/b3lb/rest/migrations/0014_remove_clustergroup_sha_function.py b/b3lb/rest/migrations/0014_remove_clustergroup_sha_function.py new file mode 100644 index 0000000..60ad4c5 --- /dev/null +++ b/b3lb/rest/migrations/0014_remove_clustergroup_sha_function.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.19 on 2023-06-29 13:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('rest', '0013_update_sha_and_parameters'), + ] + + operations = [ + migrations.RemoveField( + model_name='clustergroup', + name='sha_function', + ), + ] diff --git a/b3lb/rest/models.py b/b3lb/rest/models.py index 2d5ea88..52a4032 100644 --- a/b3lb/rest/models.py +++ b/b3lb/rest/models.py @@ -27,6 +27,8 @@ from django.utils.html import format_html from django.utils.http import urlencode from django.urls import reverse +from hashlib import sha1, sha256, sha384, sha512 +from _hashlib import HASH from math import pow from os.path import join from re import match @@ -35,7 +37,7 @@ from rest.classes.storage import DBStorage from storages.backends.s3boto3 import ClientError, S3Boto3Storage from textwrap import wrap -from typing import Any, Dict +from typing import Any, Dict, Union import uuid as uid # @@ -92,23 +94,36 @@ def set_cluster_nodes_to_maintenance(modeladmin, request, queryset): # MODELS # class Cluster(models.Model): + SHA1 = "sha1" + SHA256 = "sha256" + SHA384 = "sha384" + SHA512 = "sha512" + + SHA_BY_LENGTH: Dict[int, HASH] = {40: sha1, 64: sha256, 96: sha384, 128: sha512} + SHA_BY_STRING: Dict[str, HASH] = {SHA1: sha1, SHA256: sha256, SHA384: sha384, SHA512: sha512} + SHA_CHOICES = [(SHA1, SHA1), (SHA256, SHA256), (SHA384, SHA384), (SHA512, SHA512)] + uuid = models.UUIDField(primary_key=True, editable=False, unique=True, default=uid.uuid4) name = models.CharField(max_length=100, help_text="cluster name", unique=True) load_a_factor = models.FloatField(default=1.0, help_text="per attendee load factor") load_m_factor = models.FloatField(default=30.0, help_text="per meeting load factor") load_cpu_iterations = models.IntegerField(default=6, help_text="max sum iteration") load_cpu_max = models.IntegerField(default=5000, help_text="max cpu load") + sha_function = models.CharField(max_length=6, choices=SHA_CHOICES, default=SHA256) + + class Meta(object): + ordering = ['name'] def __str__(self): return self.name - class Meta(object): - ordering = ['name'] + def get_sha(self) -> HASH: + return self.SHA_BY_STRING.get(self.sha_function)() class ClusterAdmin(admin.ModelAdmin): model = Cluster - list_display = ['name', 'number_of_nodes', 'available_nodes', 'maintenance_nodes', 'error_nodes', 'a_factor', 'm_factor', 'cpu_iterations', 'cpu_max'] + list_display = ['name', 'sha_function', 'number_of_nodes', 'available_nodes', 'maintenance_nodes', 'error_nodes', 'a_factor', 'm_factor', 'cpu_iterations', 'cpu_max'] actions = [set_cluster_nodes_to_active, set_cluster_nodes_to_maintenance] def a_factor(self, obj): @@ -186,10 +201,6 @@ def __str__(self): def api_base_url(self): return "{}{}.{}/{}".format(settings.B3LB_NODE_PROTOCOL, self.slug, self.domain, settings.B3LB_NODE_BBB_ENDPOINT) - @property - def load_base_url(self): - return "{}{}.{}/{}".format(settings.B3LB_NODE_PROTOCOL, self.slug, self.domain, settings.B3LB_NODE_LOAD_ENDPOINT) - @property def load(self): if self.maintenance: @@ -216,6 +227,10 @@ def load(self): # return total synthetic load return int(work_attendees + work_meetings + work_cpu) + @property + def load_base_url(self): + return "{}{}.{}/{}".format(settings.B3LB_NODE_PROTOCOL, self.slug, self.domain, settings.B3LB_NODE_LOAD_ENDPOINT) + @admin.action(description="Set Node to maintenance") def maintenance_on(modeladmin, request, queryset): @@ -274,22 +289,9 @@ def get_random_secret(): class ClusterGroup(models.Model): - SHA1 = "sha1" - SHA256 = "sha256" - SHA384 = "sha384" - SHA512 = "sha512" - - SHA_CHOICES = [ - (SHA1, SHA1), - (SHA256, SHA256), - (SHA384, SHA384), - (SHA512, SHA512) - ] - uuid = models.UUIDField(primary_key=True, editable=False, unique=True, default=uid.uuid4) name = models.CharField(max_length=100, help_text="Cluster name", unique=True) description = models.CharField(max_length=255, help_text="Cluster description", null=True) - sha_function = models.CharField(max_length=6, choices=SHA_CHOICES, default=SHA256) class Meta(object): ordering = ['name'] @@ -300,7 +302,7 @@ def __str__(self): class ClusterGroupAdmin(admin.ModelAdmin): model = ClusterGroup - list_display = ['name', 'description', 'sha_function', 'number_of_nodes', 'available_nodes', 'maintenance_nodes', 'error_nodes'] + list_display = ['name', 'description', 'number_of_nodes', 'available_nodes', 'maintenance_nodes', 'error_nodes'] def number_of_nodes(self, obj): count = 0 From 534db25fa1b2a6ac90078fcda0c1624ddc2862bb Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Thu, 29 Jun 2023 16:31:01 +0200 Subject: [PATCH 04/11] CHANGELOG.md: update for B3LB v3.1.0 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1bd518..e04eec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # ChangeLog +## 3.1.0 - 2023-06-29 + +Changes: +- add new BBB 2.6 API parameters + - notifyRecordingIsOn + - prsentationUploadExternalUrl + - prsentationUploadExternalDescription + - recordFullDurationMedia (v2.6.9) + - disabledFeaturesExclude(2.6.9) + - userdata-bbb_hide_presentation_on_join +- move configuration of checksum hash function from `ClusterGroups` to `Clusters` + ## 3.0.7 - 2023-06-02 Fixes: From 3daed6fb7e15364bd27a705768e8396bec4b7acc Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Thu, 29 Jun 2023 16:32:31 +0200 Subject: [PATCH 05/11] debug: remove debug statement --- b3lb/rest/classes/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/b3lb/rest/classes/api.py b/b3lb/rest/classes/api.py index febaa6d..1404c0d 100644 --- a/b3lb/rest/classes/api.py +++ b/b3lb/rest/classes/api.py @@ -443,7 +443,6 @@ def get_node_endpoint_url(self) -> str: parameter_str = "" if self.parameters: parameter_str = urlencode(self.parameters, safe='*') - print(get_checksum(self.node.cluster.get_sha(), f'{self.endpoint}{parameter_str}{self.node.secret}')) return f"{self.node.api_base_url}{self.endpoint}?{parameter_str}&checksum={get_checksum(self.node.cluster.get_sha(), f'{self.endpoint}{parameter_str}{self.node.secret}')}" def get_node_endpoint_url_encoded(self) -> URL: From 2d6d2b36192353b2578093b51d473559c41ebd5d Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Fri, 30 Jun 2023 09:44:02 +0200 Subject: [PATCH 06/11] review: optimise get_sha_algorithm_by_checksum_length and update CHANGELOG.md --- CHANGELOG.md | 3 ++- b3lb/rest/classes/api.py | 9 +++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e04eec5..b8c1dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ Changes: - recordFullDurationMedia (v2.6.9) - disabledFeaturesExclude(2.6.9) - userdata-bbb_hide_presentation_on_join -- move configuration of checksum hash function from `ClusterGroups` to `Clusters` +- move configuration of checksum hash function from `ClusterGroups` to `Clusters` +- bump dependencies ## 3.0.7 - 2023-06-02 diff --git a/b3lb/rest/classes/api.py b/b3lb/rest/classes/api.py index 1404c0d..b0d0a5d 100644 --- a/b3lb/rest/classes/api.py +++ b/b3lb/rest/classes/api.py @@ -328,7 +328,7 @@ def check_checksum(self) -> bool: endpoint_string = f"{self.endpoint}{self.get_query_string()}" for secret in [self.secret.secret, self.secret.secret2]: - if get_checksum(algorithm, f"{endpoint_string}{secret}") == self.checksum: + if get_checksum(algorithm(), f"{endpoint_string}{secret}") == self.checksum: return True return False @@ -471,13 +471,10 @@ def get_secret_metrics(self) -> str: def get_sha_algorithm(sha: str) -> Union[HASH, None]: if sha not in settings.B3LB_ALLOWED_SHA_ALGORITHMS or sha not in Cluster.SHA_BY_STRING: return None - return Cluster.SHA_BY_STRING.get(sha)() + return Cluster.SHA_BY_STRING.get(sha) def get_sha_algorithm_by_checksum_length(self) -> Union[HASH, None]: - check_length = len(self.checksum) - if check_length not in Cluster.SHA_BY_LENGTH: - return self.get_sha_algorithm("") - return Cluster.SHA_BY_LENGTH.get(check_length)() + return Cluster.SHA_BY_LENGTH.get(len(self.checksum)) def get_sha_algorithm_by_parameter(self) -> Union[HASH, None]: return self.get_sha_algorithm(self.parameters.get("checksumHash", "")) From 69a7d0173953918e3db0d2463e346f52d0c22350 Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Fri, 30 Jun 2023 11:05:49 +0200 Subject: [PATCH 07/11] checksum: optimize check for allowed sha functions --- b3lb/rest/classes/api.py | 20 +++++++------------- b3lb/rest/models.py | 27 +++++++++++++++++++-------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/b3lb/rest/classes/api.py b/b3lb/rest/classes/api.py index b0d0a5d..ada7743 100644 --- a/b3lb/rest/classes/api.py +++ b/b3lb/rest/classes/api.py @@ -33,7 +33,7 @@ from requests import get from rest.b3lb.metrics import incr_metric, update_create_metrics from rest.b3lb.utils import get_checksum -from rest.models import Cluster, ClusterGroupRelation, Meeting, Metric, Node, Parameter, Record, RecordSet, Secret, SecretMeetingList, SecretMetricsList, Stats, RECORD_PROFILE_DESCRIPTION_LENGTH, MEETING_NAME_LENGTH +from rest.models import ClusterGroupRelation, Meeting, Metric, Node, Parameter, Record, RecordSet, Secret, SecretMeetingList, SecretMetricsList, Stats, MEETING_NAME_LENGTH, RECORD_PROFILE_DESCRIPTION_LENGTH, SHA_ALGORITHMS from typing import Any, Dict, List, Literal, Union from urllib.parse import urlencode from xmltodict import parse @@ -319,9 +319,9 @@ def delete_recordings_by_recording_id(self, recording_id: str = ""): ## Check Routines ## def check_checksum(self) -> bool: - algorithm = self.get_sha_algorithm_by_parameter() + algorithm = self.get_sha_by_parameter() if not algorithm: - algorithm = self.get_sha_algorithm_by_checksum_length() + algorithm = self.get_sha_by_length() if not algorithm: return False @@ -467,17 +467,11 @@ def get_recording_dicts(self, records: List[Dict[str, Any]], meeting_id: str = " def get_secret_metrics(self) -> str: return SecretMetricsList.objects.get(secret=self.secret).metrics - @staticmethod - def get_sha_algorithm(sha: str) -> Union[HASH, None]: - if sha not in settings.B3LB_ALLOWED_SHA_ALGORITHMS or sha not in Cluster.SHA_BY_STRING: - return None - return Cluster.SHA_BY_STRING.get(sha) - - def get_sha_algorithm_by_checksum_length(self) -> Union[HASH, None]: - return Cluster.SHA_BY_LENGTH.get(len(self.checksum)) + def get_sha_by_length(self) -> Union[HASH, None]: + return SHA_ALGORITHMS.get(len(self.checksum)) - def get_sha_algorithm_by_parameter(self) -> Union[HASH, None]: - return self.get_sha_algorithm(self.parameters.get("checksumHash", "")) + def get_sha_by_parameter(self) -> Union[HASH, None]: + return SHA_ALGORITHMS.get(self.parameters.get("checksumHash", "")) def get_tenant_statistic(self) -> str: statistic = {} diff --git a/b3lb/rest/models.py b/b3lb/rest/models.py index 52a4032..2d9ae2f 100644 --- a/b3lb/rest/models.py +++ b/b3lb/rest/models.py @@ -49,6 +49,24 @@ NONCE_CHAR_POOL = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@*(-_)' NONCE_LENGTH = 64 RECORD_PROFILE_DESCRIPTION_LENGTH = 255 +SHA1 = "sha1" +SHA256 = "sha256" +SHA384 = "sha384" +SHA512 = "sha512" +SHA_BY_STRING: Dict[Any, HASH] = {SHA1: sha1, SHA256: sha256, SHA384: sha384, SHA512: sha512} +SHA_ALGORITHMS: Dict[Any, HASH] = {} +if SHA1 in settings.B3LB_ALLOWED_SHA_ALGORITHMS: + SHA_ALGORITHMS[40] = sha1 + SHA_ALGORITHMS[SHA1] = sha1 +if SHA256 in settings.B3LB_ALLOWED_SHA_ALGORITHMS: + SHA_ALGORITHMS[64] = sha256 + SHA_ALGORITHMS[SHA256] = sha256 +if SHA384 in settings.B3LB_ALLOWED_SHA_ALGORITHMS: + SHA_ALGORITHMS[96] = sha384 + SHA_ALGORITHMS[SHA384] = sha384 +if SHA512 in settings.B3LB_ALLOWED_SHA_ALGORITHMS: + SHA_ALGORITHMS[128] = sha512 + SHA_ALGORITHMS[SHA512] = sha512 # @@ -94,13 +112,6 @@ def set_cluster_nodes_to_maintenance(modeladmin, request, queryset): # MODELS # class Cluster(models.Model): - SHA1 = "sha1" - SHA256 = "sha256" - SHA384 = "sha384" - SHA512 = "sha512" - - SHA_BY_LENGTH: Dict[int, HASH] = {40: sha1, 64: sha256, 96: sha384, 128: sha512} - SHA_BY_STRING: Dict[str, HASH] = {SHA1: sha1, SHA256: sha256, SHA384: sha384, SHA512: sha512} SHA_CHOICES = [(SHA1, SHA1), (SHA256, SHA256), (SHA384, SHA384), (SHA512, SHA512)] uuid = models.UUIDField(primary_key=True, editable=False, unique=True, default=uid.uuid4) @@ -118,7 +129,7 @@ def __str__(self): return self.name def get_sha(self) -> HASH: - return self.SHA_BY_STRING.get(self.sha_function)() + return SHA_BY_STRING.get(self.sha_function)() class ClusterAdmin(admin.ModelAdmin): From a32cbf4ffbf65a1992b6095c2b28e1b2a3eb5785 Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Fri, 7 Jul 2023 16:08:17 +0200 Subject: [PATCH 08/11] constants: move constants to seperate file --- b3lb/rest/b3lb/contants.py | 42 ++++++++++++++++++++ b3lb/rest/classes/api.py | 81 +++++++++++++++----------------------- b3lb/rest/models.py | 66 ++++++++++--------------------- b3lb/rest/task/core.py | 4 +- b3lb/rest/views.py | 2 +- 5 files changed, 95 insertions(+), 100 deletions(-) create mode 100644 b3lb/rest/b3lb/contants.py diff --git a/b3lb/rest/b3lb/contants.py b/b3lb/rest/b3lb/contants.py new file mode 100644 index 0000000..0d12bc4 --- /dev/null +++ b/b3lb/rest/b3lb/contants.py @@ -0,0 +1,42 @@ +from django.conf import settings +from hashlib import sha1, sha256, sha384, sha512 +from _hashlib import HASH +from re import compile, escape +from typing import Any, Dict + +# +# CONSTANTS +# +API_MATE_CHAR_POOL = 'abcdefghijklmnopqrstuvwxyz0123456789' +CONTENT_TYPE = "text/xml" +HOST_REGEX = compile(r'([^:]+)(:\d+)?$') +MEETING_ID_LENGTH = 100 +MEETING_NAME_LENGTH = 500 +NONCE_CHAR_POOL = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@*(-_)' +NONCE_LENGTH = 64 +RECORD_PROFILE_DESCRIPTION_LENGTH = 255 +RETURN_STRING_VERSION = '\r\nSUCCESS\r\n2.0\r\n2.0\r\n\r\n' +RETURN_STRING_CREATE_LIMIT_REACHED = '\r\nFAILED\r\nMeeting/Attendee limit reached.\r\n' +RETURN_STRING_CREATE_NO_NODE_AVAILABE = '\r\nFAILED\r\nNo Node available.\r\n' +RETURN_STRING_IS_MEETING_RUNNING_FALSE = '\r\nSUCCESS\r\nfalse\r\n' +RETURN_STRING_GET_MEETING_INFO_FALSE = '\r\nFAILED\r\nnotFound\r\nA meeting with that ID does not exist\r\n' +RETURN_STRING_GET_MEETINGS_NO_MEETINGS = '\r\nSUCCESS\r\n\r\nnoMeetings\r\nno meetings were found on this server\r\n' +RETURN_STRING_GET_RECORDING_TEXT_TRACKS_NOTHING_FOUND_JSON = '{"response":{"returncode":"FAILED","messageKey":"noRecordings","message":"No recording found"}}' +RETURN_STRING_GET_RECORDING_NO_RECORDINGS = '\r\nSUCCESS\r\n\r\nnoRecordings\r\nThere are no recordings for the meeting(s).\r\n' +RETURN_STRING_MISSING_MEETING_ID = '\r\nFAILED\r\nmissingParamMeetingID\r\nYou must specify a meeting ID for the meeting.\r\n' +RETURN_STRING_MISSING_RECORD_ID = '\r\nFAILED\r\nmissingParamRecordID\r\nYou must specify one or more a record IDs.\r\n' +RETURN_STRING_MISSING_RECORD_PUBLISH = '\r\nFAILED\r\nmissingParamPublish\r\nYou must specify one a publish value true or false.\r\n' +RETURN_STRING_RECORD_PUBLISHED = '\r\nSUCCESS\r\n{}\r\n' +RETURN_STRING_RECORD_DELETED = '\r\nSUCCESS\r\ntrue\r\n' +RETURN_STRING_RECORD_UPDATED = '\r\nSUCCESS\r\ntrue\r\n' +SHA1 = "sha1" +SHA256 = "sha256" +SHA384 = "sha384" +SHA512 = "sha512" +SHA_BY_STRING: Dict[str, HASH] = {SHA1: sha1, SHA256: sha256, SHA384: sha384, SHA512: sha512} +SHA_ALGORITHMS: Dict[Any, HASH] = {} +for desc, length, algorithm in [(SHA1, 40, sha1), (SHA256, 64, sha256), (SHA384, 96, sha384), (SHA512, 128, sha512)]: + if desc in settings.B3LB_ALLOWED_SHA_ALGORITHMS: + SHA_ALGORITHMS[length] = algorithm + SHA_ALGORITHMS[desc] = algorithm +SLUG_REGEX = compile(r'^([a-z]{2,10})(-(\d{3}))?\.' + escape(settings.B3LB_API_BASE_DOMAIN) + '$') diff --git a/b3lb/rest/classes/api.py b/b3lb/rest/classes/api.py index ada7743..a6c8b33 100644 --- a/b3lb/rest/classes/api.py +++ b/b3lb/rest/classes/api.py @@ -29,33 +29,14 @@ from json import dumps from _hashlib import HASH from random import randint -from re import compile, escape from requests import get from rest.b3lb.metrics import incr_metric, update_create_metrics from rest.b3lb.utils import get_checksum -from rest.models import ClusterGroupRelation, Meeting, Metric, Node, Parameter, Record, RecordSet, Secret, SecretMeetingList, SecretMetricsList, Stats, MEETING_NAME_LENGTH, RECORD_PROFILE_DESCRIPTION_LENGTH, SHA_ALGORITHMS +from rest.models import ClusterGroupRelation, Meeting, Metric, Node, Parameter, Record, RecordSet, Secret, SecretMeetingList, SecretMetricsList, Stats from typing import Any, Dict, List, Literal, Union from urllib.parse import urlencode from xmltodict import parse - - -CONTENT_TYPE = "text/xml" -HOST_REGEX = compile(r'([^:]+)(:\d+)?$') -RETURN_STRING_GET_MEETINGS_NO_MEETINGS = '\r\nSUCCESS\r\n\r\nnoMeetings\r\nno meetings were found on this server\r\n' -RETURN_STRING_VERSION = '\r\nSUCCESS\r\n2.0\r\n2.0\r\n\r\n' -RETURN_STRING_CREATE_LIMIT_REACHED = '\r\nFAILED\r\nMeeting/Attendee limit reached.\r\n' -RETURN_STRING_CREATE_NO_NODE_AVAILABE = '\r\nFAILED\r\nNo Node available.\r\n' -RETURN_STRING_IS_MEETING_RUNNING_FALSE = '\r\nSUCCESS\r\nfalse\r\n' -RETURN_STRING_GET_MEETING_INFO_FALSE = '\r\nFAILED\r\nnotFound\r\nA meeting with that ID does not exist\r\n' -RETURN_STRING_GET_RECORDING_TEXT_TRACKS_NOTHING_FOUND_JSON = '{"response":{"returncode":"FAILED","messageKey":"noRecordings","message":"No recording found"}}' -RETURN_STRING_GET_RECORDING_NO_RECORDINGS = '\r\nSUCCESS\r\n\r\nnoRecordings\r\nThere are no recordings for the meeting(s).\r\n' -RETURN_STRING_MISSING_MEETING_ID = '\r\nFAILED\r\nmissingParamMeetingID\r\nYou must specify a meeting ID for the meeting.\r\n' -RETURN_STRING_MISSING_RECORD_ID = '\r\nFAILED\r\nmissingParamRecordID\r\nYou must specify one or more a record IDs.\r\n' -RETURN_STRING_MISSING_RECORD_PUBLISH = '\r\nFAILED\r\nmissingParamPublish\r\nYou must specify one a publish value true or false.\r\n' -RETURN_STRING_RECORD_PUBLISHED = '\r\nSUCCESS\r\n{}\r\n' -RETURN_STRING_RECORD_DELETED = '\r\nSUCCESS\r\ntrue\r\n' -RETURN_STRING_RECORD_UPDATED = '\r\nSUCCESS\r\ntrue\r\n' -SLUG_REGEX = compile(r'^([a-z]{2,10})(-(\d{3}))?\.' + escape(settings.B3LB_API_BASE_DOMAIN) + '$') +import rest.b3lb.contants as cst class ClientB3lbRequest: @@ -82,13 +63,13 @@ async def create(self) -> HttpResponse: Creates a new meeting on a node if not exists. """ if not self.meeting_id: - return HttpResponse(RETURN_STRING_MISSING_MEETING_ID, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_MISSING_MEETING_ID, content_type=cst.CONTENT_TYPE) if not await self.is_meeting(): if not await sync_to_async(self.is_node_free)(): - return HttpResponse(RETURN_STRING_CREATE_NO_NODE_AVAILABE, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_CREATE_NO_NODE_AVAILABE, content_type=cst.CONTENT_TYPE) elif not await sync_to_async(self.is_in_limit)(): - return HttpResponse(RETURN_STRING_CREATE_LIMIT_REACHED, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_CREATE_LIMIT_REACHED, content_type=cst.CONTENT_TYPE) meeting, created = await sync_to_async(Meeting.objects.get_or_create)(id=self.meeting_id, secret=self.secret, defaults=self.get_meeting_defaults()) @@ -103,7 +84,7 @@ async def join(self) -> HttpResponse: Get node and delegate (redirect) client to node. """ if not self.meeting_id: - return HttpResponse(RETURN_STRING_MISSING_MEETING_ID, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_MISSING_MEETING_ID, content_type=cst.CONTENT_TYPE) if not await self.is_meeting(): return HttpResponseBadRequest() await sync_to_async(self.check_parameters)() @@ -117,16 +98,16 @@ async def get_meetings(self) -> HttpResponse: """ try: secret_meeting_list = await sync_to_async(SecretMeetingList.objects.get)(secret=self.secret) - return HttpResponse(secret_meeting_list.xml, content_type=CONTENT_TYPE) + return HttpResponse(secret_meeting_list.xml, content_type=cst.CONTENT_TYPE) except ObjectDoesNotExist: - return HttpResponse(RETURN_STRING_GET_MEETINGS_NO_MEETINGS, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_GET_MEETINGS_NO_MEETINGS, content_type=cst.CONTENT_TYPE) async def get_recordings(self) -> HttpResponse: """ 'getRecordings' endpoint. """ if not self.secret.is_record_enabled: - return HttpResponse(RETURN_STRING_GET_RECORDING_NO_RECORDINGS, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_GET_RECORDING_NO_RECORDINGS, content_type=cst.CONTENT_TYPE) records = [] recording_ids = self.parameters.get("recordID", "") @@ -140,8 +121,8 @@ async def get_recordings(self) -> HttpResponse: records = await sync_to_async(self.get_recording_dicts)() if records: - return HttpResponse(render_to_string(template_name="getRecordings.xml", context={"records": records}), content_type=CONTENT_TYPE) - return HttpResponse(RETURN_STRING_GET_RECORDING_NO_RECORDINGS, content_type=CONTENT_TYPE) + return HttpResponse(render_to_string(template_name="getRecordings.xml", context={"records": records}), content_type=cst.CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_GET_RECORDING_NO_RECORDINGS, content_type=cst.CONTENT_TYPE) async def publish_recordings(self) -> HttpResponse: """ @@ -150,10 +131,10 @@ async def publish_recordings(self) -> HttpResponse: recording_ids = self.parameters.get("recordID", "") publish = self.parameters.get("publish", "") if not recording_ids: - return HttpResponse(RETURN_STRING_MISSING_RECORD_ID, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_MISSING_RECORD_ID, content_type=cst.CONTENT_TYPE) if not publish or publish.lower() not in ["true", "false"]: - return HttpResponse(RETURN_STRING_MISSING_RECORD_PUBLISH, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_MISSING_RECORD_PUBLISH, content_type=cst.CONTENT_TYPE) self.state = "" # set to empty string for not filtering by state if publish == "true": @@ -165,7 +146,7 @@ async def publish_recordings(self) -> HttpResponse: recordings = await sync_to_async(self.filter_recordings)(recording_id=recording_id) await sync_to_async(recordings.update)(published=published) - return HttpResponse(RETURN_STRING_RECORD_PUBLISHED.format(publish), content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_RECORD_PUBLISHED.format(publish), content_type=cst.CONTENT_TYPE) async def delete_recordings(self) -> HttpResponse: """ @@ -173,13 +154,13 @@ async def delete_recordings(self) -> HttpResponse: """ recording_ids = self.parameters.get("recordID", "") if not recording_ids: - return HttpResponse(RETURN_STRING_MISSING_RECORD_ID, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_MISSING_RECORD_ID, content_type=cst.CONTENT_TYPE) self.state = "" # set to empty string for not filtering by state for recording_id in recording_ids.split(","): await sync_to_async(self.delete_recordings_by_recording_id)(recording_id) - return HttpResponse(RETURN_STRING_RECORD_DELETED, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_RECORD_DELETED, content_type=cst.CONTENT_TYPE) async def update_recordings(self) -> HttpResponse: """ @@ -190,14 +171,14 @@ async def update_recordings(self) -> HttpResponse: """ recording_ids = self.parameters.get("recordID", "") if not recording_ids: - return HttpResponse(RETURN_STRING_MISSING_RECORD_ID, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_MISSING_RECORD_ID, content_type=cst.CONTENT_TYPE) gl_listed = self.parameters.get("meta_gl-listed", "") name = self.parameters.get("meta_name", "").strip() # replacing Greenlight parameter syntax, which isn't part of the entered name # prevent database update error with arbitrary long name parameter value - if len(name) > RECORD_PROFILE_DESCRIPTION_LENGTH + MEETING_NAME_LENGTH + 3: - name = name[:RECORD_PROFILE_DESCRIPTION_LENGTH + MEETING_NAME_LENGTH + 3] + if len(name) > cst.RECORD_PROFILE_DESCRIPTION_LENGTH + cst.MEETING_NAME_LENGTH + 3: + name = name[:cst.RECORD_PROFILE_DESCRIPTION_LENGTH + cst.MEETING_NAME_LENGTH + 3] self.state = "" # no recording filter by state @@ -214,7 +195,7 @@ async def update_recordings(self) -> HttpResponse: if gl_listed: await sync_to_async(recordings.update)(gl_listed=listed) - return HttpResponse(RETURN_STRING_RECORD_UPDATED, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_RECORD_UPDATED, content_type=cst.CONTENT_TYPE) @staticmethod async def get_recording_text_tracks() -> HttpResponse: @@ -222,7 +203,7 @@ async def get_recording_text_tracks() -> HttpResponse: 'getRecordingTextTracks' endpoint. Currently, hardcoded for future implementation. """ - return HttpResponse(RETURN_STRING_GET_RECORDING_TEXT_TRACKS_NOTHING_FOUND_JSON, content_type="application/json") + return HttpResponse(cst.RETURN_STRING_GET_RECORDING_TEXT_TRACKS_NOTHING_FOUND_JSON, content_type="application/json") async def is_meeting_running(self) -> HttpResponse: """ @@ -231,7 +212,7 @@ async def is_meeting_running(self) -> HttpResponse: Send client request to node and return response to client if exists otherwise return hardcoded answer. """ if not await self.is_meeting(): - return HttpResponse(RETURN_STRING_IS_MEETING_RUNNING_FALSE, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_IS_MEETING_RUNNING_FALSE, content_type=cst.CONTENT_TYPE) return await self.pass_through() async def pass_through(self) -> HttpResponse: @@ -240,18 +221,18 @@ async def pass_through(self) -> HttpResponse: Send client request to correct node and return node response to client. """ if not self.meeting_id: - return HttpResponse(RETURN_STRING_MISSING_MEETING_ID, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_MISSING_MEETING_ID, content_type=cst.CONTENT_TYPE) if not await self.is_meeting(): - return HttpResponse(RETURN_STRING_GET_MEETING_INFO_FALSE, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_GET_MEETING_INFO_FALSE, content_type=cst.CONTENT_TYPE) if not self.node: await self.set_node_by_meeting_id() async with ClientSession() as session: if self.request.method == "POST": async with session.post(await sync_to_async(self.get_node_endpoint_url_encoded)(), data=self.body) as res: - return HttpResponse(await res.text(), status=res.status, content_type=res.headers.get('content-type', CONTENT_TYPE)) + return HttpResponse(await res.text(), status=res.status, content_type=res.headers.get('content-type', cst.CONTENT_TYPE)) else: async with session.get(await sync_to_async(self.get_node_endpoint_url_encoded)()) as res: - return HttpResponse(await res.text(), status=res.status, content_type=res.headers.get('content-type', CONTENT_TYPE)) + return HttpResponse(await res.text(), status=res.status, content_type=res.headers.get('content-type', cst.CONTENT_TYPE)) @staticmethod async def version() -> HttpResponse: @@ -259,7 +240,7 @@ async def version() -> HttpResponse: Empty ('')/root endpoint. Returns the BigBlueButton API version. """ - return HttpResponse(RETURN_STRING_VERSION, content_type=CONTENT_TYPE) + return HttpResponse(cst.RETURN_STRING_VERSION, content_type=cst.CONTENT_TYPE) #### Own Routines & Endpoints #### async def endpoint_delegation(self) -> HttpResponse: @@ -449,7 +430,7 @@ def get_node_endpoint_url_encoded(self) -> URL: return URL(self.get_node_endpoint_url(), encoded=True) def get_forwarded_host(self) -> str: - return HOST_REGEX.sub(r'\1', self.request.META.get('HTTP_X_FORWARDED_HOST', self.request.META.get('HTTP_HOST'))) + return cst.HOST_REGEX.sub(r'\1', self.request.META.get('HTTP_X_FORWARDED_HOST', self.request.META.get('HTTP_HOST'))) def get_query_string(self) -> str: query_string = self.request.META.get("QUERY_STRING", "") @@ -468,10 +449,10 @@ def get_secret_metrics(self) -> str: return SecretMetricsList.objects.get(secret=self.secret).metrics def get_sha_by_length(self) -> Union[HASH, None]: - return SHA_ALGORITHMS.get(len(self.checksum)) + return cst.SHA_ALGORITHMS.get(len(self.checksum)) def get_sha_by_parameter(self) -> Union[HASH, None]: - return SHA_ALGORITHMS.get(self.parameters.get("checksumHash", "")) + return cst.SHA_ALGORITHMS.get(self.parameters.get("checksumHash", "")) def get_tenant_statistic(self) -> str: statistic = {} @@ -518,7 +499,7 @@ def set_node_by_lowest_workload(self): async def set_secret_by_slug_and_slug_id(self, slug: str, sub_id: int): if not slug: - search = SLUG_REGEX.search(self.get_forwarded_host()) + search = cst.SLUG_REGEX.search(self.get_forwarded_host()) if search: slug = search.group(1).upper() sub_id = int(search.group(3) or 0) diff --git a/b3lb/rest/models.py b/b3lb/rest/models.py index 2d9ae2f..454fc4d 100644 --- a/b3lb/rest/models.py +++ b/b3lb/rest/models.py @@ -27,7 +27,6 @@ from django.utils.html import format_html from django.utils.http import urlencode from django.urls import reverse -from hashlib import sha1, sha256, sha384, sha512 from _hashlib import HASH from math import pow from os.path import join @@ -37,43 +36,18 @@ from rest.classes.storage import DBStorage from storages.backends.s3boto3 import ClientError, S3Boto3Storage from textwrap import wrap -from typing import Any, Dict, Union +from typing import Any, Dict +import rest.b3lb.contants as cst import uuid as uid -# -# CONSTANTS -# -API_MATE_CHAR_POOL = 'abcdefghijklmnopqrstuvwxyz0123456789' -MEETING_ID_LENGTH = 100 -MEETING_NAME_LENGTH = 500 -NONCE_CHAR_POOL = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@*(-_)' -NONCE_LENGTH = 64 -RECORD_PROFILE_DESCRIPTION_LENGTH = 255 -SHA1 = "sha1" -SHA256 = "sha256" -SHA384 = "sha384" -SHA512 = "sha512" -SHA_BY_STRING: Dict[Any, HASH] = {SHA1: sha1, SHA256: sha256, SHA384: sha384, SHA512: sha512} -SHA_ALGORITHMS: Dict[Any, HASH] = {} -if SHA1 in settings.B3LB_ALLOWED_SHA_ALGORITHMS: - SHA_ALGORITHMS[40] = sha1 - SHA_ALGORITHMS[SHA1] = sha1 -if SHA256 in settings.B3LB_ALLOWED_SHA_ALGORITHMS: - SHA_ALGORITHMS[64] = sha256 - SHA_ALGORITHMS[SHA256] = sha256 -if SHA384 in settings.B3LB_ALLOWED_SHA_ALGORITHMS: - SHA_ALGORITHMS[96] = sha384 - SHA_ALGORITHMS[SHA384] = sha384 -if SHA512 in settings.B3LB_ALLOWED_SHA_ALGORITHMS: - SHA_ALGORITHMS[128] = sha512 - SHA_ALGORITHMS[SHA512] = sha512 + # # FUNCTIONS # def get_nonce(): - return get_random_string(NONCE_LENGTH, NONCE_CHAR_POOL) + return get_random_string(cst.NONCE_LENGTH, cst.NONCE_CHAR_POOL) def get_storage(): @@ -112,7 +86,7 @@ def set_cluster_nodes_to_maintenance(modeladmin, request, queryset): # MODELS # class Cluster(models.Model): - SHA_CHOICES = [(SHA1, SHA1), (SHA256, SHA256), (SHA384, SHA384), (SHA512, SHA512)] + SHA_CHOICES = [(cst.SHA1, cst.SHA1), (cst.SHA256, cst.SHA256), (cst.SHA384, cst.SHA384), (cst.SHA512, cst.SHA512)] uuid = models.UUIDField(primary_key=True, editable=False, unique=True, default=uid.uuid4) name = models.CharField(max_length=100, help_text="cluster name", unique=True) @@ -120,7 +94,7 @@ class Cluster(models.Model): load_m_factor = models.FloatField(default=30.0, help_text="per meeting load factor") load_cpu_iterations = models.IntegerField(default=6, help_text="max sum iteration") load_cpu_max = models.IntegerField(default=5000, help_text="max cpu load") - sha_function = models.CharField(max_length=6, choices=SHA_CHOICES, default=SHA256) + sha_function = models.CharField(max_length=6, choices=SHA_CHOICES, default=cst.SHA256) class Meta(object): ordering = ['name'] @@ -129,7 +103,7 @@ def __str__(self): return self.name def get_sha(self) -> HASH: - return SHA_BY_STRING.get(self.sha_function)() + return cst.SHA_BY_STRING.get(self.sha_function)() class ClusterAdmin(admin.ModelAdmin): @@ -264,8 +238,8 @@ def api_mate(self, obj): params = { "sharedSecret": obj.secret, "name": f"API Mate test room on {obj.slug.lower()}.{obj.domain}", - "attendeePW": get_random_string(settings.B3LB_API_MATE_PW_LENGTH, API_MATE_CHAR_POOL), - "moderatorPW": get_random_string(settings.B3LB_API_MATE_PW_LENGTH, API_MATE_CHAR_POOL) + "attendeePW": get_random_string(settings.B3LB_API_MATE_PW_LENGTH, cst.API_MATE_CHAR_POOL), + "moderatorPW": get_random_string(settings.B3LB_API_MATE_PW_LENGTH, cst.API_MATE_CHAR_POOL) } url_enc_params = urlencode(params) @@ -458,8 +432,8 @@ def api_mate(self, obj): params = { "sharedSecret": obj.secret, "name": f"API Mate test room for {low_slug_id}", - "attendeePW": get_random_string(settings.B3LB_API_MATE_PW_LENGTH, API_MATE_CHAR_POOL), - "moderatorPW": get_random_string(settings.B3LB_API_MATE_PW_LENGTH, API_MATE_CHAR_POOL) + "attendeePW": get_random_string(settings.B3LB_API_MATE_PW_LENGTH, cst.API_MATE_CHAR_POOL), + "moderatorPW": get_random_string(settings.B3LB_API_MATE_PW_LENGTH, cst.API_MATE_CHAR_POOL) } slide_string = "" try: @@ -593,15 +567,15 @@ class SecretMetricsListAdmin(admin.ModelAdmin): # meeting - tenant - node relation class class Meeting(models.Model): - id = models.CharField(max_length=MEETING_ID_LENGTH, primary_key=True) + id = models.CharField(max_length=cst.MEETING_ID_LENGTH, primary_key=True) secret = models.ForeignKey(Secret, on_delete=models.CASCADE) node = models.ForeignKey(Node, on_delete=models.CASCADE) - room_name = models.CharField(max_length=MEETING_NAME_LENGTH) + room_name = models.CharField(max_length=cst.MEETING_NAME_LENGTH) age = models.DateTimeField(default=timezone.now) attendees = models.SmallIntegerField(default=0) end_callback_url = models.URLField(default="") listenerCount = models.SmallIntegerField(default=0) - nonce = models.CharField(max_length=NONCE_LENGTH, default=get_nonce, editable=False, unique=True) + nonce = models.CharField(max_length=cst.NONCE_LENGTH, default=get_nonce, editable=False, unique=True) voiceParticipantCount = models.SmallIntegerField(default=0) moderatorCount = models.SmallIntegerField(default=0) videoCount = models.SmallIntegerField(default=0) @@ -642,7 +616,7 @@ class RecordSet(models.Model): created_at = models.DateTimeField(default=timezone.now) recording_archive = models.FileField(storage=get_storage) recording_ready_origin_url = models.URLField(default="") - nonce = models.CharField(max_length=NONCE_LENGTH, default=get_nonce, editable=False, unique=True) + nonce = models.CharField(max_length=cst.NONCE_LENGTH, default=get_nonce, editable=False, unique=True) status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="UNKNOWN") file_path = models.CharField(max_length=50) @@ -652,8 +626,8 @@ class RecordSet(models.Model): meta_bbb_origin_server_name = models.CharField(max_length=50, default="") meta_is_breakout = models.BooleanField(default=False) meta_end_callback_url = models.URLField(default="") - meta_meeting_id = models.CharField(max_length=MEETING_ID_LENGTH, default="") - meta_meeting_name = models.CharField(max_length=MEETING_NAME_LENGTH, default="") + meta_meeting_id = models.CharField(max_length=cst.MEETING_ID_LENGTH, default="") + meta_meeting_name = models.CharField(max_length=cst.MEETING_NAME_LENGTH, default="") meta_start_time = models.CharField(max_length=14, default="") meta_end_time = models.CharField(max_length=14, default="") meta_participants = models.SmallIntegerField(default=0) @@ -719,7 +693,7 @@ def delete_queryset(self, request, queryset): class RecordProfile(models.Model): uuid = models.UUIDField(primary_key=True, editable=False, unique=True, default=uid.uuid4) - description = models.CharField(max_length=RECORD_PROFILE_DESCRIPTION_LENGTH) + description = models.CharField(max_length=cst.RECORD_PROFILE_DESCRIPTION_LENGTH) name = models.CharField(max_length=32, unique=True) width = models.IntegerField(default=1920, help_text="width of video") height = models.IntegerField(default=1080, help_text="height of video") @@ -760,12 +734,12 @@ class Record(models.Model): uuid = models.UUIDField(primary_key=True, editable=False, unique=True, default=uid.uuid4) file = models.FileField(storage=get_storage) profile = models.ForeignKey(RecordProfile, on_delete=models.PROTECT, null=True) - name = models.CharField(max_length=RECORD_PROFILE_DESCRIPTION_LENGTH + MEETING_NAME_LENGTH + 3) + name = models.CharField(max_length=cst.RECORD_PROFILE_DESCRIPTION_LENGTH + cst.MEETING_NAME_LENGTH + 3) gl_listed = models.BooleanField(default=False) published = models.BooleanField(default=False) record_set = models.ForeignKey(RecordSet, on_delete=models.CASCADE) uploaded_at = models.DateTimeField(default=timezone.now) - nonce = models.CharField(max_length=NONCE_LENGTH, default=get_nonce, editable=False, unique=True) + nonce = models.CharField(max_length=cst.NONCE_LENGTH, default=get_nonce, editable=False, unique=True) def get_file_size(self) -> int: try: diff --git a/b3lb/rest/task/core.py b/b3lb/rest/task/core.py index e192bc1..f41a0ca 100644 --- a/b3lb/rest/task/core.py +++ b/b3lb/rest/task/core.py @@ -23,6 +23,7 @@ from django.utils import timezone from json import dumps from requests import get +from rest.b3lb.contants import RETURN_STRING_GET_MEETINGS_NO_MEETINGS from rest.b3lb.metrics import incr_metric, set_metric from rest.b3lb.utils import xml_escape from rest.classes.checks import NodeCheck @@ -30,9 +31,6 @@ from xml.etree import ElementTree -RETURN_STRING_GET_MEETINGS_NO_MEETINGS = '\r\nSUCCESS\r\n\r\nnoMeetings\r\nno meetings were found on this server\r\n' - - def check_node(check: NodeCheck): try: response = get(check.node.load_base_url, timeout=settings.B3LB_NODE_REQUEST_TIMEOUT) diff --git a/b3lb/rest/views.py b/b3lb/rest/views.py index a14c07e..84b9151 100644 --- a/b3lb/rest/views.py +++ b/b3lb/rest/views.py @@ -15,7 +15,7 @@ # along with this program. If not, see . from asgiref.sync import sync_to_async -from django.http import HttpResponse, HttpResponseNotAllowed, HttpResponseNotFound, HttpRequest, HttpResponseForbidden, HttpResponseBadRequest, FileResponse +from django.http import HttpResponse, HttpResponseNotAllowed, HttpResponseNotFound, HttpRequest, HttpResponseForbidden, FileResponse from django.core.exceptions import ObjectDoesNotExist from django.db import connection from django.db.utils import OperationalError From d30aee75eee77df1fd5e5efd0059df660a805c49 Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Tue, 18 Jul 2023 11:08:53 +0200 Subject: [PATCH 09/11] record_set: fix multiple creation of them same record set --- b3lb/rest/classes/api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/b3lb/rest/classes/api.py b/b3lb/rest/classes/api.py index a6c8b33..fec56d7 100644 --- a/b3lb/rest/classes/api.py +++ b/b3lb/rest/classes/api.py @@ -75,7 +75,7 @@ async def create(self) -> HttpResponse: if created: await sync_to_async(update_create_metrics)(self.secret, self.node) - await sync_to_async(self.check_parameters)(meeting) + await sync_to_async(self.check_parameters)(meeting, created) return await self.pass_through() async def join(self) -> HttpResponse: @@ -313,7 +313,7 @@ def check_checksum(self) -> bool: return True return False - def check_parameters(self, meeting: Meeting = None): + def check_parameters(self, meeting: Meeting = None, created: bool = False): parameters = Parameter.objects.filter(tenant=self.secret.tenant) if self.endpoint == "join": endpoint_parameters = Parameter.PARAMETERS_JOIN @@ -354,8 +354,9 @@ def check_parameters(self, meeting: Meeting = None): # check if records are enabled if self.secret.is_record_enabled: - record_set = RecordSet.objects.create(secret=self.secret, meeting=meeting, meta_meeting_id=meeting.id, recording_ready_origin_url=self.parameters.pop("meta_bbb-recording-ready-url", ""), meta_end_callback_url=meeting.end_callback_url, nonce=meeting.nonce) - self.parameters[f"meta_{settings.B3LB_RECORD_META_DATA_TAG}"] = record_set.nonce + if created: # only if meeting has been created otherwise do nothing + record_set = RecordSet.objects.create(secret=self.secret, meeting=meeting, meta_meeting_id=meeting.id, recording_ready_origin_url=self.parameters.pop("meta_bbb-recording-ready-url", ""), meta_end_callback_url=meeting.end_callback_url, nonce=meeting.nonce) + self.parameters[f"meta_{settings.B3LB_RECORD_META_DATA_TAG}"] = record_set.nonce else: # record aren't enabled -> suppress any record related parameter for param in [Parameter.RECORD, Parameter.ALLOW_START_STOP_RECORDING, Parameter.AUTO_START_RECORDING]: From 3aa4b38dd6cb8e41234ddb0161b5dbc7edaf2ebd Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Tue, 18 Jul 2023 11:18:50 +0200 Subject: [PATCH 10/11] management: expand gettenantsecrets command with recording --- b3lb/rest/management/commands/gettenantsecrets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b3lb/rest/management/commands/gettenantsecrets.py b/b3lb/rest/management/commands/gettenantsecrets.py index 94e91ad..c83f3b7 100644 --- a/b3lb/rest/management/commands/gettenantsecrets.py +++ b/b3lb/rest/management/commands/gettenantsecrets.py @@ -21,12 +21,12 @@ class Command(BaseCommand): - help = 'Get first secret and hostnames of tenants.' + help = 'Get secrets, endpoints and if recording is enabled.' def handle(self, *args, **options): tenant_dict = {} for secret in Secret.objects.all(): - tenant_dict["{}-{}".format(secret.tenant.slug, str(secret.sub_id).zfill(3))] = {"secret": secret.secret, "hostname": secret.endpoint} + tenant_dict["{}-{}".format(secret.tenant.slug, str(secret.sub_id).zfill(3))] = {"secret": secret.secret, "hostname": secret.endpoint, "recording": secret.is_record_enabled} self.stdout.write(json.dumps(tenant_dict)) From 02ede6533f53b0da00c6db5228212a326d1d03e9 Mon Sep 17 00:00:00 2001 From: Philipp Kilian Date: Thu, 10 Aug 2023 13:01:22 +0200 Subject: [PATCH 11/11] CHANGELOG.md: add bumped dependencies --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c1dab..e400252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,16 @@ Changes: - disabledFeaturesExclude(2.6.9) - userdata-bbb_hide_presentation_on_join - move configuration of checksum hash function from `ClusterGroups` to `Clusters` -- bump dependencies +- bumped python dependencies: + - boto3: `1.26.119` => `1.26.163` + - celery: `5.2.7` => `5.3.1` + - Django: `3.2.18` => `3.2.19` + - django-extensions: `3.2.1` => `3.2.3` + - requests: `2.28.2` => `2.31.0` + - django-cacheops: `7.0` => `7.0.1` + - django-celery-results: `2.5.0` => `2.5.1` + - django-redis: `5.2.0` => `5.3.0` + - uvicorn: `0.21.1` => `0.22.0` ## 3.0.7 - 2023-06-02