1
"""
2
Subclasses the bokeh serve commandline handler to extend it in various
3
ways.
4
"""
5

6 7
import ast
7 7
import base64
8 7
import logging # isort:skip
9

10 7
from glob import glob
11 7
from urllib.parse import urljoin
12

13 7
from bokeh.command.subcommands.serve import Serve as _BkServe
14

15 7
from ..auth import OAuthProvider
16 7
from ..config import config
17 7
from ..io.rest import REST_PROVIDERS
18 7
from ..io.server import INDEX_HTML, get_static_routes
19 7
from ..io.state import state
20 7
from ..util import edit_readonly
21

22 7
log = logging.getLogger(__name__)
23

24

25 7
def parse_var(s):
26
    """
27
    Parse a key, value pair, separated by '='
28
    That's the reverse of ShellArgs.
29

30
    On the command line (argparse) a declaration will typically look like:
31
        foo=hello
32
    or
33
        foo="hello world"
34
    """
35 0
    items = s.split('=')
36 0
    key = items[0].strip() # we remove blanks around keys, as is logical
37 0
    if len(items) > 1:
38
        # rejoin the rest:
39 0
        value = '='.join(items[1:])
40 0
    return (key, value)
41

42

43 7
def parse_vars(items):
44
    """
45
    Parse a series of key-value pairs and return a dictionary
46
    """
47 0
    return dict((parse_var(item) for item in items))
48

49

50 7
class Serve(_BkServe):
51

52 7
    args = _BkServe.args + (
53
        ('--static-dirs', dict(
54
            metavar="KEY=VALUE",
55
            nargs='+',
56
            help=("Static directories to serve specified as key=value "
57
                  "pairs mapping from URL route to static file directory.")
58
        )),
59
        ('--oauth-provider', dict(
60
            action = 'store',
61
            type   = str,
62
            help   = "The OAuth2 provider to use."
63
        )),
64
        ('--oauth-key', dict(
65
            action  = 'store',
66
            type    = str,
67
            help    = "The OAuth2 key to use",
68
        )),
69
        ('--oauth-secret', dict(
70
            action  = 'store',
71
            type    = str,
72
            help    = "The OAuth2 secret to use",
73
        )),
74
        ('--oauth-redirect-uri', dict(
75
            action  = 'store',
76
            type    = str,
77
            help    = "The OAuth2 redirect URI",
78
        )),
79
        ('--oauth-extra-params', dict(
80
            action  = 'store',
81
            type    = str,
82
            help    = "Additional parameters to use.",
83
        )),
84
        ('--oauth-jwt-user', dict(
85
            action  = 'store',
86
            type    = str,
87
            help    = "The key in the ID JWT token to consider the user.",
88
        )),
89
        ('--oauth-encryption-key', dict(
90
            action = 'store',
91
            type    = str,
92
            help    = "A random string used to encode the user information."
93
        )),
94
        ('--rest-provider', dict(
95
            action = 'store',
96
            type   = str,
97
            help   = "The interface to use to serve REST API"
98
        )),
99
        ('--rest-endpoint', dict(
100
            action  = 'store',
101
            type    = str,
102
            help    = "Endpoint to store REST API on.",
103
            default = 'rest'
104
        )),
105
        ('--rest-session-info', dict(
106
            action  = 'store_true',
107
            help    = "Whether to serve session info on the REST API"
108
        )),
109
        ('--session-history', dict(
110
            action  = 'store',
111
            type    = int,
112
            help    = "The length of the session history to record.",
113
            default = 0
114
        ))
115
    )
116

117 7
    def customize_kwargs(self, args, server_kwargs):
118
        '''Allows subclasses to customize ``server_kwargs``.
119

120
        Should modify and return a copy of the ``server_kwargs`` dictionary.
121
        '''
122 0
        kwargs = dict(server_kwargs)
123 0
        if 'index' not in kwargs:
124 0
            kwargs['index'] = INDEX_HTML
125

126
        # Handle tranquilized functions in the supplied functions
127 0
        kwargs['extra_patterns'] = patterns = kwargs.get('extra_patterns', [])
128

129 0
        if args.static_dirs:
130 0
            static_dirs = parse_vars(args.static_dirs)
131 0
            patterns += get_static_routes(static_dirs)
132

133 0
        files = []
134 0
        for f in args.files:
135 0
            if args.glob:
136 0
                files.extend(glob(f))
137
            else:
138 0
                files.append(f)
139

140 0
        prefix = args.prefix or ''
141 0
        if not prefix.endswith('/'):
142 0
            prefix += '/'
143 0
        with edit_readonly(state):
144 0
            state.base_url = urljoin('/', prefix)
145

146
        # Handle tranquilized functions in the supplied functions
147 0
        if args.rest_provider in REST_PROVIDERS:
148 0
            pattern = REST_PROVIDERS[args.rest_provider](files, args.rest_endpoint)
149 0
            patterns.extend(pattern)
150 0
        elif args.rest_provider is not None:
151 0
            raise ValueError("rest-provider %r not recognized." % args.rest_provider)
152

153 0
        config.session_history = args.session_history
154 0
        if args.rest_session_info:
155 0
            pattern = REST_PROVIDERS['param'](files, 'rest')
156 0
            patterns.extend(pattern)
157 0
            state.publish('session_info', state, ['session_info'])
158

159 0
        if args.oauth_provider:
160 0
            config.oauth_provider = args.oauth_provider
161 0
            if config.oauth_key and args.oauth_key:
162 0
                raise ValueError(
163
                    "Supply OAuth key either using environment variable "
164
                    "or via explicit argument, not both."
165
                )
166 0
            elif args.oauth_key:
167 0
                config.oauth_key = args.oauth_key
168 0
            elif not config.oauth_key:
169 0
                raise ValueError(
170
                    "When enabling an OAuth provider you must supply "
171
                    "a valid oauth_key either using the --oauth-key "
172
                    "CLI argument or PANEL_OAUTH_KEY environment "
173
                    "variable."
174
                )
175

176 0
            if config.oauth_secret and args.oauth_secret:
177 0
                raise ValueError(
178
                    "Supply OAuth secret either using environment variable "
179
                    "or via explicit argument, not both."
180
                )
181 0
            elif args.oauth_secret:
182 0
                config.oauth_secret = args.oauth_secret
183 0
            elif not config.oauth_secret:
184 0
                raise ValueError(
185
                    "When enabling an OAuth provider you must supply "
186
                    "a valid OAuth secret either using the --oauth-secret "
187
                    "CLI argument or PANEL_OAUTH_SECRET environment "
188
                    "variable."
189
                )
190

191 0
            if args.oauth_extra_params:
192 0
                config.oauth_extra_params = ast.literal_eval(args.oauth_extra_params)
193

194 0
            if config.oauth_encryption_key and args.oauth_encryption_key:
195 0
                raise ValueError(
196
                    "Supply OAuth encryption key either using environment "
197
                    "variable or via explicit argument, not both."
198
                )
199 0
            elif args.oauth_encryption_key:
200 0
                encryption_key = args.oauth_encryption_key.encode('ascii')
201 0
                try:
202 0
                    key = base64.urlsafe_b64decode(encryption_key)
203 0
                except Exception:
204 0
                    raise ValueError("OAuth encryption key was not a valid base64 "
205
                                     "string. Generate an encryption key with "
206
                                     "`panel oauth-secret` and ensure you did not "
207
                                     "truncate the returned string.")
208 0
                if len(key) != 32:
209 0
                    raise ValueError(
210
                        "OAuth encryption key must be 32 url-safe "
211
                        "base64-encoded bytes."
212
                    )
213 0
                config.oauth_encryption_key = encryption_key
214
            else:
215 0
                print("WARNING: OAuth has not been configured with an "
216
                      "encryption key and will potentially leak "
217
                      "credentials in cookies and a JWT token embedded "
218
                      "in the served website. Use at your own risk or "
219
                      "generate a key with the `panel oauth-key` CLI "
220
                      "command and then provide it to `panel serve` "
221
                      "using the PANEL_OAUTH_ENCRYPTION environment "
222
                      "variable or the --oauth-encryption-key CLI "
223
                      "argument.")
224

225 0
            if config.oauth_encryption_key:
226 0
                try:
227 0
                    from cryptography.fernet import Fernet
228 0
                except ImportError:
229 0
                    raise ImportError(
230
                        "Using OAuth2 provider with Panel requires the "
231
                        "cryptography library. Install it with `pip install "
232
                        "cryptography` or `conda install cryptography`."
233
                    )
234 0
                state.encryption = Fernet(config.oauth_encryption_key)
235

236 0
            if args.cookie_secret and config.cookie_secret:
237 0
                raise ValueError(
238
                    "Supply cookie secret either using environment "
239
                    "variable or via explicit argument, not both."
240
                )
241 0
            elif args.cookie_secret:
242 0
                config.cookie_secret = args.cookie_secret
243
            else:
244 0
                raise ValueError(
245
                    "When enabling an OAuth provider you must supply "
246
                    "a valid cookie_secret either using the --cookie-secret "
247
                    "CLI argument or the PANEL_COOKIE_SECRET environment "
248
                    "variable."
249
                )
250 0
            kwargs['auth_provider'] = OAuthProvider()
251

252 0
            if args.oauth_redirect_uri and config.oauth_redirect_uri:
253 0
                raise ValueError(
254
                    "Supply OAuth redirect URI either using environment "
255
                    "variable or via explicit argument, not both."
256
                )
257 0
            elif args.oauth_redirect_uri:
258 0
                config.oauth_redirect_uri = args.oauth_redirect_uri
259

260 0
            if args.oauth_jwt_user and config.oauth_jwt_user:
261 0
                raise ValueError(
262
                    "Supply OAuth JWT user either using environment "
263
                    "variable or via explicit argument, not both."
264
                )
265 0
            elif args.oauth_jwt_user:
266 0
                config.oauth_jwt_user = args.oauth_jwt_user
267

268 0
        if config.cookie_secret:
269 0
            kwargs['cookie_secret'] = config.cookie_secret
270

271 0
        return kwargs

Read our documentation on viewing source code .

Loading