def test_setting_overridden_by_setting_toggle(self): _toggle2 = SettingToggle("MYSETTING2", module_name="module1") _toggle3 = SettingDictToggle("MYDICT", "MYSETTING3", module_name="module1") with override_settings(MYSETTING1=True, MYSETTING2=False, MYDICT={"MYSETTING3": False}): # Need to pre-load settings, otherwise they are not picked up by the view self.assertTrue(settings.MYSETTING1) report = ToggleStateReport().as_dict() setting_dict = { toggle["name"]: toggle for toggle in report["django_settings"] } # Check that Django settings for which a SettingToggle exists have both the correct is_active and class values self.assertTrue(setting_dict["MYSETTING1"]["is_active"]) self.assertNotIn("class", setting_dict["MYSETTING1"]) self.assertFalse(setting_dict["MYSETTING2"]["is_active"]) self.assertEqual("SettingToggle", setting_dict["MYSETTING2"]["class"]) self.assertFalse(setting_dict["MYDICT['MYSETTING3']"]["is_active"]) self.assertEqual("SettingDictToggle", setting_dict["MYDICT['MYSETTING3']"]["class"])
def is_comprehensive_theming_enabled(): """ Returns boolean indicating whether comprehensive theming functionality is enabled or disabled. Example: >> is_comprehensive_theming_enabled() True Returns: (bool): True if comprehensive theming is enabled else False """ ENABLE_COMPREHENSIVE_THEMING = SettingToggle("ENABLE_COMPREHENSIVE_THEMING", default=False) if ENABLE_COMPREHENSIVE_THEMING.is_enabled() and current_request_has_associated_site_theme(): return True return ENABLE_COMPREHENSIVE_THEMING.is_enabled()
def test_setting_overridden_by_setting_toggle(self): _toggle2 = SettingToggle("MYSETTING2", module_name="module1") _toggle3 = SettingDictToggle("MYDICT", "MYSETTING3", module_name="module1") with override_settings(MYSETTING1=True, MYSETTING2=False, MYDICT={"MYSETTING3": False}): # Need to pre-load settings, otherwise they are not picked up by the view assert settings.MYSETTING1 response = self._get_toggle_state_response() setting_dict = { toggle["name"]: toggle for toggle in response.data["django_settings"] } # Check that Django settings for which a SettingToggle exists have both the correct is_active and class values assert setting_dict['MYSETTING1']['is_active'] assert 'class' not in setting_dict['MYSETTING1'] assert not setting_dict['MYSETTING2']['is_active'] assert 'SettingToggle' == setting_dict['MYSETTING2']['class'] assert not setting_dict["MYDICT['MYSETTING3']"]['is_active'] assert 'SettingDictToggle' == setting_dict["MYDICT['MYSETTING3']"][ 'class']
def _add_setting_toggles(self, settings_dict): """ Fill the `settings_dict` with values from the list of SettingToggle instances. """ for toggle in SettingToggle.get_instances(): toggle_response = self._get_or_create_toggle_response(settings_dict, toggle.name) toggle_response["is_active"] = toggle.is_enabled() self._add_toggle_instance_details(toggle_response, toggle)
def test_response_with_setting_toggle(self): _toggle = SettingToggle("MYSETTING", default=False, module_name="module1") with override_settings(MYSETTING=True): response = self._get_toggle_state_response() assert {'name': 'MYSETTING', 'is_active': True, 'module': 'module1', 'class': 'SettingToggle'}\ in response.data['django_settings']
def test_response_with_setting_toggle(self): _toggle = SettingToggle("MYSETTING", default=False, module_name="module1") with override_settings(MYSETTING=True): response = self._get_toggle_state_response() self.assertIn( { "name": "MYSETTING", "is_active": True, "module": "module1", "class": "SettingToggle", }, response.data["django_settings"], )
def test_response_with_setting_toggle(self): _toggle = SettingToggle("MYSETTING", default=False, module_name="module1") with override_settings(MYSETTING=True): report = ToggleStateReport().as_dict() self.assertIn( { "name": "MYSETTING", "is_active": True, "module": "module1", "class": "SettingToggle", }, report["django_settings"], )
def test_no_duplicate_setting_toggle(self): _toggle1 = SettingToggle( "MYSETTING1", module_name="module1" ) _toggle2 = SettingDictToggle( "MYDICT", "MYSETTING2", module_name="module1" ) with override_settings(MYSETTING1=True, MYDICT={"MYSETTING2": False}): response = self._get_toggle_state_response() # Check there are no duplicate setting/toggle response_toggles_1 = [toggle for toggle in response.data["django_settings"] if toggle["name"] == "MYSETTING1"] response_toggles_2 = [ toggle for toggle in response.data["django_settings"] if toggle["name"] == "MYDICT['MYSETTING2']" ] self.assertEqual(1, len(response_toggles_1)) self.assertEqual(1, len(response_toggles_2))
""" xAPI processors and spec implementation. """ from edx_toggles.toggles import SettingToggle # .. toggle_name: XAPI_EVENTS_ENABLED # .. toggle_implementation: SettingToggle # .. toggle_default: True # .. toggle_description: Allow sending events to external servers via xAPI. # Toggle intended both for gating initial release and for shutting off all # xAPI events in case of emergency. # .. toggle_warnings: Do not enable sending of xAPI events until there has # been a thorough review of PII implications and safeguards put in place to # prevent accidental leakage of novel event fields to third parties. See # ARCHBOM-1655 for details. # .. toggle_use_cases: circuit_breaker # .. toggle_creation_date: 2021-01-01 # .. toggle_tickets: https://openedx.atlassian.net/browse/ARCHBOM-1658 XAPI_EVENTS_ENABLED = SettingToggle("XAPI_EVENTS_ENABLED", default=False)
# .. toggle_name: LOG_REQUEST_USER_CHANGE_HEADERS # .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: Turn this toggle on to log all request headers, for all requests, for all user ids involved in # any user id change detected by safe sessions. The headers will provide additional debugging information. The # headers will be logged for all requests up until LOG_REQUEST_USER_CHANGE_HEADERS_DURATION seconds after # the time of the last mismatch. The header details will be encrypted, and only available with the private key. # .. toggle_warnings: To work correctly, LOG_REQUEST_USER_CHANGES must be enabled and ENFORCE_SAFE_SESSIONS must be # disabled. Also, SAFE_SESSIONS_DEBUG_PUBLIC_KEY must be set. See # https://github.com/edx/edx-platform/blob/master/common/djangoapps/util/log_sensitive.py # for instructions. # .. toggle_use_cases: opt_in # .. toggle_creation_date: 2021-12-22 # .. toggle_tickets: https://openedx.atlassian.net/browse/ARCHBOM-1940 LOG_REQUEST_USER_CHANGE_HEADERS = SettingToggle( 'LOG_REQUEST_USER_CHANGE_HEADERS', default=False) # Duration in seconds to log user change request headers for additional requests; defaults to 5 minutes LOG_REQUEST_USER_CHANGE_HEADERS_DURATION = getattr( settings, 'LOG_REQUEST_USER_CHANGE_HEADERS_DURATION', 300) # .. toggle_name: ENFORCE_SAFE_SESSIONS # .. toggle_implementation: SettingToggle # .. toggle_default: True # .. toggle_description: Invalidate session and response if mismatch detected. # That is, when the `user` attribute of the request object gets changed or # no longer matches the session, the session will be invalidated and the # response cancelled (changed to an error). This is intended as a backup # safety measure in case an attacker (or bug) is able to change the user # on a session in an unexpected way. # .. toggle_warnings: Should be disabled if debugging mismatches using the
from requests.exceptions import RequestException, HTTPError from simplejson import JSONDecodeError from .exceptions import CodejailServiceParseError, CodejailServiceStatusError, CodejailServiceUnavailable log = logging.getLogger(__name__) # .. toggle_name: ENABLE_CODEJAIL_REST_SERVICE # .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: Set this to True if you want to run Codejail code using # a separate VM or container and communicate with edx-platform using REST API. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2021-08-19 ENABLE_CODEJAIL_REST_SERVICE = SettingToggle("ENABLE_CODEJAIL_REST_SERVICE", default=False, module_name=__name__) def is_codejail_rest_service_enabled(): return ENABLE_CODEJAIL_REST_SERVICE.is_enabled() def get_remote_exec(*args, **kwargs): """Get remote exec function based on setting and executes it.""" remote_exec_function_name = settings.CODE_JAIL_REST_SERVICE_REMOTE_EXEC try: mod_name, func_name = remote_exec_function_name.rsplit('.', 1) remote_exec_module = import_module(mod_name) remote_exec_function = getattr(remote_exec_module, func_name) if not remote_exec_function:
""" This module contains various configuration toggles for edx's analytics dashboard. """ from edx_toggles.toggles import SettingToggle # .. toggle_name: ENROLLMENT_AGE_AVAILABLE # .. toggle_implementation: SettingToggle # .. toggle_default: True # .. toggle_description: Turn this toggle on to show age demographic information in the analytics dashboard. # Turn it off if your installation cannot or does not collect or share age information. # .. toggle_use_cases: opt_out # .. toggle_creation_date: 2021-01-12 # .. toggle_tickets: https://openedx.atlassian.net/browse/MST-1241 ENROLLMENT_AGE_AVAILABLE = SettingToggle('ENROLLMENT_AGE_AVAILABLE', default=True) def age_available(): return ENROLLMENT_AGE_AVAILABLE.is_enabled()
def check_comprehensive_theme_settings(app_configs, **kwargs): # lint-amnesty, pylint: disable=unused-argument """ Checks the comprehensive theming theme directory settings. Raises compatibility Errors upon: - COMPREHENSIVE_THEME_DIRS is not a list - theme dir path is not a string - theme dir path is not an absolute path - path specified in COMPREHENSIVE_THEME_DIRS does not exist Returns: List of any Errors. """ if not SettingToggle("ENABLE_COMPREHENSIVE_THEMING", default=False).is_enabled(): # Only perform checks when comprehensive theming is enabled. return [] errors = [] # COMPREHENSIVE_THEME_DIR is no longer supported - support has been removed. if hasattr(settings, "COMPREHENSIVE_THEME_DIR"): theme_dir = settings.COMPREHENSIVE_THEME_DIR errors.append( Error( "COMPREHENSIVE_THEME_DIR setting has been removed in favor of COMPREHENSIVE_THEME_DIRS.", hint= 'Transfer the COMPREHENSIVE_THEME_DIR value to COMPREHENSIVE_THEME_DIRS.', obj=theme_dir, id='openedx.core.djangoapps.theming.E001', )) if hasattr(settings, "COMPREHENSIVE_THEME_DIRS"): theme_dirs = settings.COMPREHENSIVE_THEME_DIRS if not isinstance(theme_dirs, list): errors.append( Error( "COMPREHENSIVE_THEME_DIRS must be a list.", obj=theme_dirs, id='openedx.core.djangoapps.theming.E004', )) if not all([ isinstance(theme_dir, six.string_types) for theme_dir in theme_dirs ]): errors.append( Error( "COMPREHENSIVE_THEME_DIRS must contain only strings.", obj=theme_dirs, id='openedx.core.djangoapps.theming.E005', )) if not all([theme_dir.startswith("/") for theme_dir in theme_dirs]): errors.append( Error( "COMPREHENSIVE_THEME_DIRS must contain only absolute paths to themes dirs.", obj=theme_dirs, id='openedx.core.djangoapps.theming.E006', )) if not all([os.path.isdir(theme_dir) for theme_dir in theme_dirs]): errors.append( Error( "COMPREHENSIVE_THEME_DIRS must contain valid paths.", obj=theme_dirs, id='openedx.core.djangoapps.theming.E007', )) return errors
from django.conf import settings from edx_toggles.toggles import SettingToggle from .event_bus_utils import create_topic_if_not_exists logger = logging.getLogger(__name__) # .. toggle_name: KAFKA_ENABLED # .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: Enable producing events to the Kafka event bus # .. toggle_creation_date: 2022-01-12 # .. toggle_target_removal_date: 2022-03-31 # .. toggle_tickets: https://openedx.atlassian.net/browse/ARCHBOM-1991 # .. toggle_use_cases: temporary KAFKA_ENABLED = SettingToggle("KAFKA_ENABLED", default=False) class SubscriptionsConfig(AppConfig): """ The app config for subscriptions. """ name = 'license_manager.apps.subscriptions' default = False def ready(self): if getattr(settings, 'SEGMENT_KEY', None): logger.debug("Found segment key, setting up") analytics.write_key = settings.SEGMENT_KEY # TODO: (ARCHBOM-2004) remove pragma and add tests when finalizing
def is_email_use_default_from_bulk_enabled(): return SettingToggle("EMAIL_USE_DEFAULT_FROM_FOR_BULK", default=False).is_enabled()
COURSEWARE_OPTIMIZED_RENDER_XBLOCK = CourseWaffleFlag( WAFFLE_FLAG_NAMESPACE, 'optimized_render_xblock', __name__ ) # .. toggle_name: COURSES_INVITE_ONLY # .. toggle_implementation: SettingToggle # .. toggle_type: feature_flag # .. toggle_default: False # .. toggle_description: Setting this sets the default value of INVITE_ONLY across all courses in a given deployment # .. toggle_category: admin # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2019-05-16 # .. toggle_expiration_date: None # .. toggle_tickets: https://github.com/mitodl/edx-platform/issues/123 # .. toggle_status: unsupported COURSES_INVITE_ONLY = SettingToggle('COURSES_INVITE_ONLY', default=False) def courseware_mfe_is_active(course_key: CourseKey) -> bool: """ Should we serve the Learning MFE as the canonical courseware experience? """ #Avoid circular imports. from lms.djangoapps.courseware.access_utils import in_preview_mode # NO: Old Mongo courses are always served in the Legacy frontend, # regardless of configuration. if course_key.deprecated: return False # NO: MFE courseware can be disabled for users/courses/globally via this # Waffle flag. if COURSEWARE_USE_LEGACY_FRONTEND.is_enabled(course_key):
def is_email_use_course_id_from_for_bulk_enabled(): return SettingToggle("EMAIL_USE_COURSE_ID_FROM_FOR_BULK", default=False).is_enabled()
# .. toggle_name: GLOBAL_NOTICE_ENABLED # .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: When enabled, show the contents of GLOBAL_NOTICE_MESSAGE # as a message on every page. This is intended to be used as a way of # communicating an upcoming or currently active maintenance window or to # warn of known site issues. HTML is not supported for the message content, # only plaintext. Message styling can be controlled with GLOBAL_NOTICE_TYPE, # set to one of INFO, SUCCESS, WARNING, or ERROR (defaulting to INFO). Also # see openedx.core.djangoapps.util.maintenance_banner.add_maintenance_banner # for a variation that only shows a message on specific views. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2021-09-08 GLOBAL_NOTICE_ENABLED = SettingToggle('GLOBAL_NOTICE_ENABLED', default=False) class PageLevelMessages(UserMessageCollection): """ This set of messages appears as top page level messages. """ NAMESPACE = 'page_level_messages' @classmethod def user_messages(cls, request): """ Returns outstanding user messages, along with any persistent site-wide messages. """ msgs = list(super().user_messages(request))
set_custom_attribute(name_attribute, name) set_custom_attribute(size_attribute, size) log.debug('%s = %d', name, size) # .. toggle_name: ENABLE_403_MONITORING # .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: Temporary toggle to track down the source of 403s for /oauth2/exchange_access_token/. # .. toggle_use_cases: temporary # .. toggle_creation_date: 2021-02-12 # .. toggle_target_removal_date: 2021-03-12 # .. toggle_tickets: https://openedx.atlassian.net/browse/ARCHBOM-1667 ENABLE_403_MONITORING = SettingToggle('ENABLE_403_MONITORING', default=False, module_name=__name__) def custom_exception_handler(exc, context): """ Enables monitoring of 403s for /oauth2/exchange_access_token/ to gather data. """ # Call REST framework's default exception handler first, # to get the standard error response. response = exception_handler(exc, context) log_403s = ENABLE_403_MONITORING.is_enabled( ) and response.status_code == 403 log_403s = log_403s and 'request' in context and context[ 'request'] and context['request'].path log_403s = log_403s and context['request'].path.startswith( '/oauth2/exchange_access_token/')
def is_courses_default_invite_only_enabled(): return SettingToggle("COURSES_INVITE_ONLY", default=False).is_enabled()
def is_bulk_email_edx_ace_enabled(): return SettingToggle("BULK_EMAIL_SEND_USING_EDX_ACE", default=False).is_enabled()