]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/controllers/home.py
import ceph quincy 17.2.1
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / home.py
1 # -*- coding: utf-8 -*-
2
3 import json
4 import logging
5 import os
6 import re
7
8 try:
9 from functools import lru_cache
10 except ImportError:
11 from ..plugins.lru_cache import lru_cache
12
13 import cherrypy
14 from cherrypy.lib.static import serve_file
15
16 from .. import mgr
17 from ..services.custom_banner import get_login_banner_mgr
18 from . import BaseController, Endpoint, Proxy, Router, UIRouter
19
20 logger = logging.getLogger("controllers.home")
21
22
23 class LanguageMixin(object):
24 def __init__(self):
25 try:
26 self.LANGUAGES = {
27 f
28 for f in os.listdir(mgr.get_frontend_path())
29 if os.path.isdir(os.path.join(mgr.get_frontend_path(), f))
30 }
31 except FileNotFoundError:
32 logger.exception("Build directory missing")
33 self.LANGUAGES = {}
34
35 self.LANGUAGES_PATH_MAP = {
36 f.lower(): {
37 'lang': f,
38 'path': os.path.join(mgr.get_frontend_path(), f)
39 }
40 for f in self.LANGUAGES
41 }
42 # pre-populating with the primary language subtag.
43 for lang in list(self.LANGUAGES_PATH_MAP.keys()):
44 if '-' in lang:
45 self.LANGUAGES_PATH_MAP[lang.split('-')[0]] = {
46 'lang': self.LANGUAGES_PATH_MAP[lang]['lang'],
47 'path': self.LANGUAGES_PATH_MAP[lang]['path']
48 }
49 with open(os.path.normpath("{}/../package.json".format(mgr.get_frontend_path())),
50 "r") as f:
51 config = json.load(f)
52 self.DEFAULT_LANGUAGE = config['config']['locale']
53 self.DEFAULT_LANGUAGE_PATH = os.path.join(mgr.get_frontend_path(),
54 self.DEFAULT_LANGUAGE)
55 super().__init__()
56
57
58 @Router("/", secure=False)
59 class HomeController(BaseController, LanguageMixin):
60 LANG_TAG_SEQ_RE = re.compile(r'\s*([^,]+)\s*,?\s*')
61 LANG_TAG_RE = re.compile(
62 r'^(?P<locale>[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*|\*)(;q=(?P<weight>[01]\.\d{0,3}))?$')
63 MAX_ACCEPTED_LANGS = 10
64
65 @lru_cache()
66 def _parse_accept_language(self, accept_lang_header):
67 result = []
68 for i, m in enumerate(self.LANG_TAG_SEQ_RE.finditer(accept_lang_header)):
69 if i >= self.MAX_ACCEPTED_LANGS:
70 logger.debug("reached max accepted languages, skipping remaining")
71 break
72
73 tag_match = self.LANG_TAG_RE.match(m.group(1))
74 if tag_match is None:
75 raise cherrypy.HTTPError(400, "Malformed 'Accept-Language' header")
76 locale = tag_match.group('locale').lower()
77 weight = tag_match.group('weight')
78 if weight:
79 try:
80 ratio = float(weight)
81 except ValueError:
82 raise cherrypy.HTTPError(400, "Malformed 'Accept-Language' header")
83 else:
84 ratio = 1.0
85 result.append((locale, ratio))
86
87 result.sort(key=lambda l: l[0])
88 result.sort(key=lambda l: l[1], reverse=True)
89 logger.debug("language preference: %s", result)
90 return [r[0] for r in result]
91
92 def _language_dir(self, langs):
93 for lang in langs:
94 if lang in self.LANGUAGES_PATH_MAP:
95 logger.debug("found directory for language '%s'", lang)
96 cherrypy.response.headers[
97 'Content-Language'] = self.LANGUAGES_PATH_MAP[lang]['lang']
98 return self.LANGUAGES_PATH_MAP[lang]['path']
99
100 logger.debug("Languages '%s' not available, falling back to %s",
101 langs, self.DEFAULT_LANGUAGE)
102 cherrypy.response.headers['Content-Language'] = self.DEFAULT_LANGUAGE
103 return self.DEFAULT_LANGUAGE_PATH
104
105 @Proxy()
106 def __call__(self, path, **params):
107 if not path:
108 path = "index.html"
109
110 if 'cd-lang' in cherrypy.request.cookie:
111 langs = [cherrypy.request.cookie['cd-lang'].value.lower()]
112 logger.debug("frontend language from cookie: %s", langs)
113 else:
114 if 'Accept-Language' in cherrypy.request.headers:
115 accept_lang_header = cherrypy.request.headers['Accept-Language']
116 langs = self._parse_accept_language(accept_lang_header)
117 else:
118 langs = [self.DEFAULT_LANGUAGE.lower()]
119 logger.debug("frontend language from headers: %s", langs)
120
121 base_dir = self._language_dir(langs)
122 full_path = os.path.join(base_dir, path)
123
124 # Block uplevel attacks
125 if not os.path.normpath(full_path).startswith(os.path.normpath(base_dir)):
126 raise cherrypy.HTTPError(403) # Forbidden
127
128 logger.debug("serving static content: %s", full_path)
129 if 'Vary' in cherrypy.response.headers:
130 cherrypy.response.headers['Vary'] = "{}, Accept-Language"
131 else:
132 cherrypy.response.headers['Vary'] = "Accept-Language"
133
134 cherrypy.response.headers['Cache-control'] = "no-cache"
135 return serve_file(full_path)
136
137
138 @UIRouter("/langs", secure=False)
139 class LangsController(BaseController, LanguageMixin):
140 @Endpoint('GET')
141 def __call__(self):
142 return list(self.LANGUAGES)
143
144
145 @UIRouter("/login", secure=False)
146 class LoginController(BaseController):
147 @Endpoint('GET', 'custom_banner')
148 def __call__(self):
149 return get_login_banner_mgr()