/
config.py
553 lines (469 loc) · 18.6 KB
/
config.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# -*- coding: utf-8 -*-
# rdiffweb, A web interface to rdiff-backup repositories
# Copyright (C) 2012-2021 rdiffweb contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
import logging
import re
import sys
from collections import OrderedDict
import cherrypy
import configargparse
import pkg_resources
from cherrypy import Application
# Define the logger
logger = logging.getLogger(__name__)
# Get rdiffweb version.
try:
VERSION = pkg_resources.get_distribution("rdiffweb").version
except pkg_resources.DistributionNotFound:
VERSION = "DEV"
def get_parser():
# Get global config argument parser
parser = configargparse.ArgumentParser(
prog='rdiffweb',
description='Web interface to browse and restore rdiff-backup repositories.',
default_config_files=['/etc/rdiffweb/rdw.conf', '/etc/rdiffweb/rdw.conf.d/*.conf'],
add_env_var_help=True,
auto_env_var_prefix='RDIFFWEB_',
config_file_parser_class=ConfigFileParser,
conflict_handler='resolve',
)
parser.add_argument(
'-f', '--config', is_config_file=True, metavar='FILE', help='location of Rdiffweb configuration file'
)
parser.add(
'--database-uri',
'--sqlitedb-file',
'--sqlitedbfile',
metavar='URI',
help="""Location of the database used for persistence. SQLite and PostgreSQL
database are supported officially. To use a SQLite database you may
define the location using a file path or a URI.
e.g.: /srv/rdiffweb/file.db or sqlite:///srv/rdiffweb/file.db`.
To use PostgreSQL server you must provide
a URI similar to postgresql://user:pass@10.255.1.34/dbname and you
must install required dependencies.
By default, Rdiffweb uses a SQLite embedded database located at
/etc/rdiffweb/rdw.db.""",
default='/etc/rdiffweb/rdw.db',
)
parser.add_argument(
'-d',
'--debug',
action='store_true',
help='enable rdiffweb debug mode - change the log level to DEBUG, print exception stack trace to the web interface and show SQL query in logs',
)
parser.add_argument(
'--admin-user',
'--adminuser',
metavar='USERNAME',
help='administrator username. The administrator user get created on startup if the database is empty.',
default='admin',
)
parser.add_argument(
'--admin-password',
metavar='USERNAME',
help="""administrator encrypted password as SSHA. Read online
documentation to know more about how to encrypt your password
into SSHA or use http://projects.marsching.org/weave4j/util/genpassword.php
When defined, administrator password cannot be updated using the web interface.
When undefined, default administrator password is `admin123` and
it can be updated using the web interface.""",
)
parser.add_argument(
'--default-theme',
'--defaulttheme',
help='define the default theme. Either: default, blue or orange. Define the CSS file to be loaded in the web interface. You may manually edit a CSS file to customize it. The location is similar to `/usr/local/lib/python3.9/dist-packages/rdiffweb/static/`',
choices=['default', 'blue', 'orange'],
default='default',
)
parser.add_argument(
'--environment',
choices=['development', 'production'],
help='define the type of environment: development, production. This is used to limit the information shown to the user when an error occur.',
default='production',
)
parser.add_argument(
'--email-encryption',
'--emailencryption',
choices=['none', 'ssl', 'starttls'],
help='type of encryption to be used when establishing communication with SMTP server. Default: none',
default='none',
)
parser.add_argument(
'--email-host',
'--emailhost',
metavar='HOST',
help='SMTP server used to send email in the form <host>:<port>. If the port is not provided, default to standard port 25 or 465 is used. e.g.: smtp.gmail.com:587',
)
parser.add_argument(
'--email-sender',
'--emailsender',
metavar='EMAIL',
help='email addres used for the `from:` field when sending email.',
)
parser.add_argument(
'--email-notification-time',
'--emailnotificationtime',
metavar='TIME',
help='time when the email notifcation should be sent for inactive backups. e.g.: 22:00 Default value: 23:00',
default='23:00',
)
parser.add_argument(
'--email-username',
'--emailusername',
metavar='USERNAME',
help='username used for authentication with the SMTP server.',
)
parser.add_argument(
'--email-password',
'--emailpassword',
metavar='PASSWORD',
help='password used for authentication with the SMTP server.',
)
parser.add_argument(
'--email-send-changed-notification',
'--emailsendchangednotification',
help='True to send notification when sensitive information get change in user profile.',
action='store_true',
default=False,
)
parser.add_argument(
'--favicon',
help='location of an icon to be used as a favicon displayed in web browser.',
default=pkg_resources.resource_filename('rdiffweb', 'static/favicon.ico'),
) # @UndefinedVariable
parser.add_argument(
'--footer-name', '--footername', help=argparse.SUPPRESS, default='rdiffweb'
) # @UndefinedVariable
parser.add_argument(
'--footer-url', '--footerurl', help=argparse.SUPPRESS, default='https://rdiffweb.org/'
) # @UndefinedVariable
parser.add_argument(
'--header-logo',
'--headerlogo',
help='location of an image (preferably a .png) to be used as a replacement for the rdiffweb logo.',
)
parser.add_argument(
'--header-name',
'--headername',
help='application name displayed in the title bar and header menu.',
default='rdiffweb',
)
parser.add_argument(
'--ldap-add-missing-user',
'--addmissinguser',
action='store_true',
help='enable creation of users from LDAP when the credential are valid.',
default=False,
)
parser.add_argument(
'--ldap-add-user-default-role',
help='default role used when creating users from LDAP. This parameter is only useful when `--ldap-add-missing-user` is enabled.',
default='user',
choices=['admin', 'maintainer', 'user'],
)
parser.add_argument(
'--ldap-add-user-default-userroot',
help='default user root directory used when creating users from LDAP. LDAP attributes may be used to define the default location. e.g.: `/backups/{uid[0]}/`. This parameter is only useful when `--ldap-add-missing-user` is enabled.',
default='',
)
parser.add_argument(
'--ldap-uri',
'--ldapuri',
help='URL to the LDAP server used to validate user credentials. e.g.: ldap://localhost:389',
)
parser.add_argument(
'--ldap-base-dn',
'--ldapbasedn',
metavar='DN',
help='DN of the branch of the directory where all searches should start from. e.g.: dc=my,dc=domain',
default="",
)
parser.add_argument(
'--ldap-scope',
'--ldapscope',
help='scope of the search. Can be either base, onelevel or subtree',
choices=['base', 'onelevel', 'subtree'],
default="subtree",
)
parser.add_argument('--ldap-tls', '--ldaptls', action='store_true', help='enable TLS')
parser.add_argument(
'--ldap-username-attribute',
'--ldapattribute',
metavar='ATTRIBUTE',
help="The attribute to search username. If no attributes are provided, the default is to use `uid`. It's a good idea to choose an attribute that will be unique across all entries in the subtree you will be using.",
default='uid',
)
parser.add_argument(
'--ldap-filter',
'--ldapfilter',
help="search filter to limit LDAP lookup. If not provided, defaults to (objectClass=*), which searches for all objects in the tree.",
default='(objectClass=*)',
)
parser.add_argument(
'--ldap-required-group',
'--ldaprequiredgroup',
metavar='GROUPNAME',
help="name of the group of which the user must be a member to access rdiffweb. Should be used with ldap-group-attribute and ldap-group-attribute-is-dn.",
)
parser.add_argument(
'--ldap-group-attribute',
'--ldapgroupattribute',
metavar='ATTRIBUTE',
help="name of the attribute defining the groups of which the user is a member. Should be used with ldap-required-group and ldap-group-attribute-is-dn.",
default='member',
)
parser.add_argument(
'--ldap-group-attribute-is-dn',
'--ldapgroupattributeisdn',
help="True if the content of the attribute `ldap-group-attribute` is a DN.",
action='store_true',
)
parser.add_argument(
'--ldap-bind-dn',
'--ldapbinddn',
metavar='DN',
help="optional DN used to bind to the server when searching for entries. If not provided, will use an anonymous bind.",
default="",
)
parser.add_argument(
'--ldap-bind-password',
'--ldapbindpassword',
metavar='PASSWORD',
help="password to use in conjunction with LdapBindDn. Note that the bind password is probably sensitive data, and should be properly protected. You should only use the LdapBindDn and LdapBindPassword if you absolutely need them to search the directory.",
default="",
)
parser.add_argument(
'--ldap-version',
'--ldapversion',
'--ldapprotocolversion',
help="version of LDAP in use either 2 or 3. Default to 3.",
default=3,
type=int,
choices=[2, 3],
)
parser.add_argument(
'--ldap-network-timeout',
'--ldapnetworktimeout',
metavar='SECONDS',
help="timeout in seconds value used for LDAP connection",
default=100,
type=int,
)
parser.add_argument(
'--ldap-timeout',
'--ldaptimeout',
metavar='SECONDS',
help="timeout in seconds value used for LDAP request",
default=300,
type=int,
)
parser.add_argument(
'--ldap-encoding',
'--ldapencoding',
metavar='ENCODING',
help="encoding used by your LDAP server.",
default="utf-8",
)
parser.add_argument(
'--log-access-file', '--logaccessfile', metavar='FILE', help='location of Rdiffweb log access file.'
)
parser.add_argument(
'--log-file',
'--logfile',
metavar='FILE',
help='location of Rdiffweb log file. Print log to the console if not define in config file.',
)
parser.add_argument(
'--log-level',
'--loglevel',
help='Define the log level.',
choices=['ERROR', 'WARN', 'INFO', 'DEBUG'],
default='INFO',
)
parser.add_argument(
'--max-depth',
'--maxdepth',
metavar='DEPTH',
help="define the maximum folder depthness to search into the user's root directory to find repositories. This is commonly used if you repositories are organised with multiple sub-folder.",
type=int,
default=3,
)
parser.add('--quota-set-cmd', '--quotasetcmd', metavar='COMMAND', help="command line to set the user's quota.")
parser.add('--quota-get-cmd', '--quotagetcmd', metavar='COMMAND', help="command line to get the user's quota.")
parser.add(
'--quota-used-cmd', '--quotausedcmd', metavar='COMMAND', help="Command line to get user's quota disk usage."
)
parser.add(
'--remove-older-time',
'--removeoldertime',
metavar='TIME',
help="Time when to execute the remove older scheduled job. e.g.: 22:30",
default='23:00',
)
parser.add('--server-host', '--serverhost', metavar='IP', default='127.0.0.1', help='IP address to listen to')
parser.add(
'--server-port',
'--serverport',
metavar='PORT',
help='port to listen to for HTTP request',
default='8080',
type=int,
)
parser.add(
'--session-dir',
'--sessiondir',
metavar='FOLDER',
help='location where to store user session information. When undefined, the user sessions are kept in memory.',
)
parser.add(
'--rate-limit',
metavar='LIMIT',
type=int,
default=10,
help='maximum number of requests per minute that can be made by an IP address for an unauthenticated connection. When this limit is reached, an HTTP 429 message is returned to the user. This security measure is used to limit brute force attacks on the login page and the RESTful API.',
)
parser.add(
'--ssl-certificate',
'--sslcertificate',
metavar='CERT',
help='location of the SSL Certification to enable HTTPS (not recommended)',
)
parser.add(
'--ssl-private-key',
'--sslprivatekey',
metavar='KEY',
help='location of the SSL Private Key to enable HTTPS (not recommended)',
)
parser.add(
'--tempdir',
metavar='FOLDER',
help='alternate temporary folder to be used when restoring files. Might be useful if the default location has limited disk space. Default to TEMPDIR environment or `/tmp`.',
)
parser.add(
'--disable-ssh-keys',
action='store_true',
help='used to hide SSH Key management to avoid users to add or remove SSH Key using the web application',
default=False,
)
parser.add(
'--password-min-length',
type=int,
help="Minimum length of the user's password",
default=8,
)
parser.add(
'--password-max-length',
type=int,
help="Maximum length of the user's password",
default=128,
)
parser.add(
'--password-score',
type=lambda x: max(1, min(int(x), 4)),
help="Minimum zxcvbn's score for password. Value from 1 to 4. Default value 2. Read more about it here: https://github.com/dropbox/zxcvbn",
default=2,
)
parser.add_argument('--version', action='version', version='%(prog)s ' + VERSION)
# Here we append a list of arguments for each locale.
flags = ['--welcome-msg'] + ['--welcome-msg-' + i for i in ['ca', 'en', 'es', 'fr', 'ru']] + ['--welcomemsg']
parser.add_argument(
*flags,
metavar='HTML',
help='replace the welcome message displayed in the login page for default locale or for a specific locale',
action=LocaleAction
)
return parser
def parse_args(args=None, config_file_contents=None):
args = sys.argv[1:] if args is None else args
return get_parser().parse_args(args, config_file_contents=config_file_contents)
class LocaleAction(argparse.Action):
"""
Custom Action to support defining arguments with locale.
"""
def __init__(self, option_strings, dest, nargs=None, **kwargs):
super(LocaleAction, self).__init__(option_strings, dest, **kwargs)
def __call__(self, parser, namespace, value, option_string=None):
if option_string[-3] == '-':
# When using arguments, we can extract the locale from the argument key
locale = option_string[-2:]
elif value[2] == ':':
# When using config file, the locale could be extract from the value e.g. fr:message
locale = value[0:2]
value = value[3:]
else:
locale = ''
# Create a dictionary with locale.
items = getattr(namespace, self.dest) or {}
items[locale] = value
setattr(namespace, self.dest, items)
class ConfigFileParser(object):
"""
Custom config file parser to support rdiffweb config file format.
"""
def get_syntax_description(self):
msg = "Configuration file syntax allows: key=value, flag=true."
return msg
def parse(self, stream):
"""
Used to read the rdiffweb config file as dict.
"""
result = OrderedDict()
for i, line in enumerate(stream):
line = re.compile("(.*?)#.*").sub(r'\1', line).strip()
if not line:
continue
if '=' not in line:
raise configargparse.ConfigFileParserException(
"Unexpected line {} in {}: {}".format(i, getattr(stream, 'name', 'stream'), line)
)
split_line = line.partition('=')
if not len(split_line) == 3:
raise configargparse.ConfigFileParserException(
"Unexpected line {} in {}: {}".format(i, getattr(stream, 'name', 'stream'), line)
)
# Get key a& value
key = split_line[0].lower().strip().replace('_', '-')
value = split_line[2].strip()
# Support welcome-msg locale for backward compatibility
m = re.match("welcome-?msg\\[(ca|en|es|fr|ru)\\]", key.lower())
if m:
key = "welcome-msg-" + m.group(1)
value = m.group(1) + ":" + value
result[key] = value
# This dictionary is read by cherrypy. So create appropriate structure.
return result
class Option(object):
def __init__(self, key):
assert key
self.key = key
def __get__(self, instance, owner):
"""
Return a property to wrap the given option.
"""
return self.get(instance)
def get(self, instance=None):
"""
Return the value of this options.
"""
if isinstance(instance, Application):
app = instance
else:
app = cherrypy.request.app or getattr(instance, 'app', None)
assert app, "Option() can't get reference to app"
assert app.cfg, "Option() can't get reference to app.cfg"
return getattr(app.cfg, self.key)