alex@706: # -*- coding: iso-8859-1 -*- alex@706: """ alex@706: MoinMoin - Multiple configuration handler and Configuration defaults class alex@706: tw@1918: @copyright: 2000-2004 Juergen Hermann , tw@3126: 2005-2008 MoinMoin:ThomasWaldmann. alex@706: @license: GNU GPL, see COPYING for details. alex@706: """ alex@706: alex@1053: import re alex@1053: import os alex@1053: import sys alex@1053: import time tw@3107: tw@3110: from MoinMoin import log tw@3110: logging = log.getLogger(__name__) alex@1053: tw@1582: from MoinMoin import config, error, util, wikiutil alex@706: import MoinMoin.auth as authmodule grzywacz@2307: import MoinMoin.events as events johannes@2429: from MoinMoin.events import PageChangedEvent, PageRenamedEvent johannes@2429: from MoinMoin.events import PageDeletedEvent, PageCopiedEvent johannes@2429: from MoinMoin.events import PageRevertedEvent, FileAttachedEvent johannes@2009: from MoinMoin import session alex@1096: from MoinMoin.packages import packLine tw@1276: from MoinMoin.security import AccessControlList tw@2595: from MoinMoin.support.python_compatibility import set alex@706: alex@706: _url_re_cache = None alex@706: _farmconfig_mtime = None alex@706: _config_cache = {} alex@706: alex@706: alex@706: def _importConfigModule(name): alex@706: """ Import and return configuration module and its modification time tw@2286: alex@706: Handle all errors except ImportError, because missing file is not alex@706: always an error. tw@2286: alex@706: @param name: module name alex@706: @rtype: tuple alex@706: @return: module, modification time alex@706: """ alex@706: try: alex@706: module = __import__(name, globals(), {}) alex@706: mtime = os.path.getmtime(module.__file__) alex@706: except ImportError: alex@706: raise alex@706: except IndentationError, err: tw@3157: logging.exception('Your source code / config file is not correctly indented!') tw@915: msg = '''IndentationError: %(err)s alex@706: alex@706: The configuration files are python modules. Therefore, whitespace is alex@706: important. Make sure that you use only spaces, no tabs are allowed here! alex@706: You have to use four spaces at the beginning of the line mostly. tw@915: ''' % { tw@915: 'err': err, tw@915: } alex@706: raise error.ConfigurationError(msg) alex@706: except Exception, err: tw@3157: logging.exception('An exception happened.') alex@706: msg = '%s: %s' % (err.__class__.__name__, str(err)) alex@706: raise error.ConfigurationError(msg) alex@706: return module, mtime alex@706: alex@706: alex@706: def _url_re_list(): alex@706: """ Return url matching regular expression alex@706: alex@706: Import wikis list from farmconfig on the first call and compile the alex@706: regexes. Later then return the cached regex list. alex@706: alex@706: @rtype: list of tuples of (name, compiled re object) alex@706: @return: url to wiki config name matching list alex@706: """ alex@706: global _url_re_cache, _farmconfig_mtime alex@706: if _url_re_cache is None: alex@706: try: alex@706: farmconfig, _farmconfig_mtime = _importConfigModule('farmconfig') tw@3626: except ImportError, err: tw@3626: if 'farmconfig' in str(err): tw@3626: # we failed importing farmconfig tw@3626: logging.debug("could not import farmconfig, mapping all URLs to wikiconfig") tw@3626: _farmconfig_mtime = 0 tw@3626: _url_re_cache = [('wikiconfig', re.compile(r'.')), ] # matches everything tw@3626: else: tw@3626: # maybe there was a failing import statement inside farmconfig tw@3626: raise alex@706: else: tw@3129: logging.info("using farm config: %s" % os.path.abspath(farmconfig.__file__)) alex@706: try: alex@706: cache = [] alex@706: for name, regex in farmconfig.wikis: alex@706: cache.append((name, re.compile(regex))) alex@706: _url_re_cache = cache alex@706: except AttributeError: tw@3126: logging.error("required 'wikis' list missing in farmconfig") alex@706: msg = """ alex@706: Missing required 'wikis' list in 'farmconfig.py'. alex@706: alex@706: If you run a single wiki you do not need farmconfig.py. Delete it and alex@706: use wikiconfig.py. alex@706: """ tw@931: raise error.ConfigurationError(msg) alex@706: return _url_re_cache alex@706: alex@706: alex@706: def _makeConfig(name): tw@2286: """ Create and return a config instance alex@706: alex@706: Timestamp config with either module mtime or farmconfig mtime. This alex@706: mtime can be used later to invalidate older caches. alex@706: alex@706: @param name: module name alex@706: @rtype: DefaultConfig sub class instance alex@706: @return: new configuration instance alex@706: """ alex@706: global _farmconfig_mtime alex@706: try: alex@706: module, mtime = _importConfigModule(name) alex@706: configClass = getattr(module, 'Config') alex@706: cfg = configClass(name) alex@706: cfg.cfg_mtime = max(mtime, _farmconfig_mtime) tw@3129: logging.info("using wiki config: %s" % os.path.abspath(module.__file__)) alex@706: except ImportError, err: tw@3157: logging.exception('Could not import.') tw@915: msg = '''ImportError: %(err)s alex@706: alex@706: Check that the file is in the same directory as the server script. If alex@706: it is not, you must add the path of the directory where the file is alex@706: located to the python path in the server script. See the comments at alex@706: the top of the server script. alex@706: alex@706: Check that the configuration file name is either "wikiconfig.py" or the alex@706: module name specified in the wikis list in farmconfig.py. Note that the alex@706: module name does not include the ".py" suffix. tw@915: ''' % { tw@915: 'err': err, tw@915: } alex@706: raise error.ConfigurationError(msg) tw@915: except AttributeError, err: tw@3157: logging.exception('An exception occured.') tw@915: msg = '''AttributeError: %(err)s tw@915: tw@915: Could not find required "Config" class in "%(name)s.py". tw@915: tw@915: This might happen if you are trying to use a pre 1.3 configuration file, or tw@915: made a syntax or spelling error. tw@915: tw@915: Another reason for this could be a name clash. It is not possible to have tw@915: config names like e.g. stats.py - because that colides with MoinMoin/stats/ - tw@915: have a look into your MoinMoin code directory what other names are NOT tw@915: possible. alex@706: alex@706: Please check your configuration file. As an example for correct syntax, alex@706: use the wikiconfig.py file from the distribution. tw@915: ''' % { tw@915: 'name': name, tw@915: 'err': err, tw@915: } alex@706: raise error.ConfigurationError(msg) johannes@2009: johannes@2009: # postprocess configuration johannes@2009: # 'setuid' special auth method auth method can log out johannes@2009: cfg.auth_can_logout = ['setuid'] johannes@2009: cfg.auth_login_inputs = [] johannes@2009: found_names = [] johannes@2009: for auth in cfg.auth: johannes@2009: if not auth.name: johannes@2009: raise error.ConfigurationError("Auth methods must have a name.") johannes@2009: if auth.name in found_names: johannes@2009: raise error.ConfigurationError("Auth method names must be unique.") johannes@2009: found_names.append(auth.name) johannes@2009: if auth.logout_possible and auth.name: johannes@2009: cfg.auth_can_logout.append(auth.name) johannes@2009: for input in auth.login_inputs: johannes@2009: if not input in cfg.auth_login_inputs: johannes@2009: cfg.auth_login_inputs.append(input) johannes@2009: cfg.auth_have_login = len(cfg.auth_login_inputs) > 0 johannes@2009: alex@706: return cfg alex@706: alex@706: alex@706: def _getConfigName(url): alex@706: """ Return config name for url or raise """ alex@706: for name, regex in _url_re_list(): alex@706: match = regex.match(url) alex@706: if match: alex@706: return name tw@1575: raise error.NoConfigMatchedError alex@706: alex@706: alex@706: def getConfig(url): alex@706: """ Return cached config instance for url or create new one alex@706: alex@706: If called by many threads in the same time multiple config alex@706: instances might be created. The first created item will be alex@706: returned, using dict.setdefault. alex@706: alex@706: @param url: the url from request, possibly matching specific wiki alex@706: @rtype: DefaultConfig subclass instance alex@706: @return: config object for specific wiki alex@706: """ tw@1582: cfgName = _getConfigName(url) alex@706: try: tw@1582: cfg = _config_cache[cfgName] alex@706: except KeyError: tw@1582: cfg = _makeConfig(cfgName) tw@1582: cfg = _config_cache.setdefault(cfgName, cfg) tw@1582: return cfg alex@706: alex@706: alex@706: # This is a way to mark some text for the gettext tools so that they don't alex@706: # get orphaned. See http://www.python.org/doc/current/lib/node278.html. tw@1920: def _(text): tw@1920: return text alex@706: alex@706: tw@1549: class CacheClass: tw@1549: """ just a container for stuff we cache """ tw@1549: pass tw@1549: tw@1549: tw@3215: class DefaultConfig(object): johannes@3740: """ Configuration base class with default config values johannes@3740: (added below) tw@3482: """ johannes@3740: # Things that shouldn't be here... johannes@3740: _subscribable_events = None alex@706: alex@706: def __init__(self, siteid): alex@706: """ Init Config instance """ alex@706: self.siteid = siteid tw@1549: self.cache = CacheClass() tw@1549: tw@1624: from MoinMoin.Page import ItemCache tw@1624: self.cache.meta = ItemCache('meta') tw@1624: self.cache.pagelists = ItemCache('pagelists') tw@1620: alex@706: if self.config_check_enabled: alex@706: self._config_check() alex@706: alex@706: # define directories tw@1011: self.moinmoin_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)) alex@706: data_dir = os.path.normpath(self.data_dir) alex@706: self.data_dir = data_dir alex@706: for dirname in ('user', 'cache', 'plugin'): alex@706: name = dirname + '_dir' alex@706: if not getattr(self, name, None): johannes@1929: setattr(self, name, os.path.abspath(os.path.join(data_dir, dirname))) fpletz@855: alex@706: # Try to decode certain names which allow unicode alex@706: self._decode() alex@706: tw@1549: # After that, pre-compile some regexes tw@1549: self.cache.page_category_regex = re.compile(self.page_category_regex, re.UNICODE) tw@1549: self.cache.page_dict_regex = re.compile(self.page_dict_regex, re.UNICODE) tw@1549: self.cache.page_group_regex = re.compile(self.page_group_regex, re.UNICODE) tw@1549: self.cache.page_template_regex = re.compile(self.page_template_regex, re.UNICODE) tw@3573: tw@3573: # the ..._regexact versions only match if nothing is left (exact match) tw@3573: self.cache.page_category_regexact = re.compile(u'^%s$' % self.page_category_regex, re.UNICODE) tw@3573: self.cache.page_dict_regexact = re.compile(u'^%s$' % self.page_dict_regex, re.UNICODE) tw@3573: self.cache.page_group_regexact = re.compile(u'^%s$' % self.page_group_regex, re.UNICODE) tw@3573: self.cache.page_template_regexact = re.compile(u'^%s$' % self.page_template_regex, re.UNICODE) tw@3573: alex@1557: self.cache.ua_spiders = self.ua_spiders and re.compile(self.ua_spiders, re.I) tw@1549: alex@706: self._check_directories() alex@706: alex@706: if not isinstance(self.superuser, list): alex@706: msg = """The superuser setting in your wiki configuration is not a list alex@706: (e.g. ['Sample User', 'AnotherUser']). alex@706: Please change it in your wiki configuration and try again.""" alex@706: raise error.ConfigurationError(msg) tw@931: alex@706: self._loadPluginModule() alex@706: alex@706: # Preparse user dicts alex@706: self._fillDicts() tw@931: alex@706: # Normalize values alex@706: self.language_default = self.language_default.lower() alex@706: alex@706: # Use site name as default name-logo alex@706: if self.logo_string is None: alex@706: self.logo_string = self.sitename alex@706: alex@706: # Check for needed modules alex@706: alex@706: # FIXME: maybe we should do this check later, just before a alex@706: # chart is needed, maybe in the chart module, instead doing it alex@706: # for each request. But this require a large refactoring of alex@706: # current code. alex@706: if self.chart_options: alex@706: try: alex@706: import gdchart alex@706: except ImportError: alex@706: self.chart_options = None tw@931: alex@706: # post process johannes@2430: johannes@2430: # internal dict for plugin `modules' lists johannes@2430: self._site_plugin_lists = {} johannes@2430: alex@706: # we replace any string placeholders with config values alex@706: # e.g u'%(page_front_page)s' % self alex@706: self.navi_bar = [elem % self for elem in self.navi_bar] alex@706: self.backup_exclude = [elem % self for elem in self.backup_exclude] alex@706: tw@2989: # check if python-xapian is installed tw@2989: if self.xapian_search: tw@2989: try: tw@2989: import xapian tw@2989: except ImportError, err: tw@2989: self.xapian_search = False tw@2989: logging.error("xapian_search was auto-disabled because python-xapian is not installed [%s]." % str(err)) tw@2989: tw@788: # list to cache xapian searcher objects tw@788: self.xapian_searchers = [] alex@706: alex@706: # check if mail is possible and set flag: alex@706: self.mail_enabled = (self.mail_smarthost is not None or self.mail_sendmail is not None) and self.mail_from tw@2286: grzywacz@2072: # check if jabber bot is available and set flag: grzywacz@2160: self.jabber_enabled = self.notification_bot_uri is not None tw@2286: grzywacz@2147: # if we are to use the jabber bot, instantiate a server object for future use grzywacz@2147: if self.jabber_enabled: grzywacz@2336: grzywacz@2331: errmsg = "You must set a (long) secret string to send notifications!" grzywacz@2331: try: grzywacz@2331: if not self.secret: grzywacz@2331: raise error.ConfigurationError(errmsg) grzywacz@2331: except AttributeError, err: tw@3175: raise error.ConfigurationError(errmsg) grzywacz@2336: grzywacz@2147: from xmlrpclib import Server grzywacz@2160: self.notification_server = Server(self.notification_bot_uri, ) alex@1080: alex@1118: # Cache variables for the properties below alex@1118: self._iwid = self._iwid_full = self._meta_dict = None alex@1118: tw@1552: self.cache.acl_rights_before = AccessControlList(self, [self.acl_rights_before]) tw@1552: self.cache.acl_rights_default = AccessControlList(self, [self.acl_rights_default]) tw@1552: self.cache.acl_rights_after = AccessControlList(self, [self.acl_rights_after]) tw@1276: tw@1318: if self.url_prefix is not None: # remove this code when url_prefix setting is removed tw@1318: self.url_prefix_static = self.url_prefix tw@1318: tw@1339: action_prefix = self.url_prefix_action tw@1339: if action_prefix is not None and action_prefix.endswith('/'): # make sure there is no trailing '/' tw@1339: self.url_prefix_action = action_prefix[:-1] tw@1339: tw@1654: if self.url_prefix_local is None: tw@1654: self.url_prefix_local = self.url_prefix_static tw@1654: tw@1654: alex@1118: def load_meta_dict(self): alex@1118: """ The meta_dict contains meta data about the wiki instance. """ alex@1118: if getattr(self, "_meta_dict", None) is None: alex@1118: self._meta_dict = wikiutil.MetaDict(os.path.join(self.data_dir, 'meta'), self.cache_dir) alex@1118: return self._meta_dict alex@1118: meta_dict = property(load_meta_dict) alex@1118: alex@1118: # lazily load iwid(_full) alex@1118: def make_iwid_property(attr): alex@1118: def getter(self): alex@1118: if getattr(self, attr, None) is None: alex@1118: self.load_IWID() alex@1118: return getattr(self, attr) alex@1118: return property(getter) alex@1118: iwid = make_iwid_property("_iwid") alex@1118: iwid_full = make_iwid_property("_iwid_full") alex@1053: grzywacz@2307: # lazily load a list of events a user can subscribe to grzywacz@2307: def make_subscribable_events_prop(): grzywacz@2307: def getter(self): grzywacz@2307: if getattr(self, "_subscribable_events", None) is None: grzywacz@2307: self._subscribable_events = events.get_subscribable_events() grzywacz@2307: return getattr(self, "_subscribable_events") grzywacz@2643: grzywacz@3223: def setter(self, new_events): grzywacz@3223: self._subscribable_events = new_events grzywacz@2643: grzywacz@2643: return property(getter, setter) grzywacz@2643: subscribable_events = make_subscribable_events_prop() grzywacz@2643: grzywacz@2643: # lazily create a list of event handlers grzywacz@2643: def make_event_handlers_prop(): grzywacz@2643: def getter(self): grzywacz@2643: if getattr(self, "_event_handlers", None) is None: grzywacz@2643: self._event_handlers = events.get_handlers(self) grzywacz@2643: return getattr(self, "_event_handlers") rb@3240: grzywacz@3223: def setter(self, new_handlers): grzywacz@3223: self._event_handlers = new_handlers rb@3240: grzywacz@3223: return property(getter, setter) grzywacz@2643: event_handlers = make_event_handlers_prop() alex@1053: alex@1053: def load_IWID(self): alex@1053: """ Loads the InterWikiID of this instance. It is used to identify the instance alex@1053: globally. alex@1053: The IWID is available as cfg.iwid alex@1053: The full IWID containing the interwiki name is available as cfg.iwid_full alex@1118: This method is called by the property. alex@1053: """ alex@1053: try: alex@1080: iwid = self.meta_dict['IWID'] alex@1080: except KeyError: alex@1053: iwid = util.random_string(16).encode("hex") + "-" + str(int(time.time())) alex@1080: self.meta_dict['IWID'] = iwid alex@1080: self.meta_dict.sync() alex@1053: alex@1118: self._iwid = iwid alex@1053: if self.interwikiname is not None: alex@1118: self._iwid_full = packLine([iwid, self.interwikiname]) alex@1053: else: alex@1118: self._iwid_full = packLine([iwid]) alex@706: alex@706: def _config_check(self): alex@706: """ Check namespace and warn about unknown names tw@2286: alex@706: Warn about names which are not used by DefaultConfig, except alex@706: modules, classes, _private or __magic__ names. alex@706: alex@706: This check is disabled by default, when enabled, it will show an alex@706: error message with unknown names. tw@931: """ alex@706: unknown = ['"%s"' % name for name in dir(self) tw@931: if not name.startswith('_') and tw@1868: name not in DefaultConfig.__dict__ and alex@706: not isinstance(getattr(self, name), (type(sys), type(DefaultConfig)))] alex@706: if unknown: alex@706: msg = """ alex@706: Unknown configuration options: %s. alex@706: alex@706: For more information, visit HelpOnConfiguration. Please check your alex@706: configuration for typos before requesting support or reporting a bug. alex@706: """ % ', '.join(unknown) alex@706: raise error.ConfigurationError(msg) alex@706: alex@706: def _decode(self): alex@706: """ Try to decode certain names, ignore unicode values tw@2286: tw@2286: Try to decode str using utf-8. If the decode fail, raise FatalError. alex@706: alex@706: Certain config variables should contain unicode values, and alex@706: should be defined with u'text' syntax. Python decode these if alex@706: the file have a 'coding' line. tw@2286: alex@706: This will allow utf-8 users to use simple strings using, without alex@706: using u'string'. Other users will have to use u'string' for alex@706: these names, because we don't know what is the charset of the alex@706: config files. alex@706: """ alex@706: charset = 'utf-8' alex@706: message = u''' alex@706: "%(name)s" configuration variable is a string, but should be alex@706: unicode. Use %(name)s = u"value" syntax for unicode variables. alex@706: alex@706: Also check your "-*- coding -*-" line at the top of your configuration alex@706: file. It should match the actual charset of the configuration file. alex@706: ''' tw@931: alex@706: decode_names = ( alex@706: 'sitename', 'logo_string', 'navi_bar', 'page_front_page', tw@931: 'page_category_regex', 'page_dict_regex', alex@706: 'page_group_regex', 'page_template_regex', 'page_license_page', alex@706: 'page_local_spelling_words', 'acl_rights_default', alex@706: 'acl_rights_before', 'acl_rights_after', 'mail_from' alex@706: ) tw@931: alex@706: for name in decode_names: alex@706: attr = getattr(self, name, None) alex@706: if attr: alex@706: # Try to decode strings alex@706: if isinstance(attr, str): alex@706: try: tw@931: setattr(self, name, unicode(attr, charset)) alex@706: except UnicodeError: alex@706: raise error.ConfigurationError(message % alex@706: {'name': name}) alex@706: # Look into lists and try to decode strings inside them alex@706: elif isinstance(attr, list): alex@706: for i in xrange(len(attr)): alex@706: item = attr[i] alex@706: if isinstance(item, str): alex@706: try: alex@706: attr[i] = unicode(item, charset) alex@706: except UnicodeError: alex@706: raise error.ConfigurationError(message % alex@706: {'name': name}) alex@706: alex@706: def _check_directories(self): alex@706: """ Make sure directories are accessible alex@706: alex@706: Both data and underlay should exists and allow read, write and alex@706: execute. alex@706: """ alex@706: mode = os.F_OK | os.R_OK | os.W_OK | os.X_OK alex@706: for attr in ('data_dir', 'data_underlay_dir'): alex@706: path = getattr(self, attr) tw@931: alex@706: # allow an empty underlay path or None alex@706: if attr == 'data_underlay_dir' and not path: alex@706: continue alex@706: alex@706: path_pages = os.path.join(path, "pages") alex@706: if not (os.path.isdir(path_pages) and os.access(path_pages, mode)): alex@706: msg = ''' tw@3490: %(attr)s "%(path)s" does not exist, or has incorrect ownership or alex@706: permissions. alex@706: tw@3490: Make sure the directory and the subdirectory "pages" are owned by the web alex@706: server and are readable, writable and executable by the web server user alex@706: and group. alex@706: alex@706: It is recommended to use absolute paths and not relative paths. Check alex@706: also the spelling of the directory name. tw@931: ''' % {'attr': attr, 'path': path, } alex@706: raise error.ConfigurationError(msg) alex@706: alex@706: def _loadPluginModule(self): alex@706: """ import plugin module under configname.plugin alex@706: alex@706: To be able to import plugin from arbitrary path, we have to load alex@706: the base package once using imp.load_module. Later, we can use alex@706: standard __import__ call to load plugins in this package. alex@706: alex@706: Since each wiki has unique plugins, we load the plugin package alex@706: under the wiki configuration module, named self.siteid. alex@706: """ tw@1791: import imp alex@706: alex@706: name = self.siteid + '.plugin' alex@706: try: alex@706: # Lock other threads while we check and import alex@706: imp.acquire_lock() alex@706: try: alex@706: # If the module is not loaded, try to load it alex@706: if not name in sys.modules: alex@706: # Find module on disk and try to load - slow! alex@706: plugin_parent_dir = os.path.abspath(os.path.join(self.plugin_dir, '..')) alex@706: fp, path, info = imp.find_module('plugin', [plugin_parent_dir]) alex@706: try: tw@2286: # Load the module and set in sys.modules alex@706: module = imp.load_module(name, fp, path, info) alex@706: sys.modules[self.siteid].plugin = module alex@706: finally: alex@706: # Make sure fp is closed properly alex@706: if fp: alex@706: fp.close() alex@706: finally: alex@706: imp.release_lock() alex@706: except ImportError, err: alex@706: msg = ''' alex@706: Could not import plugin package "%(path)s/plugin" because of ImportError: alex@706: %(err)s. alex@706: alex@706: Make sure your data directory path is correct, check permissions, and alex@706: that the data/plugin directory has an __init__.py file. tw@915: ''' % { tw@915: 'path': self.data_dir, tw@915: 'err': str(err), tw@915: } alex@706: raise error.ConfigurationError(msg) alex@706: alex@706: def _fillDicts(self): alex@706: """ fill config dicts alex@706: alex@706: Fills in missing dict keys of derived user config by copying alex@706: them from this base class. alex@706: """ alex@706: # user checkbox defaults alex@706: for key, value in DefaultConfig.user_checkbox_defaults.items(): tw@1868: if key not in self.user_checkbox_defaults: alex@706: self.user_checkbox_defaults[key] = value alex@706: alex@706: def __getitem__(self, item): alex@706: """ Make it possible to access a config object like a dict """ alex@706: return getattr(self, item) tw@931: johannes@3740: johannes@3740: def _default_password_checker(request, username, password): johannes@3740: """ Check if a password is secure enough. johannes@3740: We use a built-in check to get rid of the worst passwords. johannes@3740: johannes@3740: We do NOT use cracklib / python-crack here any more because it is johannes@3740: not thread-safe (we experienced segmentation faults when using it). johannes@3740: johannes@3740: If you don't want to check passwords, use password_checker = None. johannes@3740: johannes@3740: @return: None if there is no problem with the password, johannes@3740: some string with an error msg, if the password is problematic. johannes@3740: """ johannes@3740: johannes@3740: try: johannes@3740: # in any case, do a very simple built-in check to avoid the worst passwords johannes@3740: if len(password) < 6: johannes@3740: raise ValueError("Password too short.") johannes@3740: if len(set(password)) < 4: johannes@3740: raise ValueError("Password has not enough different characters.") johannes@3740: johannes@3740: username_lower = username.lower() johannes@3740: password_lower = password.lower() johannes@3740: if username in password or password in username or \ johannes@3740: username_lower in password_lower or password_lower in username_lower: johannes@3740: raise ValueError("Password too easy (containment).") johannes@3740: johannes@3740: keyboards = (ur"`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./", # US kbd johannes@3740: ur"^1234567890ß´qwertzuiopü+asdfghjklöä#yxcvbnm,.-", # german kbd johannes@3740: ) # add more keyboards! johannes@3740: for kbd in keyboards: johannes@3740: rev_kbd = kbd[::-1] johannes@3740: if password in kbd or password in rev_kbd or \ johannes@3740: password_lower in kbd or password_lower in rev_kbd: johannes@3740: raise ValueError("Password too easy (kbd sequence)") johannes@3740: return None johannes@3740: except ValueError, err: johannes@3740: return str(err) johannes@3740: johannes@3740: johannes@3740: options_no_group_name = { johannes@3744: 'various': ('Various', None, ( johannes@3740: ('DesktopEdition', johannes@3740: False, johannes@3740: 'True gives all local users special powers - ONLY use for MMDE style usage!'), johannes@3740: ('SecurityPolicy', johannes@3740: None, johannes@3740: None), johannes@3740: johannes@3740: ('actions_excluded', johannes@3740: ['xmlrpc', # we do not want wiki admins unknowingly offering xmlrpc service johannes@3740: 'MyPages', # only works when used with a non-default SecurityPolicy (e.g. autoadmin) johannes@3740: 'CopyPage', # has questionable behaviour regarding subpages a user can't read, but can copy johannes@3740: ], johannes@3740: None), johannes@3740: johannes@3740: ('allow_xslt', False, None), johannes@3740: ('antispam_master_url', "http://master.moinmo.in/?action=xmlrpc2", None), johannes@3740: ('auth', [authmodule.MoinAuth()], None), johannes@3740: ('auth_methods_trusted', ['http', 'xmlrpc_applytoken'], None), johannes@3740: johannes@3740: ('bang_meta', True, None), johannes@3740: ('caching_formats', ['text_html'], None), johannes@3740: ('changed_time_fmt', '%H:%M', None), johannes@3740: johannes@3740: ('chart_options', None, None), johannes@3740: johannes@3740: ('config_check_enabled', False, None), johannes@3740: johannes@3740: ('cookie_domain', None, None), johannes@3740: ('cookie_path', None, None), johannes@3740: ('cookie_lifetime', 12, None), johannes@3740: johannes@3740: ('data_dir', './data/', None), johannes@3740: ('data_underlay_dir', './underlay/', None), johannes@3740: johannes@3740: ('date_fmt', '%Y-%m-%d', None), johannes@3740: ('datetime_fmt', '%Y-%m-%d %H:%M:%S', None), johannes@3740: johannes@3740: ('default_markup', 'wiki', None), johannes@3740: ('docbook_html_dir', r"/usr/share/xml/docbook/stylesheet/nwalsh/html/", None), johannes@3740: johannes@3740: ('edit_bar', ['Edit', 'Comments', 'Discussion', 'Info', 'Subscribe', 'Quicklink', 'Attachments', 'ActionsMenu'], None), johannes@3740: ('editor_default', 'text', None), johannes@3740: ('editor_force', False, None), johannes@3740: ('editor_ui', 'freechoice', None), johannes@3740: ('editor_quickhelp', { johannes@3740: # editor markup hints quickhelp johannes@3740: # MUST be in wiki markup, even if the help is not for the wiki parser! johannes@3740: 'wiki': _(u"""\ johannes@3740: Emphasis:: <>''italics''<>; <>'''bold'''<>; <>'''''bold italics'''''<>; <>''mixed ''<>'''''bold'''<> and italics''<>; <> horizontal rule. johannes@3740: Headings:: = Title 1 =; == Title 2 ==; === Title 3 ===; ==== Title 4 ====; ===== Title 5 =====. johannes@3740: Lists:: space and one of: * bullets; 1., a., A., i., I. numbered items; 1.#n start numbering at n; space alone indents. johannes@3740: Links:: <>; <>. johannes@3740: Tables:: || cell text |||| cell text spanning 2 columns ||; no trailing white space allowed after tables or titles. johannes@3740: johannes@3740: (!) For more help, see HelpOnEditing or SyntaxReference. johannes@3740: """), johannes@3740: 'rst': _("""\ johannes@3740: {{{ johannes@3740: Emphasis: *italic* **bold** ``monospace`` johannes@3740: johannes@3740: Headings: Heading 1 Heading 2 Heading 3 johannes@3740: ========= --------- ~~~~~~~~~ johannes@3740: johannes@3740: Horizontal rule: ---- johannes@3740: johannes@3740: Links: TrailingUnderscore_ `multi word with backticks`_ external_ johannes@3740: johannes@3740: .. _external: http://external-site.example.org/foo/ johannes@3740: johannes@3740: Lists: * bullets; 1., a. numbered items. johannes@3740: }}} johannes@3740: (!) For more help, see the johannes@3740: [[http://docutils.sourceforge.net/docs/user/rst/quickref.html|reStructuredText Quick Reference]]. johannes@3740: """), johannes@3740: 'creole': _(u"""\ johannes@3740: Emphasis:: <>''italics''<>; <>'''bold'''<>; <>'''''bold italics'''''<>; <>''mixed ''<>'''''bold'''<> and italics''<>; johannes@3740: Horizontal Rule:: <> johannes@3740: Force Linebreak:: <> johannes@3740: Headings:: = Title 1 =; == Title 2 ==; === Title 3 ===; ==== Title 4 ====; ===== Title 5 =====. johannes@3740: Lists:: * bullets; ** sub-bullets; # numbered items; ## numbered sub items. johannes@3740: Links:: <>; <>. johannes@3740: Tables:: |= header text | cell text | more cell text |; johannes@3740: johannes@3740: (!) For more help, see HelpOnEditing or HelpOnCreoleSyntax. johannes@3740: """), johannes@3740: }, None), johannes@3740: ('edit_locking', 'warn 10', None), johannes@3740: ('edit_ticketing', True, None), johannes@3740: ('edit_rows', 20, None), johannes@3740: johannes@3740: ('history_count', (100, 200), None), johannes@3740: johannes@3740: ('hosts_deny', [], None), johannes@3740: johannes@3740: ('html_head', '', None), johannes@3740: ('html_head_queries', '''\n''', None), johannes@3740: ('html_head_posts', '''\n''', None), johannes@3740: ('html_head_index', '''\n''', None), johannes@3740: ('html_head_normal', '''\n''', None), johannes@3740: ('html_pagetitle', None, None), johannes@3740: johannes@3740: ('interwikiname', None, None), johannes@3740: ('interwiki_preferred', [], None), johannes@3740: johannes@3740: ('language_default', 'en', None), johannes@3740: ('language_ignore_browser', False, None), johannes@3740: johannes@3740: ('logo_string', None, None), johannes@3740: johannes@3740: ('log_reverse_dns_lookups', True, None), johannes@3740: ('log_timing', False, None), johannes@3740: johannes@3740: ('mail_from', None, None), johannes@3740: ('mail_login', None, None), johannes@3740: ('mail_smarthost', None, None), johannes@3740: ('mail_sendmail', None, None), johannes@3740: johannes@3740: ('mail_import_secret', "", None), johannes@3740: ('mail_import_subpage_template', u"$from-$date-$subject", None), johannes@3740: ('mail_import_pagename_search', ['subject', 'to', ], None), johannes@3740: ('mail_import_pagename_envelope', u"%s", None), johannes@3740: ('mail_import_pagename_regex', r'\[\[([^\]]*)\]\]', None), johannes@3740: ('mail_import_wiki_addrs', [], None), johannes@3740: johannes@3740: # some dangerous mimetypes (we don't use "content-disposition: inline" for them when a user johannes@3740: # downloads such attachments, because the browser might execute e.g. Javascript contained johannes@3740: # in the HTML and steal your moin session cookie or do other nasty stuff) johannes@3740: ('mimetypes_xss_protect', johannes@3740: [ johannes@3740: 'text/html', johannes@3740: 'application/x-shockwave-flash', johannes@3740: 'application/xhtml+xml', johannes@3740: ], None), johannes@3740: johannes@3740: ('mimetypes_embed', johannes@3740: [ johannes@3740: 'application/x-dvi', johannes@3740: 'application/postscript', johannes@3740: 'application/pdf', johannes@3740: 'application/ogg', johannes@3740: 'application/vnd.visio', johannes@3740: 'image/x-ms-bmp', johannes@3740: 'image/svg+xml', johannes@3740: 'image/tiff', johannes@3740: 'image/x-photoshop', johannes@3740: 'audio/mpeg', johannes@3740: 'audio/midi', johannes@3740: 'audio/x-wav', johannes@3740: 'video/fli', johannes@3740: 'video/mpeg', johannes@3740: 'video/quicktime', johannes@3740: 'video/x-msvideo', johannes@3740: 'chemical/x-pdb', johannes@3740: 'x-world/x-vrml', johannes@3740: ], None), johannes@3740: johannes@3740: johannes@3740: ('navi_bar', [u'RecentChanges', u'FindPage', u'HelpContents', ], None), johannes@3740: ('nonexist_qm', False, None), johannes@3740: johannes@3740: ('notification_bot_uri', None, None), johannes@3740: johannes@3740: ('page_credits', johannes@3740: [ johannes@3740: 'MoinMoin Powered', johannes@3740: 'Python Powered', johannes@3740: 'GPL licensed', johannes@3740: 'Valid HTML 4.01', johannes@3740: ], None), johannes@3740: johannes@3740: ('page_footer1', '', None), johannes@3740: ('page_footer2', '', None), johannes@3740: ('page_header1', '', None), johannes@3740: ('page_header2', '', None), johannes@3740: johannes@3740: ('page_front_page', u'HelpOnLanguages', None), johannes@3740: ('page_local_spelling_words', u'LocalSpellingWords', None), johannes@3740: johannes@3740: # the following regexes should match the complete name when used in free text johannes@3740: # the group 'all' shall match all, while the group 'key' shall match the key only johannes@3740: # e.g. CategoryFoo -> group 'all' == CategoryFoo, group 'key' == Foo johannes@3740: # moin's code will add ^ / $ at beginning / end when needed johannes@3740: ('page_category_regex', ur'(?PCategory(?P\S+))', None), johannes@3740: ('page_dict_regex', ur'(?P(?P\S+)Dict)', None), johannes@3740: ('page_group_regex', ur'(?P(?P\S+)Group)', None), johannes@3740: ('page_template_regex', ur'(?P(?P\S+)Template)', None), johannes@3740: johannes@3740: ('page_license_enabled', False, None), johannes@3740: ('page_license_page', u'WikiLicense', None), johannes@3740: johannes@3740: # These icons will show in this order in the iconbar, unless they johannes@3740: # are not relevant, e.g email icon when the wiki is not configured johannes@3740: # for email. johannes@3740: ('page_iconbar', ["up", "edit", "view", "diff", "info", "subscribe", "raw", "print", ], None), johannes@3740: johannes@3740: # Standard buttons in the iconbar johannes@3740: ('page_icons_table', johannes@3740: { johannes@3740: # key pagekey, querystr dict, title, icon-key johannes@3740: 'diff': ('page', {'action': 'diff'}, _("Diffs"), "diff"), johannes@3740: 'info': ('page', {'action': 'info'}, _("Info"), "info"), johannes@3740: 'edit': ('page', {'action': 'edit'}, _("Edit"), "edit"), johannes@3740: 'unsubscribe': ('page', {'action': 'unsubscribe'}, _("UnSubscribe"), "unsubscribe"), johannes@3740: 'subscribe': ('page', {'action': 'subscribe'}, _("Subscribe"), "subscribe"), johannes@3740: 'raw': ('page', {'action': 'raw'}, _("Raw"), "raw"), johannes@3740: 'xml': ('page', {'action': 'show', 'mimetype': 'text/xml'}, _("XML"), "xml"), johannes@3740: 'print': ('page', {'action': 'print'}, _("Print"), "print"), johannes@3740: 'view': ('page', {}, _("View"), "view"), johannes@3740: 'up': ('page_parent_page', {}, _("Up"), "up"), johannes@3740: }, None), johannes@3740: johannes@3740: johannes@3740: johannes@3740: ('password_checker', _default_password_checker, None), johannes@3740: johannes@3740: ('quicklinks_default', [], None), johannes@3740: johannes@3740: ('refresh', None, None), johannes@3740: ('rss_cache', 60, None), johannes@3740: johannes@3740: ('search_results_per_page', 25, None), johannes@3740: johannes@3740: ('session_handler', session.DefaultSessionHandler(), None), johannes@3740: ('session_id_handler', session.MoinCookieSessionIDHandler(), None), johannes@3740: johannes@3740: ('shared_intermap', None, None), johannes@3740: johannes@3740: ('show_hosts', True, None), johannes@3740: ('show_interwiki', False, None), johannes@3740: ('show_names', True, None), johannes@3740: ('show_section_numbers', 0, None), johannes@3740: ('show_timings', False, None), johannes@3740: ('show_version', False, None), johannes@3740: johannes@3740: ('sistersites', [], None), johannes@3740: johannes@3740: ('siteid', 'default', None), johannes@3740: ('sitename', u'Untitled Wiki', None), johannes@3740: johannes@3740: ('stylesheets', [], None), johannes@3740: johannes@3740: ('subscribed_pages_default', [], None), johannes@3740: ('email_subscribed_events_default', johannes@3740: [ johannes@3740: PageChangedEvent.__name__, johannes@3740: PageRenamedEvent.__name__, johannes@3740: PageDeletedEvent.__name__, johannes@3740: PageCopiedEvent.__name__, johannes@3740: PageRevertedEvent.__name__, johannes@3740: FileAttachedEvent.__name__, johannes@3740: ], None), johannes@3740: ('jabber_subscribed_events_default', [], None), johannes@3740: johannes@3740: ('superuser', [], None), johannes@3740: johannes@3740: ('supplementation_page', False, None), johannes@3740: ('supplementation_page_name', u'Discussion', None), johannes@3740: ('supplementation_page_template', u'DiscussionTemplate', None), johannes@3740: johannes@3740: ('surge_action_limits', johannes@3740: {# allow max. requests per
secs johannes@3740: # action: (count, dt) johannes@3740: 'all': (30, 30), johannes@3740: 'show': (30, 60), johannes@3740: 'recall': (10, 120), johannes@3740: 'raw': (20, 40), # some people use this for css johannes@3740: 'AttachFile': (90, 60), johannes@3740: 'diff': (30, 60), johannes@3740: 'fullsearch': (10, 120), johannes@3740: 'edit': (30, 300), # can be lowered after making preview different from edit johannes@3740: 'rss_rc': (1, 60), johannes@3740: 'default': (30, 60), johannes@3740: }, None), johannes@3740: ('surge_lockout_time', 3600, None), johannes@3740: johannes@3740: ('textchas', None, None), johannes@3740: ('textchas_disabled_group', None, None), johannes@3740: johannes@3740: ('theme_default', 'modern', None), johannes@3740: ('theme_force', False, None), johannes@3740: johannes@3740: ('traceback_show', True, None), johannes@3740: ('traceback_log_dir', None, None), johannes@3740: johannes@3740: ('trail_size', 5, None), johannes@3740: ('tz_offset', 0.0, None), johannes@3740: johannes@3740: # a regex of HTTP_USER_AGENTS that should be excluded from logging johannes@3740: # and receive a FORBIDDEN for anything except viewing a page johannes@3740: # list must not contain 'java' because of twikidraw wanting to save drawing uses this useragent johannes@3740: ('ua_spiders', johannes@3740: ('archiver|cfetch|charlotte|crawler|curl|gigabot|googlebot|heritrix|holmes|htdig|httrack|httpunit|' johannes@3740: 'intelix|jeeves|larbin|leech|libwww-perl|linkbot|linkmap|linkwalk|litefinder|mercator|' johannes@3740: 'microsoft.url.control|mirror| mj12bot|msnbot|msrbot|neomo|nutbot|omniexplorer|puf|robot|scooter|seekbot|' johannes@3740: 'sherlock|slurp|sitecheck|snoopy|spider|teleport|twiceler|voilabot|voyager|webreaper|wget|yeti'), johannes@3740: None), johannes@3740: johannes@3740: ('unzip_single_file_size', 2.0 * 1000 ** 2, None), johannes@3740: ('unzip_attachments_space', 200.0 * 1000 ** 2, None), johannes@3740: ('unzip_attachments_count', 101, None), johannes@3740: johannes@3740: ('url_mappings', {}, None), johannes@3740: johannes@3740: # url_prefix is DEPRECATED and not used any more by the code. johannes@3740: # it confused many people by its name and default value of '/wiki' to the johannes@3740: # wrong conclusion that it is the url of the wiki (the dynamic) stuff, johannes@3740: # but it was used to address the static stuff (images, css, js). johannes@3740: # Thus we use the more clear url_prefix_static ['/moin_staticVVV'] setting now. johannes@3740: # For a limited time, we still look at url_prefix - if it is not None, we johannes@3740: # copy the value to url_prefix_static to ease transition. johannes@3740: ('url_prefix', None, None), johannes@3740: johannes@3740: # includes the moin version number, so we can have a unlimited cache lifetime johannes@3740: # for the static stuff. if stuff changes on version upgrade, url will change johannes@3740: # immediately and we have no problem with stale caches. johannes@3740: ('url_prefix_static', config.url_prefix_static, None), johannes@3740: ('url_prefix_local', None, None), johannes@3740: johannes@3740: # we could prefix actions to be able to exclude them by robots.txt: johannes@3740: #url_prefix_action', 'action' # no leading or trailing '/' johannes@3740: ('url_prefix_action', None, None), johannes@3740: johannes@3740: # allow disabling certain userpreferences plugins johannes@3740: ('userprefs_disabled', [], None), johannes@3740: )), johannes@3740: } johannes@3740: johannes@3740: options = { johannes@3744: 'acl': ('Access control lists', None, ( johannes@3740: ('hierarchic', False, 'True to use hierarchical ACLs'), johannes@3740: ('rights_default', u"Trusted:read,write,delete,revert Known:read,write,delete,revert All:read,write", None), johannes@3740: ('rights_before', u"", None), johannes@3740: ('rights_after', u"", None), johannes@3740: ('rights_valid', ['read', 'write', 'delete', 'revert', 'admin'], None), johannes@3740: )), johannes@3740: johannes@3744: 'xapian': ('Xapian search', None, ( johannes@3740: ('search', False, None), johannes@3740: ('index_dir', None, None), johannes@3740: ('stemming', False, None), johannes@3740: ('index_history', False, None), johannes@3740: )), johannes@3740: johannes@3744: 'user': ('Users / User settings', None, ( johannes@3740: ('autocreate', False, None), johannes@3740: ('email_unique', True, None), johannes@3740: ('jid_unique', True, None), johannes@3740: johannes@3740: ('homewiki', 'Self', None), johannes@3740: johannes@3740: ('checkbox_fields', johannes@3740: [ johannes@3740: ('mailto_author', lambda _: _('Publish my email (not my wiki homepage) in author info')), johannes@3740: ('edit_on_doubleclick', lambda _: _('Open editor on double click')), johannes@3740: ('remember_last_visit', lambda _: _('After login, jump to last visited page')), johannes@3740: ('show_comments', lambda _: _('Show comment sections')), johannes@3740: ('show_nonexist_qm', lambda _: _('Show question mark for non-existing pagelinks')), johannes@3740: ('show_page_trail', lambda _: _('Show page trail')), johannes@3740: ('show_toolbar', lambda _: _('Show icon toolbar')), johannes@3740: ('show_topbottom', lambda _: _('Show top/bottom links in headings')), johannes@3740: ('show_fancy_diff', lambda _: _('Show fancy diffs')), johannes@3740: ('wikiname_add_spaces', lambda _: _('Add spaces to displayed wiki names')), johannes@3740: ('remember_me', lambda _: _('Remember login information')), johannes@3740: johannes@3740: ('disabled', lambda _: _('Disable this account forever')), johannes@3740: # if an account is disabled, it may be used for looking up johannes@3740: # id -> username for page info and recent changes, but it johannes@3740: # is not usable for the user any more: johannes@3740: ], johannes@3740: None), johannes@3740: johannes@3740: ('checkbox_defaults', johannes@3740: { johannes@3740: 'mailto_author': 0, johannes@3740: 'edit_on_doubleclick': 0, johannes@3740: 'remember_last_visit': 0, johannes@3740: 'show_comments': 0, johannes@3740: 'show_nonexist_qm': False, johannes@3740: 'show_page_trail': 1, johannes@3740: 'show_toolbar': 1, johannes@3740: 'show_topbottom': 0, johannes@3740: 'show_fancy_diff': 1, johannes@3740: 'wikiname_add_spaces': 0, johannes@3740: 'remember_me': 1, johannes@3740: }, johannes@3740: None), johannes@3740: johannes@3740: ('checkbox_disable', [], None), johannes@3740: johannes@3740: ('checkbox_remove', [], None), johannes@3740: johannes@3740: ('form_fields', johannes@3740: [ johannes@3740: ('name', _('Name'), "text", "36", _("(Use FirstnameLastname)")), johannes@3740: ('aliasname', _('Alias-Name'), "text", "36", ''), johannes@3740: ('email', _('Email'), "text", "36", ''), johannes@3740: ('jid', _('Jabber ID'), "text", "36", ''), johannes@3740: ('css_url', _('User CSS URL'), "text", "40", _('(Leave it empty for disabling user CSS)')), johannes@3740: ('edit_rows', _('Editor size'), "text", "3", ''), johannes@3740: ], johannes@3740: None), johannes@3740: johannes@3740: ('form_defaults', johannes@3740: { # key: default - do NOT remove keys from here! johannes@3740: 'name': '', johannes@3740: 'aliasname': '', johannes@3740: 'password': '', johannes@3740: 'password2': '', johannes@3740: 'email': '', johannes@3740: 'jid': '', johannes@3740: 'css_url': '', johannes@3740: 'edit_rows': "20", johannes@3740: }, johannes@3740: None), johannes@3740: johannes@3740: ('form_disable', [], None), johannes@3740: johannes@3740: ('form_remove', [], None), johannes@3740: johannes@3740: ('transient_fields', johannes@3740: ['id', 'valid', 'may', 'auth_username', 'password', 'password2', 'auth_method', 'auth_attribs', ], johannes@3740: None), johannes@3740: )), johannes@3740: johannes@3744: 'backup': ('Backup', None, ( johannes@3740: ('compression', 'gz', None), johannes@3740: ('users', [], None), johannes@3740: ('include', [], None), johannes@3740: ('exclude', johannes@3740: [ johannes@3740: r"(.+\.py(c|o)$)", johannes@3740: r"%(cache_dir)s", johannes@3740: r"%(/)spages%(/)s.+%(/)scache%(/)s[^%(/)s]+$" % {'/': os.sep}, johannes@3740: r"%(/)s(edit-lock|event-log|\.DS_Store)$" % {'/': os.sep}, johannes@3740: ], johannes@3740: None), johannes@3740: ('storage_dir', '/tmp', None), johannes@3740: ('restore_target_dir', '/tmp', None), johannes@3740: )), johannes@3740: johannes@3744: 'openid_server': ('OpenID Server', johannes@3744: 'These settings control the built-in OpenID Identity Provider (server).', johannes@3744: ( johannes@3740: ('enabled', False, None), johannes@3740: ('restricted_users_group', None, None), johannes@3740: ('enable_user', False, None), johannes@3740: )), johannes@3740: } johannes@3740: johannes@3740: def _add_options_to_defconfig(opts, addgroup=True): johannes@3740: for groupname in opts: johannes@3744: group_short, group_doc, group_opts = opts[groupname] johannes@3740: for name, default, doc in group_opts: johannes@3740: if addgroup: johannes@3740: name = groupname + '_' + name johannes@3740: setattr(DefaultConfig, name, default) johannes@3740: johannes@3740: _add_options_to_defconfig(options) johannes@3740: _add_options_to_defconfig(options_no_group_name, False) johannes@3740: tw@2286: # remove the gettext pseudo function alex@706: del _