[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