def associate(services, url): '''Create an association (OpenID section 8) between RP and OP. Return response as a dictionary.''' req_data = { 'openid.ns':"http://specs.openid.net/auth/2.0", 'openid.mode':"associate", 'openid.assoc_type':"HMAC-SHA1", 'openid.session_type':"no-encryption", } if url.startswith('http:'): # Use DH exchange req_data['openid.session_type'] = "DH-SHA1" # Private key: random number between 1 and dh_prime-1 priv = random.SystemRandom().randrange(1, dh_prime - 1) # Public key: 2^priv mod prime pubkey = pow(2L, priv, dh_prime) dh_public_base64 = base64.b64encode(btwoc(pubkey)) # No need to send key and generator req_data['openid.dh_consumer_public'] = dh_public_base64 if is_compat_1x(services): # 14.2.1: clear session_type in 1.1 compatibility mode if req_data['openid.session_type'] == "no-encryption": req_data['openid.session_type'] = '' del req_data['openid.ns'] res = urllib.urlopen(url, b(urllib.urlencode(req_data))) try: if res.getcode() != 200: raise ValueError("OpenID provider refuses connection with status %s" % res.getcode()) data = parse_response(res.read()) except ValueError: endpoint = OpenIDServiceEndpoint.fromOPEndpointURL(url) store = MemoryStore() consumer = GenericConsumer(store) try: assoc = consumer._requestAssociation(endpoint, req_data['openid.assoc_type'], req_data['openid.session_type']) data = { 'assoc_handle': assoc.handle, 'expires_in': assoc.lifetime, 'mac_key': oidutil.toBase64(assoc.secret), } except ServerError: data = { 'error': 'Server %s refused its suggested association type: session_type=%s, assoc_type=%s' % (url, req_data['openid.assoc_type'], req_data['openid.session_type']), } if 'error' in data: raise ValueError, "associate failed: "+data['error'] if url.startswith('http:'): 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(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'] = b(base64.b64encode(string_xor(enc_mac_key, shared_secret))) return data
class IdResCheckForFieldsTest(TestIdRes): def setUp(self): self.consumer = GenericConsumer(None) def mkSuccessTest(openid_args, signed_list): def test(self): message = Message.fromOpenIDArgs(openid_args) message.setArg(OPENID_NS, 'signed', ','.join(signed_list)) self.consumer._idResCheckForFields(message) return test test_openid1Success = mkSuccessTest( {'return_to':'return', 'assoc_handle':'assoc handle', 'sig':'a signature', 'identity':'someone', }, ['return_to', 'identity']) test_openid2Success = mkSuccessTest( {'ns':OPENID2_NS, 'return_to':'return', 'assoc_handle':'assoc handle', 'sig':'a signature', 'op_endpoint':'my favourite server', 'response_nonce':'use only once', }, ['return_to', 'response_nonce', 'assoc_handle', 'op_endpoint']) test_openid2Success_identifiers = mkSuccessTest( {'ns':OPENID2_NS, 'return_to':'return', 'assoc_handle':'assoc handle', 'sig':'a signature', 'claimed_id':'i claim to be me', 'identity':'my server knows me as me', 'op_endpoint':'my favourite server', 'response_nonce':'use only once', }, ['return_to', 'response_nonce', 'identity', 'claimed_id', 'assoc_handle', 'op_endpoint']) def mkMissingFieldTest(openid_args): def test(self): message = Message.fromOpenIDArgs(openid_args) try: self.consumer._idResCheckForFields(message) except ProtocolError, why: self.failUnless(why[0].startswith('Missing required')) else:
def setUp(self): self.store = GoodAssocStore() self.consumer = GenericConsumer(self.store) self.server_url = "http://idp.unittest/" CatchLogs.setUp(self) claimed_id = 'bogus.claimed' self.message = Message.fromOpenIDArgs( {'mode': 'id_res', 'return_to': 'return_to (just anything)', 'identity': claimed_id, 'assoc_handle': 'does not matter', 'sig': GOODSIG, 'response_nonce': mkNonce(), 'signed': 'identity,return_to,response_nonce,assoc_handle,claimed_id,op_endpoint', 'claimed_id': claimed_id, 'op_endpoint': self.server_url, 'ns':OPENID2_NS, }) self.endpoint = OpenIDServiceEndpoint() self.endpoint.server_url = self.server_url self.endpoint.claimed_id = claimed_id self.consumer._checkReturnTo = lambda unused1, unused2 : True
def run(): trust_root = consumer_url consumer = GenericConsumer(store) setConsumerSession(consumer) request = consumer.begin(endpoint) return_to = consumer_url m = request.getMessage(trust_root, return_to, immediate) redirect_url = request.redirectURL(trust_root, return_to, immediate) parsed = urlparse.urlparse(redirect_url) qs = parsed[4] q = parseQuery(qs) new_return_to = q['openid.return_to'] del q['openid.return_to'] assert q == { 'openid.mode':mode, 'openid.identity':delegate_url, 'openid.trust_root':trust_root, 'openid.assoc_handle':fetcher.assoc_handle, }, (q, user_url, delegate_url, mode) assert new_return_to.startswith(return_to) assert redirect_url.startswith(server_url) parsed = urlparse.urlparse(new_return_to) query = parseQuery(parsed[4]) query.update({ 'openid.mode':'id_res', 'openid.return_to':new_return_to, 'openid.identity':delegate_url, 'openid.assoc_handle':fetcher.assoc_handle, }) assoc = store.getAssociation(server_url, fetcher.assoc_handle) message = Message.fromPostArgs(query) message = assoc.signMessage(message) info = consumer.complete(message, request.endpoint, new_return_to) assert info.status == SUCCESS, info.message assert info.identity_url == user_url
def run(): trust_root = consumer_url consumer = GenericConsumer(store) request = consumer.begin(endpoint) return_to = consumer_url redirect_url = request.redirectURL(trust_root, return_to, immediate) parsed = urlparse.urlparse(redirect_url) qs = parsed[4] q = parseQuery(qs) new_return_to = q['openid.return_to'] del q['openid.return_to'] assert q == { 'openid.mode':mode, 'openid.identity':delegate_url, 'openid.trust_root':trust_root, 'openid.assoc_handle':fetcher.assoc_handle, }, (q, user_url, delegate_url, mode) assert new_return_to.startswith(return_to) assert redirect_url.startswith(server_url) query = { 'nonce':request.return_to_args['nonce'], 'openid.mode':'id_res', 'openid.return_to':new_return_to, 'openid.identity':delegate_url, 'openid.assoc_handle':fetcher.assoc_handle, } assoc = store.getAssociation(server_url, fetcher.assoc_handle) assoc.addSignature(['mode', 'return_to', 'identity'], query) info = consumer.complete(query, request.endpoint) assert info.status == SUCCESS, info.message assert info.identity_url == user_url
def setUp(self): self.store = memstore.MemoryStore() self.consumer = GenericConsumer(self.store) self.endpoint = OpenIDServiceEndpoint()
def setUp(self): self.consumer = GenericConsumer(None)
class TestCompleteMissingSig(unittest.TestCase, CatchLogs): def setUp(self): self.store = GoodAssocStore() self.consumer = GenericConsumer(self.store) self.server_url = "http://idp.unittest/" CatchLogs.setUp(self) claimed_id = 'bogus.claimed' self.message = Message.fromOpenIDArgs( {'mode': 'id_res', 'return_to': 'return_to (just anything)', 'identity': claimed_id, 'assoc_handle': 'does not matter', 'sig': GOODSIG, 'response_nonce': mkNonce(), 'signed': 'identity,return_to,response_nonce,assoc_handle,claimed_id,op_endpoint', 'claimed_id': claimed_id, 'op_endpoint': self.server_url, 'ns':OPENID2_NS, }) self.endpoint = OpenIDServiceEndpoint() self.endpoint.server_url = self.server_url self.endpoint.claimed_id = claimed_id self.consumer._checkReturnTo = lambda unused1, unused2 : True def tearDown(self): CatchLogs.tearDown(self) def test_idResMissingNoSigs(self): def _vrfy(resp_msg, endpoint=None): return endpoint self.consumer._verifyDiscoveryResults = _vrfy r = self.consumer.complete(self.message, self.endpoint, None) self.failUnlessSuccess(r) def test_idResNoIdentity(self): self.message.delArg(OPENID_NS, 'identity') self.message.delArg(OPENID_NS, 'claimed_id') self.endpoint.claimed_id = None self.message.setArg(OPENID_NS, 'signed', 'return_to,response_nonce,assoc_handle,op_endpoint') r = self.consumer.complete(self.message, self.endpoint, None) self.failUnlessSuccess(r) def test_idResMissingIdentitySig(self): self.message.setArg(OPENID_NS, 'signed', 'return_to,response_nonce,assoc_handle,claimed_id') r = self.consumer.complete(self.message, self.endpoint, None) self.failUnlessEqual(r.status, FAILURE) def test_idResMissingReturnToSig(self): self.message.setArg(OPENID_NS, 'signed', 'identity,response_nonce,assoc_handle,claimed_id') r = self.consumer.complete(self.message, self.endpoint, None) self.failUnlessEqual(r.status, FAILURE) def test_idResMissingAssocHandleSig(self): self.message.setArg(OPENID_NS, 'signed', 'identity,response_nonce,return_to,claimed_id') r = self.consumer.complete(self.message, self.endpoint, None) self.failUnlessEqual(r.status, FAILURE) def test_idResMissingClaimedIDSig(self): self.message.setArg(OPENID_NS, 'signed', 'identity,response_nonce,return_to,assoc_handle') r = self.consumer.complete(self.message, self.endpoint, None) self.failUnlessEqual(r.status, FAILURE) def failUnlessSuccess(self, response): if response.status != SUCCESS: self.fail("Non-successful response: %s" % (response,))
def setUp(self): store = object() self.consumer = GenericConsumer(store)
class TestReturnToArgs(unittest.TestCase): """Verifying the Return URL paramaters. From the specification "Verifying the Return URL":: To verify that the "openid.return_to" URL matches the URL that is processing this assertion: - The URL scheme, authority, and path MUST be the same between the two URLs. - Any query parameters that are present in the "openid.return_to" URL MUST also be present with the same values in the accepting URL. XXX: So far we have only tested the second item on the list above. XXX: _verifyReturnToArgs is not invoked anywhere. """ def setUp(self): store = object() self.consumer = GenericConsumer(store) def test_returnToArgsOkay(self): query = { 'openid.mode': 'id_res', 'openid.return_to': 'http://example.com/?foo=bar', 'foo': 'bar', } # no return value, success is assumed if there are no exceptions. self.consumer._verifyReturnToArgs(query) def test_returnToArgsUnexpectedArg(self): query = { 'openid.mode': 'id_res', 'openid.return_to': 'http://example.com/', 'foo': 'bar', } # no return value, success is assumed if there are no exceptions. self.failUnlessRaises(ProtocolError, self.consumer._verifyReturnToArgs, query) def test_returnToMismatch(self): query = { 'openid.mode': 'id_res', 'openid.return_to': 'http://example.com/?foo=bar', } # fail, query has no key 'foo'. self.failUnlessRaises(ValueError, self.consumer._verifyReturnToArgs, query) query['foo'] = 'baz' # fail, values for 'foo' do not match. self.failUnlessRaises(ValueError, self.consumer._verifyReturnToArgs, query) def test_noReturnTo(self): query = {'openid.mode': 'id_res'} self.failUnlessRaises(ValueError, self.consumer._verifyReturnToArgs, query) def test_completeBadReturnTo(self): """Test GenericConsumer.complete()'s handling of bad return_to values. """ return_to = "http://some.url/path?foo=bar" # Scheme, authority, and path differences are checked by # GenericConsumer._checkReturnTo. Query args checked by # GenericConsumer._verifyReturnToArgs. bad_return_tos = [ # Scheme only "https://some.url/path?foo=bar", # Authority only "http://some.url.invalid/path?foo=bar", # Path only "http://some.url/path_extra?foo=bar", # Query args differ "http://some.url/path?foo=bar2", "http://some.url/path?foo2=bar", ] m = Message(OPENID1_NS) m.setArg(OPENID_NS, 'mode', 'cancel') m.setArg(BARE_NS, 'foo', 'bar') endpoint = None for bad in bad_return_tos: m.setArg(OPENID_NS, 'return_to', bad) self.failIf(self.consumer._checkReturnTo(m, return_to)) def test_completeGoodReturnTo(self): """Test GenericConsumer.complete()'s handling of good return_to values. """ return_to = "http://some.url/path" good_return_tos = [ (return_to, {}), (return_to + "?another=arg", {(BARE_NS, 'another'): 'arg'}), (return_to + "?another=arg#fragment", {(BARE_NS, 'another'): 'arg'}), ("HTTP"+return_to[4:], {}), (return_to.replace('url','URL'), {}), ("http://some.url:80/path", {}), ("http://some.url/p%61th", {}), ("http://some.url/./path", {}), ] endpoint = None for good, extra in good_return_tos: m = Message(OPENID1_NS) m.setArg(OPENID_NS, 'mode', 'cancel') for ns, key in extra: m.setArg(ns, key, extra[(ns, key)]) m.setArg(OPENID_NS, 'return_to', good) result = self.consumer.complete(m, endpoint, return_to) self.failUnless(isinstance(result, CancelResponse), \ "Expected CancelResponse, got %r for %s" % (result, good,))
def associate(services, url): '''Create an association (OpenID section 8) between RP and OP. Return response as a dictionary.''' req_data = { 'openid.ns': "http://specs.openid.net/auth/2.0", 'openid.mode': "associate", 'openid.assoc_type': "HMAC-SHA1", 'openid.session_type': "no-encryption", } if url.startswith('http:'): # Use DH exchange req_data['openid.session_type'] = "DH-SHA1" # Private key: random number between 1 and dh_prime-1 priv = random.SystemRandom().randrange(1, dh_prime - 1) # Public key: 2^priv mod prime pubkey = pow(2L, priv, dh_prime) dh_public_base64 = base64.b64encode(btwoc(pubkey)) # No need to send key and generator req_data['openid.dh_consumer_public'] = dh_public_base64 if is_compat_1x(services): # 14.2.1: clear session_type in 1.1 compatibility mode if req_data['openid.session_type'] == "no-encryption": req_data['openid.session_type'] = '' del req_data['openid.ns'] res = urllib.urlopen(url, b(urllib.urlencode(req_data))) try: if res.getcode() != 200: raise ValueError( "OpenID provider refuses connection with status %s" % res.getcode()) data = parse_response(res.read()) except ValueError: endpoint = OpenIDServiceEndpoint.fromOPEndpointURL(url) store = MemoryStore() consumer = GenericConsumer(store) try: assoc = consumer._requestAssociation( endpoint, req_data['openid.assoc_type'], req_data['openid.session_type']) data = { 'assoc_handle': assoc.handle, 'expires_in': assoc.lifetime, 'mac_key': oidutil.toBase64(assoc.secret), } except ServerError: data = { 'error': 'Server %s refused its suggested association type: session_type=%s, assoc_type=%s' % (url, req_data['openid.assoc_type'], req_data['openid.session_type']), } if 'error' in data: raise ValueError, "associate failed: " + data['error'] if url.startswith('http:'): 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(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'] = b( base64.b64encode(string_xor(enc_mac_key, shared_secret))) return data