1# -*- coding: iso-8859-1 -*- 2""" 3 MoinMoin - Multiple configuration handler and Configuration defaults class 4 5 @copyright: 2000-2004 Juergen Hermann <jh@web.de>, 6 2005-2008 MoinMoin:ThomasWaldmann. 7 2008 MoinMoin:JohannesBerg 8 @license: GNU GPL, see COPYING for details. 9""" 10 11import hashlib 12import re 13import os 14import sys 15import time 16 17from MoinMoin import log 18logging = log.getLogger(__name__) 19 20from MoinMoin import config, error, util, wikiutil, web 21from MoinMoin import datastruct 22from MoinMoin.auth import MoinAuth 23import MoinMoin.auth as authmodule 24import MoinMoin.events as events 25from MoinMoin.events import PageChangedEvent, PageRenamedEvent 26from MoinMoin.events import PageDeletedEvent, PageCopiedEvent 27from MoinMoin.events import PageRevertedEvent, FileAttachedEvent 28import MoinMoin.web.session 29from MoinMoin.packages import packLine 30from MoinMoin.security import AccessControlList 31 32_url_re_cache = None 33_farmconfig_mtime = None 34_config_cache = {} 35 36 37def _importConfigModule(name): 38 """ Import and return configuration module and its modification time 39 40 Handle all errors except ImportError, because missing file is not 41 always an error. 42 43 @param name: module name 44 @rtype: tuple 45 @return: module, modification time 46 """ 47 try: 48 module = __import__(name, globals(), {}) 49 mtime = os.path.getmtime(module.__file__) 50 except ImportError: 51 raise 52 except IndentationError, err: 53 logging.exception('Your source code / config file is not correctly indented!') 54 msg = """IndentationError: %(err)s 55 56The configuration files are Python modules. Therefore, whitespace is 57important. Make sure that you use only spaces, no tabs are allowed here! 58You have to use four spaces at the beginning of the line mostly. 59""" % { 60 'err': err, 61} 62 raise error.ConfigurationError(msg) 63 except Exception, err: 64 logging.exception('An exception happened.') 65 msg = '%s: %s' % (err.__class__.__name__, str(err)) 66 raise error.ConfigurationError(msg) 67 return module, mtime 68 69 70def _url_re_list(): 71 """ Return url matching regular expression 72 73 Import wikis list from farmconfig on the first call and compile the 74 regexes. Later just return the cached regex list. 75 76 @rtype: list of tuples of (name, compiled re object) 77 @return: url to wiki config name matching list 78 """ 79 global _url_re_cache, _farmconfig_mtime 80 if _url_re_cache is None: 81 try: 82 farmconfig, _farmconfig_mtime = _importConfigModule('farmconfig') 83 except ImportError, err: 84 if 'farmconfig' in str(err): 85 # we failed importing farmconfig 86 logging.debug("could not import farmconfig, mapping all URLs to wikiconfig") 87 _farmconfig_mtime = 0 88 _url_re_cache = [('wikiconfig', re.compile(r'.')), ] # matches everything 89 else: 90 # maybe there was a failing import statement inside farmconfig 91 raise 92 else: 93 logging.info("using farm config: %s" % os.path.abspath(farmconfig.__file__)) 94 try: 95 cache = [] 96 for name, regex in farmconfig.wikis: 97 cache.append((name, re.compile(regex))) 98 _url_re_cache = cache 99 except AttributeError: 100 logging.error("required 'wikis' list missing in farmconfig") 101 msg = """ 102Missing required 'wikis' list in 'farmconfig.py'. 103 104If you run a single wiki you do not need farmconfig.py. Delete it and 105use wikiconfig.py. 106""" 107 raise error.ConfigurationError(msg) 108 return _url_re_cache 109 110 111def _makeConfig(name): 112 """ Create and return a config instance 113 114 Timestamp config with either module mtime or farmconfig mtime. This 115 mtime can be used later to invalidate older caches. 116 117 @param name: module name 118 @rtype: DefaultConfig sub class instance 119 @return: new configuration instance 120 """ 121 global _farmconfig_mtime 122 try: 123 module, mtime = _importConfigModule(name) 124 configClass = getattr(module, 'Config') 125 cfg = configClass(name) 126 cfg.cfg_mtime = max(mtime, _farmconfig_mtime) 127 logging.info("using wiki config: %s" % os.path.abspath(module.__file__)) 128 except ImportError, err: 129 logging.exception('Could not import.') 130 msg = """ImportError: %(err)s 131 132Check that the file is in the same directory as the server script. If 133it is not, you must add the path of the directory where the file is 134located to the python path in the server script. See the comments at 135the top of the server script. 136 137Check that the configuration file name is either "wikiconfig.py" or the 138module name specified in the wikis list in farmconfig.py. Note that the 139module name does not include the ".py" suffix. 140""" % { 141 'err': err, 142} 143 raise error.ConfigurationError(msg) 144 except AttributeError, err: 145 logging.exception('An exception occurred.') 146 msg = """AttributeError: %(err)s 147 148Could not find required "Config" class in "%(name)s.py". 149 150This might happen if you are trying to use a pre 1.3 configuration file, or 151made a syntax or spelling error. 152 153Another reason for this could be a name clash. It is not possible to have 154config names like e.g. stats.py - because that collides with MoinMoin/stats/ - 155have a look into your MoinMoin code directory what other names are NOT 156possible. 157 158Please check your configuration file. As an example for correct syntax, 159use the wikiconfig.py file from the distribution. 160""" % { 161 'name': name, 162 'err': err, 163} 164 raise error.ConfigurationError(msg) 165 166 return cfg 167 168 169def _getConfigName(url): 170 """ Return config name for url or raise """ 171 for name, regex in _url_re_list(): 172 match = regex.match(url) 173 if match: 174 return name 175 raise error.NoConfigMatchedError 176 177 178def getConfig(url): 179 """ Return cached config instance for url or create new one 180 181 If called by many threads in the same time multiple config 182 instances might be created. The first created item will be 183 returned, using dict.setdefault. 184 185 @param url: the url from request, possibly matching specific wiki 186 @rtype: DefaultConfig subclass instance 187 @return: config object for specific wiki 188 """ 189 cfgName = _getConfigName(url) 190 try: 191 cfg = _config_cache[cfgName] 192 except KeyError: 193 cfg = _makeConfig(cfgName) 194 cfg = _config_cache.setdefault(cfgName, cfg) 195 return cfg 196 197 198# This is a way to mark some text for the gettext tools so that they don't 199# get orphaned. See http://www.python.org/doc/current/lib/node278.html. 200def _(text): 201 return text 202 203 204class CacheClass: 205 """ just a container for stuff we cache """ 206 pass 207 208 209class ConfigFunctionality(object): 210 """ Configuration base class with config class behaviour. 211 212 This class contains the functionality for the DefaultConfig 213 class for the benefit of the WikiConfig macro. 214 """ 215 216 # attributes of this class that should not be shown 217 # in the WikiConfig() macro. 218 cfg_mtime = None 219 siteid = None 220 cache = None 221 mail_enabled = None 222 jabber_enabled = None 223 auth_can_logout = None 224 auth_have_login = None 225 auth_login_inputs = None 226 _site_plugin_lists = None 227 _iwid = None 228 _iwid_full = None 229 xapian_searchers = None 230 moinmoin_dir = None 231 # will be lazily loaded by interwiki code when needed (?) 232 shared_intermap_files = None 233 234 def __init__(self, siteid): 235 """ Init Config instance """ 236 self.siteid = siteid 237 self.cache = CacheClass() 238 239 from MoinMoin.Page import ItemCache 240 self.cache.meta = ItemCache('meta') 241 self.cache.pagelists = ItemCache('pagelists') 242 243 if self.config_check_enabled: 244 self._config_check() 245 246 # define directories 247 self.moinmoin_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)) 248 data_dir = os.path.normpath(self.data_dir) 249 self.data_dir = data_dir 250 for dirname in ('user', 'cache', 'plugin'): 251 name = dirname + '_dir' 252 if not getattr(self, name, None): 253 setattr(self, name, os.path.abspath(os.path.join(data_dir, dirname))) 254 # directories below cache_dir (using __dirname__ to avoid conflicts) 255 for dirname in ('session', ): 256 name = dirname + '_dir' 257 if not getattr(self, name, None): 258 setattr(self, name, os.path.abspath(os.path.join(self.cache_dir, '__%s__' % dirname))) 259 260 # Try to decode certain names which allow unicode 261 self._decode() 262 263 # After that, pre-compile some regexes 264 self.cache.page_category_regex = re.compile(self.page_category_regex, re.UNICODE) 265 self.cache.page_dict_regex = re.compile(self.page_dict_regex, re.UNICODE) 266 self.cache.page_group_regex = re.compile(self.page_group_regex, re.UNICODE) 267 self.cache.page_template_regex = re.compile(self.page_template_regex, re.UNICODE) 268 269 # the ..._regexact versions only match if nothing is left (exact match) 270 self.cache.page_category_regexact = re.compile(u'^%s$' % self.page_category_regex, re.UNICODE) 271 self.cache.page_dict_regexact = re.compile(u'^%s$' % self.page_dict_regex, re.UNICODE) 272 self.cache.page_group_regexact = re.compile(u'^%s$' % self.page_group_regex, re.UNICODE) 273 self.cache.page_template_regexact = re.compile(u'^%s$' % self.page_template_regex, re.UNICODE) 274 275 self.cache.ua_spiders = self.ua_spiders and re.compile(self.ua_spiders, re.IGNORECASE) 276 277 self._check_directories() 278 279 if not isinstance(self.superuser, list): 280 msg = """The superuser setting in your wiki configuration is not a list 281 (e.g. ['Sample User', 'AnotherUser']). 282 Please change it in your wiki configuration and try again.""" 283 raise error.ConfigurationError(msg) 284 285 if not isinstance(self.actions_superuser, list): 286 msg = """The actions_superuser setting in your wiki configuration is not a list 287 (e.g. ['newaccount', 'some_other_action']). 288 Please change it in your wiki configuration and try again.""" 289 raise error.ConfigurationError(msg) 290 291 # moin < 1.9 used cookie_lifetime = <float> (but converted it to int) for logged-in users and 292 # anonymous_session_lifetime = <float> or None for anon users 293 # moin >= 1.9 uses cookie_lifetime = (<float>, <float>) - first is anon, second is logged-in 294 if not (isinstance(self.cookie_lifetime, tuple) and len(self.cookie_lifetime) == 2): 295 logging.error("wiki configuration has an invalid setting: " + 296 "cookie_lifetime = %r" % (self.cookie_lifetime, )) 297 try: 298 anon_lifetime = self.anonymous_session_lifetime 299 logging.warning("wiki configuration has an unsupported setting: " + 300 "anonymous_session_lifetime = %r - " % anon_lifetime + 301 "please remove it.") 302 if anon_lifetime is None: 303 anon_lifetime = 0 304 anon_lifetime = float(anon_lifetime) 305 except: 306 # if anything goes wrong, use default value 307 anon_lifetime = 0 308 try: 309 logged_in_lifetime = int(self.cookie_lifetime) 310 except: 311 # if anything goes wrong, use default value 312 logged_in_lifetime = 12 313 self.cookie_lifetime = (anon_lifetime, logged_in_lifetime) 314 logging.warning("using cookie_lifetime = %r - " % (self.cookie_lifetime, ) + 315 "please fix your wiki configuration.") 316 317 self._loadPluginModule() 318 319 # Preparse user dicts 320 self._fillDicts() 321 322 # Normalize values 323 self.language_default = self.language_default.lower() 324 325 # Use site name as default name-logo 326 if self.logo_string is None: 327 self.logo_string = self.sitename 328 329 # Check for needed modules 330 331 # FIXME: maybe we should do this check later, just before a 332 # chart is needed, maybe in the chart module, instead doing it 333 # for each request. But this require a large refactoring of 334 # current code. 335 if self.chart_options: 336 try: 337 import gdchart 338 except ImportError: 339 self.chart_options = None 340 341 # 'setuid' special auth method auth method can log out 342 self.auth_can_logout = ['setuid'] 343 self.auth_login_inputs = [] 344 found_names = [] 345 for auth in self.auth: 346 if not auth.name: 347 raise error.ConfigurationError("Auth methods must have a name.") 348 if auth.name in found_names: 349 raise error.ConfigurationError("Auth method names must be unique.") 350 found_names.append(auth.name) 351 if auth.logout_possible and auth.name: 352 self.auth_can_logout.append(auth.name) 353 for input in auth.login_inputs: 354 if not input in self.auth_login_inputs: 355 self.auth_login_inputs.append(input) 356 self.auth_have_login = len(self.auth_login_inputs) > 0 357 self.auth_methods = found_names 358 359 # internal dict for plugin `modules' lists 360 self._site_plugin_lists = {} 361 362 # we replace any string placeholders with config values 363 # e.g u'%(page_front_page)s' % self 364 self.navi_bar = [elem % self for elem in self.navi_bar] 365 366 # check if python-xapian is installed 367 if self.xapian_search: 368 try: 369 import xapian 370 except ImportError, err: 371 self.xapian_search = False 372 logging.error("xapian_search was auto-disabled because python-xapian is not installed [%s]." % str(err)) 373 374 # list to cache xapian searcher objects 375 self.xapian_searchers = [] 376 377 # check if mail is possible and set flag: 378 self.mail_enabled = (self.mail_smarthost is not None or self.mail_sendmail is not None) and self.mail_from 379 self.mail_enabled = self.mail_enabled and True or False 380 381 # check if jabber bot is available and set flag: 382 self.jabber_enabled = self.notification_bot_uri is not None 383 384 # if we are to use the jabber bot, instantiate a server object for future use 385 if self.jabber_enabled: 386 from xmlrpclib import Server 387 self.notification_server = Server(self.notification_bot_uri, ) 388 389 # Cache variables for the properties below 390 self._iwid = self._iwid_full = self._meta_dict = None 391 392 self.cache.acl_rights_before = AccessControlList(self, [self.acl_rights_before]) 393 self.cache.acl_rights_default = AccessControlList(self, [self.acl_rights_default]) 394 self.cache.acl_rights_after = AccessControlList(self, [self.acl_rights_after]) 395 396 action_prefix = self.url_prefix_action 397 if action_prefix is not None and action_prefix.endswith('/'): # make sure there is no trailing '/' 398 self.url_prefix_action = action_prefix[:-1] 399 400 if self.url_prefix_local is None: 401 self.url_prefix_local = self.url_prefix_static 402 403 if self.url_prefix_fckeditor is None: 404 self.url_prefix_fckeditor = self.url_prefix_local + '/applets/FCKeditor' 405 406 if self.secrets is None: # admin did not setup a real secret, so make up something 407 self.secrets = self.calc_secrets() 408 409 secret_key_names = ['action/cache', 'wikiutil/tickets', 'xmlrpc/ProcessMail', 'xmlrpc/RemoteScript', ] 410 if self.jabber_enabled: 411 secret_key_names.append('jabberbot') 412 if self.textchas: 413 secret_key_names.append('security/textcha') 414 415 secret_min_length = 10 416 if isinstance(self.secrets, str): 417 if len(self.secrets) < secret_min_length: 418 raise error.ConfigurationError("The secrets = '...' wiki config setting is a way too short string (minimum length is %d chars)!" % ( 419 secret_min_length)) 420 # for lazy people: set all required secrets to same value 421 secrets = {} 422 for key in secret_key_names: 423 secrets[key] = self.secrets 424 self.secrets = secrets 425 426 # we check if we have all secrets we need and that they have minimum length 427 for secret_key_name in secret_key_names: 428 try: 429 secret = self.secrets[secret_key_name] 430 if len(secret) < secret_min_length: 431 raise ValueError 432 except (KeyError, ValueError): 433 raise error.ConfigurationError("You must set a (at least %d chars long) secret string for secrets['%s']!" % ( 434 secret_min_length, secret_key_name)) 435 436 if self.password_scheme not in config.password_schemes_configurable: 437 raise error.ConfigurationError("not supported: password_scheme = %r" % self.password_scheme) 438 439 if self.passlib_support: 440 try: 441 from passlib.context import CryptContext 442 except ImportError, err: 443 raise error.ConfigurationError("Wiki is configured to use passlib, but importing passlib failed [%s]!" % str(err)) 444 try: 445 self.cache.pwd_context = CryptContext(**self.passlib_crypt_context) 446 except (ValueError, KeyError, TypeError, UserWarning), err: 447 # ValueError: wrong configuration values 448 # KeyError: unsupported hash (seen with passlib 1.3) 449 # TypeError: configuration value has wrong type 450 raise error.ConfigurationError("passlib_crypt_context configuration is invalid [%s]." % str(err)) 451 elif self.password_scheme == '{PASSLIB}': 452 raise error.ConfigurationError("passlib_support is switched off, thus you can't use password_scheme = '{PASSLIB}'.") 453 454 def calc_secrets(self): 455 """ make up some 'secret' using some config values """ 456 varnames = ['data_dir', 'data_underlay_dir', 'language_default', 457 'mail_smarthost', 'mail_from', 'page_front_page', 458 'theme_default', 'sitename', 'logo_string', 459 'interwikiname', 'user_homewiki', 'acl_rights_before', ] 460 secret = '' 461 for varname in varnames: 462 var = getattr(self, varname, None) 463 if isinstance(var, (str, unicode)): 464 secret += repr(var) 465 return secret 466 467 _meta_dict = None 468 def load_meta_dict(self): 469 """ The meta_dict contains meta data about the wiki instance. """ 470 if self._meta_dict is None: 471 self._meta_dict = wikiutil.MetaDict(os.path.join(self.data_dir, 'meta'), self.cache_dir) 472 return self._meta_dict 473 meta_dict = property(load_meta_dict) 474 475 # lazily load iwid(_full) 476 def make_iwid_property(attr): 477 def getter(self): 478 if getattr(self, attr, None) is None: 479 self.load_IWID() 480 return getattr(self, attr) 481 return property(getter) 482 iwid = make_iwid_property("_iwid") 483 iwid_full = make_iwid_property("_iwid_full") 484 485 # lazily create a list of event handlers 486 _event_handlers = None 487 def make_event_handlers_prop(): 488 def getter(self): 489 if self._event_handlers is None: 490 self._event_handlers = events.get_handlers(self) 491 return self._event_handlers 492 493 def setter(self, new_handlers): 494 self._event_handlers = new_handlers 495 496 return property(getter, setter) 497 event_handlers = make_event_handlers_prop() 498 499 def load_IWID(self): 500 """ Loads the InterWikiID of this instance. It is used to identify the instance 501 globally. 502 The IWID is available as cfg.iwid 503 The full IWID containing the interwiki name is available as cfg.iwid_full 504 This method is called by the property. 505 """ 506 try: 507 iwid = self.meta_dict['IWID'] 508 except KeyError: 509 iwid = util.random_string(16).encode("hex") + "-" + str(int(time.time())) 510 self.meta_dict['IWID'] = iwid 511 self.meta_dict.sync() 512 513 self._iwid = iwid 514 if self.interwikiname is not None: 515 self._iwid_full = packLine([iwid, self.interwikiname]) 516 else: 517 self._iwid_full = packLine([iwid]) 518 519 def _config_check(self): 520 """ Check namespace and warn about unknown names 521 522 Warn about names which are not used by DefaultConfig, except 523 modules, classes, _private or __magic__ names. 524 525 This check is disabled by default, when enabled, it will show an 526 error message with unknown names. 527 """ 528 unknown = ['"%s"' % name for name in dir(self) 529 if not name.startswith('_') and 530 name not in DefaultConfig.__dict__ and 531 not isinstance(getattr(self, name), (type(sys), type(DefaultConfig)))] 532 if unknown: 533 msg = """ 534Unknown configuration options: %s. 535 536For more information, visit HelpOnConfiguration. Please check your 537configuration for typos before requesting support or reporting a bug. 538""" % ', '.join(unknown) 539 raise error.ConfigurationError(msg) 540 541 def _decode(self): 542 """ Try to decode certain names, ignore unicode values 543 544 Try to decode str using utf-8. If the decode fail, raise FatalError. 545 546 Certain config variables should contain unicode values, and 547 should be defined with u'text' syntax. Python decode these if 548 the file have a 'coding' line. 549 550 This will allow utf-8 users to use simple strings using, without 551 using u'string'. Other users will have to use u'string' for 552 these names, because we don't know what is the charset of the 553 config files. 554 """ 555 charset = 'utf-8' 556 message = u""" 557"%(name)s" configuration variable is a string, but should be 558unicode. Use %(name)s = u"value" syntax for unicode variables. 559 560Also check your "-*- coding -*-" line at the top of your configuration 561file. It should match the actual charset of the configuration file. 562""" 563 564 decode_names = ( 565 'sitename', 'interwikiname', 'user_homewiki', 'logo_string', 'navi_bar', 566 'page_front_page', 'page_category_regex', 'page_dict_regex', 567 'page_group_regex', 'page_template_regex', 'page_license_page', 568 'page_local_spelling_words', 'acl_rights_default', 569 'acl_rights_before', 'acl_rights_after', 'mail_from', 570 'quicklinks_default', 'subscribed_pages_default', 571 ) 572 573 for name in decode_names: 574 attr = getattr(self, name, None) 575 if attr: 576 # Try to decode strings 577 if isinstance(attr, str): 578 try: 579 setattr(self, name, unicode(attr, charset)) 580 except UnicodeError: 581 raise error.ConfigurationError(message % 582 {'name': name}) 583 # Look into lists and try to decode strings inside them 584 elif isinstance(attr, list): 585 for i in xrange(len(attr)): 586 item = attr[i] 587 if isinstance(item, str): 588 try: 589 attr[i] = unicode(item, charset) 590 except UnicodeError: 591 raise error.ConfigurationError(message % 592 {'name': name}) 593 594 def _check_directories(self): 595 """ Make sure directories are accessible 596 597 Both data and underlay should exists and allow read, write and 598 execute. 599 """ 600 mode = os.F_OK | os.R_OK | os.W_OK | os.X_OK 601 for attr in ('data_dir', 'data_underlay_dir'): 602 path = getattr(self, attr) 603 604 # allow an empty underlay path or None 605 if attr == 'data_underlay_dir' and not path: 606 continue 607 608 path_pages = os.path.join(path, "pages") 609 if not (os.path.isdir(path_pages) and os.access(path_pages, mode)): 610 msg = """ 611%(attr)s "%(path)s" does not exist, or has incorrect ownership or 612permissions. 613 614Make sure the directory and the subdirectory "pages" are owned by the web 615server and are readable, writable and executable by the web server user 616and group. 617 618It is recommended to use absolute paths and not relative paths. Check 619also the spelling of the directory name. 620""" % {'attr': attr, 'path': path, } 621 raise error.ConfigurationError(msg) 622 623 def _loadPluginModule(self): 624 """ 625 import all plugin modules 626 627 To be able to import plugin from arbitrary path, we have to load 628 the base package once using imp.load_module. Later, we can use 629 standard __import__ call to load plugins in this package. 630 631 Since each configured plugin path has unique plugins, we load the 632 plugin packages as "moin_plugin_<sha1(path)>.plugin". 633 """ 634 import imp 635 636 plugin_dirs = [self.plugin_dir] + self.plugin_dirs 637 self._plugin_modules = [] 638 639 try: 640 # Lock other threads while we check and import 641 imp.acquire_lock() 642 try: 643 for pdir in plugin_dirs: 644 csum = 'p_%s' % hashlib.new('sha1', pdir).hexdigest() 645 modname = '%s.%s' % (self.siteid, csum) 646 # If the module is not loaded, try to load it 647 if not modname in sys.modules: 648 # Find module on disk and try to load - slow! 649 abspath = os.path.abspath(pdir) 650 parent_dir, pname = os.path.split(abspath) 651 fp, path, info = imp.find_module(pname, [parent_dir]) 652 try: 653 # Load the module and set in sys.modules 654 module = imp.load_module(modname, fp, path, info) 655 setattr(sys.modules[self.siteid], 'csum', module) 656 finally: 657 # Make sure fp is closed properly 658 if fp: 659 fp.close() 660 if modname not in self._plugin_modules: 661 self._plugin_modules.append(modname) 662 finally: 663 imp.release_lock() 664 except ImportError, err: 665 msg = """ 666Could not import plugin package "%(path)s" because of ImportError: 667%(err)s. 668 669Make sure your data directory path is correct, check permissions, and 670that the data/plugin directory has an __init__.py file. 671""" % { 672 'path': pdir, 673 'err': str(err), 674} 675 raise error.ConfigurationError(msg) 676 677 def _fillDicts(self): 678 """ fill config dicts 679 680 Fills in missing dict keys of derived user config by copying 681 them from this base class. 682 """ 683 # user checkbox defaults 684 for key, value in DefaultConfig.user_checkbox_defaults.items(): 685 if key not in self.user_checkbox_defaults: 686 self.user_checkbox_defaults[key] = value 687 688 def __getitem__(self, item): 689 """ Make it possible to access a config object like a dict """ 690 return getattr(self, item) 691 692 693class DefaultConfig(ConfigFunctionality): 694 """ Configuration base class with default config values 695 (added below) 696 """ 697 # Do not add anything into this class. Functionality must 698 # be added above to avoid having the methods show up in 699 # the WikiConfig macro. Settings must be added below to 700 # the options dictionary. 701 702_default_backlink_method = lambda cfg, req: 'backlink' if req.user.valid else 'pagelink' 703 704 705def _default_password_checker(cfg, request, username, password, 706 min_length=6, min_different=4): 707 """ Check if a password is secure enough. 708 We use a built-in check to get rid of the worst passwords. 709 710 We do NOT use cracklib / python-crack here any more because it is 711 not thread-safe (we experienced segmentation faults when using it). 712 713 If you don't want to check passwords, use password_checker = None. 714 715 @return: None if there is no problem with the password, 716 some unicode object with an error msg, if the password is problematic. 717 """ 718 _ = request.getText 719 # in any case, do a very simple built-in check to avoid the worst passwords 720 if len(password) < min_length: 721 return _("Password is too short.") 722 if len(set(password)) < min_different: 723 return _("Password has not enough different characters.") 724 725 username_lower = username.lower() 726 password_lower = password.lower() 727 if username in password or password in username or \ 728 username_lower in password_lower or password_lower in username_lower: 729 return _("Password is too easy (password contains name or name contains password).") 730 731 keyboards = (ur"`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./", # US kbd 732 ur"^1234567890ߴqwertzuiop�+asdfghjkl��#yxcvbnm,.-", # german kbd 733 ) # add more keyboards! 734 for kbd in keyboards: 735 rev_kbd = kbd[::-1] 736 if password in kbd or password in rev_kbd or \ 737 password_lower in kbd or password_lower in rev_kbd: 738 return _("Password is too easy (keyboard sequence).") 739 return None 740 741 742class DefaultExpression(object): 743 def __init__(self, exprstr): 744 self.text = exprstr 745 self.value = eval(exprstr) 746 747 748# 749# Options that are not prefixed automatically with their 750# group name, see below (at the options dict) for more 751# information on the layout of this structure. 752# 753options_no_group_name = { 754 # ========================================================================= 755 'attachment_extension': ("Mapping of attachment extensions to actions", None, 756 ( 757 ('extensions_mapping', 758 {'.tdraw': {'modify': 'twikidraw'}, 759 '.adraw': {'modify': 'anywikidraw'}, 760 }, "file extension -> do -> action"), 761 )), 762 # ========================================================================== 763 'datastruct': ('Datastruct settings', None, ( 764 ('dicts', lambda cfg, request: datastruct.WikiDicts(request), 765 "function f(cfg, request) that returns a backend which is used to access dicts definitions."), 766 ('groups', lambda cfg, request: datastruct.WikiGroups(request), 767 "function f(cfg, request) that returns a backend which is used to access groups definitions."), 768 )), 769 # ========================================================================== 770 'session': ('Session settings', "Session-related settings, see HelpOnSessions.", ( 771 ('session_service', DefaultExpression('web.session.FileSessionService()'), 772 "The session service."), 773 ('cookie_name', None, 774 'The variable part of the session cookie name. (None = determine from URL, siteidmagic = use siteid, any other string = use that)'), 775 ('cookie_secure', None, 776 'Use secure cookie. (None = auto-enable secure cookie for https, True = ever use secure cookie, False = never use secure cookie).'), 777 ('cookie_httponly', False, 778 'Use a httponly cookie that can only be used by the server, not by clientside scripts.'), 779 ('cookie_domain', None, 780 'Domain used in the session cookie. (None = do not specify domain).'), 781 ('cookie_path', None, 782 'Path used in the session cookie (None = auto-detect). Please only set if you know exactly what you are doing.'), 783 ('cookie_lifetime', (0, 12), 784 'Session lifetime [h] of (anonymous, logged-in) users (see HelpOnSessions for details).'), 785 )), 786 # ========================================================================== 787 'auth': ('Authentication / Authorization / Security settings', None, ( 788 ('superuser', [], 789 "List of trusted user names with wiki system administration super powers (not to be confused with ACL admin rights!). Used for e.g. software installation, language installation via SystemPagesSetup and more. See also HelpOnSuperUser."), 790 ('auth', DefaultExpression('[MoinAuth()]'), 791 "list of auth objects, to be called in this order (see HelpOnAuthentication)"), 792 ('auth_methods_trusted', ['http', 'given', 'xmlrpc_applytoken'], # Note: 'http' auth method is currently just a redirect to 'given' 793 'authentication methods for which users should be included in the special "Trusted" ACL group.'), 794 ('secrets', None, """Either a long shared secret string used for multiple purposes or a dict {"purpose": "longsecretstring", ...} for setting up different shared secrets for different purposes. If you don't setup own secret(s), a secret string will be auto-generated from other config settings."""), 795 ('DesktopEdition', 796 False, 797 "if True, give all local users special powers - ''only use this for a local desktop wiki!''"), 798 ('SecurityPolicy', 799 None, 800 "Class object hook for implementing security restrictions or relaxations"), 801 ('actions_superuser', 802 ['newaccount', # spam bots create tons of user accounts, so better allow it only for superuser 803 ], 804 "Restrict actions to superuser only (list of strings)"), 805 ('actions_excluded', 806 ['xmlrpc', # we do not want wiki admins unknowingly offering xmlrpc service 807 'MyPages', # only works when used with a non-default SecurityPolicy (e.g. autoadmin) 808 'CopyPage', # has questionable behaviour regarding subpages a user can't read, but can copy 809 ], 810 "Exclude unwanted actions (list of strings)"), 811 812 ('allow_xslt', False, 813 "if True, enables XSLT processing via 4Suite (Note that this is DANGEROUS. It enables anyone who can edit the wiki to get '''read/write access to your filesystem as the moin process uid/gid''' and to insert '''arbitrary HTML''' into your wiki pages, which is why this setting defaults to `False` (XSLT disabled). Do not set it to other values, except if you know what you do and if you have very trusted editors only)."), 814 815 ('password_checker', DefaultExpression('_default_password_checker'), 816 'checks whether a password is acceptable (default check is length >= 6, at least 4 different chars, no keyboard sequence, not username used somehow (you can switch this off by using `None`)'), 817 818 ('password_scheme', '{PASSLIB}', 819 'Either "{PASSLIB}" (default) to use passlib for creating and upgrading password hashes (see also passlib_crypt_context for passlib configuration), ' 820 'or "{SSHA}" (or any other of the builtin password schemes) to not use passlib (not recommended).'), 821 822 ('passlib_support', True, 823 'If True (default), import passlib and support password hashes offered by it.'), 824 825 ('passlib_crypt_context', dict( 826 # schemes we want to support (or deprecated schemes for which we still have 827 # hashes in our storage). 828 # note: bcrypt: we did not include it as it needs additional code (that is not pure python 829 # and thus either needs compiling or installing platform-specific binaries) and 830 # also there was some bcrypt issue in passlib < 1.5.3. 831 # pbkdf2_sha512: not included as it needs at least passlib 1.4.0 832 # sha512_crypt: supported since passlib 1.3.0 (first public release) 833 schemes=["sha512_crypt", ], 834 # default scheme for creating new pw hashes (if not given, passlib uses first from schemes) 835 #default="sha512_crypt", 836 # deprecated schemes get auto-upgraded to the default scheme at login 837 # time or when setting a password (including doing a moin account pwreset). 838 # for passlib >= 1.6, giving ["auto"] means that all schemes except the default are deprecated: 839 #deprecated=["auto"], 840 # to support also older passlib versions, rather give a explicit list: 841 #deprecated=[], 842 # vary rounds parameter randomly when creating new hashes... 843 #all__vary_rounds=0.1, 844 ), 845 "passlib CryptContext arguments, see passlib docs"), 846 847 ('recovery_token_lifetime', 12, 848 'how long the password recovery token is valid [h]'), 849 )), 850 # ========================================================================== 851 'spam_leech_dos': ('Anti-Spam/Leech/DOS', 852 'These settings help limiting ressource usage and avoiding abuse.', 853 ( 854 ('hosts_deny', [], "List of denied IPs; if an IP ends with a dot, it denies a whole subnet (class A, B or C)"), 855 ('surge_action_limits', 856 {# allow max. <count> <action> requests per <dt> secs 857 # action: (count, dt) 858 'all': (30, 30), # all requests (except cache/AttachFile action) count for this limit 859 'default': (30, 60), # default limit for actions without a specific limit 860 'show': (30, 60), 861 'recall': (10, 120), 862 'raw': (20, 40), # some people use this for css 863 'diff': (30, 60), 864 'fullsearch': (10, 120), 865 'edit': (30, 300), # can be lowered after making preview different from edit 866 'rss_rc': (1, 60), 867 # The following actions are often used for images - to avoid pages with lots of images 868 # (like photo galleries) triggering surge protection, we assign rather high limits: 869 'AttachFile': (300, 30), 870 'cache': (600, 30), # cache action is very cheap/efficient 871 # special stuff to prevent someone trying lots of usernames / passwords to log in. 872 # we keep this commented / disabled so that this feature does not get activated by default 873 # (if somebody does not override surge_action_limits with own values): 874 #'auth-ip': (10, 3600), # same remote ip (any name) 875 #'auth-name': (10, 3600), # same name (any remote ip) 876 }, 877 "Surge protection tries to deny clients causing too much load/traffic, see HelpOnConfiguration/SurgeProtection."), 878 ('surge_lockout_time', 3600, "time [s] someone gets locked out when ignoring the warnings"), 879 880 ('textchas', None, 881 "Spam protection setup using site-specific questions/answers, see HelpOnSpam."), 882 ('textchas_disabled_group', None, 883 "Name of a group of trusted users who do not get asked !TextCha questions."), 884 ('textchas_expiry_time', 600, 885 "Time [s] for a !TextCha to expire."), 886 887 ('antispam_master_url', "http://master.moinmo.in/?action=xmlrpc2", 888 "where antispam security policy fetches spam pattern updates (if it is enabled)"), 889 890 # a regex of HTTP_USER_AGENTS that should be excluded from logging 891 # and receive a FORBIDDEN for anything except viewing a page 892 # list must not contain 'java' because of twikidraw wanting to save drawing uses this useragent 893 ('ua_spiders', 894 ('archiver|bingbot|cfetch|charlotte|crawler|gigabot|googlebot|heritrix|holmes|htdig|httrack|httpunit|' 895 'intelix|jeeves|larbin|leech|libwww-perl|linkbot|linkmap|linkwalk|litefinder|mercator|' 896 'microsoft.url.control|mirror| mj12bot|msnbot|msrbot|neomo|nutbot|omniexplorer|puf|robot|scooter|seekbot|' 897 'sherlock|slurp|sitecheck|snoopy|spider|teleport|twiceler|voilabot|voyager|webreaper|wget|yeti'), 898 "A regex of HTTP_USER_AGENTs that should be excluded from logging and are not allowed to use actions."), 899 900 ('unzip_single_file_size', 2.0 * 1000 ** 2, 901 "max. size of a single file in the archive which will be extracted [bytes]"), 902 ('unzip_attachments_space', 200.0 * 1000 ** 2, 903 "max. total amount of bytes can be used to unzip files [bytes]"), 904 ('unzip_attachments_count', 101, 905 "max. number of files which are extracted from the zip file"), 906 )), 907 # ========================================================================== 908 'style': ('Style / Theme / UI related', 909 'These settings control how the wiki user interface will look like.', 910 ( 911 ('sitename', u'Untitled Wiki', 912 "Short description of your wiki site, displayed below the logo on each page, and used in RSS documents as the channel title [Unicode]"), 913 ('interwikiname', None, "unique and stable InterWiki name (prefix, moniker) of the site [Unicode], or None"), 914 ('logo_string', None, "The wiki logo top of page, HTML is allowed (`<img>` is possible as well) [Unicode]"), 915 ('html_pagetitle', None, "Allows you to set a specific HTML page title (if None, it defaults to the value of `sitename`)"), 916 ('navi_bar', [u'RecentChanges', u'FindPage', u'HelpContents', ], 917 'Most important page names. Users can add more names in their quick links in user preferences. To link to URL, use `u"[[url|link title]]"`, to use a shortened name for long page name, use `u"[[LongLongPageName|title]]"`. [list of Unicode strings]'), 918 919 ('theme_default', 'modernized', 920 "the name of the theme that is used by default (see HelpOnThemes)"), 921 ('theme_force', False, 922 "if True, do not allow to change the theme"), 923 924 ('stylesheets', [], 925 "List of tuples (media, csshref) to insert after theme css, before user css, see HelpOnThemes."), 926 927 ('supplementation_page', False, 928 "if True, show a link to the supplementation page in the theme"), 929 ('supplementation_page_name', u'Discussion', 930 "default name of the supplementation (sub)page [unicode]"), 931 ('supplementation_page_template', u'DiscussionTemplate', 932 "default template used for creation of the supplementation page [unicode]"), 933 934 ('interwiki_preferred', [], "In dialogues, show those wikis at the top of the list."), 935 ('sistersites', [], "list of tuples `('WikiName', 'sisterpagelist_fetch_url')`"), 936 937 ('trail_size', 5, 938 "Number of pages in the trail of visited pages"), 939 940 ('page_footer1', '', "Custom HTML markup sent ''before'' the system footer."), 941 ('page_footer2', '', "Custom HTML markup sent ''after'' the system footer."), 942 ('page_header1', '', "Custom HTML markup sent ''before'' the system header / title area but after the body tag."), 943 ('page_header2', '', "Custom HTML markup sent ''after'' the system header / title area (and body tag)."), 944 945 ('changed_time_fmt', '%H:%M', "Time format used on Recent``Changes for page edits within the last 24 hours"), 946 ('date_fmt', '%Y-%m-%d', "System date format, used mostly in Recent``Changes"), 947 ('datetime_fmt', '%Y-%m-%d %H:%M:%S', 'Default format for dates and times (when the user has no preferences or chose the "default" date format)'), 948 ('chart_options', None, "If you have gdchart, use something like chart_options = {'width': 720, 'height': 540}"), 949 950 ('edit_bar', ['Edit', 'Comments', 'Discussion', 'Info', 'Subscribe', 'Quicklink', 'Attachments', 'ActionsMenu'], 951 'list of edit bar entries'), 952 ('history_count', (100, 200, 5, 10, 25, 50), "Number of revisions shown for info/history action (default_count_shown, max_count_shown, [other values shown as page size choices]). At least first two values (default and maximum) should be provided. If additional values are provided, user will be able to change number of items per page in the UI."), 953 ('history_paging', True, "Enable paging functionality for info action's history display."), 954 955 ('show_hosts', True, 956 "if True, show host names and IPs. Set to False to hide them."), 957 ('show_interwiki', False, 958 "if True, let the theme display your interwiki name"), 959 ('show_names', True, 960 "if True, show user names in the revision history and on Recent``Changes. Set to False to hide them."), 961 ('show_section_numbers', False, 962 'show section numbers in headings by default'), 963 ('show_timings', False, "show some timing values at bottom of a page"), 964 ('show_version', False, "show moin's version at the bottom of a page"), 965 ('show_rename_redirect', False, "if True, offer creation of redirect pages when renaming wiki pages"), 966 967 ('backlink_method', DefaultExpression('_default_backlink_method'), 968 "function determining how the (last part of the) pagename should be rendered in the title area"), 969 970 ('packagepages_actions_excluded', 971 ['setthemename', # related to questionable theme stuff, see below 972 'copythemefile', # maybe does not work, e.g. if no fs write permissions or real theme file path is unknown to moin 973 'installplugin', # code installation, potentially dangerous 974 'renamepage', # dangerous with hierarchical acls 975 'deletepage', # dangerous with hierarchical acls 976 'delattachment', # dangerous, no revisioning 977 ], 978 'list with excluded package actions (e.g. because they are dangerous / questionable)'), 979 980 ('page_credits', 981 [ 982 '<a href="http://moinmo.in/" title="This site uses the MoinMoin Wiki software.">MoinMoin Powered</a>', 983 '<a href="http://moinmo.in/Python" title="MoinMoin is written in Python.">Python Powered</a>', 984 '<a href="http://moinmo.in/GPL" title="MoinMoin is GPL licensed.">GPL licensed</a>', 985 '<a href="http://validator.w3.org/check?uri=referer" title="Click here to validate this page.">Valid HTML 4.01</a>', 986 ], 987 'list with html fragments with logos or strings for crediting.'), 988 989 # These icons will show in this order in the iconbar, unless they 990 # are not relevant, e.g email icon when the wiki is not configured 991 # for email. 992 ('page_iconbar', ["up", "edit", "view", "diff", "info", "subscribe", "raw", "print", ], 993 'list of icons to show in iconbar, valid values are only those in page_icons_table. Available only in classic theme.'), 994 995 # Standard buttons in the iconbar 996 ('page_icons_table', 997 { 998 # key pagekey, querystr dict, title, icon-key 999 'diff': ('page', {'action': 'diff'}, _("Diffs"), "diff"), 1000 'info': ('page', {'action': 'info'}, _("Info"), "info"), 1001 'edit': ('page', {'action': 'edit'}, _("Edit"), "edit"), 1002 'unsubscribe': ('page', {'action': 'unsubscribe'}, _("UnSubscribe"), "unsubscribe"), 1003 'subscribe': ('page', {'action': 'subscribe'}, _("Subscribe"), "subscribe"), 1004 'raw': ('page', {'action': 'raw'}, _("Raw"), "raw"), 1005 'xml': ('page', {'action': 'show', 'mimetype': 'text/xml'}, _("XML"), "xml"), 1006 'print': ('page', {'action': 'print'}, _("Print"), "print"), 1007 'view': ('page', {}, _("View"), "view"), 1008 'up': ('page_parent_page', {}, _("Up"), "up"), 1009 }, 1010 "dict of {'iconname': (url, title, icon-img-key), ...}. Available only in classic theme."), 1011 ('show_highlight_msg', False, "Show message that page has highlighted text " 1012 "and provide link to non-highlighted " 1013 "version."), 1014 )), 1015 # ========================================================================== 1016 'editor': ('Editor related', None, ( 1017 ('editor_default', 'text', "Editor to use by default, 'text' or 'gui'"), 1018 ('editor_force', True, "if True, force using the default editor"), 1019 ('editor_ui', 'theonepreferred', "Editor choice shown on the user interface, 'freechoice' or 'theonepreferred'"), 1020 ('page_license_enabled', False, 'if True, show a license hint in page editor.'), 1021 ('page_license_page', u'WikiLicense', 'Page linked from the license hint. [Unicode]'), 1022 ('edit_locking', 'warn 10', "Editor locking policy: `None`, `'warn <timeout in minutes>'`, or `'lock <timeout in minutes>'`"), 1023 ('edit_ticketing', True, None), 1024 ('edit_rows', 20, "Default height of the edit box"), 1025 ('comment_required', False, "if True, only allow saving if a comment is filled in"), 1026 1027 )), 1028 # ========================================================================== 1029 'paths': ('Paths', None, ( 1030 ('data_dir', './data/', "Path to the data directory containing your (locally made) wiki pages."), 1031 ('data_underlay_dir', './underlay/', "Path to the underlay directory containing distribution system and help pages."), 1032 ('cache_dir', None, "Directory for caching, by default computed from `data_dir`/cache."), 1033 ('session_dir', None, "Directory for session storage, by default computed to be `cache_dir`/__session__."), 1034 ('user_dir', None, "Directory for user storage, by default computed to be `data_dir`/user."), 1035 ('plugin_dir', None, "Plugin directory, by default computed to be `data_dir`/plugin."), 1036 ('plugin_dirs', [], "Additional plugin directories."), 1037 1038 ('docbook_html_dir', r"/usr/share/xml/docbook/stylesheet/nwalsh/html/", 1039 'Path to the directory with the Docbook to HTML XSLT files (optional, used by the docbook parser). The default value is correct for Debian Etch.'), 1040 ('shared_intermap', None, 1041 "Path to a file containing global InterWiki definitions (or a list of such filenames)"), 1042 )), 1043 # ========================================================================== 1044 'urls': ('URLs', None, ( 1045 # includes the moin version number, so we can have a unlimited cache lifetime 1046 # for the static stuff. if stuff changes on version upgrade, url will change 1047 # immediately and we have no problem with stale caches. 1048 ('url_prefix_static', config.url_prefix_static, 1049 "used as the base URL for icons, css, etc. - includes the moin version number and changes on every release. This replaces the deprecated and sometimes confusing `url_prefix = '/wiki'` setting."), 1050 ('url_prefix_local', None, 1051 "used as the base URL for some Javascript - set this to a URL on same server as the wiki if your url_prefix_static points to a different server."), 1052 ('url_prefix_fckeditor', None, 1053 "used as the base URL for FCKeditor - similar to url_prefix_local, but just for FCKeditor."), 1054 1055 ('url_prefix_action', None, 1056 "Use 'action' to enable action URL generation to be compatible with robots.txt. It will generate .../action/info/PageName?action=info then. Recommended for internet wikis."), 1057 1058 ('notification_bot_uri', None, "URI of the Jabber notification bot."), 1059 1060 ('url_mappings', {}, 1061 "lookup table to remap URL prefixes (dict of {{{'prefix': 'replacement'}}}); especially useful in intranets, when whole trees of externally hosted documents move around"), 1062 1063 )), 1064 # ========================================================================== 1065 'pages': ('Special page names', None, ( 1066 ('page_front_page', u'LanguageSetup', 1067 "Name of the front page. We don't expect you to keep the default. Just read LanguageSetup in case you're wondering... [Unicode]"), 1068 1069 # the following regexes should match the complete name when used in free text 1070 # the group 'all' shall match all, while the group 'key' shall match the key only 1071 # e.g. CategoryFoo -> group 'all' == CategoryFoo, group 'key' == Foo 1072 # moin's code will add ^ / $ at beginning / end when needed 1073 ('page_category_regex', ur'(?P<all>Category(?P<key>(?!Template)\S+))', 1074 'Pagenames exactly matching this regex are regarded as Wiki categories [Unicode]'), 1075 ('page_dict_regex', ur'(?P<all>(?P<key>\S+)Dict)', 1076 'Pagenames exactly matching this regex are regarded as pages containing variable dictionary definitions [Unicode]'), 1077 ('page_group_regex', ur'(?P<all>(?P<key>\S+)Group)', 1078 'Pagenames exactly matching this regex are regarded as pages containing group definitions [Unicode]'), 1079 ('page_template_regex', ur'(?P<all>(?P<key>\S+)Template)', 1080 'Pagenames exactly matching this regex are regarded as pages containing templates for new pages [Unicode]'), 1081 1082 ('page_local_spelling_words', u'LocalSpellingWords', 1083 'Name of the page containing user-provided spellchecker words [Unicode]'), 1084 )), 1085 # ========================================================================== 1086 'user': ('User Preferences related', None, ( 1087 ('quicklinks_default', [], 1088 'List of preset quicklinks for a newly created user accounts. Existing accounts are not affected by this option whereas changes in navi_bar do always affect existing accounts. Preset quicklinks can be removed by the user in the user preferences menu, navi_bar settings not.'), 1089 ('subscribed_pages_default', [], 1090 "List of pagenames used for presetting page subscriptions for newly created user accounts."), 1091 1092 ('email_subscribed_events_default', 1093 [ 1094 PageChangedEvent.__name__, 1095 PageRenamedEvent.__name__, 1096 PageDeletedEvent.__name__, 1097 PageCopiedEvent.__name__, 1098 PageRevertedEvent.__name__, 1099 FileAttachedEvent.__name__, 1100 ], None), 1101 ('jabber_subscribed_events_default', [], None), 1102 1103 ('tz_offset', 0.0, 1104 "default time zone offset in hours from UTC"), 1105 1106 ('userprefs_disabled', [], 1107 "Disable the listed user preferences plugins."), 1108 ('require_email_verification', False , 1109 "Require verification of new user accounts."), 1110 )), 1111 # ========================================================================== 1112 'various': ('Various', None, ( 1113 ('bang_meta', True, 'if True, enable {{{!NoWikiName}}} markup'), 1114 ('caching_formats', ['text_html'], "output formats that are cached; set to [] to turn off caching (useful for development)"), 1115 1116 ('config_check_enabled', False, "if True, check configuration for unknown settings."), 1117 1118 ('default_markup', 'wiki', 'Default page parser / format (name of module in `MoinMoin.parser`)'), 1119 1120 ('html_head', '', "Additional <HEAD> tags, see HelpOnThemes."), 1121 ('html_head_queries', '<meta name="robots" content="noindex,nofollow">\n', 1122 "Additional <HEAD> tags for requests with query strings, like actions."), 1123 ('html_head_posts', '<meta name="robots" content="noindex,nofollow">\n', 1124 "Additional <HEAD> tags for POST requests."), 1125 ('html_head_index', '<meta name="robots" content="index,follow">\n', 1126 "Additional <HEAD> tags for some few index pages."), 1127 ('html_head_normal', '<meta name="robots" content="index,nofollow">\n', 1128 "Additional <HEAD> tags for most normal pages."), 1129 1130 ('language_default', 'en', "Default language for user interface and page content, see HelpOnLanguages."), 1131 ('language_ignore_browser', False, "if True, ignore user's browser language settings, see HelpOnLanguages."), 1132 1133 ('log_remote_addr', True, 1134 "if True, log the remote IP address (and maybe hostname)."), 1135 ('log_reverse_dns_lookups', False, 1136 "if True, do a reverse DNS lookup on page SAVE."), 1137 ('log_timing', False, 1138 "if True, add timing infos to the log output to analyse load conditions"), 1139 ('log_events_format', 1, 1140 "0 = no events logging, 1 = standard format (like <= 1.9.7) [default], 2 = extended format"), 1141 1142 # some dangerous mimetypes (we don't use "content-disposition: inline" for them when a user 1143 # downloads such attachments, because the browser might execute e.g. Javascript contained 1144 # in the HTML and steal your moin session cookie or do other nasty stuff) 1145 ('mimetypes_xss_protect', 1146 [ 1147 'text/html', 1148 'image/svg+xml', 1149 'application/x-shockwave-flash', 1150 'application/xhtml+xml', 1151 ], 1152 '"content-disposition: inline" isn\'t used for them when a user downloads such attachments'), 1153 1154 ('mimetypes_embed', 1155 [ 1156 'application/x-dvi', 1157 'application/postscript', 1158 'application/pdf', 1159 'application/ogg', 1160 'application/vnd.visio', 1161 'image/x-ms-bmp', 1162 'image/svg+xml', 1163 'image/tiff', 1164 'image/x-photoshop', 1165 'audio/mpeg', 1166 'audio/midi', 1167 'audio/x-wav', 1168 'video/fli', 1169 'video/mpeg', 1170 'video/quicktime', 1171 'video/x-msvideo', 1172 'chemical/x-pdb', 1173 'x-world/x-vrml', 1174 ], 1175 'mimetypes that can be embedded by the [[HelpOnMacros/EmbedObject|EmbedObject macro]]'), 1176 1177 ('refresh', None, 1178 "refresh = (minimum_delay_s, targets_allowed) enables use of `#refresh 5 PageName` processing instruction, targets_allowed must be either `'internal'` or `'external'`"), 1179 ('rss_cache', 60, "suggested caching time for Recent''''''Changes RSS, in second"), 1180 1181 ('search_results_per_page', 25, "Number of hits shown per page in the search results"), 1182 1183 ('siteid', 'default', None), 1184 ('xmlrpc_overwrite_user', True, "Overwrite authenticated user at start of xmlrpc code"), 1185 )), 1186} 1187 1188# 1189# The 'options' dict carries default MoinMoin options. The dict is a 1190# group name to tuple mapping. 1191# Each group tuple consists of the following items: 1192# group section heading, group help text, option list 1193# 1194# where each 'option list' is a tuple or list of option tuples 1195# 1196# each option tuple consists of 1197# option name, default value, help text 1198# 1199# All the help texts will be displayed by the WikiConfigHelp() macro. 1200# 1201# Unlike the options_no_group_name dict, option names in this dict 1202# are automatically prefixed with "group name '_'" (i.e. the name of 1203# the group they are in and an underscore), e.g. the 'hierarchic' 1204# below creates an option called "acl_hierarchic". 1205# 1206# If you need to add a complex default expression that results in an 1207# object and should not be shown in the __repr__ form in WikiConfigHelp(), 1208# you can use the DefaultExpression class, see 'auth' above for example. 1209# 1210# 1211options = { 1212 'acl': ('Access control lists', 1213 'ACLs control who may do what, see HelpOnAccessControlLists.', 1214 ( 1215 ('hierarchic', False, 'True to use hierarchical ACLs'), 1216 ('rights_default', u"Trusted:read,write,delete,revert Known:read All:read", 1217 "ACL used if no ACL is specified on the page"), 1218 ('rights_before', u"", 1219 "ACL that is processed before the on-page/default ACL"), 1220 ('rights_after', u"", 1221 "ACL that is processed after the on-page/default ACL"), 1222 ('rights_valid', ['read', 'write', 'delete', 'revert', 'admin'], 1223 "Valid tokens for right sides of ACL entries."), 1224 )), 1225 1226 'xapian': ('Xapian search', "Configuration of the Xapian based indexed search, see HelpOnXapian.", ( 1227 ('search', False, 1228 "True to enable the fast, indexed search (based on the Xapian search library)"), 1229 ('index_dir', None, 1230 "Directory where the Xapian search index is stored (None = auto-configure wiki local storage)"), 1231 ('stemming', False, 1232 "True to enable Xapian word stemmer usage for indexing / searching."), 1233 ('index_history', False, 1234 "True to enable indexing of non-current page revisions."), 1235 )), 1236 1237 'user': ('Users / User settings', None, ( 1238 ('email_unique', True, 1239 "if True, check email addresses for uniqueness and don't accept duplicates."), 1240 ('jid_unique', True, 1241 "if True, check Jabber IDs for uniqueness and don't accept duplicates."), 1242 1243 ('homewiki', u'Self', 1244 "interwiki name of the wiki where the user home pages are located [Unicode] - useful if you have ''many'' users. You could even link to nonwiki \"user pages\" if the wiki username is in the target URL."), 1245 1246 ('checkbox_fields', 1247 [ 1248 ('mailto_author', lambda _: _('Publish my email (not my wiki homepage) in author info')), 1249 ('edit_on_doubleclick', lambda _: _('Open editor on double click')), 1250 ('remember_last_visit', lambda _: _('After login, jump to last visited page')), 1251 ('show_comments', lambda _: _('Show comment sections')), 1252 ('show_nonexist_qm', lambda _: _('Show question mark for non-existing pagelinks')), 1253 ('show_page_trail', lambda _: _('Show page trail')), 1254 ('show_toolbar', lambda _: _('Show icon toolbar')), 1255 ('show_topbottom', lambda _: _('Show top/bottom links in headings')), 1256 ('show_fancy_diff', lambda _: _('Show fancy diffs')), 1257 ('wikiname_add_spaces', lambda _: _('Add spaces to displayed wiki names')), 1258 ('remember_me', lambda _: _('Remember login information')), 1259 1260 ('disabled', lambda _: _('Disable this account forever')), 1261 # if an account is disabled, it may be used for looking up 1262 # id -> username for page info and recent changes, but it 1263 # is not usable for the user any more: 1264 ], 1265 "Describes user preferences, see HelpOnConfiguration/UserPreferences."), 1266 1267 ('checkbox_defaults', 1268 { 1269 'mailto_author': 0, 1270 'edit_on_doubleclick': 1, 1271 'remember_last_visit': 0, 1272 'show_comments': 0, 1273 'show_nonexist_qm': False, 1274 'show_page_trail': 1, 1275 'show_toolbar': 1, 1276 'show_topbottom': 0, 1277 'show_fancy_diff': 1, 1278 'wikiname_add_spaces': 0, 1279 'remember_me': 1, 1280 }, 1281 "Defaults for user preferences, see HelpOnConfiguration/UserPreferences."), 1282 1283 ('checkbox_disable', [], 1284 "Disable user preferences, see HelpOnConfiguration/UserPreferences."), 1285 1286 ('checkbox_remove', [], 1287 "Remove user preferences, see HelpOnConfiguration/UserPreferences."), 1288 1289 ('form_fields', 1290 [ 1291 ('name', _('Name'), "text", "36", _("(Use FirstnameLastname)")), 1292 ('aliasname', _('Alias-Name'), "text", "36", ''), 1293 ('email', _('Email'), "text", "36", ''), 1294 ('jid', _('Jabber ID'), "text", "36", ''), 1295 ('css_url', _('User CSS URL'), "text", "40", _('(Leave it empty for disabling user CSS)')), 1296 ('edit_rows', _('Editor size'), "text", "3", ''), 1297 ], 1298 None), 1299 1300 ('form_defaults', 1301 {# key: default - do NOT remove keys from here! 1302 'name': '', 1303 'aliasname': '', 1304 'password': '', 1305 'password2': '', 1306 'email': '', 1307 'jid': '', 1308 'css_url': '', 1309 'edit_rows': "20", 1310 }, 1311 None), 1312 1313 ('form_disable', [], "list of field names used to disable user preferences form fields"), 1314 1315 ('form_remove', [], "list of field names used to remove user preferences form fields"), 1316 1317 ('transient_fields', 1318 ['id', 'valid', 'may', 'auth_username', 'password', 'password2', 'auth_method', 'auth_attribs', ], 1319 "User object attributes that are not persisted to permanent storage (internal use)."), 1320 )), 1321 1322 'openidrp': ('OpenID Relying Party', 1323 'These settings control the built-in OpenID Relying Party (client).', 1324 ( 1325 ('allowed_op', [], "List of forced providers"), 1326 )), 1327 1328 'openid_server': ('OpenID Server', 1329 'These settings control the built-in OpenID Identity Provider (server).', 1330 ( 1331 ('enabled', False, "True to enable the built-in OpenID server."), 1332 ('restricted_users_group', None, "If set to a group name, the group members are allowed to use the wiki as an OpenID provider. (None = allow for all users)"), 1333 ('enable_user', False, "If True, the OpenIDUser processing instruction is allowed."), 1334 )), 1335 1336 'mail': ('Mail settings', 1337 'These settings control outgoing and incoming email from and to the wiki.', 1338 ( 1339 ('from', None, "Used as From: address for generated mail."), 1340 ('login', None, "'username userpass' for SMTP server authentication (None = don't use auth)."), 1341 ('smarthost', None, "Address of SMTP server to use for sending mail (None = don't use SMTP server)."), 1342 ('sendmail', None, "sendmail command to use for sending mail (None = don't use sendmail)"), 1343 1344 ('import_subpage_template', u"$from-$date-$subject", "Create subpages using this template when importing mail."), 1345 ('import_pagename_search', ['subject', 'to', ], "Where to look for target pagename specification."), 1346 ('import_pagename_envelope', u"%s", "Use this to add some fixed prefix/postfix to the generated target pagename."), 1347 ('import_pagename_regex', r'\[\[([^\]]*)\]\]', "Regular expression used to search for target pagename specification."), 1348 ('import_wiki_addrs', [], "Target mail addresses to consider when importing mail"), 1349 1350 ('notify_page_text', '%(intro)s%(difflink)s\n\n%(comment)s%(diff)s', 1351 "Template for putting together the pieces for the page changed/deleted/renamed notification mail text body"), 1352 ('notify_page_changed_subject', _('[%(sitename)s] %(trivial)sUpdate of "%(pagename)s" by %(username)s'), 1353 "Template for the page changed notification mail subject header"), 1354 ('notify_page_changed_intro', 1355 _("Dear Wiki user,\n\n" 1356 'You have subscribed to a wiki page or wiki category on "%(sitename)s" for change notification.\n\n' 1357 'The "%(pagename)s" page has been changed by %(editor)s:\n'), 1358 "Template for the page changed notification mail intro text"), 1359 ('notify_page_deleted_subject', _('[%(sitename)s] %(trivial)sUpdate of "%(pagename)s" by %(username)s'), 1360 "Template for the page deleted notification mail subject header"), 1361 ('notify_page_deleted_intro', 1362 _("Dear wiki user,\n\n" 1363 'You have subscribed to a wiki page "%(sitename)s" for change notification.\n\n' 1364 'The page "%(pagename)s" has been deleted by %(editor)s:\n\n'), 1365 "Template for the page deleted notification mail intro text"), 1366 ('notify_page_renamed_subject', _('[%(sitename)s] %(trivial)sUpdate of "%(pagename)s" by %(username)s'), 1367 "Template for the page renamed notification mail subject header"), 1368 ('notify_page_renamed_intro', 1369 _("Dear wiki user,\n\n" 1370 'You have subscribed to a wiki page "%(sitename)s" for change notification.\n\n' 1371 'The page "%(pagename)s" has been renamed from "%(oldname)s" by %(editor)s:\n'), 1372 "Template for the page renamed notification mail intro text"), 1373 ('notify_att_added_subject', _('[%(sitename)s] New attachment added to page %(pagename)s'), 1374 "Template for the attachment added notification mail subject header"), 1375 ('notify_att_added_intro', 1376 _("Dear Wiki user,\n\n" 1377 'You have subscribed to a wiki page "%(page_name)s" for change notification. ' 1378 "An attachment has been added to that page by %(editor)s. " 1379 "Following detailed information is available:\n\n" 1380 "Attachment name: %(attach_name)s\n" 1381 "Attachment size: %(attach_size)s\n"), 1382 "Template for the attachment added notification mail intro text"), 1383 ('notify_att_removed_subject', _('[%(sitename)s] Removed attachment from page %(pagename)s'), 1384 "Template for the attachment removed notification mail subject header"), 1385 ('notify_att_removed_intro', 1386 _("Dear Wiki user,\n\n" 1387 'You have subscribed to a wiki page "%(page_name)s" for change notification. ' 1388 "An attachment has been removed from that page by %(editor)s. " 1389 "Following detailed information is available:\n\n" 1390 "Attachment name: %(attach_name)s\n" 1391 "Attachment size: %(attach_size)s\n"), 1392 "Template for the attachment removed notification mail intro text"), 1393 ('notify_user_created_subject', 1394 _("[%(sitename)s] New user account created"), 1395 "Template for the user created notification mail subject header"), 1396 ('notify_user_created_intro', 1397 _('Dear Superuser, a new user has just been created on "%(sitename)s". Details follow:\n\n' 1398 ' User name: %(username)s\n' 1399 ' Email address: %(useremail)s'), 1400 "Template for the user created notification mail intro text"), 1401 )), 1402 1403 'backup': ('Backup settings', 1404 'These settings control how the backup action works and who is allowed to use it.', 1405 ( 1406 ('compression', 'gz', 'What compression to use for the backup ("gz" or "bz2").'), 1407 ('users', [], 'List of trusted user names who are allowed to get a backup.'), 1408 ('include', [], 'List of pathes to backup.'), 1409 ('exclude', lambda self, filename: False, 'Function f(self, filename) that tells whether a file should be excluded from backup. By default, nothing is excluded.'), 1410 )), 1411 'rss': ('RSS settings', 1412 'These settings control RSS behaviour.', 1413 ( 1414 ('items_default', 15, "Default maximum items value for RSS feed. Can be " 1415 "changed via items URL query parameter of rss_rc " 1416 "action."), 1417 ('items_limit', 100, "Limit for item count got via RSS (i. e. user " 1418 "can't get more than items_limit items even via " 1419 "changing items URL query parameter)."), 1420 ('unique', 0, "If set to 1, for each page name only one RSS item would " 1421 "be shown. Can be changed via unique rss_rc action URL " 1422 "query parameter."), 1423 ('diffs', 0, "Add diffs in RSS item descriptions by default. Can be " 1424 "changed via diffs URL query parameter of rss_rc action."), 1425 ('ddiffs', 0, "If set to 1, links to diff view instead of page itself " 1426 "would be generated by default. Can be changed via ddiffs " 1427 "URL query parameter of rss_rc action."), 1428 ('lines_default', 20, "Default line count limit for diffs added as item " 1429 "descriptions for RSS items. Can be changed via " 1430 "lines URL query parameter of rss_rc action."), 1431 ('lines_limit', 100, "Limit for possible line count for diffs added as " 1432 "item descriptions in RSS."), 1433 ('show_attachment_entries', 0, "If set to 1, items, related to " 1434 "attachment management, would be added to " 1435 "RSS feed. Can be changed via show_att " 1436 "URL query parameter of rss_rc action."), 1437 ('page_filter_pattern', "", "Default page filter pattern for RSS feed. " 1438 "Empty pattern matches to any page. Pattern " 1439 "beginning with circumflex is interpreted as " 1440 "regular expression. Pattern ending with " 1441 "slash matches page and all its subpages. " 1442 "Otherwise pattern sets specific pagename. " 1443 "Can be changed via page URL query parameter " 1444 "of rss_rc action."), 1445 ('show_page_history_link', True, "Add link to page change history " 1446 "RSS feed in theme."), 1447 )), 1448 'search_macro': ('Search macro settings', 1449 'Settings related to behaviour of search macros (such as FullSearch, ' 1450 'FullSearchCached, PageList)', 1451 ( 1452 ('parse_args', False, "Do search macro parameter parsing. In previous " 1453 "versions of MoinMoin, whole search macro " 1454 "parameter string had been interpreted as needle. " 1455 "Now, to provide ability to pass additional " 1456 "parameters, this behaviour should be changed."), 1457 ('highlight_titles', 1, "Perform title matches highlighting by default " 1458 "in search results generated by macro."), 1459 ('highlight_pages', 1, "Add highlight parameter to links in search " 1460 "results generated by search macros by default."), 1461 )), 1462} 1463 1464def _add_options_to_defconfig(opts, addgroup=True): 1465 for groupname in opts: 1466 group_short, group_doc, group_opts = opts[groupname] 1467 for name, default, doc in group_opts: 1468 if addgroup: 1469 name = groupname + '_' + name 1470 if isinstance(default, DefaultExpression): 1471 default = default.value 1472 setattr(DefaultConfig, name, default) 1473 1474_add_options_to_defconfig(options) 1475_add_options_to_defconfig(options_no_group_name, False) 1476 1477# remove the gettext pseudo function 1478del _ 1479 1480