def parse_arguments(*args, **options): # pylint: disable=unused-argument """ Parse and validate arguments for compile_sass command. Args: *args: Positional arguments passed to the update_assets command **options: optional arguments passed to the update_assets command Returns: A tuple containing parsed values for themes, system, source comments and output style. 1. system (list): list of system names for whom to compile theme sass e.g. 'lms', 'cms' 2. theme_dirs (list): list of Theme objects 3. themes (list): list of Theme objects 4. force (bool): Force full compilation 5. debug (bool): Disable Sass compression """ system = options.get("system", ALL_SYSTEMS) given_themes = options.get("themes", ["all"]) theme_dirs = options.get("theme_dirs", None) force = options.get("force", True) debug = options.get("debug", True) if theme_dirs: available_themes = {} for theme_dir in theme_dirs: available_themes.update({t.theme_dir_name: t for t in get_themes([theme_dir])}) else: theme_dirs = get_theme_base_dirs() available_themes = {t.theme_dir_name: t for t in get_themes()} if 'no' in given_themes or 'all' in given_themes: # Raise error if 'all' or 'no' is present and theme names are also given. if len(given_themes) > 1: raise CommandError("Invalid themes value, It must either be 'all' or 'no' or list of themes.") # Raise error if any of the given theme name is invalid # (theme name would be invalid if it does not exist in themes directory) elif (not set(given_themes).issubset(list(available_themes.keys()))) and is_comprehensive_theming_enabled(): raise CommandError( "Given themes '{themes}' do not exist inside any of the theme directories '{theme_dirs}'".format( themes=", ".join(set(given_themes) - set(available_themes.keys())), theme_dirs=theme_dirs, ), ) if "all" in given_themes: themes = list(available_themes.values()) elif "no" in given_themes: themes = [] else: # convert theme names to Theme objects, this will remove all themes if theming is disabled themes = [available_themes.get(theme) for theme in given_themes if theme in available_themes] return system, theme_dirs, themes, force, debug
def parse_arguments(*args, **options): # pylint: disable=unused-argument """ Parse and validate arguments for compile_sass command. Args: *args: Positional arguments passed to the update_assets command **options: optional arguments passed to the update_assets command Returns: A tuple containing parsed values for themes, system, source comments and output style. 1. system (list): list of system names for whom to compile theme sass e.g. 'lms', 'cms' 2. theme_dirs (list): list of Theme objects 3. themes (list): list of Theme objects 4. force (bool): Force full compilation 5. debug (bool): Disable Sass compression """ system = options.get("system", ALL_SYSTEMS) given_themes = options.get("themes", ["all"]) theme_dirs = options.get("theme_dirs", None) force = options.get("force", True) debug = options.get("debug", True) if theme_dirs: available_themes = {} for theme_dir in theme_dirs: available_themes.update({t.theme_dir_name: t for t in get_themes(theme_dir)}) else: theme_dirs = get_theme_base_dirs() available_themes = {t.theme_dir_name: t for t in get_themes()} if 'no' in given_themes or 'all' in given_themes: # Raise error if 'all' or 'no' is present and theme names are also given. if len(given_themes) > 1: raise CommandError("Invalid themes value, It must either be 'all' or 'no' or list of themes.") # Raise error if any of the given theme name is invalid # (theme name would be invalid if it does not exist in themes directory) elif (not set(given_themes).issubset(available_themes.keys())) and is_comprehensive_theming_enabled(): raise CommandError( "Given themes '{themes}' do not exist inside any of the theme directories '{theme_dirs}'".format( themes=", ".join(set(given_themes) - set(available_themes.keys())), theme_dirs=theme_dirs, ), ) if "all" in given_themes: themes = list(available_themes.itervalues()) elif "no" in given_themes: themes = [] else: # convert theme names to Theme objects, this will remove all themes if theming is disabled themes = [available_themes.get(theme) for theme in given_themes if theme in available_themes] return system, theme_dirs, themes, force, debug
def test_parse_arguments(self): """ Test parse arguments method for update_asset command. """ # make sure compile_sass picks all themes when called with 'themes=all' option parsed_args = Command.parse_arguments(themes=["all"]) self.assertItemsEqual(parsed_args[2], get_themes()) # make sure compile_sass picks no themes when called with 'themes=no' option parsed_args = Command.parse_arguments(themes=["no"]) self.assertItemsEqual(parsed_args[2], []) # make sure compile_sass picks only specified themes parsed_args = Command.parse_arguments(themes=["test-theme"]) self.assertItemsEqual(parsed_args[2], [theme for theme in get_themes() if theme.theme_dir_name == "test-theme"])
def post_process(self, paths, dry_run=False, **options): """ This post_process hook is used to package all themed assets. """ if dry_run: return themes = get_themes() for theme in themes: css_packages = self.get_themed_packages( theme.theme_dir_name, settings.PIPELINE['STYLESHEETS']) from pipeline.packager import Packager packager = Packager(storage=self, css_packages=css_packages) for package_name in packager.packages['css']: package = packager.package_for('css', package_name) output_file = package.output_filename if self.packing: packager.pack_stylesheets(package) paths[output_file] = (self, output_file) yield output_file, output_file, True super_class = super(ThemePipelineMixin, self) if hasattr(super_class, 'post_process'): for name, hashed_name, processed in super_class.post_process( paths.copy(), dry_run, **options): yield name, hashed_name, processed
def post_process(self, paths, dry_run=False, **options): """ This post_process hook is used to package all themed assets. """ if dry_run: return themes = get_themes() for theme in themes: css_packages = self.get_themed_packages(theme.theme_dir_name, settings.PIPELINE_CSS) from pipeline.packager import Packager packager = Packager(storage=self, css_packages=css_packages) for package_name in packager.packages['css']: package = packager.package_for('css', package_name) output_file = package.output_filename if self.packing: packager.pack_stylesheets(package) paths[output_file] = (self, output_file) yield output_file, output_file, True super_class = super(ThemePipelineMixin, self) if hasattr(super_class, 'post_process'): for name, hashed_name, processed in super_class.post_process(paths.copy(), dry_run, **options): yield name, hashed_name, processed
def _get_redirect_to(request): """ Determine the redirect url and return if safe :argument request: request object :returns: redirect url if safe else None """ redirect_to = request.GET.get('next') header_accept = request.META.get('HTTP_ACCEPT', '') # If we get a redirect parameter, make sure it's safe i.e. not redirecting outside our domain. # Also make sure that it is not redirecting to a static asset and redirected page is web page # not a static file. As allowing assets to be pointed to by "next" allows 3rd party sites to # get information about a user on edx.org. In any such case drop the parameter. if redirect_to: mime_type, _ = mimetypes.guess_type(redirect_to, strict=False) if not is_safe_login_or_logout_redirect(request, redirect_to): log.warning( u"Unsafe redirect parameter detected after login page: '%(redirect_to)s'", {"redirect_to": redirect_to} ) redirect_to = None elif 'text/html' not in header_accept: log.info( u"Redirect to non html content '%(content_type)s' detected from '%(user_agent)s'" u" after login page: '%(redirect_to)s'", { "redirect_to": redirect_to, "content_type": header_accept, "user_agent": request.META.get('HTTP_USER_AGENT', '') } ) redirect_to = None elif mime_type: log.warning( u"Redirect to url path with specified filed type '%(mime_type)s' not allowed: '%(redirect_to)s'", {"redirect_to": redirect_to, "mime_type": mime_type} ) redirect_to = None elif settings.STATIC_URL in redirect_to: log.warning( u"Redirect to static content detected after login page: '%(redirect_to)s'", {"redirect_to": redirect_to} ) redirect_to = None else: themes = get_themes() next_path = six.moves.urllib.parse.urlparse(redirect_to).path for theme in themes: if theme.theme_dir_name in next_path: log.warning( u"Redirect to theme content detected after login page: '%(redirect_to)s'", {"redirect_to": redirect_to} ) redirect_to = None break return redirect_to
def get_redirect_to(request): """ Determine the redirect url and return if safe :argument request: request object :returns: redirect url if safe else None """ redirect_to = request.GET.get('next') header_accept = request.META.get('HTTP_ACCEPT', '') # If we get a redirect parameter, make sure it's safe i.e. not redirecting outside our domain. # Also make sure that it is not redirecting to a static asset and redirected page is web page # not a static file. As allowing assets to be pointed to by "next" allows 3rd party sites to # get information about a user on edx.org. In any such case drop the parameter. if redirect_to: mime_type, _ = mimetypes.guess_type(redirect_to, strict=False) if not http.is_safe_url(redirect_to, host=request.get_host()): log.warning( u'Unsafe redirect parameter detected after login page: %(redirect_to)r', {"redirect_to": redirect_to} ) redirect_to = None elif 'text/html' not in header_accept: log.warning( u'Redirect to non html content %(content_type)r detected from %(user_agent)r' u' after login page: %(redirect_to)r', { "redirect_to": redirect_to, "content_type": header_accept, "user_agent": request.META.get('HTTP_USER_AGENT', '') } ) redirect_to = None elif mime_type: log.warning( u'Redirect to url path with specified filed type %(mime_type)r not allowed: %(redirect_to)r', {"redirect_to": redirect_to, "mime_type": mime_type} ) redirect_to = None elif settings.STATIC_URL in redirect_to: log.warning( u'Redirect to static content detected after login page: %(redirect_to)r', {"redirect_to": redirect_to} ) redirect_to = None else: themes = get_themes() next_path = urlparse.urlparse(redirect_to).path for theme in themes: if theme.theme_dir_name in next_path: log.warning( u'Redirect to theme content detected after login page: %(redirect_to)r', {"redirect_to": redirect_to} ) redirect_to = None break return redirect_to
def test_get_themes_2(self): """ Tests template paths are returned from enabled theme. """ expected_themes = [ Theme('test-theme', 'test-theme'), ] actual_themes = get_themes() self.assertItemsEqual(expected_themes, actual_themes)
def test_get_themes_2(self): """ Tests template paths are returned from enabled theme. """ expected_themes = [ Theme('test-theme', 'test-theme', get_theme_base_dir('test-theme'), settings.PROJECT_ROOT), ] actual_themes = get_themes() self.assertItemsEqual(expected_themes, actual_themes)
def test_parse_arguments(self): """ Test parse arguments method for update_asset command. """ # make sure compile_sass picks all themes when called with 'themes=all' option parsed_args = Command.parse_arguments(themes=["all"]) six.assertCountEqual(self, parsed_args[2], get_themes()) # make sure compile_sass picks no themes when called with 'themes=no' option parsed_args = Command.parse_arguments(themes=["no"]) six.assertCountEqual(self, parsed_args[2], []) # make sure compile_sass picks only specified themes parsed_args = Command.parse_arguments(themes=["test-theme"]) six.assertCountEqual(self, parsed_args[2], [ theme for theme in get_themes() if theme.theme_dir_name == "test-theme" ])
def test_get_themes(self): """ Tests template paths are returned from enabled theme. """ expected_themes = [ Theme('red-theme', 'red-theme'), Theme('edge.edx.org', 'edge.edx.org'), Theme('edx.org', 'edx.org'), Theme('stanford-style', 'stanford-style'), ] actual_themes = get_themes() self.assertItemsEqual(expected_themes, actual_themes)
def test_get_themes(self): """ Tests template paths are returned from enabled theme. """ expected_themes = [ Theme('test-theme', 'test-theme', get_theme_base_dir('test-theme')), Theme('red-theme', 'red-theme', get_theme_base_dir('red-theme')), Theme('edge.edx.org', 'edge.edx.org', get_theme_base_dir('edge.edx.org')), Theme('edx.org', 'edx.org', get_theme_base_dir('edx.org')), Theme('example', 'example', get_theme_base_dir('example')), Theme('stanford-style', 'stanford-style', get_theme_base_dir('stanford-style')), ] actual_themes = get_themes() self.assertItemsEqual(expected_themes, actual_themes)
def test_get_themes(self): """ Tests template paths are returned from enabled theme. """ expected_themes = [ Theme('dark-theme', 'dark-theme', get_theme_base_dir('dark-theme'), settings.PROJECT_ROOT), Theme('edge.edx.org', 'edge.edx.org', get_theme_base_dir('edge.edx.org'), settings.PROJECT_ROOT), Theme('edx.org', 'edx.org', get_theme_base_dir('edx.org'), settings.PROJECT_ROOT), Theme('open-edx', 'open-edx', get_theme_base_dir('open-edx'), settings.PROJECT_ROOT), Theme('red-theme', 'red-theme', get_theme_base_dir('red-theme'), settings.PROJECT_ROOT), Theme('stanford-style', 'stanford-style', get_theme_base_dir('stanford-style'), settings.PROJECT_ROOT), Theme('test-theme', 'test-theme', get_theme_base_dir('test-theme'), settings.PROJECT_ROOT), ] actual_themes = get_themes() self.assertItemsEqual(expected_themes, actual_themes)
def find(self, path, all=False): # pylint: disable=redefined-builtin """ Looks for files in the theme directories. """ matches = [] theme_dir_name = path.split("/", 1)[0] themes = {t.theme_dir_name: t for t in get_themes()} # if path is prefixed by theme name then search in the corresponding storage other wise search all storages. if theme_dir_name in themes: theme = themes[theme_dir_name] path = "/".join(path.split("/")[1:]) match = self.find_in_theme(theme.theme_dir_name, path) if match: if not all: return match matches.append(match) return matches
def __init__(self, *args, **kwargs): # The list of themes that are handled self.themes = [] # Mapping of theme names to storage instances self.storages = OrderedDict() themes = get_themes() for theme in themes: theme_storage = self.storage_class( os.path.join(theme.path, self.source_dir), prefix=theme.theme_dir_name, ) self.storages[theme.theme_dir_name] = theme_storage if theme.theme_dir_name not in self.themes: self.themes.append(theme.theme_dir_name) super(ThemeFilesFinder, self).__init__(*args, **kwargs)
def url(self, name, force=False): """ Returns themed url for the given asset. """ theme = get_current_theme() if theme and theme.theme_dir_name not in name: # during server run, append theme name to the asset name if it is not already there # this is ensure that correct hash is created and default asset is not always # used to create hash of themed assets. name = os.path.join(theme.theme_dir_name, name) parsed_name = urlsplit(unquote(name)) clean_name = parsed_name.path.strip() asset_name = name if not self.exists(clean_name): # if themed asset does not exists then use default asset theme = name.split("/", 1)[0] # verify that themed asset was accessed if theme in [theme.theme_dir_name for theme in get_themes()]: asset_name = "/".join(name.split("/")[1:]) return super(ThemeCachedFilesMixin, self).url(asset_name, force)
def _processed_asset_name(self, name): """ Returns either a themed or unthemed version of the given asset name, depending on several factors. See the class docstring for more info. """ theme = get_current_theme() if theme and theme.theme_dir_name not in name: # during server run, append theme name to the asset name if it is not already there # this is ensure that correct hash is created and default asset is not always # used to create hash of themed assets. name = os.path.join(theme.theme_dir_name, name) parsed_name = urlsplit(unquote(name)) clean_name = parsed_name.path.strip() asset_name = name if not self.exists(clean_name): # if themed asset does not exists then use default asset theme = name.split("/", 1)[0] # verify that themed asset was accessed if theme in [theme.theme_dir_name for theme in get_themes()]: asset_name = "/".join(name.split("/")[1:]) return asset_name
def setUp(self): super(TestUpdateAssets, self).setUp() self.themes = get_themes()
def _get_redirect_to(request_host, request_headers, request_params, request_is_https): """ Determine the redirect url and return if safe Arguments: request_host (str) request_headers (dict) request_params (QueryDict) request_is_https (bool) Returns: str redirect url if safe else None """ redirect_to = request_params.get('next') header_accept = request_headers.get('HTTP_ACCEPT', '') accepts_text_html = any(mime_type in header_accept for mime_type in {'*/*', 'text/*', 'text/html'}) # If we get a redirect parameter, make sure it's safe i.e. not redirecting outside our domain. # Also make sure that it is not redirecting to a static asset and redirected page is web page # not a static file. As allowing assets to be pointed to by "next" allows 3rd party sites to # get information about a user on edx.org. In any such case drop the parameter. if redirect_to: mime_type, _ = mimetypes.guess_type(redirect_to, strict=False) safe_redirect = is_safe_login_or_logout_redirect( redirect_to=redirect_to, request_host=request_host, dot_client_id=request_params.get('client_id'), require_https=request_is_https, ) if not safe_redirect: log.warning( u"Unsafe redirect parameter detected after login page: '%(redirect_to)s'", {"redirect_to": redirect_to}) redirect_to = None elif not accepts_text_html: log.info( u"Redirect to non html content '%(content_type)s' detected from '%(user_agent)s'" u" after login page: '%(redirect_to)s'", { "redirect_to": redirect_to, "content_type": header_accept, "user_agent": request_headers.get('HTTP_USER_AGENT', '') }) redirect_to = None elif mime_type: log.warning( u"Redirect to url path with specified filed type '%(mime_type)s' not allowed: '%(redirect_to)s'", { "redirect_to": redirect_to, "mime_type": mime_type }) redirect_to = None elif settings.STATIC_URL in redirect_to: log.warning( u"Redirect to static content detected after login page: '%(redirect_to)s'", {"redirect_to": redirect_to}) redirect_to = None else: themes = get_themes() next_path = urllib.parse.urlparse(redirect_to).path for theme in themes: if theme.theme_dir_name in next_path: log.warning( u"Redirect to theme content detected after login page: '%(redirect_to)s'", {"redirect_to": redirect_to}) redirect_to = None break return redirect_to
def setUp(self): super().setUp() self.themes = get_themes()
def setUp(self): super(TestUpdateAssets, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments self.themes = get_themes()