Esempio n. 1
0
    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
Esempio n. 2
0
    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
Esempio n. 3
0
    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"])
Esempio n. 4
0
    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
Esempio n. 5
0
    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
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 8
0
 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)
Esempio n. 9
0
 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)
Esempio n. 10
0
 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)
Esempio n. 11
0
    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"
        ])
Esempio n. 12
0
 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)
Esempio n. 13
0
 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)
Esempio n. 14
0
 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)
Esempio n. 15
0
 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)
Esempio n. 16
0
    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
Esempio n. 17
0
    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)
Esempio n. 18
0
    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
Esempio n. 19
0
    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)
Esempio n. 20
0
    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)
Esempio n. 21
0
    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
Esempio n. 22
0
    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
Esempio n. 23
0
 def setUp(self):
     super(TestUpdateAssets, self).setUp()
     self.themes = get_themes()
Esempio n. 24
0
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
Esempio n. 25
0
 def setUp(self):
     super().setUp()
     self.themes = get_themes()
Esempio n. 26
0
 def setUp(self):
     super(TestUpdateAssets, self).setUp()
     self.themes = get_themes()
Esempio n. 27
0
 def setUp(self):
     super(TestUpdateAssets, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
     self.themes = get_themes()