diff options
Diffstat (limited to '')
-rw-r--r-- | src/pybind/mgr/dashboard/controllers/home.py | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/controllers/home.py b/src/pybind/mgr/dashboard/controllers/home.py new file mode 100644 index 000000000..f911cf388 --- /dev/null +++ b/src/pybind/mgr/dashboard/controllers/home.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- + +import json +import logging +import os +import re + +try: + from functools import lru_cache +except ImportError: + from ..plugins.lru_cache import lru_cache + +import cherrypy +from cherrypy.lib.static import serve_file + +from .. import mgr +from . import BaseController, Endpoint, Proxy, Router, UIRouter + +logger = logging.getLogger("controllers.home") + + +class LanguageMixin(object): + def __init__(self): + try: + self.LANGUAGES = { + f + for f in os.listdir(mgr.get_frontend_path()) + if os.path.isdir(os.path.join(mgr.get_frontend_path(), f)) + } + except FileNotFoundError: + logger.exception("Build directory missing") + self.LANGUAGES = {} + + self.LANGUAGES_PATH_MAP = { + f.lower(): { + 'lang': f, + 'path': os.path.join(mgr.get_frontend_path(), f) + } + for f in self.LANGUAGES + } + # pre-populating with the primary language subtag. + for lang in list(self.LANGUAGES_PATH_MAP.keys()): + if '-' in lang: + self.LANGUAGES_PATH_MAP[lang.split('-')[0]] = { + 'lang': self.LANGUAGES_PATH_MAP[lang]['lang'], + 'path': self.LANGUAGES_PATH_MAP[lang]['path'] + } + with open(os.path.normpath("{}/../package.json".format(mgr.get_frontend_path())), + "r") as f: + config = json.load(f) + self.DEFAULT_LANGUAGE = config['config']['locale'] + self.DEFAULT_LANGUAGE_PATH = os.path.join(mgr.get_frontend_path(), + self.DEFAULT_LANGUAGE) + super().__init__() + + +@Router("/", secure=False) +class HomeController(BaseController, LanguageMixin): + LANG_TAG_SEQ_RE = re.compile(r'\s*([^,]+)\s*,?\s*') + LANG_TAG_RE = re.compile( + r'^(?P<locale>[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*|\*)(;q=(?P<weight>[01]\.\d{0,3}))?$') + MAX_ACCEPTED_LANGS = 10 + + @lru_cache() + def _parse_accept_language(self, accept_lang_header): + result = [] + for i, m in enumerate(self.LANG_TAG_SEQ_RE.finditer(accept_lang_header)): + if i >= self.MAX_ACCEPTED_LANGS: + logger.debug("reached max accepted languages, skipping remaining") + break + + tag_match = self.LANG_TAG_RE.match(m.group(1)) + if tag_match is None: + raise cherrypy.HTTPError(400, "Malformed 'Accept-Language' header") + locale = tag_match.group('locale').lower() + weight = tag_match.group('weight') + if weight: + try: + ratio = float(weight) + except ValueError: + raise cherrypy.HTTPError(400, "Malformed 'Accept-Language' header") + else: + ratio = 1.0 + result.append((locale, ratio)) + + result.sort(key=lambda l: l[0]) + result.sort(key=lambda l: l[1], reverse=True) + logger.debug("language preference: %s", result) + return [r[0] for r in result] + + def _language_dir(self, langs): + for lang in langs: + if lang in self.LANGUAGES_PATH_MAP: + logger.debug("found directory for language '%s'", lang) + cherrypy.response.headers[ + 'Content-Language'] = self.LANGUAGES_PATH_MAP[lang]['lang'] + return self.LANGUAGES_PATH_MAP[lang]['path'] + + logger.debug("Languages '%s' not available, falling back to %s", + langs, self.DEFAULT_LANGUAGE) + cherrypy.response.headers['Content-Language'] = self.DEFAULT_LANGUAGE + return self.DEFAULT_LANGUAGE_PATH + + @Proxy() + def __call__(self, path, **params): + if not path: + path = "index.html" + + if 'cd-lang' in cherrypy.request.cookie: + langs = [cherrypy.request.cookie['cd-lang'].value.lower()] + logger.debug("frontend language from cookie: %s", langs) + else: + if 'Accept-Language' in cherrypy.request.headers: + accept_lang_header = cherrypy.request.headers['Accept-Language'] + langs = self._parse_accept_language(accept_lang_header) + else: + langs = [self.DEFAULT_LANGUAGE.lower()] + logger.debug("frontend language from headers: %s", langs) + + base_dir = self._language_dir(langs) + full_path = os.path.join(base_dir, path) + + # Block uplevel attacks + if not os.path.normpath(full_path).startswith(os.path.normpath(base_dir)): + raise cherrypy.HTTPError(403) # Forbidden + + logger.debug("serving static content: %s", full_path) + if 'Vary' in cherrypy.response.headers: + cherrypy.response.headers['Vary'] = "{}, Accept-Language" + else: + cherrypy.response.headers['Vary'] = "Accept-Language" + + cherrypy.response.headers['Cache-control'] = "no-cache" + return serve_file(full_path) + + +@UIRouter("/langs", secure=False) +class LangsController(BaseController, LanguageMixin): + @Endpoint('GET') + def __call__(self): + return list(self.LANGUAGES) + + +@UIRouter("/login", secure=False) +class LoginController(BaseController): + @Endpoint('GET', 'custom_banner') + def __call__(self): + return mgr.get_store('custom_login_banner') |