def _validatePassword(self, data, password): """ Check user password. This is a private method and should not be used by clients. @param data: dict with user data (from storage) @param password: password to verify [unicode] @rtype: 2 tuple (bool, bool) @return: password is valid, enc_password changed """ epwd = data['enc_password'] # If we have no password set, we don't accept login with username if not epwd: return False, False # require non empty password if not password: return False, False # Check and upgrade passwords from earlier MoinMoin versions and # passwords imported from other wiki systems. for method in ['{SHA}', '{APR1}', '{MD5}', '{DES}']: if epwd.startswith(method): d = epwd[len(method):] if method == '{SHA}': enc = base64.encodestring( hash_new('sha1', password.encode('utf-8')).digest()).rstrip() elif method == '{APR1}': # d is of the form "$apr1$<salt>$<hash>" salt = d.split('$')[2] enc = md5crypt.apache_md5_crypt(password.encode('utf-8'), salt.encode('ascii')) elif method == '{MD5}': # d is of the form "$1$<salt>$<hash>" salt = d.split('$')[2] enc = md5crypt.unix_md5_crypt(password.encode('utf-8'), salt.encode('ascii')) elif method == '{DES}': if crypt is None: return False, False # d is 2 characters salt + 11 characters hash salt = d[:2] enc = crypt.crypt(password.encode('utf-8'), salt.encode('ascii')) if epwd == method + enc: data['enc_password'] = encodePassword(password) # upgrade to SSHA return True, True return False, False if epwd[:6] == '{SSHA}': data = base64.decodestring(epwd[6:]) salt = data[20:] hash = hash_new('sha1', password.encode('utf-8')) hash.update(salt) return hash.digest() == data[:20], False # No encoded password match, this must be wrong password return False, False
def encodePassword(cfg, pwd, salt=None, scheme=None): """ Encode a cleartext password using the default algorithm. @param cfg: the wiki config @param pwd: the cleartext password, (unicode) @param salt: the salt for the password (string) or None to generate a random salt. @param scheme: scheme to use (by default will use cfg.password_scheme) @rtype: string @return: the password hash in apache htpasswd compatible encoding, """ if scheme is None: scheme = cfg.password_scheme configured_scheme = True else: configured_scheme = False if scheme == '{PASSLIB}': return '{PASSLIB}' + cfg.cache.pwd_context.encrypt(pwd, salt=salt) elif scheme == '{SSHA}': pwd = pwd.encode('utf-8') if salt is None: salt = random_string(20) assert isinstance(salt, str) hash = hash_new('sha1', pwd) hash.update(salt) return '{SSHA}' + base64.encodestring(hash.digest() + salt).rstrip() elif scheme == '{SHA}': pwd = pwd.encode('utf-8') hash = hash_new('sha1', pwd) return '{SHA}' + base64.encodestring(hash.digest()).rstrip() else: # should never happen as we check the value of cfg.password_scheme raise NotImplementedError
def generate_ver(identities, features, algo='sha-1'): """Generate the 'ver' attribute according to XEP-0115. See http://www.xmpp.org/extensions/xep-0115.html#ver @param identities: a number of (category, type) identity pairs @param algo: optional algo attribute with IANA aliasing @type identities: iterable of 2-tuples of strings @type features: iterable of strings @type algo: string (IANA Hash Function Textual Name) """ # only IANA aliases are supported if algo not in HASHALIASES: raise ValueError("undefined hash algorithm") algo = hash_new(HASHALIASES[algo]) ident = list(identities) # default sorting already considers both, category and type ident.sort() ident = ['%s/%s' % (idcat, idtype) for idcat, idtype in ident] feat = list(features) # strings (byte arrays) are ordered by i;octet by default feat.sort() s = '<'.join(itertools.chain(ident, feat, ('', ))) # the trailing empty string adds a trailing '<' to the result algo.update(s) s = base64.b64encode(algo.digest()) return s
def _heading_repl(self, word): """Handle section headings.""" from MoinMoin.support.python_compatibility import hash_new h = word.strip() level = 1 while h[level:level + 1] == '=': level += 1 depth = min(5, level) # FIXME: needed for Included pages but might still result in unpredictable results # when included the same page multiple times title_text = h[level:-level].strip() pntt = self.formatter.page.page_name + title_text self.titles.setdefault(pntt, 0) self.titles[pntt] += 1 unique_id = '' if self.titles[pntt] > 1: unique_id = '-%d' % self.titles[pntt] result = self._closeP() result += self.formatter.heading( 1, depth, id="head-" + hash_new('sha1', pntt.encode(config.charset)).hexdigest() + unique_id) return (result + self.formatter.text(title_text) + self.formatter.heading(0, depth))
def format(self, formatter): """ Parse and send the colored source. """ # store line offsets in self.lines self.lines = [0, 0] pos = 0 while 1: try: pos = self.raw.index('\n', pos) + 1 except ValueError: break self.lines.append(pos) self.lines.append(len(self.raw)) self.result = [] # collects output self._code_id = hash_new('sha1', self.raw.encode(config.charset)).hexdigest() self.result.append(formatter.code_area(1, self._code_id, 'ColorizedPython', self.show_num, self.num_start, self.num_step)) self.formatter = formatter self.result.append(formatter.code_line(1)) #len('%d' % (len(self.lines)-1, ))) # parse the source and write it self.pos = 0 text = StringIO.StringIO(self.raw) try: tokenize.tokenize(text.readline, self) except IndentationError, ex: msg = ex[0] errmsg = (self.formatter.linebreak() + self.formatter.strong(1) + "ERROR: %s" % msg + self.formatter.strong(0) + self.formatter.linebreak()) self.result.append(errmsg)
def _heading_repl(self, word): """Handle section headings.""" from MoinMoin.support.python_compatibility import hash_new h = word.strip() level = 1 while h[level:level+1] == '=': level += 1 depth = min(5, level) # FIXME: needed for Included pages but might still result in unpredictable results # when included the same page multiple times title_text = h[level:-level].strip() pntt = self.formatter.page.page_name + title_text self.titles.setdefault(pntt, 0) self.titles[pntt] += 1 unique_id = '' if self.titles[pntt] > 1: unique_id = '-%d' % self.titles[pntt] result = self._closeP() result += self.formatter.heading(1, depth, id="head-"+hash_new('sha1', pntt.encode(config.charset)).hexdigest()+unique_id) return (result + self.formatter.text(title_text) + self.formatter.heading(0, depth))
def general(page, pagename, request): _ = request.getText f = request.formatter request.write(f.heading(1, 1), f.text(_('General Information')), f.heading(0, 1)) request.write(f.paragraph(1), f.text(_("Page size: %d") % page.size()), f.paragraph(0)) from MoinMoin.support.python_compatibility import hash_new digest = hash_new('sha1', page.get_raw_body().encode( config.charset)).hexdigest().upper() request.write( f.paragraph(1), f.rawHTML( '%(label)s <tt>%(value)s</tt>' % { 'label': _("SHA digest of this page's content is:"), 'value': digest, }), f.paragraph(0)) # show attachments (if allowed) attachment_info = action.getHandler(request, 'AttachFile', 'info') if attachment_info: request.write(attachment_info(pagename, request)) # show subscribers subscribers = page.getSubscribers(request, include_self=1, return_users=1) if subscribers: request.write(f.paragraph(1)) request.write( f.text(_('The following users subscribed to this page:'))) for lang in subscribers: request.write(f.linebreak(), f.text('[%s] ' % lang)) for user in subscribers[lang]: # do NOT disclose email addr, only WikiName userhomepage = Page(request, user.name) if userhomepage.exists(): request.write( f.rawHTML(userhomepage.link_to(request) + ' ')) else: request.write(f.text(user.name + ' ')) request.write(f.paragraph(0)) # show links links = page.getPageLinks(request) if links: request.write(f.paragraph(1)) request.write(f.text(_('This page links to the following pages:'))) request.write(f.linebreak()) for linkedpage in links: request.write( f.rawHTML("%s%s " % (Page(request, linkedpage).link_to(request), ",."[linkedpage == links[-1]]))) request.write(f.paragraph(0))
def format(self, formatter, form=None): """ Send the text. """ self.setupRules() formatting_regexes = [ "(?P<%s>%s)" % (n, f.getStartRe()) for n, f in self._formatting_rules ] re_flags = re.M if self._ignore_case: re_flags |= re.I scan_re = re.compile("|".join(formatting_regexes), re_flags) self.text = self.raw # dirty little trick to work around re lib's limitations (it can't have # zero length matches at line beginning for ^ and at the same time match # something else at the beginning of the line): self.text = self.LINESEP.join( [line.replace('\r', '') for line in self.text.splitlines()]) self.text = self.STARTL + self.text + self.ENDL self.text_len = len(self.text) result = [] # collects output self._code_id = hash_new('sha1', self.raw.encode(config.charset)).hexdigest() result.append( formatter.code_area(1, self._code_id, self.parsername, self.show_nums, self.num_start, self.num_step)) self.lastpos = 0 match = scan_re.search(self.text) while match and self.lastpos < self.text_len: # add the rendering of the text left of the match we found text = self.text[self.lastpos:match.start()] if text: result.extend(self.format_normal_text(formatter, text)) self.lastpos = match.end() + (match.end() == self.lastpos) # add the rendering of the match we found result.extend(self.format_match(formatter, match)) # search for the next one match = scan_re.search(self.text, self.lastpos) # add the rendering of the text right of the last match we found text = self.text[self.lastpos:] if text: result.extend(self.format_normal_text(formatter, text)) result.append(formatter.code_area(0, self._code_id)) self.request.write(''.join(result))
def _oidlist(self): _ = self.request.getText form = self._make_form() for oid in self.request.user.openids: name = "rm-%s" % hash_new('sha1', oid).hexdigest() form.append(html.INPUT(type="checkbox", name=name, id=name)) form.append(html.LABEL(for_=name).append(html.Text(oid))) form.append(html.BR()) self._make_row(_("Current OpenIDs"), [form], valign='top') label = _("Remove selected") form.append(html.BR()) form.append(html.INPUT(type="submit", name="remove", value=label))
def useNonce(self, server_url, timestamp, salt): val = ''.join([str(server_url), str(timestamp), str(salt)]) csum = hash_new('sha1', val).hexdigest() ce = caching.CacheEntry(self.request, 'openid-nonce', csum, scope='farm', use_pickle=False) if ce.exists(): # nonce already used! return False ce.update(str(timestamp)) if randint(0, 999) == 0: self.request.add_finisher(_cleanup_nonces) return True
def header_emit(self, node): from MoinMoin.support.python_compatibility import hash_new pntt = "%s%s%d" % (self.formatter.page.page_name, self.get_text(node), node.level) ident = "head-%s" % hash_new("sha1", pntt.encode(config.charset)).hexdigest() return "".join( [ self.formatter.heading(1, node.level, id=ident), self.formatter.text(node.content or ""), self.formatter.heading(0, node.level), ] )
def _validatePassword(self, data, password): """ Check user password. This is a private method and should not be used by clients. @param data: dict with user data (from storage) @param password: password to verify [unicode] @rtype: 2 tuple (bool, bool) @return: password is valid, enc_password changed """ epwd = data['enc_password'] # If we have no password set, we don't accept login with username if not epwd: return False, False # require non empty password if not password: return False, False if epwd[:5] == '{SHA}': enc = '{SHA}' + base64.encodestring( hash_new('sha1', password.encode('utf-8')).digest()).rstrip() if epwd == enc: data['enc_password'] = encodePassword( password) # upgrade to SSHA return True, True return False, False if epwd[:6] == '{SSHA}': data = base64.decodestring(epwd[6:]) salt = data[20:] hash = hash_new('sha1', password.encode('utf-8')) hash.update(salt) return hash.digest() == data[:20], False # No encoded password match, this must be wrong password return False, False
def _validatePassword(self, data, password): """ Check user password. This is a private method and should not be used by clients. @param data: dict with user data (from storage) @param password: password to verify @rtype: 2 tuple (bool, bool) @return: password is valid, enc_password changed """ epwd = data['enc_password'] # If we have no password set, we don't accept login with username if not epwd: return False, False # require non empty password if not password: return False, False password = password.encode('utf-8') if epwd[:5] == '{SHA}': enc = '{SHA}' + base64.encodestring(hash_new('sha1', password).digest()).rstrip() if epwd == enc: data['enc_password'] = encodePassword(password) return True, True return False, False if epwd[:6] == '{SSHA}': data = base64.decodestring(epwd[6:]) salt = data[20:] hash = hash_new('sha1', password) hash.update(salt) return hash.digest() == data[:20], False # No encoded password match, this must be wrong password return False, False
def general(page, pagename, request): _ = request.getText f = request.formatter request.write(f.heading(1, 1), f.text(_('General Information')), f.heading(0, 1)) request.write(f.paragraph(1), f.text(_("Page size: %d") % page.size()), f.paragraph(0)) from MoinMoin.support.python_compatibility import hash_new digest = hash_new('sha1', page.get_raw_body().encode(config.charset)).hexdigest().upper() request.write(f.paragraph(1), f.rawHTML('%(label)s <tt>%(value)s</tt>' % { 'label': _("SHA digest of this page's content is:"), 'value': digest, }), f.paragraph(0)) # show attachments (if allowed) attachment_info = action.getHandler(request, 'AttachFile', 'info') if attachment_info: request.write(attachment_info(pagename, request)) # show subscribers subscribers = page.getSubscribers(request, include_self=1, return_users=1) if subscribers: request.write(f.paragraph(1)) request.write(f.text(_('The following users subscribed to this page:'))) for lang in subscribers: request.write(f.linebreak(), f.text('[%s] ' % lang)) for user in subscribers[lang]: # do NOT disclose email addr, only WikiName userhomepage = Page(request, user.name) if userhomepage.exists(): request.write(f.rawHTML(userhomepage.link_to(request) + ' ')) else: request.write(f.text(user.name + ' ')) request.write(f.paragraph(0)) # show links links = page.getPageLinks(request) if links: request.write(f.paragraph(1)) request.write(f.text(_('This page links to the following pages:'))) request.write(f.linebreak()) for linkedpage in links: request.write(f.rawHTML("%s%s " % (Page(request, linkedpage).link_to(request), ",."[linkedpage == links[-1]]))) request.write(f.paragraph(0))
def format(self, formatter, form=None, **kw): """ Send the text. """ self.setupRules() formatting_regexes = ["(?P<%s>%s)" % (n, f.getStartRe()) for n, f in self._formatting_rules] re_flags = re.M if self._ignore_case: re_flags |= re.I scan_re = re.compile("|".join(formatting_regexes), re_flags) self.text = self.raw # dirty little trick to work around re lib's limitations (it can't have # zero length matches at line beginning for ^ and at the same time match # something else at the beginning of the line): self.text = self.LINESEP.join([line.replace("\r", "") for line in self.text.splitlines()]) self.text = self.STARTL + self.text + self.ENDL self.text_len = len(self.text) result = [] # collects output self._code_id = hash_new("sha1", self.raw.encode(config.charset)).hexdigest() result.append( formatter.code_area(1, self._code_id, self.parsername, self.show_nums, self.num_start, self.num_step) ) self.lastpos = 0 match = scan_re.search(self.text) while match and self.lastpos < self.text_len: # add the rendering of the text left of the match we found text = self.text[self.lastpos : match.start()] if text: result.extend(self.format_normal_text(formatter, text)) self.lastpos = match.end() + (match.end() == self.lastpos) # add the rendering of the match we found result.extend(self.format_match(formatter, match)) # search for the next one match = scan_re.search(self.text, self.lastpos) # add the rendering of the text right of the last match we found text = self.text[self.lastpos :] if text: result.extend(self.format_normal_text(formatter, text)) result.append(formatter.code_area(0, self._code_id)) self.request.write("".join(result))
def _handle_remove(self): _ = self.request.getText if not hasattr(self.request.user, 'openids'): return openids = self.request.user.openids[:] for oid in self.request.user.openids: name = "rm-%s" % hash_new('sha1', oid).hexdigest() if name in self.request.form: openids.remove(oid) if not openids and len(self.request.cfg.auth) == 1: return 'error', _("Cannot remove all OpenIDs.") self.request.user.openids = openids self.request.user.save() return 'info', _("The selected OpenIDs have been removed.")
def format(self, formatter): _ = self.request.getText fmt = PygmentsFormatter(formatter, start_line=self.start_line) # adding line number anchors for process instruction lines for lineno in range(1, self.num_start + 1): fmt.result.append(formatter.line_anchordef(lineno)) fmt.result.append( formatter.div(1, css_class="highlight %s" % self.syntax)) self._code_id = hash_new('sha1', self.raw.encode(config.charset)).hexdigest() msg = None if self.filename is not None: try: lexer = pygments.lexers.get_lexer_for_filename(self.filename) except pygments.util.ClassNotFound: fmt.result.append(formatter.text(self.filename)) lexer = pygments.lexers.TextLexer() else: try: lexer = pygments.lexers.get_lexer_by_name(self.syntax) except pygments.util.ClassNotFound: f = self.request.formatter url = ''.join([ f.url(1, href=Page(self.request, _("HelpOnParsers")).url(self.request, escape=0)), _("HelpOnParsers"), f.url(0) ]) msg = _( "Syntax highlighting not supported for '%(syntax)s', see %(highlight_help_page)s." ) % { "syntax": wikiutil.escape(self.syntax), "highlight_help_page": url } lexer = pygments.lexers.TextLexer() fmt.result.append( formatter.code_area(1, self._code_id, self.parsername, self.show_nums, self.num_start, self.num_step, msg)) pygments.highlight(self.raw, lexer, fmt) fmt.result.append(formatter.code_area(0, self._code_id)) fmt.result.append(formatter.div(0)) self.request.write("".join(fmt.result))
def encodePassword(pwd, salt=None): """ Encode a cleartext password @param pwd: the cleartext password, (unicode) @param salt: the salt for the password (string) @rtype: string @return: the password in apache htpasswd compatible SHA-encoding, or None """ pwd = pwd.encode('utf-8') if salt is None: salt = random_string(20) assert isinstance(salt, str) hash = hash_new('sha1', pwd) hash.update(salt) return '{SSHA}' + base64.encodestring(hash.digest() + salt).rstrip()
def execute(macro, args): request = macro.request formatter = macro.formatter # create storage for footnotes if not hasattr(request, 'footnotes'): request.footnotes = {} request.footnote_ctr = 0 request.footnote_show_ctr = 0 if not args: return emit_footnotes(request, formatter) else: # grab new footnote backref number idx = request.footnote_ctr request.footnote_ctr += 1 shahex = hash_new('sha1', args.encode(config.charset)).hexdigest() backlink_id = "fndef-%s-%d" % (shahex, idx) fwdlink_id = "fnref-%s" % shahex if not args in request.footnotes: showidx = request.footnote_show_ctr request.footnote_show_ctr += 1 request.footnotes[args] = ([], fwdlink_id, showidx) flist, dummy, showidx = request.footnotes[args] request.footnotes[args] = (flist + [(backlink_id, idx)], fwdlink_id, showidx) # do index -> text mapping in the same dict, that's fine because # args is always a string and idx alwas a number. request.footnotes[idx] = args return "%s%s%s%s%s" % ( formatter.sup(1), formatter.anchorlink(1, fwdlink_id, id=backlink_id), formatter.text(str(showidx + 1)), formatter.anchorlink(0), formatter.sup(0), ) # nothing to do or emit return ''
def execute(macro, args): request = macro.request formatter = macro.formatter # create storage for footnotes if not hasattr(request, 'footnotes'): request.footnotes = {} request.footnote_ctr = 0 request.footnote_show_ctr = 0 if not args: return emit_footnotes(request, formatter) else: # grab new footnote backref number idx = request.footnote_ctr request.footnote_ctr += 1 shahex = hash_new('sha1', args.encode(config.charset)).hexdigest() backlink_id = "fndef-%s-%d" % (shahex, idx) fwdlink_id = "fnref-%s" % shahex if not args in request.footnotes: showidx = request.footnote_show_ctr request.footnote_show_ctr += 1 request.footnotes[args] = ([], fwdlink_id, showidx) flist, dummy, showidx = request.footnotes[args] request.footnotes[args] = (flist + [(backlink_id, idx)], fwdlink_id, showidx) # do index -> text mapping in the same dict, that's fine because # args is always a string and idx alwas a number. request.footnotes[idx] = args return "%s%s%s%s%s" % ( formatter.sup(1), formatter.anchorlink(1, fwdlink_id, id=backlink_id), formatter.text(str(showidx+1)), formatter.anchorlink(0), formatter.sup(0), ) # nothing to do or emit return ''
def format(self, formatter, **kw): _ = self.request.getText fmt = PygmentsFormatter(formatter, start_line=self.start_line) # adding line number anchors for process instruction lines for lineno in range(1, self.num_start + 1): fmt.result.append(formatter.line_anchordef(lineno)) fmt.result.append(formatter.div(1, css_class="highlight %s" % self.syntax)) self._code_id = hash_new('sha1', self.raw.encode(config.charset)).hexdigest() msg = None if self.filename is not None: try: lexer = pygments.lexers.get_lexer_for_filename(self.filename) except pygments.util.ClassNotFound: fmt.result.append(formatter.text(self.filename)) lexer = pygments.lexers.TextLexer() else: try: lexer = pygments.lexers.get_lexer_by_name(self.syntax) except pygments.util.ClassNotFound: f = self.request.formatter url = ''.join([ f.url(1, href=Page(self.request, _("HelpOnParsers")).url(self.request, escape=0)), _("HelpOnParsers"), f.url(0)]) msg = _("Syntax highlighting not supported for '%(syntax)s', see %(highlight_help_page)s.") % {"syntax": wikiutil.escape(self.syntax), "highlight_help_page": url } lexer = pygments.lexers.TextLexer() fmt.result.append(formatter.code_area(1, self._code_id, self.parsername, self.show_nums, self.num_start, self.num_step, msg)) pygments.highlight(self.raw, lexer, fmt) fmt.result.append(formatter.code_area(0, self._code_id)) fmt.result.append(formatter.div(0)) self.request.write("".join(fmt.result))
def key(self, url): '''return cache key''' return hash_new('sha1', url).hexdigest()
def _validatePassword(self, data, password): """ Check user password. This is a private method and should not be used by clients. @param data: dict with user data (from storage) @param password: password to verify [unicode] @rtype: 2 tuple (bool, bool) @return: password is valid, enc_password changed """ epwd = data['enc_password'] # If we have no password set, we don't accept login with username if not epwd: return False, False # require non empty password if not password: return False, False password_correct = recompute_hash = False wanted_scheme = self._cfg.password_scheme # Check password and upgrade weak hashes to strong default algorithm: for scheme in config.password_schemes_supported: if epwd.startswith(scheme): is_passlib = False d = epwd[len(scheme):] if scheme == '{PASSLIB}': # a password hash to be checked by passlib library code if not self._cfg.passlib_support: logging.error('in user profile %r, password hash with {PASSLIB} scheme encountered, but passlib_support is False' % (self.id, )) else: pwd_context = self._cfg.cache.pwd_context try: password_correct = pwd_context.verify(password, d) except ValueError, err: # can happen for unknown scheme logging.error('in user profile %r, verifying the passlib pw hash crashed [%s]' % (self.id, str(err))) if password_correct: # check if we need to recompute the hash. this is needed if either the # passlib hash scheme / hash params changed or if we shall change to a # builtin hash scheme (not recommended): recompute_hash = pwd_context.hash_needs_update(d) or wanted_scheme != '{PASSLIB}' else: # a password hash to be checked by legacy, builtin code if scheme == '{SSHA}': d = base64.decodestring(d) salt = d[20:] hash = hash_new('sha1', password.encode('utf-8')) hash.update(salt) enc = base64.encodestring(hash.digest() + salt).rstrip() elif scheme == '{SHA}': enc = base64.encodestring( hash_new('sha1', password.encode('utf-8')).digest()).rstrip() elif scheme == '{APR1}': # d is of the form "$apr1$<salt>$<hash>" salt = d.split('$')[2] enc = md5crypt.apache_md5_crypt(password.encode('utf-8'), salt.encode('ascii')) elif scheme == '{MD5}': # d is of the form "$1$<salt>$<hash>" salt = d.split('$')[2] enc = md5crypt.unix_md5_crypt(password.encode('utf-8'), salt.encode('ascii')) elif scheme == '{DES}': if crypt is None: return False, False # d is 2 characters salt + 11 characters hash salt = d[:2] enc = crypt.crypt(password.encode('utf-8'), salt.encode('ascii')) else: logging.error('in user profile %r, password hash with unknown scheme encountered: %r' % (self.id, scheme)) raise NotImplementedError if safe_str_equal(epwd, scheme + enc): password_correct = True recompute_hash = scheme != wanted_scheme if recompute_hash: data['enc_password'] = encodePassword(self._cfg, password) return password_correct, recompute_hash
def _validatePassword(self, data, password): """ Check user password. This is a private method and should not be used by clients. @param data: dict with user data (from storage) @param password: password to verify [unicode] @rtype: 2 tuple (bool, bool) @return: password is valid, enc_password changed """ epwd = data['enc_password'] # If we have no password set, we don't accept login with username if not epwd: return False, False # require non empty password if not password: return False, False password_correct = recompute_hash = False wanted_scheme = self._cfg.password_scheme # Check password and upgrade weak hashes to strong default algorithm: for scheme in config.password_schemes_supported: if epwd.startswith(scheme): is_passlib = False d = epwd[len(scheme):] if scheme == '{PASSLIB}': # a password hash to be checked by passlib library code if not self._cfg.passlib_support: logging.error( 'in user profile %r, password hash with {PASSLIB} scheme encountered, but passlib_support is False' % (self.id, )) else: pwd_context = self._cfg.cache.pwd_context try: password_correct = pwd_context.verify(password, d) except ValueError, err: # can happen for unknown scheme logging.error( 'in user profile %r, verifying the passlib pw hash crashed [%s]' % (self.id, str(err))) if password_correct: # check if we need to recompute the hash. this is needed if either the # passlib hash scheme / hash params changed or if we shall change to a # builtin hash scheme (not recommended): recompute_hash = pwd_context.hash_needs_update( d) or wanted_scheme != '{PASSLIB}' else: # a password hash to be checked by legacy, builtin code if scheme == '{SSHA}': d = base64.decodestring(d) salt = d[20:] hash = hash_new('sha1', password.encode('utf-8')) hash.update(salt) enc = base64.encodestring(hash.digest() + salt).rstrip() elif scheme == '{SHA}': enc = base64.encodestring( hash_new( 'sha1', password.encode('utf-8')).digest()).rstrip() elif scheme == '{APR1}': # d is of the form "$apr1$<salt>$<hash>" salt = d.split('$')[2] enc = md5crypt.apache_md5_crypt( password.encode('utf-8'), salt.encode('ascii')) elif scheme == '{MD5}': # d is of the form "$1$<salt>$<hash>" salt = d.split('$')[2] enc = md5crypt.unix_md5_crypt(password.encode('utf-8'), salt.encode('ascii')) elif scheme == '{DES}': if crypt is None: return False, False # d is 2 characters salt + 11 characters hash salt = d[:2] enc = crypt.crypt(password.encode('utf-8'), salt.encode('ascii')) else: logging.error( 'in user profile %r, password hash with unknown scheme encountered: %r' % (self.id, scheme)) raise NotImplementedError if safe_str_equal(epwd, scheme + enc): password_correct = True recompute_hash = scheme != wanted_scheme if recompute_hash: data['enc_password'] = encodePassword(self._cfg, password) return password_correct, recompute_hash