[Pypi-checkins] r985 - in trunk/pypi: . tools

martin.von.loewis python-checkins at python.org
Sat Nov 19 16:03:07 CET 2011


Author: martin.von.loewis
Date: Sat Nov 19 16:03:07 2011
New Revision: 985

Added:
   trunk/pypi/tools/sql-migrate-20111119.sql
Modified:
   trunk/pypi/openid2rp.py
   trunk/pypi/pkgbase_schema.sql
   trunk/pypi/store.py
   trunk/pypi/webui.py
Log:
Redo openid login to support direct verification.


Modified: trunk/pypi/openid2rp.py
==============================================================================
--- trunk/pypi/openid2rp.py	(original)
+++ trunk/pypi/openid2rp.py	Sat Nov 19 16:03:07 2011
@@ -6,7 +6,7 @@
 # This library implements OpenID Authentication 2.0,
 # in the role of a relying party
 
-import urlparse, urllib, httplib, time, cgi, htmllib, formatter
+import urlparse, urllib, httplib, time, cgi, HTMLParser
 import cStringIO, base64, hmac, hashlib, datetime, re, random
 import itertools, cPickle, sys
 
@@ -26,9 +26,51 @@
 if sys.version_info < (3,):
     def b(s):
         return s
+    # Convert byte to integer
+    b2i = ord
+    def bytes_from_ints(L):
+        return ''.join([chr(i) for i in L])
 else:
     def b(s):
-        return s.encode('ascii')
+        return s.encode('latin-1')
+    def b2i(char):
+        # 3.x: bytes are already sequences of integers
+        return char
+    bytes_from_ints = bytes
+
+class NotAuthenticated(Exception):
+    CONNECTION_REFUSED = 1
+    DIRECT_VERIFICATION_FAILED = 2
+    CANCELLED = 3
+    UNSUPPORTED_VERSION = 4
+    UNEXPECTED_MODE = 5
+    CLAIMED_ID_MISSING = 6
+    DISCOVERY_FAILED = 7
+    INCONSISTENT_IDS = 8
+    REPLAY_ATTACK = 9
+    MISSING_NONCE = 10
+    msgs = {
+        CONNECTION_REFUSED : 'OP refuses connection with status %d',
+        DIRECT_VERIFICATION_FAILED : 'OP doesn\'t assert that the signature of the verification request is valid',
+        CANCELLED : 'OP did not authenticate user (cancelled)',
+        UNSUPPORTED_VERSION : 'Unsupported OpenID version',
+        UNEXPECTED_MODE : 'Unexpected mode %s',
+        CLAIMED_ID_MISSING : 'Cannot determine claimed ID',
+        DISCOVERY_FAILED : 'Claimed ID %s cannot be rediscovered',
+        INCONSISTENT_IDS : 'Discovered and asserted endpoints differ',
+        REPLAY_ATTACK : 'Replay attack detected',
+        MISSING_NONCE : 'Nonce missing in OpenID 2 response',
+        }
+
+    def __init__(self, errno, *args):
+        msg = self.msgs[errno]
+        if args:
+            msg %= args
+        self.errno = errno
+        Exception.__init__(self, msg, errno, *args)
+
+    def __str__(self):
+        return self.args[0]
 
 def normalize_uri(uri):
     """Normalize an uri according to OpenID section 7.2. Return a pair
@@ -138,24 +180,24 @@
         res[k] = v
     return res
 
-class OpenIDParser(htmllib.HTMLParser):
+class OpenIDParser(HTMLParser.HTMLParser):
     def __init__(self):
-        htmllib.HTMLParser.__init__(self, formatter.NullFormatter())
+        HTMLParser.HTMLParser.__init__(self)
         self.links = {}
         self.xrds_location=None
 
-    def do_link(self, attrs):
-        attrs = dict(attrs)
-        try:
-            self.links[attrs['rel']] = attrs['href']
-        except KeyError:
-            pass
-
-    def do_meta(self, attrs):
-        attrs = dict(attrs)
-        # Yadis 6.2.5 option 1: meta tag
-        if attrs.get('http-equiv','').lower() == 'x-xrds-location':
-            self.xrds_location = attrs['content']
+    def handle_starttag(self, tag, attrs):
+        if tag == 'link':
+            attrs = dict(attrs)
+            try:
+                self.links[attrs['rel']] = attrs['href']
+            except KeyError:
+                pass
+        elif tag == 'meta':
+            attrs = dict(attrs)
+            # Yadis 6.2.5 option 1: meta tag
+            if attrs.get('http-equiv','').lower() == 'x-xrds-location':
+                self.xrds_location = attrs['content']
 
 def _extract_services(doc):
     for svc in doc.findall(".//{xri://$xrd*($v*2.0)}Service"):
@@ -241,7 +283,7 @@
 
     if content_type in ('text/html', 'application/xhtml+xml'):
         parser = OpenIDParser()
-        parser.feed(data)
+        parser.feed(data.decode('latin-1'))
         parser.close()
         # Yadis 6.2.5 option 1: meta tag
         if parser.xrds_location:
@@ -331,13 +373,13 @@
     res = cPickle.dumps(l, 2)
     # Pickle result: proto 2, long1 (integer < 256 bytes)
     # number of bytes, little-endian integer, stop
-    assert res[:3] == '\x80\x02\x8a' 
+    assert res[:3] == b('\x80\x02\x8a')
     # btwoc ought to produce the shortest representation in two's
     # complement. Fortunately, pickle already does that.
-    return res[3+ord(res[3]):3:-1]
+    return res[3+b2i(res[3]):3:-1]
 
-def unbtwoc(b):
-    return cPickle.loads('\x80\x02\x8a'+chr(len(b))+b[::-1]+'.')
+def unbtwoc(B):
+    return cPickle.loads(b('\x80\x02\x8a')+bytes_from_ints([len(B)])+B[::-1]+(b'.'))
 
 # Appendix B; DH default prime
 dh_prime = """
@@ -351,8 +393,8 @@
 def string_xor(s1, s2):
     res = []
     for c1, c2 in itertools.izip(s1, s2):
-        res.append(chr(ord(c1) ^ ord(c2)))
-    return ''.join(res)
+        res.append(b2i(c1) ^ b2i(c2))
+    return bytes_from_ints(res)
 
 def associate(services, url):
     '''Create an association (OpenID section 8) between RP and OP.
@@ -378,25 +420,25 @@
         if data['openid.session_type'] == "no-encryption":
             data['openid.session_type'] = ''
         del data['openid.ns']
-    res = urllib.urlopen(url, urllib.urlencode(data))
+    res = urllib.urlopen(url, b(urllib.urlencode(data)))
     if res.getcode() != 200:
         raise ValueError, "OpenID provider refuses connection with status %d" % res.getcode()
     data = parse_response(res.read())
     if 'error' in data:
         raise ValueError, "associate failed: "+data['error']
     if url.startswith('http:'):
-        enc_mac_key = data.get('enc_mac_key')
+        enc_mac_key = b(data.get('enc_mac_key'))
         if not enc_mac_key:
             raise ValueError, "Provider protocol error: not using DH-SHA1"
-        enc_mac_key = base64.b64decode(data['enc_mac_key'])
-        dh_server_public = unbtwoc(base64.b64decode(data['dh_server_public']))
+        enc_mac_key = base64.b64decode(enc_mac_key)
+        dh_server_public = unbtwoc(base64.b64decode(b(data['dh_server_public'])))
         # shared secret: sha1(2^(server_priv*priv) mod prime) xor enc_mac_key
         shared_secret = btwoc(pow(dh_server_public, priv, dh_prime))
         shared_secret = hashlib.sha1(shared_secret).digest()
         if len(shared_secret) != len(enc_mac_key):
             raise ValueError, "incorrect DH key size"
         # Fake mac_key result
-        data['mac_key'] = base64.b64encode(string_xor(enc_mac_key, shared_secret))
+        data['mac_key'] = b(base64.b64encode(string_xor(enc_mac_key, shared_secret)))
     return data
 
 class _AX:
@@ -471,10 +513,15 @@
         if sreg_opt:
             data['openid.sreg.optional'] =  sreg11['openid.sreg11.optional'] =','.join(sreg_opt)
     if is_compat_1x(services):
+        # OpenID 1.1 does not communicate claimed_ids. Put them into the return URL
+        return_to += '&' if '?' in return_to else '?'
+        return_to += '&openid1=' + urllib.quote(claimed)
+        data['openid.return_to'] = return_to
         del data['openid.ns']
         del data['openid.claimed_id']
         del data['openid.realm']
-        data['openid.trust_root'] = return_to
+        trust_root = urlparse.urlparse(return_to)[:3] + (None,None,None)
+        data['openid.trust_root'] = urlparse.urlunparse(trust_root)
     ax_req, ax_opt = ax
     if "http://openid.net/srv/ax/1.0" in services and (ax_req or ax_opt):
         data.update({
@@ -497,8 +544,20 @@
     else:
         return url+"?"+urllib.urlencode(data)
 
-class NotAuthenticated(Exception):
-    pass
+# 11.4.2 Verifying Directly with the OpenID Provider
+def verify_signature_directly(op_endpoint, response):
+    '''Request that the OP verify the signature via Direct Verification'''
+
+    request = [('openid.mode', 'check_authentication')]
+    # Exact copies of all fields from the authentication response, except for
+    # "openid.mode"
+    request.extend((k, v) for k, (v,) in response.items() if 'openid.mode' != k)
+    res = urllib.urlopen(op_endpoint, urllib.urlencode(request))
+    if 200 != res.getcode():
+        raise NotAuthenticated(NotAuthenticated.CONNECTION_REFUSED, res.getcode())
+    response = parse_response(res.read())
+    if 'true' != response['is_valid']:
+        raise NotAuthenticated(NotAuthenticated.DIRECT_VERIFICATION_FAILED)
 
 def _prepare_response(response):
     if isinstance(response, str):
@@ -529,7 +588,7 @@
     if session['assoc_handle'] != response['openid.assoc_handle'][0]:
         raise ValueError('incorrect session')
     if response['openid.mode'][0] == 'cancel':
-        raise NotAuthenticated('provider did not authenticate user (cancelled)')
+        raise NotAuthenticated(NotAuthenticated.CANCELLED)
     if response['openid.mode'][0] != 'id_res':
         raise ValueError('invalid openid.mode')
     if  'openid.identity' not in response:
@@ -547,7 +606,7 @@
         query.append(value)
     query = b('').join(query)
 
-    mac_key = base64.decodestring(b(session['mac_key']))
+    mac_key = base64.decodestring(session['mac_key'])
     transmitted_sig = base64.decodestring(b(response['openid.sig'][0]))
     computed_sig = hmac.new(mac_key, query, hashlib.sha1).digest()
 
@@ -569,6 +628,69 @@
 
     return signed
 
+def verify(response, discovery_cache, find_association, nonce_seen):
+    response = _prepare_response(response)
+    if 'openid.ns' in response:
+        ns = response['openid.ns'][0]
+        if ns != 'http://specs.openid.net/auth/2.0':
+            raise NotAuthenticated(NotAuthenticate.UNSUPPORTED_VERSION)
+    else:
+        ns = None
+    mode = response['openid.mode'][0]
+    if mode == 'cancel':
+        raise NotAuthenticated(NotAuthenticated.CANCELLED)
+    if mode != 'id_res':
+        raise NotAuthenticated(NotAuthenticated.UNEXPECTED_MODE, mode)
+    # Establish claimed ID
+    if 'openid.claimed_id' in response:
+        claimed_id = response['openid.claimed_id'][0]
+        # 11.2. Drop Fragment from claimed_id
+        fragment = claimed_id.find('#')
+        if fragment != -1:
+            claimed_id = claimed_id[:fragment]
+    elif 'openid1' in response:
+        claimed_id = response['openid1'][0]
+    else:
+        raise NotAuthenticated(NotAuthenticated.CLAIMED_ID_MISSING)
+    discovered = discovery_cache(claimed_id)
+    if not discovered:
+        discovered = discover(claimed_id)
+        if not discovered:
+            raise NotAuthenticated(NotAuthenticated.DISCOVERY_FAILED, claimed_id)
+    services, op_endpoint, op_local = discovered
+    # For a provider-allocated claimed_id, there will be no op_local ID,
+    # and there is no point checking it.
+    if op_local and op_local != response['openid.identity'][0]:
+        raise NotAuthenticated('Discovered and asserted local identifiers differ')
+    # For OpenID 1.1, op_endpoint may not be included in the response
+    if ('openid.op_endpoint' in response and
+        op_endpoint != response['openid.op_endpoint'][0]):
+        raise NotAuthenticated(NotAuthenticated.INCONSISTENT_IDS)
+    # XXX verify protocol version, verify claimed_id wrt. original request,
+    # verify return_to URL
+    
+    # verify the signature
+    assoc_handle = response['openid.assoc_handle'][0]
+    session = find_association(assoc_handle)
+    if session:
+        signed = authenticate(session, response)
+    else:
+        verify_signature_directly(op_endpoint, response)
+        signed = response['openid.signed'][0].split(',')
+
+    # Check the nonce. OpenID 1.1 doesn't have them
+    if 'openid.response_nonce' in response:
+        nonce = response['openid.response_nonce'][0]
+        timestamp = parse_nonce(nonce)
+        if (datetime.datetime.utcnow() - timestamp).total_seconds() > 10:
+            # allow for at most 10s transmission time and time shift
+            raise NotAuthenticated(NotAuthenticated.REPLAY_ATTACK)
+        if nonce_seen(nonce):
+            raise NotAuthenticated(NotAuthenticated.REPLAY_ATTACK)
+    elif ns:
+        raise NotAuthenticated(NotAuthenticated.MISSING_NONCE)
+    return signed, claimed_id
+
 def parse_nonce(nonce):
     '''Extract a datetime.datetime stamp from the nonce'''
     stamp = nonce.split('Z', 1)[0]

Modified: trunk/pypi/pkgbase_schema.sql
==============================================================================
--- trunk/pypi/pkgbase_schema.sql	(original)
+++ trunk/pypi/pkgbase_schema.sql	Sat Nov 19 16:03:07 2011
@@ -16,6 +16,14 @@
    name TEXT REFERENCES users ON DELETE CASCADE
 );
 
+CREATE TABLE openid_discovered (
+    created TIMESTAMP,
+    url TEXT PRIMARY KEY,
+    services BYTEA,
+    op_endpoint TEXT,
+    op_local TEXT
+);
+
 CREATE TABLE openid_sessions (
    id SERIAL PRIMARY KEY,
    provider TEXT,

Modified: trunk/pypi/store.py
==============================================================================
--- trunk/pypi/store.py	(original)
+++ trunk/pypi/store.py	Sat Nov 19 16:03:07 2011
@@ -3,6 +3,7 @@
 import sys, os, re, time, hashlib, random, types, math, stat, errno
 import logging, cStringIO, string, datetime, calendar, binascii, urllib2, cgi
 from collections import defaultdict
+import cPickle as pickle
 try:
     import psycopg2
 except ImportError:
@@ -199,6 +200,12 @@
             safe_params.append(param)
     return cursor.execute(sql, safe_params)
 
+def binary(cursor, bytes):
+    if isinstance(cursor, sqlite3_cursor):
+        # XXX is this correct?
+        return bytes
+    return psycopg2.Binary(bytes)
+
 class StorageError(Exception):
     pass
 
@@ -1826,50 +1833,69 @@
 
     # OpenID
 
+    def store_discovered(self, url, services, op_endpoint, op_local):
+        cursor = self.get_cursor()
+        sql = '''delete from openid_discovered where url = %s'''
+        safe_execute(cursor, sql, (url,))
+        services = binary(cursor, pickle.dumps(services, pickle.HIGHEST_PROTOCOL))
+        sql = '''insert into openid_discovered(created, url, services, op_endpoint, op_local)
+        values(%s, %s, %s, %s, %s)'''
+        now = datetime.datetime.now()
+        safe_execute(cursor, sql, (now, url, services, op_endpoint, op_local))
+
+    def discovered(self, url):
+        cursor = self.get_cursor()
+        sql = '''select services, op_endpoint, op_local from openid_discovered where url=%s'''
+        safe_execute(cursor, sql, (url,))
+        result = cursor.fetchall()
+        if result:
+            services, endpoint, local = result[0]
+            services = pickle.loads(str(services))
+            return services, endpoint, local
+        else:
+            return None
+
     def get_provider_session(self, provider):
         cursor = self.get_cursor()
+        # discover service URL, possibly from cache
+        res = self.discovered(provider[2])
+        if not res:
+            res = openid2rp.discover(provider[2])
+            assert res
+            self.store_discovered(provider[2], *res)
+        stypes, url, op_local = res
         # Check for existing session
-        sql = '''select id,url, assoc_handle from openid_sessions
-                 where provider=%s and expires>current_timestamp'''
-        safe_execute(cursor, sql, (provider[0],))
+        sql = '''select assoc_handle from openid_sessions
+                 where url=%s and expires>current_timestamp'''
+        safe_execute(cursor, sql, (url,))
         sessions = cursor.fetchall()
         if sessions:
-            id, url, assoc_handle = sessions[0]
-            safe_execute(cursor, 'select stype from openid_stypes where id=%s',
-                         (id,))
-            stypes = [t[0] for t in cursor.fetchall()]
+            assoc_handle = sessions[0][0]
             return stypes, url, assoc_handle
 
         # start from scratch:
-        # discover service URL
-        stypes, url, op_local = openid2rp.discover(provider[2])
         # associate session
         now = datetime.datetime.now()
         session = openid2rp.associate(stypes, url)
         # store it
         sql = '''insert into openid_sessions
-                 (provider, url, assoc_handle, expires, mac_key)
-                 values (%s, %s, %s, %s, %s)'''
-        safe_execute(cursor, sql, (provider[0], url,
+                 (url, assoc_handle, expires, mac_key)
+                 values (%s, %s, %s, %s)'''
+        safe_execute(cursor, sql, (url,
                                    session['assoc_handle'],
                                    now+datetime.timedelta(0,int(session['expires_in'])),
                                    session['mac_key']))
-        safe_execute(cursor, 'select %s' % self.last_id('openid_sessions'))
-        session_id = cursor.fetchone()[0]
-        for t in stypes:
-            safe_execute(cursor, '''insert into openid_stypes(id, stype)
-                                    values(%s, %s)''', (session_id, t))
         return stypes, url, session['assoc_handle']
 
-    def get_session_for_endpoint(self, claimed, stypes, endpoint):
+    def get_session_for_endpoint(self, endpoint, stypes):
         '''Return the assoc_handle for the a claimed ID/endpoint pair;
         create a new session if necessary. Discovery is supposed to be
         done by the caller.'''
         cursor = self.get_cursor()
         # Check for existing session
         sql = '''select assoc_handle from openid_sessions
-                 where provider=%s and url=%s and expires>current_timestamp'''
-        safe_execute(cursor, sql, (claimed, endpoint,))
+                 where url=%s and expires>current_timestamp'''
+        safe_execute(cursor, sql, (endpoint,))
         sessions = cursor.fetchall()
         if sessions:
             return sessions[0][0]
@@ -1879,34 +1905,26 @@
         session = openid2rp.associate(stypes, endpoint)
         # store it
         sql = '''insert into openid_sessions
-                 (provider, url, assoc_handle, expires, mac_key)
+                 (url, assoc_handle, expires, mac_key)
                  values (%s, %s, %s, %s, %s)'''
-        safe_execute(cursor, sql, (claimed, endpoint,
+        safe_execute(cursor, sql, (endpoint,
                                    session['assoc_handle'],
                                    now+datetime.timedelta(0,int(session['expires_in'])),
                                    session['mac_key']))
         safe_execute(cursor, 'select %s' % self.last_id('openid_sessions'))
         session_id = cursor.fetchone()[0]
-        # store stypes as well, so we can remember whether claimed is an OP ID or a user ID
-        for t in stypes:
-            safe_execute(cursor, '''insert into openid_stypes(id, stype)
-                                    values(%s, %s)''', (session_id, t))
         return session['assoc_handle']
 
-    def get_session_by_handle(self, assoc_handle):
+    def find_association(self, assoc_handle):
         cursor = self.get_cursor()
-        sql = 'select id, provider, url, mac_key from openid_sessions where assoc_handle=%s'
+        sql ='select mac_key from openid_sessions where assoc_handle=%s'
         safe_execute(cursor, sql, (assoc_handle,))
         sessions = cursor.fetchall()
         if sessions:
-            id, provider, url, mac_key = sessions[0]
-            safe_execute(cursor, 'select stype from openid_stypes where id=%s',
-                         (id,))
-            stypes = [t[0] for t in cursor.fetchall()]
-            return provider, url, stypes, {'assoc_handle':assoc_handle, 'mac_key':mac_key}
+            return {'assoc_handle':assoc_handle, 'mac_key':sessions[0][0]}
         return None
 
-    def duplicate_nonce(self, nonce):
+    def duplicate_nonce(self, nonce, checkonly = False):
         '''Return true if we might have seen this nonce before.'''
         stamp = openid2rp.parse_nonce(nonce)
         utc = calendar.timegm(stamp.utctimetuple())
@@ -1919,10 +1937,14 @@
                      (nonce,))
         if cursor.fetchone():
             return True
-        safe_execute(cursor, '''insert into openid_nonces(created, nonce)
-                                values(%s,%s)''', (stamp, nonce))
+        if not checkonly:
+            safe_execute(cursor, '''insert into openid_nonces(created, nonce)
+            values(%s,%s)''', (stamp, nonce))
         return False
 
+    def check_nonce(self, nonce):
+        return self.duplicate_nonce(nonce, checkonly=True)
+
     def associate_openid(self, username, openid):
         cursor = self.get_cursor()
         safe_execute(cursor, 'insert into openids(id, name) values(%s,%s)',

Added: trunk/pypi/tools/sql-migrate-20111119.sql
==============================================================================
--- (empty file)
+++ trunk/pypi/tools/sql-migrate-20111119.sql	Sat Nov 19 16:03:07 2011
@@ -0,0 +1,11 @@
+CREATE TABLE openid_discovered (
+    created TIMESTAMP,
+    url TEXT PRIMARY KEY,
+    services BYTEA,
+    op_endpoint TEXT,
+    op_local TEXT
+);
+alter table openid_sessions drop provider;
+drop table openid_stypes;
+
+

Modified: trunk/pypi/webui.py
==============================================================================
--- trunk/pypi/webui.py	(original)
+++ trunk/pypi/webui.py	Sat Nov 19 16:03:07 2011
@@ -947,10 +947,11 @@
             if not res:
                 return self.fail('Discovery failed. If you think this is in error, please submit a bug report.')
             stypes, op_endpoint, op_local = res
+            self.store.store_discovered(claimed_id, stypes, op_endpoint, op_local)
             if not op_local:
                 op_local = claimed_id
             try:
-                assoc_handle = self.store.get_session_for_endpoint(claimed_id, stypes, op_endpoint)
+                assoc_handle = self.store.get_session_for_endpoint(op_endpoint, stypes)
             except ValueError, e:
                 return self.fail('Cannot establish OpenID session: ' + str(e))
             return_to = self.config.url+'?:action=openid_return'
@@ -2505,6 +2506,7 @@
                                     otk=info['otk'], user=user)
                 return
         elif self.username is None:
+            nonce = None
             for param in 'name email'.split():
                 if not info.has_key(param):
                     raise FormError, '%s is required'%param
@@ -2517,33 +2519,16 @@
                 qs = {}
                 for key, value in self.form.items():
                     qs[key] = [value.encode('utf-8')]
-                session = self.store.get_session_by_handle(self.form['openid.assoc_handle'])
-                if not session:
-                    raise FormError, "Invalid session"
-                provider, url, stypes, session = session
                 try:
-                    signed = openid2rp.authenticate(session, qs)
+                    signed, claimed_id = openid2rp.verify(qs, self.store.discovered,
+                                                          self.store.find_association,
+                                                          self.store.check_nonce)
                 except Exception, e:
                     return self.fail('OpenID response has been tampered with:'+repr(e))
-                if not openid2rp.is_op_endpoint(stypes):
-                    claimed_id = provider
-                elif 'claimed_id' in signed:
-                    claimed_id = qs['openid.claimed_id'][0]
-                    # Need to perform discovery to verify claimed ID is really managed by provider
-                    discovered = openid2rp.discover(claimed_id)
-                    if not discovered or discovered[1] != url:
-                        return self.fail('Provider %s cannot make assertions about ID %s' % (url, claimed_id))
-                else:
-                    return self.fail('Claimed ID got lost. Please report this as a bug.')
-                if self.store.get_user_by_openid(claimed_id):
-                    return self.fail('OpenID already associated with a different account')
                 if 'response_nonce' in signed:
-                    nonce = qs['openid.response_nonce'][0]
-                else:
-                    # OpenID 1.1
-                    nonce = None
+                    nonce = qs['response_nonce'][0]
             else:
-                claimed_id = nonce = None
+                claimed_id = None
                 if not info.has_key('confirm') or info['password'] <> info['confirm']:
                     self.fail("password and confirm don't match", heading='Users')
                     return
@@ -2813,37 +2798,19 @@
             return self.fail('OpenID login failed: '+qs['openid.error'][0])
         if mode != 'id_res':
             return self.fail('OpenID login failed')
-        session = self.store.get_session_by_handle(qs['openid.assoc_handle'][0])
-        if not session:
-            return self.fail('invalid session')
-        provider, url, stypes, session = session
         try:
-            signed = openid2rp.authenticate(session, qs)
+            signed, claimed_id = openid2rp.verify(qs, self.store.discovered,
+                                                  self.store.find_association,
+                                                  self.store.check_nonce)
         except Exception, e:
             return self.fail('Login failed:'+repr(e))
-        # the claimed ID in the response can't be trusted for signon requests,
-        # as the user may have changed it when getting redirected.
-        # For a signon login, the database has stored the claimed id in the
-        # provider field of the session table.
-        # XXX as the assoc_handle may not be signed, the return_to url should
-        # contain a nonce for 1.1 providers
-        if not openid2rp.is_op_endpoint(stypes):
-            claimed_id = provider
-        elif 'claimed_id' in signed:
-            claimed_id = qs['openid.claimed_id'][0]
-            # Need to perform discovery to verify claimed ID is really managed by provider
-            discovered = openid2rp.discover(claimed_id)
-            if not discovered or discovered[1] != url:
-                return self.fail('Provider %s cannot make assertions about ID %s' % (url, claimed_id))
-        else:
-            return self.fail('Claimed ID got lost. Please report this as a bug.')
+
         if 'response_nonce' in signed:
             nonce = qs['openid.response_nonce'][0]
         else:
             # OpenID 1.1
             nonce = None
-            if 'openid.ns' in qs and qs['openid.ns'][0] == 'http://specs.openid.net/auth/2.0':
-                return self.fail('OpenID 2.0 provider failed to protect against replay attacks')
+
         user = self.store.get_user_by_openid(claimed_id)
         # Three cases: logged-in user claimed some ID,
         # new login, or registration


More information about the Pypi-checkins mailing list