Claimed (cygnus)

Wed Sep  5 15:20:45 PDT 2007  http://j3h.us/
  * Fix the extraction of return_to URLs for relying party verification

    hunk ./openid/server/trustroot.py 343
    -def extractReturnToURLs(rp_uri, xrds_text):
    -    """Given a relying party discovery URL and its corresponding XRDS
    -    document, return a list of return_to URLs.
    -
    -    @param rp_uri: The discovery URL
    -    @param xrds_text: The xrds document, as a string
    -    @returns: A list of all relying party URLs that were found in the document.
    -    @rtype: [str]
    -    """
    -    assert isinstance(xrds_text, basestring), xrds_text
    -    return services.applyFilter(rp_uri, xrds_text, _extractReturnURL)
    -
    hunk ./openid/server/trustroot.py 368
    -def verifyWithRelyingPartyURL(relying_party_url):
    -    """Verify that the return_to URL is listed by the relying party URL.
    -
    -    Similar to verifyReturnTo, except it takes a relying party URL instead
    -    of a realm.
    +def getAllowedReturnURLs(relying_party_url):
    +    """Given a relying party discovery URL return a list of return_to URLs.
    hunk ./openid/server/trustroot.py 372
    -        relying_party_url, extractReturnToURLs)
    +        relying_party_url, _extractReturnURL)
    hunk ./openid/server/trustroot.py 382
    -def verifyReturnTo(realm_str, return_to, _vrfy=verifyWithRelyingPartyURL):
    +def verifyReturnTo(realm_str, return_to, _vrfy=getAllowedReturnURLs):
    hunk ./openid/test/test_rpverify.py 6
    -from openid.yadis.etxrd import XRDSError
    +from openid.yadis.discover import DiscoveryResult, DiscoveryFailure
    +from openid.yadis import services
    hunk ./openid/test/test_rpverify.py 42
    +    def setUp(self):
    +        self.original_discover = services.discover
    +        services.discover = self.mockDiscover
    +        self.data = None
    +
    +    def tearDown(self):
    +        services.discover = self.original_discover
    +
    +    def mockDiscover(self, uri):
    +        result = DiscoveryResult(uri)
    +        result.response_text = self.data
    +        result.normalized_uri = uri
    +        return result
    +
    hunk ./openid/test/test_rpverify.py 61
    -        actual_return_urls = list(trustroot.extractReturnToURLs(
    -            self.disco_url, data))
    +        self.data = data
    +        actual_return_urls = list(trustroot.getAllowedReturnURLs(
    +            self.disco_url))
    +
    hunk ./openid/test/test_rpverify.py 67
    -    def failUnlessXRDSError(self, text):
    -        self.failUnlessRaises(XRDSError, trustroot.extractReturnToURLs, self.disco_url, text)
    +    def failUnlessDiscoveryFailure(self, text):
    +        self.data = text
    +        self.failUnlessRaises(
    +            DiscoveryFailure, trustroot.getAllowedReturnURLs, self.disco_url)
    hunk ./openid/test/test_rpverify.py 73
    -        self.failUnlessXRDSError('')
    +        self.failUnlessDiscoveryFailure('')
    hunk ./openid/test/test_rpverify.py 76
    -        self.failUnlessXRDSError('>')
    +        self.failUnlessDiscoveryFailure('>')


Wed Sep  5 14:02:41 PDT 2007  Kevin Turner <kevin@janrain.com>
  * server.trustroot.verifyReturnTo: verifyReturnTo needed a return_to

    hunk ./openid/server/trustroot.py 19
    +from openid import oidutil
    hunk ./openid/server/trustroot.py 41
    +class RealmVerificationRedirected(Exception):
    +    """Attempting to verify this realm resulted in a redirect.
    +    """
    +    def __init__(self, relying_party_url, rp_url_after_redirects):
    +        self.relying_party_url = relying_party_url
    +        self.rp_url_after_redirects = rp_url_after_redirects
    +
    +    def __str__(self):
    +        return ("Attempting to verify %r resulted in "
    +                "redirect to %r" %
    +                (self.relying_party_url,
    +                 self.rp_url_after_redirects))
    +
    +
    hunk ./openid/server/trustroot.py 286
    -        """Given the a return_to string, return a discovery URL for
    -        the relying party
    +        """Return a discovery URL for this realm.
    hunk ./openid/server/trustroot.py 288
    -        This function does not check to make sure that the realm or
    -        return_to are valid or match each other. Its behaviour on invalid
    -        inputs is undefined.
    +        This function does not check to make sure that the realm is
    +        valid. Its behaviour on invalid inputs is undefined.
    hunk ./openid/server/trustroot.py 380
    -def verifyWithRelyingPartyURL(relying_party_url, return_to):
    +def verifyWithRelyingPartyURL(relying_party_url):
    hunk ./openid/server/trustroot.py 391
    -        return False
    +        raise RealmVerificationRedirected(
    +            relying_party_url, rp_url_after_redirects)
    hunk ./openid/server/trustroot.py 394
    -    # Return whether the return_to URL matches any one of the
    -    # discovered return_to URLs
    -    return returnToMatches(return_to_urls, return_to)
    +    return return_to_urls
    hunk ./openid/server/trustroot.py 397
    -def verifyReturnTo(realm_str, _vrfy=verifyWithRelyingPartyURL):
    +def verifyReturnTo(realm_str, return_to, _vrfy=verifyWithRelyingPartyURL):
    hunk ./openid/server/trustroot.py 413
    -    return _vrfy(realm.buildDiscoveryURL())
    +    try:
    +        allowable_urls = _vrfy(realm.buildDiscoveryURL())
    +    except RealmVerificationRedirected, err:
    +        oidutil.log(str(err))
    +        return False
    +    [_$_]
    +    if returnToMatches(allowable_urls, return_to):
    +        return True
    +    else:
    +        oidutil.log("Failed to validate return_to %r for realm %r, was not "
    +                    "in %s" % (return_to, realm_str, allowable_urls))
    +        return False
    hunk ./openid/test/test_rpverify.py 8
    +from openid.test.support import CatchLogs
    hunk ./openid/test/test_rpverify.py 179
    -class TestVerifyReturnTo(unittest.TestCase):
    +class TestVerifyReturnTo(unittest.TestCase, CatchLogs):
    +
    +    def setUp(self):
    +        CatchLogs.setUp(self)
    +
    +    def tearDown(self):
    +        CatchLogs.tearDown(self)
    +    [_$_]
    hunk ./openid/test/test_rpverify.py 188
    -        self.failIf(trustroot.verifyReturnTo(''))
    +        self.failIf(trustroot.verifyReturnTo('', 'http://example.com/'))
    hunk ./openid/test/test_rpverify.py 191
    -        sentinel = object()
    hunk ./openid/test/test_rpverify.py 196
    -            return sentinel
    +            return [return_to]
    hunk ./openid/test/test_rpverify.py 199
    -            trustroot.verifyReturnTo(realm, _vrfy=vrfy) is sentinel)
    +            trustroot.verifyReturnTo(realm, return_to, _vrfy=vrfy))
    +        self.failUnlessLogEmpty()
    +
    +    def test_verifyFailWithDiscoveryCalled(self):
    +        realm = 'http://*.example.com/'
    +        return_to = 'http://www.example.com/foo'
    +
    +        def vrfy(disco_url):
    +            self.failUnlessEqual('http://www.example.com/', disco_url)
    +            return ['http://something-else.invalid/']
    +
    +        self.failIf(
    +            trustroot.verifyReturnTo(realm, return_to, _vrfy=vrfy))
    +        self.failUnlessLogMatches("Failed to validate return_to")
    +
    +    def test_verifyFailIfDiscoveryRedirects(self):
    +        realm = 'http://*.example.com/'
    +        return_to = 'http://www.example.com/foo'
    +
    +        def vrfy(disco_url):
    +            raise trustroot.RealmVerificationRedirected(
    +                disco_url, "http://redirected.invalid")
    +
    +        self.failIf(
    +            trustroot.verifyReturnTo(realm, return_to, _vrfy=vrfy))
    +        self.failUnlessLogMatches("Attempting to verify")

Fri Aug 31 16:34:00 PDT 2007  Kevin Turner <kevin@janrain.com>
  * consumer.consumer: OpenID 2 draft 12 fragment handling

    hunk ./openid/consumer/consumer.py 193
    -from urlparse import urlparse
    +from urlparse import urlparse, urldefrag
    hunk ./openid/consumer/consumer.py 903
    +        # Fragments do not influence discovery, so we can't compare a
    +        # claimed identifier with a fragment to discovered information.
    +        defragged_claimed_id, _ = urldefrag(to_match.claimed_id)
    +
    hunk ./openid/consumer/consumer.py 911
    -        elif not endpoint:
    +        if not endpoint:
    hunk ./openid/consumer/consumer.py 913
    -            return self._discoverAndVerify(to_match)
    +            endpoint = self._discoverAndVerify(to_match)
    hunk ./openid/consumer/consumer.py 915
    -        elif to_match.claimed_id != endpoint.claimed_id:
    +        elif defragged_claimed_id != endpoint.claimed_id:
    hunk ./openid/consumer/consumer.py 918
    -                        (endpoint.claimed_id, to_match.claimed_id))
    -            return self._discoverAndVerify(to_match)
    +                        (endpoint.claimed_id, defragged_claimed_id))
    +            endpoint = self._discoverAndVerify(to_match)
    hunk ./openid/consumer/consumer.py 926
    -            return endpoint
    +
    +        # The endpoint we return should have the claimed ID from the
    +        # message we just verified, fragment and all.
    +        if endpoint.claimed_id != to_match.claimed_id:
    +            endpoint = copy.copy(endpoint)
    +            endpoint.claimed_id = to_match.claimed_id
    +        return endpoint
    hunk ./openid/consumer/consumer.py 980
    -        if to_match.claimed_id != endpoint.claimed_id:
    +        # Fragments do not influence discovery, so we can't compare a
    +        # claimed identifier with a fragment to discovered information.
    +        defragged_claimed_id, _ = urldefrag(to_match.claimed_id)
    +        if defragged_claimed_id != endpoint.claimed_id:
    hunk ./openid/consumer/consumer.py 987
    -                (to_match.claimed_id, endpoint.claimed_id))
    +                (defragged_claimed_id, endpoint.claimed_id))
    hunk ./openid/test/test_verifydisco.py 21
    +    def tearDown(self):
    +        CatchLogs.tearDown(self)
    +        TestIdRes.tearDown(self)
    +
    hunk ./openid/test/test_verifydisco.py 88
    -        sentinel = object()
    +        sentinel = discover.OpenIDServiceEndpoint()
    +        sentinel.claimed_id = 'monkeysoft'
    hunk ./openid/test/test_verifydisco.py 106
    -        sentinel = object()
    +        sentinel = discover.OpenIDServiceEndpoint()
    +        sentinel.claimed_id = 'monkeysoft'
    hunk ./openid/test/test_verifydisco.py 180
    +    def test_openid2Fragment(self):
    +        claimed_id = "http://unittest.invalid/"
    +        claimed_id_frag = claimed_id + "#fragment"
    +        endpoint = discover.OpenIDServiceEndpoint()
    +        endpoint.local_id = 'my identity'
    +        endpoint.claimed_id = claimed_id
    +        endpoint.server_url = 'Phone Home'
    +        endpoint.type_uris = [discover.OPENID_2_0_TYPE]
    +
    +        msg = message.Message.fromOpenIDArgs(
    +            {'ns':message.OPENID2_NS,
    +             'identity':endpoint.local_id,
    +             'claimed_id': claimed_id_frag,
    +             'op_endpoint': endpoint.server_url})
    +        result = self.consumer._verifyDiscoveryResults(msg, endpoint)
    +        [_$_]
    +        self.failUnlessEqual(result.local_id, endpoint.local_id)
    +        self.failUnlessEqual(result.server_url, endpoint.server_url)
    +        self.failUnlessEqual(result.type_uris, endpoint.type_uris)
    +
    +        self.failUnlessEqual(result.claimed_id, claimed_id_frag)
    +        [_$_]
    +        self.failUnlessLogEmpty()
    +
    +

Fri Aug 31 15:11:04 PDT 2007  Kevin Turner <kevin@janrain.com>
  * consumer.consumer: put more debugging information in TypeURIMismatch

    hunk ./openid/consumer/consumer.py 518
    +    def __init__(self, expected, endpoint):
    +        ProtocolError.__init__(self, expected, endpoint)
    +        self.expected = expected
    +        self.endpoint = endpoint
    +
    +    def __str__(self):
    +        s = '<%s.%s: Required type %s not found in %s for endpoint %s>' % (
    +            self.__class__.__module__, self.__class__.__name__,
    +            self.expected, self.endpoint.type_uris, self.endpoint)
    +        return s
    +
    +
    +
    hunk ./openid/consumer/consumer.py 968
    -                raise TypeURIMismatch(
    -                    'Required type %r not present' % (type_uri,))
    +                raise TypeURIMismatch(type_uri, endpoint)

Fri Aug 31 14:55:31 PDT 2007  http://j3h.us/
  * Remove fragments as part of normalization

    hunk ./openid/consumer/discover.py 291
    -        return urinorm.urinorm(url)
    +        normalized = urinorm.urinorm(url)
    hunk ./openid/consumer/discover.py 294
    +    else:
    +        return urlparse.urldefrag(normalized)[0]
    hunk ./openid/test/test_discover.py 274
    +
    +    def test_html1Fragment(self):
    +        content_type = 'text/html'
    +        data = readDataFile('openid.html')
    +        expected_services = 1
    +
    +        self.documents[self.id_url] = (content_type, data)
    +        expected_id = self.id_url
    +        self.id_url = self.id_url + '#fragment'
    +        id_url, services = discover.discover(self.id_url)
    +        self.failUnlessEqual(expected_services, len(services))
    +        self.failUnlessEqual(expected_id, id_url)
    +
    +        self._checkService(
    +            services[0],
    +            used_yadis=False,
    +            types=['1.1'],
    +            server_url="http://www.myopenid.com/server",
    +            claimed_id=expected_id,
    +            local_id='http://smoker.myopenid.com/',
    +            )

Finished

Wed Aug 29 15:25:57 PDT 2007  http://j3h.us/
  * Spec draft 12 compliance: use www in place of * for realm verification
  Between drafts 11 and 12, the specified behaviour was to use the
  return-to URL's domain.

    hunk ./openid/server/trustroot.py 270
    -    def buildDiscoveryURL(self, return_to):
    +    def buildDiscoveryURL(self):
    hunk ./openid/server/trustroot.py 286
    -        if not self.wildcard:
    +        if self.wildcard:
    +            # Use "www." in place of the star
    +            assert self.host.startswith('.'), self.host
    +            www_domain = 'www' + self.host
    +            return '%s://%s%s' % (self.proto, www_domain, self.path)
    +        else:
    hunk ./openid/server/trustroot.py 294
    -        # Use the domain name from the return_to URL and everything else
    -        # from the realm (trust_root)
    -        return_to_url_parts = urlparse(return_to)
    -        return_to_domain = return_to_url_parts[1]
    -
    -        return '%s://%s%s' % (self.proto, return_to_domain, self.path)
    -
    hunk ./openid/server/trustroot.py 384
    -def verifyReturnTo(realm_str, return_to,
    -                   _vrfy=verifyWithRelyingPartyURL):
    +# _vrfy parameter is there to make testing easier
    +def verifyReturnTo(realm_str, _vrfy=verifyWithRelyingPartyURL):
    hunk ./openid/server/trustroot.py 401
    -    discovery_url = realm.buildDiscoveryURL(return_to)
    -    return _vrfy(discovery_url, return_to)
    +    return _vrfy(realm.buildDiscoveryURL())
    hunk ./openid/test/test_rpverify.py 17
    -    def failUnlessDiscoURL(self, realm, return_to,
    -                                  expected_discovery_url):
    +    def failUnlessDiscoURL(self, realm, expected_discovery_url):
    hunk ./openid/test/test_rpverify.py 22
    -        actual_discovery_url = realm_obj.buildDiscoveryURL(return_to)
    +        actual_discovery_url = realm_obj.buildDiscoveryURL()
    hunk ./openid/test/test_rpverify.py 29
    -                                'http://example.com/foo',
    hunk ./openid/test/test_rpverify.py 32
    -        """There is a wildcard, but there is no difference in the path"""
    -        self.failUnlessDiscoURL('http://*.example.com/foo',
    -                                'http://example.com/foo',
    -                                'http://example.com/foo')
    -
    -    def test_wildcardSibling(self):
    -        """There is a wildcard, there is no difference in the path,
    -        and the domain name on the return_to URL has more subdomains
    -        in it than segments in the realm"""
    -        self.failUnlessDiscoURL('http://*.example.com/foo',
    -                                'http://strong.types.example.com/foo',
    -                                'http://strong.types.example.com/foo')
    -
    -    def test_pathDifference(self):
    -        """There is no wildcard and the return_to URL's path is not
    -        the same as the realm
    -        """
    -        self.failUnlessDiscoURL('http://example.com/foo',
    -                                'http://example.com/foo/bar',
    -                                'http://example.com/foo')
    -
    -    def test_queryAdded(self):
    -        """There is no a wildcard and the return_to URL has a query is not
    -        the same as the realm
    -        """
    -        self.failUnlessDiscoURL('http://example.com/foo',
    -                                'http://example.com/foo?x=y',
    -                                'http://example.com/foo')
    -
    -    def test_pathDifference_wild(self):
    -        """There is a wildcard and the return_to URL's path is not
    -        the same as the realm
    -        """
    -        self.failUnlessDiscoURL('http://*.example.com/foo',
    -                                'http://example.com/foo/bar',
    -                                'http://example.com/foo')
    -
    -    def test_queryAdded_wild(self):
    -        """There is a wildcard and the return_to URL has a query is not
    -        the same as the realm
    +        """There is a wildcard
    hunk ./openid/test/test_rpverify.py 35
    -                                'http://example.com/foo?x=y',
    -                                'http://example.com/foo')
    -
    -
    +                                'http://www.example.com/foo')
    hunk ./openid/test/test_rpverify.py 180
    -        self.failIf(trustroot.verifyReturnTo('', None))
    +        self.failIf(trustroot.verifyReturnTo(''))
    hunk ./openid/test/test_rpverify.py 186
    -        def vrfy(disco_url, passed_return_to):
    +
    +        def vrfy(disco_url):
    hunk ./openid/test/test_rpverify.py 189
    -            self.failUnlessEqual(return_to, passed_return_to)
    hunk ./openid/test/test_rpverify.py 192
    -            trustroot.verifyReturnTo(realm, return_to, _vrfy=vrfy) is sentinel)
    +            trustroot.verifyReturnTo(realm, _vrfy=vrfy) is sentinel)

Sun Jul  1 22:21:23 PDT 2007  Kevin Turner <kevin@janrain.com>
  * consumer.consumer.GenericConsumer._verifyDiscoveredServices: split from _discoverAndVerify [async]
  and yes, there are far, far too many things with "verify" and 
  "discovery" in the name.
  

    hunk ./openid/consumer/consumer.py 997
    -        @raises ProtocolError: when discovery fails.
    +        @raises DiscoveryFailure: when discovery fails.
    hunk ./openid/consumer/consumer.py 1004
    +        return _verifyDiscoveredServices(services, to_match)
    +
    +
    +    def _verifyDiscoveredServices(self, services, to_match):
    +        """See @L{_discoverAndVerify)"""

Sun Jul  1 20:33:42 PDT 2007  Kevin Turner <kevin@janrain.com>
  * consumer.consumer.GenericConsumer._extractSupporedAssociationType: factored out from _negotiateAssociation [async]

    hunk ./openid/consumer/consumer.py 1127
    -        @rtype: openid.association.Association
    +        @rtype: L{openid.association.Association}
    hunk ./openid/consumer/consumer.py 1136
    -            # Any error message whose code is not 'unsupported-type'
    -            # should be considered a total failure.
    -            if why.error_code != 'unsupported-type' or \
    -                   why.message.isOpenID1():
    -                oidutil.log(
    -                    'Server error when requesting an association from %r: %s'
    -                    % (endpoint.server_url, why.error_text))
    -                return None
    -
    -            # The server didn't like the association/session type
    -            # that we sent, and it sent us back a message that
    -            # might tell us how to handle it.
    -            oidutil.log(
    -                'Unsupported association type %s: %s' % (assoc_type,
    -                                                         why.error_text,))
    -
    -            # Extract the session_type and assoc_type from the
    -            # error message
    -            assoc_type = why.message.getArg(OPENID_NS, 'assoc_type')
    -            session_type = why.message.getArg(OPENID_NS, 'session_type')
    -
    -            if assoc_type is None or session_type is None:
    -                oidutil.log('Server responded with unsupported association '
    -                            'session but did not supply a fallback.')
    -                return None
    -            elif not self.negotiator.isAllowed(assoc_type, session_type):
    -                fmt = ('Server sent unsupported session/association type: '
    -                       'session_type=%s, assoc_type=%s')
    -                oidutil.log(fmt % (session_type, assoc_type))
    -                return None
    -            else:
    +            supportedTypes = self._extractSupportedAssociationType(why,
    +                                                                   endpoint,
    +                                                                   assoc_type)
    +            if supportedTypes is not None:
    +                assoc_type, session_type = supportedTypes
    hunk ./openid/consumer/consumer.py 1160
    +    def _extractSupportedAssociationType(self, server_error, endpoint,
    +                                         assoc_type):
    +        """Handle ServerErrors resulting from association requests.
    +
    +        @returns: If server replied with an C{unsupported-type} error,
    +            return a tuple of supported C{association_type}, C{session_type}.
    +            Otherwise logs the error and returns None.
    +        @rtype: tuple or None
    +        """
    +        # Any error message whose code is not 'unsupported-type'
    +        # should be considered a total failure.
    +        if server_error.error_code != 'unsupported-type' or \
    +               server_error.message.isOpenID1():
    +            oidutil.log(
    +                'Server error when requesting an association from %r: %s'
    +                % (endpoint.server_url, server_error.error_text))
    +            return None
    +
    +        # The server didn't like the association/session type
    +        # that we sent, and it sent us back a message that
    +        # might tell us how to handle it.
    +        oidutil.log(
    +            'Unsupported association type %s: %s' % (assoc_type,
    +                                                     server_error.error_text,))
    +
    +        # Extract the session_type and assoc_type from the
    +        # error message
    +        assoc_type = server_error.message.getArg(OPENID_NS, 'assoc_type')
    +        session_type = server_error.message.getArg(OPENID_NS, 'session_type')
    +
    +        if assoc_type is None or session_type is None:
    +            oidutil.log('Server responded with unsupported association '
    +                        'session but did not supply a fallback.')
    +            return None
    +        elif not self.negotiator.isAllowed(assoc_type, session_type):
    +            fmt = ('Server sent unsupported session/association type: '
    +                   'session_type=%s, assoc_type=%s')
    +            oidutil.log(fmt % (session_type, assoc_type))
    +            return None
    +        else:
    +            return assoc_type, session_type
    +
    +

Sat Jun 30 21:32:58 PDT 2007  Kevin Turner <kevin@janrain.com>
  * openid.consumer.consumer._httpResponseToMessage: split from makeKVPost [async]

    hunk ./openid/consumer/consumer.py 215
    +
    hunk ./openid/consumer/consumer.py 228
    -    response_message = Message.fromKVForm(resp.body)
    -    if resp.status == 400:
    +    # Process response in separate function that can be shared by async code.
    +    return _httpResponseToMessage(resp, server_url)
    +
    +
    +def _httpResponseToMessage(response, server_url):
    +    """Adapt a POST response to a Message.
    +
    +    @type response: L{openid.fetchers.HTTPResponse}
    +    @param response: Result of a POST to an OpenID endpoint.
    +
    +    @rtype: L{openid.message.Message}
    +
    +    @raises openid.fetchers.HTTPFetchingError: if the server returned a
    +        status of other than 200 or 400.
    +
    +    @raises ServerError: if the server returned an OpenID error.
    +    """
    +    # Should this function be named Message.fromHTTPResponse instead?
    +    response_message = Message.fromKVForm(response.body)
    +    if response.status == 400:
    hunk ./openid/consumer/consumer.py 250
    -    elif resp.status != 200:
    +    elif response.status != 200:
    hunk ./openid/consumer/consumer.py 252
    -        error_message = fmt % (server_url, resp.status)
    +        error_message = fmt % (server_url, response.status)
    hunk ./openid/consumer/consumer.py 258
    +
    hunk ./openid/test/test_consumer.py 17
    -     ProtocolError
    +     ProtocolError, _httpResponseToMessage
    hunk ./openid/test/test_consumer.py 1902
    +
    +
    +
    +class TestKVPost(unittest.TestCase):
    +    def setUp(self):
    +        self.server_url = 'http://unittest/%s' % (self.id(),)
    +
    +    def test_200(self):
    +        from openid.fetchers import HTTPResponse
    +        response = HTTPResponse()
    +        response.status = 200
    +        response.body = "foo:bar\nbaz:quux\n"
    +        r = _httpResponseToMessage(response, self.server_url)
    +        expected_msg = Message.fromOpenIDArgs({'foo':'bar','baz':'quux'})
    +        self.failUnlessEqual(expected_msg, r)
    +
    +
    +    def test_400(self):
    +        response = HTTPResponse()
    +        response.status = 400
    +        response.body = "error:bonk\nerror_code:7\n"
    +        try:
    +            r = _httpResponseToMessage(response, self.server_url)
    +        except ServerError, e:
    +            self.failUnlessEqual(e.error_text, 'bonk')
    +            self.failUnlessEqual(e.error_code, '7')
    +        else:
    +            self.fail("Expected ServerError, got return %r" % (r,))
    +
    +
    +    def test_500(self):
    +        # 500 as an example of any non-200, non-400 code.
    +        response = HTTPResponse()
    +        response.status = 500
    +        response.body = "foo:bar\nbaz:quux\n"
    +        self.failUnlessRaises(fetchers.HTTPFetchingError,
    +                              _httpResponseToMessage, response,
    +                              self.server_url)
    +
    +
    +
    +

Mon Jun 18 16:46:29 PDT 2007  Josh Hoyt <josh@janrain.com>
  * Added returnToVerified method of CheckIDRequest
  Passes the appropriate data to trustroot.verifyReturnTo

    hunk ./openid/server/server.py 107
    -from openid.server.trustroot import TrustRoot
    +from openid.server.trustroot import TrustRoot, verifyReturnTo
    hunk ./openid/server/server.py 662
    +    def returnToVerified(self):
    +        """Does the relying party publish the return_to URL for this
    +        response under the realm? It is up to the provider to set a
    +        policy for what kinds of realms should be allowed. This
    +        return_to URL verification reduces vulnerability to data-theft
    +        attacks based on open proxies, corss-site-scripting, or open
    +        redirectors.
    +
    +        This check should only be performed after making sure that the
    +        return_to URL matches the realm.
    +
    +        @see: trustRootValid
    +
    +        @raises openid.yadis.discover.DiscoveryFailure: if the realm
    +            URL does not support Yadis discovery (and so does not
    +            support the verification process).
    +
    +        @returntype: bool
    +
    +        @returns: True if the realm publishes a document with the
    +            return_to URL listed
    +        """
    +        return verifyReturnTo(self.trust_root, self.return_to)
    +
    hunk ./openid/test/test_server.py 811
    +    def test_returnToVerified_callsVerify(self):
    +        """Make sure that verifyReturnTo is calling the trustroot
    +        function verifyReturnTo
    +        """
    +        def withVerifyReturnTo(new_verify, callable):
    +            old_verify = server.verifyReturnTo
    +            try:
    +                server.verifyReturnTo = new_verify
    +                return callable()
    +            finally:
    +                server.verifyReturnTo = old_verify
    +
    +        # Ensure that exceptions are passed through
    +        sentinel = Exception()
    +        def vrfyExc(trust_root, return_to):
    +            self.failUnlessEqual(self.request.trust_root, trust_root)
    +            self.failUnlessEqual(self.request.return_to, return_to)
    +            raise sentinel
    +
    +        try:
    +            withVerifyReturnTo(vrfyExc, self.request.returnToVerified)
    +        except Exception, e:
    +            self.failUnless(e is sentinel, e)
    +
    +        # Ensure that True and False are passed through unchanged
    +        def constVerify(val):
    +            def verify(trust_root, return_to):
    +                self.failUnlessEqual(self.request.trust_root, trust_root)
    +                self.failUnlessEqual(self.request.return_to, return_to)
    +                return val
    +            return verify
    +
    +        for val in [True, False]:
    +            self.failUnlessEqual(
    +                val,
    +                withVerifyReturnTo(constVerify(val),
    +                                   self.request.returnToVerified))
    +

Thu Jun  7 16:48:48 PDT 2007  Josh Hoyt <josh@janrain.com>
  * Added function to openid.yadis.etxrd that parses the Expires date out of an XRD

    hunk ./openid/yadis/etxrd.py 24
    +from datetime import datetime
    +from time import strptime
    +
    hunk ./openid/yadis/etxrd.py 122
    +expires_tag = mkXRDTag('Expires')
    hunk ./openid/yadis/etxrd.py 148
    +def getXRDExpiration(xrd_element, default=None):
    +    """Return the expiration date of this XRD element, or None if no
    +    expiration was specified.
    +
    +    @type xrd_element: ElementTree node
    +
    +    @param default: The value to use as the expiration if no
    +        expiration was specified in the XRD.
    +
    +    @rtype: datetime.datetime
    +
    +    @raises ValueError: If the xrd:Expires element is present, but its
    +        contents are not formatted according to the specification.
    +    """
    +    expires_element = xrd_element.find(expires_tag)
    +    if expires_element is None:
    +        return default
    +    else:
    +        expires_string = expires_element.text
    +
    +        # Will raise ValueError if the string is not the expected format
    +        expires_time = strptime(expires_string, "%Y-%m-%dT%H:%M:%SZ")
    +        return datetime(*expires_time[0:6])

Wed Jun  6 15:10:45 PDT 2007  Josh Hoyt <josh@janrain.com>
  * Completed realm verification

    hunk ./openid/server/trustroot.py 6
    +
    +It also implements relying party return_to URL verification, based on
    +the realm.
    hunk ./openid/server/trustroot.py 11
    +__all__ = [
    +    'TrustRoot',
    +    'RP_RETURN_TO_URL_TYPE',
    +    'extractReturnToURLs',
    +    'returnToMatches',
    +    'verifyReturnTo',
    +    ]
    +
    +from openid.yadis import services
    hunk ./openid/server/trustroot.py 304
    +# The URI for relying party discovery, used in realm verification.
    +#
    +# XXX: This should probably live somewhere else (like in
    +# openid.consumer or openid.yadis somewhere)
    +RP_RETURN_TO_URL_TYPE = 'http://specs.openid.net/auth/2.0/return_to'
    +
    +def _extractReturnURL(endpoint):
    +    """If the endpoint is a relying party OpenID return_to endpoint,
    +    return the endpoint URL. Otherwise, return None.
    +
    +    This function is intended to be used as a filter for the Yadis
    +    filtering interface.
    +
    +    @see: C{L{openid.yadis.services}}
    +    @see: C{L{openid.yadis.filters}}
    +
    +    @param endpoint: An XRDS BasicServiceEndpoint, as returned by
    +        performing Yadis dicovery.
    +
    +    @returns: The endpoint URL or None if the endpoint is not a
    +        relying party endpoint.
    +    @rtype: str or NoneType
    +    """
    +    if endpoint.matchTypes([RP_RETURN_TO_URL_TYPE]):
    +        return endpoint.uri
    +    else:
    +        return None
    +
    +def extractReturnToURLs(rp_uri, xrds_text):
    +    """Given a relying party discovery URL and its corresponding XRDS
    +    document, return a list of return_to URLs.
    +
    +    @param rp_uri: The discovery URL
    +    @param xrds_text: The xrds document, as a string
    +    @returns: A list of all relying party URLs that were found in the document.
    +    @rtype: [str]
    +    """
    +    assert isinstance(xrds_text, basestring), xrds_text
    +    return services.applyFilter(rp_uri, xrds_text, _extractReturnURL)
    +
    +def returnToMatches(allowed_return_to_urls, return_to):
    +    """Is the return_to URL under one of the supplied allowed
    +    return_to URLs?"""
    +
    +    for allowed_return_to in allowed_return_to_urls:
    +        # A return_to pattern works the same as a realm, except that
    +        # it's not allowed to use a wildcard. We'll model this by
    +        # parsing it as a realm, and not trying to match it if it has
    +        # a wildcard.
    +
    +        return_realm = TrustRoot.parse(allowed_return_to)
    +        if (# Parses as a trust root
    +            return_realm is not None and
    +
    +            # Does not have a wildcard
    +            not return_realm.wildcard and
    +
    +            # Matches the return_to that we passed in with it
    +            return_realm.validateURL(return_to)
    +            ):
    +            return True
    +
    +    # No URL in the list matched
    +    return False
    +
    +def verifyWithRelyingPartyURL(relying_party_url, return_to):
    +    """Verify that the return_to URL is listed by the relying party URL.
    +
    +    Similar to verifyReturnTo, except it takes a relying party URL instead
    +    of a realm.
    +    """
    +    (rp_url_after_redirects, return_to_urls) = services.getServiceEndpoints(
    +        relying_party_url, extractReturnToURLs)
    +
    +    if rp_url_after_redirects != relying_party_url:
    +        # Verification caused a redirect
    +        return False
    +
    +    # Return whether the return_to URL matches any one of the
    +    # discovered return_to URLs
    +    return returnToMatches(return_to_urls, return_to)
    +
    +def verifyReturnTo(realm_str, return_to,
    +                   _vrfy=verifyWithRelyingPartyURL):
    +    """Verify that a return_to URL is valid for the given realm.
    +
    +    This function builds a discovery URL, performs Yadis discovery on
    +    it, makes sure that the URL does not redirect, parses out the
    +    return_to URLs, and finally checks to see if the current return_to
    +    URL matches the return_to.
    +
    +    @raises DiscoveryFailure: When Yadis discovery fails
    +    @returns: True if the return_to URL is valid for the realm
    +    """
    +    realm = TrustRoot.parse(realm_str)
    +    if realm is None:
    +        # The realm does not parse as a URL pattern
    +        return False
    +
    +    discovery_url = realm.buildDiscoveryURL(return_to)
    +    return _vrfy(discovery_url, return_to)
    +
    hunk ./openid/test/test_rpverify.py 6
    +from openid.yadis.etxrd import XRDSError
    hunk ./openid/test/test_rpverify.py 79
    +
    +
    +class TestExtractReturnToURLs(unittest.TestCase):
    +    disco_url = 'http://example.com/'
    +
    +    def failUnlessFileHasReturnURLs(self, filename, expected_return_urls):
    +        self.failUnlessXRDSHasReturnURLs(file(filename).read(),
    +                                         expected_return_urls)
    +
    +    def failUnlessXRDSHasReturnURLs(self, data, expected_return_urls):
    +        actual_return_urls = list(trustroot.extractReturnToURLs(
    +            self.disco_url, data))
    +        self.failUnlessEqual(expected_return_urls, actual_return_urls)
    +
    +    def failUnlessXRDSError(self, text):
    +        self.failUnlessRaises(XRDSError, trustroot.extractReturnToURLs, self.disco_url, text)
    +
    +    def test_empty(self):
    +        self.failUnlessXRDSError('')
    +
    +    def test_badXML(self):
    +        self.failUnlessXRDSError('>')
    +
    +    def test_noEntries(self):
    +        self.failUnlessXRDSHasReturnURLs('''\
    +<?xml version="1.0" encoding="UTF-8"?>
    +<xrds:XRDS xmlns:xrds="xri://$xrds"
    +           xmlns="xri://$xrd*($v*2.0)"
    +           >
    +  <XRD>
    +  </XRD>
    +</xrds:XRDS>
    +''', [])
    +
    +    def test_noReturnToEntries(self):
    +        self.failUnlessXRDSHasReturnURLs('''\
    +<?xml version="1.0" encoding="UTF-8"?>
    +<xrds:XRDS xmlns:xrds="xri://$xrds"
    +           xmlns="xri://$xrd*($v*2.0)"
    +           >
    +  <XRD>
    +    <Service priority="10">
    +      <Type>http://specs.openid.net/auth/2.0/server</Type>
    +      <URI>http://www.myopenid.com/server</URI>
    +    </Service>
    +  </XRD>
    +</xrds:XRDS>
    +''', [])
    +
    +    def test_oneEntry(self):
    +        self.failUnlessXRDSHasReturnURLs('''\
    +<?xml version="1.0" encoding="UTF-8"?>
    +<xrds:XRDS xmlns:xrds="xri://$xrds"
    +           xmlns="xri://$xrd*($v*2.0)"
    +           >
    +  <XRD>
    +    <Service>
    +      <Type>http://specs.openid.net/auth/2.0/return_to</Type>
    +      <URI>http://rp.example.com/return</URI>
    +    </Service>
    +  </XRD>
    +</xrds:XRDS>
    +''', ['http://rp.example.com/return'])
    +
    +    def test_twoEntries(self):
    +        self.failUnlessXRDSHasReturnURLs('''\
    +<?xml version="1.0" encoding="UTF-8"?>
    +<xrds:XRDS xmlns:xrds="xri://$xrds"
    +           xmlns="xri://$xrd*($v*2.0)"
    +           >
    +  <XRD>
    +    <Service priority="0">
    +      <Type>http://specs.openid.net/auth/2.0/return_to</Type>
    +      <URI>http://rp.example.com/return</URI>
    +    </Service>
    +    <Service priority="1">
    +      <Type>http://specs.openid.net/auth/2.0/return_to</Type>
    +      <URI>http://other.rp.example.com/return</URI>
    +    </Service>
    +  </XRD>
    +</xrds:XRDS>
    +''', ['http://rp.example.com/return',
    +      'http://other.rp.example.com/return'])
    +
    +    def test_twoEntries_withOther(self):
    +        self.failUnlessXRDSHasReturnURLs('''\
    +<?xml version="1.0" encoding="UTF-8"?>
    +<xrds:XRDS xmlns:xrds="xri://$xrds"
    +           xmlns="xri://$xrd*($v*2.0)"
    +           >
    +  <XRD>
    +    <Service priority="0">
    +      <Type>http://specs.openid.net/auth/2.0/return_to</Type>
    +      <URI>http://rp.example.com/return</URI>
    +    </Service>
    +    <Service priority="1">
    +      <Type>http://specs.openid.net/auth/2.0/return_to</Type>
    +      <URI>http://other.rp.example.com/return</URI>
    +    </Service>
    +    <Service priority="0">
    +      <Type>http://example.com/LOLCATS</Type>
    +      <URI>http://example.com/invisible+uri</URI>
    +    </Service>
    +  </XRD>
    +</xrds:XRDS>
    +''', ['http://rp.example.com/return',
    +      'http://other.rp.example.com/return'])
    +
    +
    +
    +class TestReturnToMatches(unittest.TestCase):
    +    def test_noEntries(self):
    +        self.failIf(trustroot.returnToMatches([], 'anything'))
    +
    +    def test_exactMatch(self):
    +        r = 'http://example.com/return.to'
    +        self.failUnless(trustroot.returnToMatches([r], r))
    +
    +    def test_garbageMatch(self):
    +        r = 'http://example.com/return.to'
    +        self.failUnless(trustroot.returnToMatches(
    +            ['This is not a URL at all. In fact, it has characters, like "<" that are not allowed in URLs',
    +             r],
    +            r))
    +
    +    def test_descendant(self):
    +        r = 'http://example.com/return.to'
    +        self.failUnless(trustroot.returnToMatches(
    +            [r],
    +            'http://example.com/return.to/user:joe'))
    +
    +    def test_wildcard(self):
    +        self.failIf(trustroot.returnToMatches(
    +            ['http://*.example.com/return.to'],
    +            'http://example.com/return.to'))
    +
    +    def test_noMatch(self):
    +        r = 'http://example.com/return.to'
    +        self.failIf(trustroot.returnToMatches(
    +            [r],
    +            'http://example.com/xss_exploit'))
    +
    +class TestVerifyReturnTo(unittest.TestCase):
    +    def test_bogusRealm(self):
    +        self.failIf(trustroot.verifyReturnTo('', None))
    +
    +    def test_verifyWithDiscoveryCalled(self):
    +        sentinel = object()
    +        realm = 'http://*.example.com/'
    +        return_to = 'http://www.example.com/foo'
    +        def vrfy(disco_url, passed_return_to):
    +            self.failUnlessEqual('http://www.example.com/', disco_url)
    +            self.failUnlessEqual(return_to, passed_return_to)
    +            return sentinel
    +
    +        self.failUnless(
    +            trustroot.verifyReturnTo(realm, return_to, _vrfy=vrfy) is sentinel)
    +

Wed May 30 17:07:37 PDT 2007  Josh Hoyt <josh@janrain.com>
  * Added relying party discovery URL building to trustroot

    hunk ./admin/runtests 70
    +    from openid.test import test_rpverify
    hunk ./admin/runtests 94
    +        test_rpverify,
    hunk ./openid/server/trustroot.py 257
    +
    +    def buildDiscoveryURL(self, return_to):
    +        """Given the a return_to string, return a discovery URL for
    +        the relying party
    +
    +        This function does not check to make sure that the realm or
    +        return_to are valid or match each other. Its behaviour on invalid
    +        inputs is undefined.
    +
    +        @param return_to: The relying party return URL of the OpenID
    +            authentication request
    +
    +        @rtype: str
    +
    +        @returns: The URL upon which relying party discovery should be run
    +            in order to verify the return_to URL
    +        """
    +        if not self.wildcard:
    +            return self.unparsed
    +
    +        # Use the domain name from the return_to URL and everything else
    +        # from the realm (trust_root)
    +        return_to_url_parts = urlparse(return_to)
    +        return_to_domain = return_to_url_parts[1]
    +
    +        return '%s://%s%s' % (self.proto, return_to_domain, self.path)
    addfile ./openid/test/test_rpverify.py
    hunk ./openid/test/test_rpverify.py 1
    +"""Unit tests for verification of return_to URLs for a realm
    +"""
    +
    +__all__ = ['TestBuildDiscoveryURL']
    +
    +from openid.server import trustroot
    +import unittest
    +
    +# Too many methods does not apply to unit test objects
    +#pylint:disable-msg=R0904
    +class TestBuildDiscoveryURL(unittest.TestCase):
    +    """Tests for building the discovery URL from a realm and a
    +    return_to URL
    +    """
    +
    +    def failUnlessDiscoURL(self, realm, return_to,
    +                                  expected_discovery_url):
    +        """Build a discovery URL out of the realm and a return_to and
    +        make sure that it matches the expected discovery URL
    +        """
    +        realm_obj = trustroot.TrustRoot.parse(realm)
    +        actual_discovery_url = realm_obj.buildDiscoveryURL(return_to)
    +        self.failUnlessEqual(expected_discovery_url, actual_discovery_url)
    +
    +    def test_trivial(self):
    +        """There is no wildcard and the realm is the same as the return_to URL
    +        """
    +        self.failUnlessDiscoURL('http://example.com/foo',
    +                                'http://example.com/foo',
    +                                'http://example.com/foo')
    +
    +    def test_wildcard(self):
    +        """There is a wildcard, but there is no difference in the path"""
    +        self.failUnlessDiscoURL('http://*.example.com/foo',
    +                                'http://example.com/foo',
    +                                'http://example.com/foo')
    +
    +    def test_wildcardSibling(self):
    +        """There is a wildcard, there is no difference in the path,
    +        and the domain name on the return_to URL has more subdomains
    +        in it than segments in the realm"""
    +        self.failUnlessDiscoURL('http://*.example.com/foo',
    +                                'http://strong.types.example.com/foo',
    +                                'http://strong.types.example.com/foo')
    +
    +    def test_pathDifference(self):
    +        """There is no wildcard and the return_to URL's path is not
    +        the same as the realm
    +        """
    +        self.failUnlessDiscoURL('http://example.com/foo',
    +                                'http://example.com/foo/bar',
    +                                'http://example.com/foo')
    +
    +    def test_queryAdded(self):
    +        """There is no a wildcard and the return_to URL has a query is not
    +        the same as the realm
    +        """
    +        self.failUnlessDiscoURL('http://example.com/foo',
    +                                'http://example.com/foo?x=y',
    +                                'http://example.com/foo')
    +
    +    def test_pathDifference_wild(self):
    +        """There is a wildcard and the return_to URL's path is not
    +        the same as the realm
    +        """
    +        self.failUnlessDiscoURL('http://*.example.com/foo',
    +                                'http://example.com/foo/bar',
    +                                'http://example.com/foo')
    +
    +    def test_queryAdded_wild(self):
    +        """There is a wildcard and the return_to URL has a query is not
    +        the same as the realm
    +        """
    +        self.failUnlessDiscoURL('http://*.example.com/foo',
    +                                'http://example.com/foo?x=y',
    +                                'http://example.com/foo')
    +
    +if __name__ == '__main__':
    +    unittest.main()

Fri May 25 15:28:29 PDT 2007  Kevin Turner <kevin@janrain.com>
  * consumer.consumer.GenericConsumer.complete: dispatch mode-specific logic to individual methods
  
  because I need to override one of them and don't want to duplicate all of it.

    hunk ./openid/consumer/consumer.py 576
    -        if mode == 'cancel':
    -            return CancelResponse(endpoint)
    -        elif mode == 'error':
    -            error = message.getArg(OPENID_NS, 'error')
    -            contact = message.getArg(OPENID_NS, 'contact')
    -            reference = message.getArg(OPENID_NS, 'reference')
    +        modeMethod = getattr(self, '_complete_' + mode,
    +                             self._completeInvalid)
    +        [_$_]
    +        return modeMethod(message, endpoint)
    hunk ./openid/consumer/consumer.py 581
    -            return FailureResponse(endpoint, error, contact=contact,
    -                                   reference=reference)
    -        elif message.isOpenID2() and mode == 'setup_needed':
    -            return SetupNeededResponse(endpoint)
    +    def _complete_cancel(self, message, endpoint):
    +        return CancelResponse(endpoint)
    hunk ./openid/consumer/consumer.py 584
    -        elif mode == 'id_res':
    -            try:
    -                self._checkSetupNeeded(message)
    -            except SetupNeededError, why:
    -                return SetupNeededResponse(endpoint, why.user_setup_url)
    -            else:
    -                try:
    -                    return self._doIdRes(message, endpoint)
    -                except (ProtocolError, DiscoveryFailure), why:
    -                    return FailureResponse(endpoint, why[0])
    +    def _complete_error(self, message, endpoint):
    +        error = message.getArg(OPENID_NS, 'error')
    +        contact = message.getArg(OPENID_NS, 'contact')
    +        reference = message.getArg(OPENID_NS, 'reference')
    +
    +        return FailureResponse(endpoint, error, contact=contact,
    +                               reference=reference)
    +
    +    def _complete_setup_needed(self, message, endpoint):
    +        if not message.isOpenID2():
    +            return self._completeInvalid(message, endpoint)
    +
    +        return SetupNeededResponse(endpoint)
    +
    +    def _complete_id_res(self, message, endpoint):
    +        try:
    +            self._checkSetupNeeded(message)
    +        except SetupNeededError, why:
    +            return SetupNeededResponse(endpoint, why.user_setup_url)
    hunk ./openid/consumer/consumer.py 604
    -            return FailureResponse(endpoint,
    -                                   'Invalid openid.mode: %r' % (mode,))
    +            try:
    +                return self._doIdRes(message, endpoint)
    +            except (ProtocolError, DiscoveryFailure), why:
    +                return FailureResponse(endpoint, why[0])
    +
    +    def _completeInvalid(self, message, endpoint):
    +        mode = message.getArg(OPENID_NS, 'mode', '<No mode set>')
    +        return FailureResponse(endpoint,
    +                               'Invalid openid.mode: %r' % (mode,))

Fri May 25 14:02:15 PDT 2007  Kevin Turner <kevin@janrain.com>
  * consumer.discover.OpenIDServiceEndpoint.from*: docstrings

    hunk ./openid/consumer/discover.py 167
    +
    +        @raises L{XRDSError}: When the XRDS does not parse.
    hunk ./openid/consumer/discover.py 176
    -        if discovery_doc.isXRDS():
    +        """Create endpoints from a DiscoveryResult.
    +        [_$_]
    +        @type discoveryResult: L{DiscoveryResult}
    +
    +        @rtype: list of L{OpenIDServiceEndpoint}
    +
    +        @raises L{XRDSError}: When the XRDS does not parse.
    +        """
    +        if discoveryResult.isXRDS():

Fri May 25 13:00:09 PDT 2007  Kevin Turner <kevin@janrain.com>
  * consumer.discover.discoverYadis: simplify a bit, use .fromXRDS

    hunk ./openid/consumer/discover.py 348
    +    body = response.response_text
    hunk ./openid/consumer/discover.py 350
    -        openid_services = extractServices(
    -            response.normalized_uri, response.response_text,
    -            OpenIDServiceEndpoint)
    +        openid_services = OpenIDServiceEndpoint.fromXRDS(yadis_url, body)
    hunk ./openid/consumer/discover.py 363
    -        else:
    -            body = response.response_text

Fri May 25 12:52:26 PDT 2007  Kevin Turner <kevin@janrain.com>
  * consumer.discover.OpenIDServiceEndpoint.fromXRDS, fromDiscoveryResult: added siblings for .fromHTML()

    hunk ./openid/consumer/discover.py 162
    +
    +    def fromXRDS(cls, uri, xrds):
    +        """Parse the given document as XRDS looking for OpenID services.
    +
    +        @rtype: [OpenIDServiceEndpoint]
    +        """
    +        return extractServices(uri, xrds, cls)
    +
    +    fromXRDS = classmethod(fromXRDS)
    +
    +
    +    def fromDiscoveryResult(cls, discoveryResult):
    +        if discovery_doc.isXRDS():
    +            method = cls.fromXRDS
    +        else:
    +            method = cls.fromHTML
    +        return method(discoveryResult.normalized_uri,
    +                      discoveryResult.response_text)
    +
    +    fromDiscoveryResult = classmethod(fromDiscoveryResult)
    +
    +

Fri May 11 14:24:05 PDT 2007  Kevin Turner <http://kevin.janrain.com/>
  * store.*: remove getExpired [#3667]

    hunk ./openid/store/filestore.py 428
    -    def getExpired(self):
    -        """Return the server URL for all expired associations"""
    -        urls = []
    -        for _, assoc in self._allAssocs():
    -            if assoc.getExpiresIn() <= 0:
    -                urls.append(assoc.server_url)
    -        return urls
    -
    hunk ./openid/store/interface.py 162
    -
    -    def getExpired(self):
    -        """Return all server URLs that have expired associations.
    -
    -        @rtype: C{[str]}
    -        """
    -        raise NotImplementedError
    hunk ./openid/store/memstore.py 93
    -
    -    def getExpired(self):
    -        expired = []
    -        for server_url, assocs in self.server_assocs.iteritems():
    -            best = assocs.best()
    -            if best is None or best.getExpiresIn() == 0:
    -                print best
    -                expired.append(server_url)
    -
    -        return expired
    hunk ./openid/store/sqlstore.py 265
    -
    -    def txn_getExpired(self):
    -        """Get the server URLs for all associations that have expired"""
    -        self.db_get_expired(int(time.time()))
    -        rows = self.cur.fetchall()
    -        return [row[0] for row in rows]
    -
    -    getExpired = _inTxn(txn_getExpired)
    hunk ./openid/test/storetest.py 40
    -    # Empty store has no associations (this just makes sure that all
    -    # of the stores have a getExpired method and that it doesn't just
    -    # blow up)
    -    assert store.getExpired() == [], store.getExpired()
    -

Wed May  9 19:33:05 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.interface.OpenIDStore.cleanupAssociations,cleanup: added

    hunk ./openid/store/interface.py 15
    -        to support one-way nonces.
    +        to support one-way nonces.  It added C{L{cleanupNonces}},
    +        C{L{cleanupAssociations}}, and C{L{cleanup}}.
    hunk ./openid/store/interface.py 185
    +    def cleanupAssociations(self):
    +        """Remove expired associations from the store.
    +
    +        This method is not called in the normal operation of the
    +        library.  It provides a way for store admins to keep
    +        their storage from filling up with expired data.
    +
    +        @return: the number of associations expired.
    +        @returntype: int
    +        """
    +        raise NotImplementedError
    +
    +    def cleanup(self):
    +        """Shortcut for C{L{cleanupNonces}()}, C{L{cleanupAssociations}()}.
    +
    +        This method is not called in the normal operation of the
    +        library.  It provides a way for store admins to keep
    +        their storage from filling up with expired data.
    +        """
    +        return self.cleanupNonces(), self.cleanupAssociations()
    +

Wed May  9 19:31:44 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.sqlstore.SQLStore.cleanupAssociations: added

    hunk ./openid/store/sqlstore.py 280
    +    def txn_cleanupAssociations(self):
    +        self.db_clean_assoc(int(time.time()))
    +        return self.cur.rowcount
    +
    +    cleanupAssociations = _inTxn(txn_cleanupAssociations)
    +
    +
    hunk ./openid/store/sqlstore.py 346
    +    clean_assoc_sql = 'DELETE FROM %(associations)s WHERE issued + lifetime < ?;'
    +
    hunk ./openid/store/sqlstore.py 433
    +    clean_assoc_sql = 'DELETE FROM %(associations)s WHERE issued + lifetime < %%s;'
    +
    hunk ./openid/store/sqlstore.py 524
    +    clean_assoc_sql = 'DELETE FROM %(associations)s WHERE issued + lifetime < %%s;'
    +

Wed May  9 19:05:42 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.filestore.FileOpenIDStore.cleanupAssociations: added

    hunk ./openid/store/filestore.py 395
    -    def clean(self):
    +    def cleanup(self):
    hunk ./openid/store/filestore.py 402
    +        cleanupAssociations()
    +        cleanupNonces()
    +
    +    def cleanupAssociations(self):
    +        removed = 0
    hunk ./openid/store/filestore.py 410
    -        cleanupNonces()
    +                removed += 1
    +        return removed

Wed May  9 19:03:03 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.memstore.MemoryStore.cleanupAssociations: added

    hunk ./openid/store/memstore.py 37
    +    def cleanup(self):
    +        """Remove expired associations.
    +
    +        @return: tuple of (removed associations, remaining associations)
    +        """
    +        remove = []
    +        for handle, assoc in self.assocs.iteritems():
    +            if assoc.getExpiresIn() == 0:
    +                remove.append(handle)
    +        for handle in remove:
    +            del self.assocs[handle]
    +        return len(remove), len(self.assocs)
    +
    +
    +
    hunk ./openid/store/memstore.py 116
    +    def cleanupAssociations(self):
    +        remove_urls = []
    +        removed_assocs = 0
    +        for server_url, assocs in self.server_assocs.iteritems():
    +            removed, remaining = assocs.cleanup()
    +            removed_assocs += removed
    +            if not remaining:
    +                remove_urls.append(server_url)
    +
    +        # Remove entries from server_assocs that had none remaining.
    +        for server_url in remove_urls:
    +            del self.server_assocs[server_url]
    +        return removed_assocs
    +

Wed May  9 18:39:54 PDT 2007  Kevin Turner <kevin@janrain.com>
  * test.storetest: add test for cleanupAssociations()

    hunk ./openid/test/storetest.py 152
    +
    +    ### test expired associations
    +    # assoc 1: server 1, valid
    +    # assoc 2: server 1, expired
    +    # assoc 3: server 2, expired
    +    # assoc 4: server 3, valid
    +    assocValid1 = genAssoc(issued=-3600,lifetime=7200)
    +    assocValid2 = genAssoc(issued=-5)
    +    assocExpired1 = genAssoc(issued=-7200,lifetime=3600)
    +    assocExpired2 = genAssoc(issued=-7200,lifetime=3600)
    +
    +    store.cleanupAssociations()
    +    store.storeAssociation(server_url + '1', assocValid1)
    +    store.storeAssociation(server_url + '1', assocExpired1)
    +    store.storeAssociation(server_url + '2', assocExpired2)
    +    store.storeAssociation(server_url + '3', assocValid2)
    +
    +    cleaned = store.cleanupAssociations()
    +    assert cleaned == 2, cleaned

Wed May  9 18:14:11 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.filestore.FileStore._allAssocs: filename must include directory

    hunk ./openid/store/filestore.py 366
    -        association_filenames = os.listdir(self.association_dir)
    +        association_filenames = map(
    +            lambda filename: os.path.join(self.association_dir, filename),
    +            os.listdir(self.association_dir))

Wed May  9 18:03:13 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.interface.OpenIDStore.cleanupNonces: simpifly docstring

    hunk ./openid/store/interface.py 170
    -        """Run garbage collection on expired nonces.
    +        """Remove expired nonces from the store.
    hunk ./openid/store/interface.py 180
    +        @returntype: int

Wed May  9 17:57:20 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.interface.OpenIDStore.getAssociation: change docstring to match test
  
  storetest says:
      # More recent, and expires earlier than assoc2 or assoc. Make sure
      # that we're picking the one with the latest issued date and not
      # taking into account the expiration.
  
  and that seems like as reasonable a policy as the other.

    hunk ./openid/store/interface.py 62
    -        method is the one that will remain valid for the longest
    -        duration.
    +        method is the one most recently issued.

Wed May  9 17:46:37 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.filestore.FileOpenIDStore._allAssocs: log error instead of silently passing.
  
  this except/pass was masking serious bugs.

    hunk ./openid/store/filestore.py 372
    -                    pass
    +                    oidutil.log("%s disappeared during %s._allAssocs" % (
    +                        association_filename, self.__class__.__name__))

Wed May  9 15:26:55 PDT 2007  Kevin Turner <kevin@janrain.com>
  * test.storetest: more comments

    hunk ./openid/test/storetest.py 198
    +        # A roundabout method of checking that the old nonces were cleaned is
    +        # to see if we're allowed to add them again.
    hunk ./openid/test/storetest.py 202
    +        # The recent nonce wasn't cleaned, so it should still fail.

Wed May  9 15:24:02 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.sqlstore.SQLStore.cleanupNonces: added

    hunk ./openid/store/sqlstore.py 274
    +    def txn_cleanupNonces(self):
    +        self.db_clean_nonce(int(time.time()) - nonce.SKEW)
    +        return self.cur.rowcount
    +
    +    cleanupNonces = _inTxn(txn_cleanupNonces)
    +
    hunk ./openid/store/sqlstore.py 341
    +    clean_nonce_sql = 'DELETE FROM %(nonces)s WHERE timestamp < ?;'
    +
    hunk ./openid/store/sqlstore.py 426
    +    clean_nonce_sql = 'DELETE FROM %(nonces)s WHERE timestamp < %%s;'
    +
    hunk ./openid/store/sqlstore.py 515
    +    clean_nonce_sql = 'DELETE FROM %(nonces)s WHERE timestamp < %%s;'
    +

Wed May  9 15:14:14 PDT 2007  Kevin Turner <kevin@janrain.com>
  * openid.filestore.FileOpenIDStore.cleanupNonces: added

    hunk ./openid/store/filestore.py 399
    +        for assoc_filename, assoc in self._allAssocs():
    +            if assoc.getExpiresIn() == 0:
    +                _removeIfPresent(assoc_filename)
    +        cleanupNonces()
    +
    +    def cleanupNonces(self):
    hunk ./openid/store/filestore.py 408
    +        removed = 0
    hunk ./openid/store/filestore.py 411
    -            if not nonce.checkTimestamp(nonce_fname, now=now):
    +            timestamp = nonce_fname.split('-', 1)[0]
    +            timestamp = int(timestamp, 16)
    +            if abs(timestamp - now) > nonce.SKEW:
    hunk ./openid/store/filestore.py 416
    -
    -        for assoc_filename, assoc in self._allAssocs():
    -            if assoc.getExpiresIn() == 0:
    -                _removeIfPresent(assoc_filename)
    +                removed += 1
    +        return removed

Wed May  9 15:01:46 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.interface.OpenIDStore.cleanupNonces: added

    hunk ./openid/store/interface.py 170
    +    def cleanupNonces(self):
    +        """Run garbage collection on expired nonces.
    +
    +        Discards any nonce from storage that is old enough that its
    +        timestamp would not pass L{useNonce}.
    +
    +        This method is not called in the normal operation of the
    +        library.  It provides a way for store admins to keep
    +        their storage from filling up with expired data.
    +
    +        @return: the number of nonces expired.
    +        """
    +        raise NotImplementedError
    +
    hunk ./openid/test/storetest.py 177
    +
    +    old_nonce1 = mkNonce(now - 20000)
    +    old_nonce2 = mkNonce(now - 10000)
    +    recent_nonce = mkNonce(now - 600)
    +
    +    from openid.store import nonce as nonceModule
    +    orig_skew = nonceModule.SKEW
    +    try:
    +        nonceModule.SKEW = 0
    +        store.cleanupNonces()
    +        # Set SKEW high so stores will keep our nonces.
    +        nonceModule.SKEW = 100000
    +        assert store.useNonce(server_url, *split(old_nonce1))
    +        assert store.useNonce(server_url, *split(old_nonce2))
    +        assert store.useNonce(server_url, *split(recent_nonce))
    +
    +        nonceModule.SKEW = 3600
    +        cleaned = store.cleanupNonces()
    +        assert cleaned == 2, "Cleaned %r nonces." % (cleaned,)
    +
    +        nonceModule.SKEW = 100000
    +        assert store.useNonce(server_url, *split(old_nonce1))
    +        assert store.useNonce(server_url, *split(old_nonce2))
    +        assert not store.useNonce(server_url, *split(recent_nonce))
    +    finally:
    +        nonceModule.SKEW = orig_skew
    +

Wed May  9 12:44:03 PDT 2007  Kevin Turner <kevin@janrain.com>
  * openid.server.server.OpenIDResponse.addExtension: more docstring

    hunk ./openid/server/server.py 963
    +        @type extension_response: L{openid.extension}
    +
    +        @returntype: None

Wed May  2 17:09:52 PDT 2007  Kevin Turner <kevin@janrain.com>
  * test.storetest.testStore: test useNonce for timestamp checking

    hunk ./openid/test/storetest.py 157
    -    def checkUseNonce(nonce, expected, server_url):
    +    def checkUseNonce(nonce, expected, server_url, msg=''):
    hunk ./openid/test/storetest.py 160
    -        assert bool(actual) == bool(expected)
    +        assert bool(actual) == bool(expected), "%r != %r: %s" % (actual, expected,
    +                                                                 msg)
    hunk ./openid/test/storetest.py 175
    +        # Nonces from when the universe was an hour old should not pass these days.
    +        old_nonce = mkNonce(3600)
    +        checkUseNonce(old_nonce, False, url, "Old nonce (%r) passed." % (old_nonce,))
    +
    +

Wed May  2 16:57:00 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.sqlstore.SQLStore.txn_useNonce: check timestamp

    hunk ./openid/store/sqlstore.py 14
    +from openid.store import nonce
    hunk ./openid/store/sqlstore.py 252
    +        if abs(timestamp - time.time()) > nonce.SKEW:
    +            return False
    +

Wed May  2 16:51:32 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.filestore.FileOpenIDStore.useNonce: add timestamp check

    hunk ./openid/store/filestore.py 334
    +        if abs(timestamp - time.time()) > nonce.SKEW:
    +            return False
    +

Wed May  2 16:50:54 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.filestore.FileOpenIDStore.clean: fix bad reference to checkTimestamp

    hunk ./openid/store/filestore.py 38
    +from openid.store import nonce
    hunk ./openid/store/filestore.py 400
    -        for nonce in nonces:
    -            if not checkTimestamp(nonce, now=now):
    -                filename = os.path.join(self.nonce_dir, nonce)
    +        for nonce_fname in nonces:
    +            if not nonce.checkTimestamp(nonce_fname, now=now):
    +                filename = os.path.join(self.nonce_dir, nonce_fname)

Wed May  2 16:45:23 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.interface.OpenIDStore.useNonce: should return False if the timestamp is not current.

    hunk ./openid/store/interface.py 133
    -        been used, return C{False}.
    +        been used or the timestamp is not current, return C{False}.
    +
    +        You may use L{openid.store.nonce.SKEW} for your timestamp window.

Tue May  8 15:30:59 PDT 2007  cygnus@janrain.com
  * Encode indirect OpenID 2 responses as POSTs when length exceeds OpenID 1 limit

    hunk ./openid/message.py 43
    +
    +# Limit, in bytes, of identity provider and return_to URLs, including
    +# response payload.  See OpenID 1.1 specification, Appendix D.
    +OPENID1_URL_LIMIT = 2047
    hunk ./openid/server/server.py 110
    -     OPENID2_NS, IDENTIFIER_SELECT
    +     OPENID2_NS, IDENTIFIER_SELECT, OPENID1_URL_LIMIT
    hunk ./openid/server/server.py 120
    +ENCODE_HTML_FORM = ('HTML form',)
    hunk ./openid/server/server.py 902
    +    def toFormMarkup(self):
    +        """Returns the form markup for this response.
    +
    +        @returntype: str
    +        """
    +        return self.fields.toFormMarkup(
    +            self.fields.getArg(OPENID_NS, 'return_to'))
    +
    +
    +    def renderAsForm(self):
    +        """Returns True if this response's encoding is
    +        ENCODE_HTML_FORM.  Convenience method for server authors.
    +
    +        @returntype: bool
    +        """
    +        return self.whichEncoding() == ENCODE_HTML_FORM
    +
    +
    hunk ./openid/server/server.py 936
    -            return ENCODE_URL
    +            if self.fields.getOpenIDNamespace() == OPENID2_NS and \
    +               len(self.encodeToURL()) > OPENID1_URL_LIMIT:
    +                return ENCODE_HTML_FORM
    +            else:
    +                return ENCODE_URL
    hunk ./openid/server/server.py 1225
    +        elif encode_as == ENCODE_HTML_FORM:
    +            wr = self.responseFactory(code=HTTP_OK,
    +                                      body=response.toFormMarkup())
    hunk ./openid/server/server.py 1571
    +    def toFormMarkup(self):
    +        return self.toMessage().toFormMarkup(self.getReturnTo())
    +
    hunk ./openid/server/server.py 1582
    -            return ENCODE_URL
    +            if self.openid_message.getOpenIDNamespace() == OPENID2_NS and \
    +               len(self.encodeToURL()) > OPENID1_URL_LIMIT:
    +                return ENCODE_HTML_FORM
    +            else:
    +                return ENCODE_URL
    hunk ./openid/test/test_server.py 6
    -     IDENTIFIER_SELECT, no_default
    +     IDENTIFIER_SELECT, no_default, OPENID1_URL_LIMIT
    hunk ./openid/test/test_server.py 56
    +    def test_browserWithReturnTo_OpenID2_GET(self):
    +        return_to = "http://rp.unittest/consumer"
    +        # will be a ProtocolError raised by Decode or CheckIDRequest.answer
    +        args = Message.fromPostArgs({
    +            'openid.ns': OPENID2_NS,
    +            'openid.mode': 'monkeydance',
    +            'openid.identity': 'http://wagu.unittest/',
    +            'openid.claimed_id': 'http://wagu.unittest/',
    +            'openid.return_to': return_to,
    +            })
    +        e = server.ProtocolError(args, "plucky")
    +        self.failUnless(e.hasReturnTo())
    +        expected_args = {
    +            'openid.mode': ['error'],
    +            'openid.error': ['plucky'],
    +            }
    +
    +        rt_base, result_args = e.encodeToURL().split('?', 1)
    +        result_args = cgi.parse_qs(result_args)
    +        self.failUnlessEqual(result_args, expected_args)
    +
    +    def test_browserWithReturnTo_OpenID2_GET(self):
    +        return_to = "http://rp.unittest/consumer"
    +        # will be a ProtocolError raised by Decode or CheckIDRequest.answer
    +        args = Message.fromPostArgs({
    +            'openid.ns': OPENID2_NS,
    +            'openid.mode': 'monkeydance',
    +            'openid.identity': 'http://wagu.unittest/',
    +            'openid.claimed_id': 'http://wagu.unittest/',
    +            'openid.return_to': return_to,
    +            })
    +        e = server.ProtocolError(args, "plucky")
    +        self.failUnless(e.hasReturnTo())
    +        expected_args = {
    +            'openid.ns': [OPENID2_NS],
    +            'openid.mode': ['error'],
    +            'openid.error': ['plucky'],
    +            }
    +
    +        rt_base, result_args = e.encodeToURL().split('?', 1)
    +        result_args = cgi.parse_qs(result_args)
    +        self.failUnlessEqual(result_args, expected_args)
    +
    +    def test_browserWithReturnTo_OpenID2_POST(self):
    +        return_to = "http://rp.unittest/consumer" + ('x' * OPENID1_URL_LIMIT)
    +        # will be a ProtocolError raised by Decode or CheckIDRequest.answer
    +        args = Message.fromPostArgs({
    +            'openid.ns': OPENID2_NS,
    +            'openid.mode': 'monkeydance',
    +            'openid.identity': 'http://wagu.unittest/',
    +            'openid.claimed_id': 'http://wagu.unittest/',
    +            'openid.return_to': return_to,
    +            })
    +        e = server.ProtocolError(args, "plucky")
    +        self.failUnless(e.hasReturnTo())
    +        expected_args = {
    +            'openid.ns': [OPENID2_NS],
    +            'openid.mode': ['error'],
    +            'openid.error': ['plucky'],
    +            }
    +
    +        self.failUnless(e.whichEncoding() == server.ENCODE_HTML_FORM)
    +        self.failUnless(e.toFormMarkup() == e.toMessage().toFormMarkup(
    +            args.getArg(OPENID_NS, 'return_to')))
    +
    +    def test_browserWithReturnTo_OpenID1_exceeds_limit(self):
    +        return_to = "http://rp.unittest/consumer" + ('x' * OPENID1_URL_LIMIT)
    +        # will be a ProtocolError raised by Decode or CheckIDRequest.answer
    +        args = Message.fromPostArgs({
    +            'openid.mode': 'monkeydance',
    +            'openid.identity': 'http://wagu.unittest/',
    +            'openid.return_to': return_to,
    +            })
    +        e = server.ProtocolError(args, "plucky")
    +        self.failUnless(e.hasReturnTo())
    +        expected_args = {
    +            'openid.mode': ['error'],
    +            'openid.error': ['plucky'],
    +            }
    +
    +        self.failUnless(e.whichEncoding() == server.ENCODE_URL)
    +
    +        rt_base, result_args = e.encodeToURL().split('?', 1)
    +        result_args = cgi.parse_qs(result_args)
    +        self.failUnlessEqual(result_args, expected_args)
    +
    hunk ./openid/test/test_server.py 531
    +    def test_id_res_OpenID2_GET(self):
    +        """
    +        Check that when an OpenID 2 response does not exceed the
    +        OpenID 1 message size, a GET response (i.e., redirect) is
    +        issued.
    +        """
    +        request = server.CheckIDRequest(
    +            identity = 'http://bombom.unittest/',
    +            trust_root = 'http://burr.unittest/',
    +            return_to = 'http://burr.unittest/999',
    +            immediate = False,
    +            op_endpoint = self.server.op_endpoint,
    +            )
    +        response = server.OpenIDResponse(request)
    +        response.fields = Message.fromOpenIDArgs({
    +            'ns': OPENID2_NS,
    +            'mode': 'id_res',
    +            'identity': request.identity,
    +            'claimed_id': request.identity,
    +            'return_to': request.return_to,
    +            })
    +
    +        self.failIf(response.renderAsForm())
    +        self.failUnless(response.whichEncoding() == server.ENCODE_URL)
    +        webresponse = self.encode(response)
    +        self.failUnless(webresponse.headers.has_key('location'))
    +
    +    def test_id_res_OpenID2_POST(self):
    +        """
    +        Check that when an OpenID 2 response exceeds the OpenID 1
    +        message size, a POST response (i.e., an HTML form) is
    +        returned.
    +        """
    +        request = server.CheckIDRequest(
    +            identity = 'http://bombom.unittest/',
    +            trust_root = 'http://burr.unittest/',
    +            return_to = 'http://burr.unittest/999',
    +            immediate = False,
    +            op_endpoint = self.server.op_endpoint,
    +            )
    +        response = server.OpenIDResponse(request)
    +        response.fields = Message.fromOpenIDArgs({
    +            'ns': OPENID2_NS,
    +            'mode': 'id_res',
    +            'identity': request.identity,
    +            'claimed_id': request.identity,
    +            'return_to': 'x' * OPENID1_URL_LIMIT,
    +            })
    +
    +        self.failUnless(response.renderAsForm())
    +        self.failUnless(len(response.encodeToURL()) > OPENID1_URL_LIMIT)
    +        self.failUnless(response.whichEncoding() == server.ENCODE_HTML_FORM)
    +        webresponse = self.encode(response)
    +        self.failUnlessEqual(webresponse.body, response.toFormMarkup())
    +
    +    def test_id_res_OpenID1_exceeds_limit(self):
    +        """
    +        Check that when an OpenID 1 response exceeds the OpenID 1
    +        message size, a GET response is issued.  Technically, this
    +        shouldn't be permitted by the library, but this test is in
    +        place to preserve the status quo for OpenID 1.
    +        """
    +        request = server.CheckIDRequest(
    +            identity = 'http://bombom.unittest/',
    +            trust_root = 'http://burr.unittest/',
    +            return_to = 'http://burr.unittest/999',
    +            immediate = False,
    +            op_endpoint = self.server.op_endpoint,
    +            )
    +        response = server.OpenIDResponse(request)
    +        response.fields = Message.fromOpenIDArgs({
    +            'mode': 'id_res',
    +            'identity': request.identity,
    +            'return_to': 'x' * OPENID1_URL_LIMIT,
    +            })
    +
    +        self.failIf(response.renderAsForm())
    +        self.failUnless(len(response.encodeToURL()) > OPENID1_URL_LIMIT)
    +        self.failUnless(response.whichEncoding() == server.ENCODE_URL)
    +        webresponse = self.encode(response)
    +        self.failUnlessEqual(webresponse.headers['location'], response.encodeToURL())
    +

Mon May  7 12:32:16 PDT 2007  cygnus@janrain.com
  * Removed sendSRegFields, added OpenIDResponse.addExtension

    hunk ./examples/server.py 197
    -    def approved(self, request, identifier=None):
    +    def addSRegResponse(self, response):
    hunk ./examples/server.py 208
    -        response = request.answer(True, identity=identifier)
    -        sreg_resp.toMessage(response.fields)
    +        response.addExtension(sreg_resp)
    hunk ./examples/server.py 210
    +    def approved(self, request, identifier=None):
    +        response = request.answer(True, identity=identifier)
    +        self.addSRegResponse(response)
    hunk ./openid/server/server.py 932
    +
    +    def addExtension(self, extension_response):
    +        """
    +        Add an extension response to this response message.
    +
    +        @param extension_response: An object that implements the
    +            extension interface for adding arguments to an OpenID
    +            message.
    +        """
    +        extension_response.toMessage(self.fields)
    +
    hunk ./openid/sreg.py 206
    -    def fromOpenIDRequest(cls, message):
    +    def fromOpenIDRequest(cls, request):
    hunk ./openid/sreg.py 211
    -        @param message: The arguments that were given for this OpenID
    -            authentication request
    -        @type message: {str:unicode}
    +        @param request: The OpenID request
    +        @type request: openid.server.CheckIDRequest
    hunk ./openid/sreg.py 221
    -        message = message.copy()
    +        message = request.message.copy()
    hunk ./openid/sreg.py 516
    -def sendSRegFields(openid_request, data, openid_response):
    -    """Convenience function for copying all the sreg data that was
    -    requested from a supplied set of sreg data into the response
    -    message. If no data were requested, no data will be sent.
    -
    -    @param openid_request: The OpenID (checkid_*) request that may be
    -        requesting sreg data.
    -    @type openid_request: C{L{openid.server.server.OpenIDRequest}}
    -
    -    @param data: The simple registration data to send. All
    -        requested fields that are present in this dictionary will be
    -        added to the response message.
    -    @type data: {str:str}
    -
    -    @param openid_response: The OpenID C{id_res} response to which the
    -        simple registration data should be added
    -    @type openid_response: openid.server.OpenIDResponse
    -
    -    @returns: Does not return a value; updates the openid_response
    -        instead.
    -    """
    -    sreg_request = SRegRequest.fromOpenIDRequest(openid_request.message)
    -    sreg_response = SRegResponse.extractResponse(sreg_request, data)
    -    sreg_response.toMessage(openid_response.fields)
    -
    hunk ./openid/test/test_sreg.py 151
    +            def __init__(self):
    +                self.message = Message()
    +
    hunk ./openid/test/test_sreg.py 169
    +        openid_req = OpenIDRequest()
    +
    hunk ./openid/test/test_sreg.py 172
    -        req = TestingReq.fromOpenIDRequest(msg)
    +        openid_req.message = msg
    +
    +        req = TestingReq.fromOpenIDRequest(openid_req)
    hunk ./openid/test/test_sreg.py 469
    -        sreg.sendSRegFields(req, data, resp)
    +        sreg_resp = sreg.SRegResponse.extractResponse(sreg_req, data)
    +        resp.addExtension(sreg_resp)

Mon Apr  9 15:27:31 PDT 2007  cygnus@janrain.com
  * Add eu to trustroot TLD list

    hunk ./openid/server/trustroot.py 16
    -    'cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|'
    +    'cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|'

Thu May  3 15:12:33 PDT 2007  Josh Hoyt <josh@janrain.com>
  * Added test clarifying that the wildcard is only in the authority section of the trust root

    hunk ./openid/test/trustroot.txt 108
    -23: does not match
    +24: does not match
    hunk ./openid/test/trustroot.txt 133
    -
    +http://foo.com/*                      http://foo.com/anything

Wed May  2 14:17:24 PDT 2007  Kevin Turner <kevin@janrain.com>
  * NEWS: mention store API changes
  
  more people have custom stores than I thought.

    hunk ./NEWS 30
    -    [_$_]
    +
    +If you've written your own custom store or code that interacts directly with it,
    +you'll need to review the change notes in openid.store.interface.
    +

Wed May  2 14:12:50 PDT 2007  Kevin Turner <kevin@janrain.com>
  * store.interface: note removal of isDumb in @change notes

    hunk ./openid/store/interface.py 13
    -    @change: Version 2.0 removed the C{storeNonce} and C{getAuthKey} methods,
    -        and changed the behavior of the C{L{useNonce}} method to support
    -        one-way nonces.
    +    @change: Version 2.0 removed the C{storeNonce}, C{getAuthKey}, and C{isDumb}
    +        methods, and changed the behavior of the C{L{useNonce}} method
    +        to support one-way nonces.
    hunk ./openid/store/interface.py 140
    -           longer part of the interface.
    +           longer part of the interface.)

Mon Apr 16 16:04:51 PDT 2007  cygnus@janrain.com
  * Do not send openid.ns values in OpenID 1 messages

    hunk ./openid/message.py 248
    -                ns_key = 'openid.ns.' + alias
    -                args[ns_key] = ns_uri
    +                if self.getOpenIDNamespace() != OPENID1_NS:
    +                    ns_key = 'openid.ns.' + alias
    +                    args[ns_key] = ns_uri
    hunk ./openid/test/test_auth_request.py 94
    -        self.failUnlessEqual('bag:', post_args['openid.ns.ext0'])

Mon Apr 16 14:16:08 PDT 2007  cygnus@janrain.com
  * Fix simple registration API to use toMessage()

    hunk ./examples/server.py 209
    -        sreg_resp.addToOpenIDResponse(response.fields)
    +        sreg_resp.toMessage(response.fields)
    hunk ./openid/sreg.py 22
    -      sreg_resp.addToOpenIDResponse(openid_response)
    +      sreg_resp.toMessage(openid_response.fields)
    hunk ./openid/sreg.py 386
    -    @group Server: extractResponse, addToOpenIDResponse
    +    @group Server: extractResponse
    hunk ./openid/sreg.py 466
    -    def addToOpenIDResponse(self, response_message):
    -        """Add the data fields contained in this simple registration
    -        response to the supplied message, in the appropriate
    -        namespace.
    -
    -        @param response_message: The OpenID id_res response message
    -            that will be returned to the relying party
    -        @type response_message: C{L{openid.message.Message}}
    -
    -        @returns: Nothing; updates the response_message
    +    def getExtensionArgs(self):
    +        """
    +        Return the sreg data to be sent in the response.
    hunk ./openid/sreg.py 470
    -        response_message.updateArgs(self.ns_uri, self.data)
    +        return self.data
    hunk ./openid/sreg.py 529
    -    sreg_response.addToOpenIDResponse(openid_response.fields)
    +    sreg_response.toMessage(openid_response.fields)


Tue Apr  3 13:59:48 PDT 2007  Kevin Turner <kevin@janrain.com>
  * consumer.discover.OpenIDServiceEndpoint.supportsType: clarification

    hunk ./openid/consumer/discover.py 64
    -        """Does this endpoint support this type?"""
    -        return ((type_uri == OPENID_2_0_TYPE and
    -                 OPENID_IDP_2_0_TYPE in self.type_uris) or
    -                self.usesExtension(type_uri))
    +        """Does this endpoint support this type?
    +
    +        I consider C{/server} endpoints to implicitly support C{/signon}.
    +        """
    +        return (
    +            (type_uri in self.type_uris) or [_$_]
    +            (type_uri == OPENID_2_0_TYPE and self.isOPIdentifier())
    +            )



Tue Apr  3 13:55:36 PDT 2007  Kevin Turner <kevin@janrain.com>
  * test.test_consumer.IDPDrivenTest: update mock verifyDiscoveryResults
  
  (the sig of the real implementation changed)

    hunk ./openid/test/test_consumer.py 1544
    -        endpoint = OpenIDServiceEndpoint()
    -        endpoint.claimed_id = identifier
    -        endpoint.server_url = self.endpoint.server_url
    -        endpoint.local_id = identifier
    +        discovered_endpoint = OpenIDServiceEndpoint()
    +        discovered_endpoint.claimed_id = identifier
    +        discovered_endpoint.server_url = self.endpoint.server_url
    +        discovered_endpoint.local_id = identifier
    hunk ./openid/test/test_consumer.py 1549
    -        def verifyDiscoveryResults(identifier, server_url):
    -            iverified.append(endpoint)
    -            return endpoint
    +        def verifyDiscoveryResults(identifier, endpoint):
    +            self.failUnless(endpoint is self.endpoint)
    +            iverified.append(discovered_endpoint)
    +            return discovered_endpoint
    hunk ./openid/test/test_consumer.py 1561
    -        self.failUnlessEqual(iverified, [endpoint])
    +        self.failUnlessEqual(iverified, [discovered_endpoint])
    hunk ./openid/test/test_consumer.py 1573
    -        def verifyDiscoveryResults(identifier, server_url):
    +        def verifyDiscoveryResults(identifier, endpoint):

Tue Apr  3 12:28:56 PDT 2007  Kevin Turner <kevin@janrain.com>
  * openid.message, openid.consumer.discover: update 2.0 URIs for spec rev 295
  
  Also correct a number of places in the code that confused OPENID_2_0_TYPE and
  OPENID2_NS.  These used to be the same string, but they are no longer.

    hunk ./openid/consumer/discover.py 20
    -OPENID_IDP_2_0_TYPE = 'http://openid.net/server/2.0'
    -OPENID_2_0_TYPE = 'http://openid.net/signon/2.0'
    +OPENID_IDP_2_0_TYPE = 'http://specs.openid.net/auth/2.0/server'
    +OPENID_2_0_TYPE = 'http://specs.openid.net/auth/2.0/signon'
    hunk ./openid/consumer/discover.py 65
    -        return ((type_uri == OPENID_2_0_MESSAGE_NS and
    +        return ((type_uri == OPENID_2_0_TYPE and
    hunk ./openid/message.py 21
    -IDENTIFIER_SELECT = "http://openid.net/identifier_select/2.0"
    +IDENTIFIER_SELECT = 'http://specs.openid.net/auth/2.0/identifier_select'
    hunk ./openid/message.py 31
    -OPENID2_NS = 'http://openid.net/signon/2.0'
    +OPENID2_NS = 'http://specs.openid.net/auth/2.0'
    hunk ./openid/test/data/test_discover/openid2_xrds.xml 7
    -      <Type>http://openid.net/signon/2.0</Type>
    +      <Type>http://specs.openid.net/auth/2.0/signon</Type>
    hunk ./openid/test/data/test_discover/openid2_xrds_no_local_id.xml 7
    -      <Type>http://openid.net/signon/2.0</Type>
    +      <Type>http://specs.openid.net/auth/2.0/signon</Type>
    hunk ./openid/test/data/test_discover/openid_1_and_2_xrds.xml 9
    -      <Type>http://openid.net/signon/2.0</Type>
    +      <Type>http://specs.openid.net/auth/2.0/signon</Type>
    hunk ./openid/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml 9
    -      <Type>http://openid.net/signon/2.0</Type>
    +      <Type>http://specs.openid.net/auth/2.0/signon</Type>
    hunk ./openid/test/data/test_discover/yadis_2_bad_local_id.xml 9
    -      <Type>http://openid.net/signon/2.0</Type>
    +      <Type>http://specs.openid.net/auth/2.0/signon</Type>
    hunk ./openid/test/data/test_discover/yadis_2entries_idp.xml 10
    -      <Type>http://openid.net/signon/2.0</Type>
    +      <Type>http://specs.openid.net/auth/2.0/signon</Type>
    hunk ./openid/test/data/test_discover/yadis_2entries_idp.xml 16
    -      <Type>http://openid.net/server/2.0</Type>
    +      <Type>http://specs.openid.net/auth/2.0/server</Type>
    hunk ./openid/test/data/test_discover/yadis_idp.xml 8
    -      <Type>http://openid.net/server/2.0</Type>
    +      <Type>http://specs.openid.net/auth/2.0/server</Type>
    hunk ./openid/test/data/test_discover/yadis_idp_delegate.xml 8
    -      <Type>http://openid.net/server/2.0</Type>
    +      <Type>http://specs.openid.net/auth/2.0/server</Type>
    hunk ./openid/test/test_consumer.py 1610
    -        endpoint.type_uris = [OPENID2_NS]
    +        endpoint.type_uris = [OPENID_2_0_TYPE]
    hunk ./openid/test/test_discover.py 187
    -                                 discover.OPENID_2_0_TYPE)
    +                                 discover.OPENID_2_0_MESSAGE_NS)
    hunk ./openid/test/test_discover.py 655
    -                self.failUnless(self.endpoint.supportsType(t))
    +                self.failUnless(self.endpoint.supportsType(t),
    +                                "Must support %r" % (t,))
    hunk ./openid/test/test_discover.py 658
    -                self.failIf(self.endpoint.supportsType(t))
    +                self.failIf(self.endpoint.supportsType(t),
    +                            "Shouldn't support %r" % (t,))
    hunk ./openid/test/test_negotiation.py 8
    -from openid.consumer.discover import OpenIDServiceEndpoint
    +from openid.consumer.discover import OpenIDServiceEndpoint, OPENID_2_0_TYPE
    hunk ./openid/test/test_negotiation.py 40
    -        self.endpoint.type_uris = [OPENID2_NS]
    +        self.endpoint.type_uris = [OPENID_2_0_TYPE]

Tue Apr  3 13:51:13 PDT 2007  Kevin Turner <kevin@janrain.com>
  * openid/test/data/test_discover/yadis_idp_last.xml: remove unused test data file

    hunk ./openid/test/data/test_discover/yadis_idp_last.xml 1
    -<?xml version="1.0" encoding="UTF-8"?>
    -<xrds:XRDS xmlns:xrds="xri://$xrds"
    -           xmlns="xri://$xrd*($v*2.0)"
    -           xmlns:openid="http://openid.net/xmlns/1.0"
    -           >
    -  <XRD>
    -    <Service priority="10">
    -      <Type>http://openid.net/signon/2.0</Type>
    -      <URI>http://www.myopenid.com/server2_0</URI>
    -    </Service>
    -
    -    <Service priority="20">
    -      <Type>http://openid.net/server/2.0</Type>
    -      <URI>http://www.myopenid.com/server_id</URI>
    -    </Service>
    -  </XRD>
    -</xrds:XRDS>
    rmfile ./openid/test/data/test_discover/yadis_idp_last.xml

Tue Apr  3 13:49:52 PDT 2007  Kevin Turner <kevin@janrain.com>
  * test.test_consumer.IDPDrivenTest.setUp: remove obsolete type_uri
  
  This value is outdated and the test still passed.
  Turns out the tests still pass if I remove it entirely.

    hunk ./openid/test/test_consumer.py 1527
    -        self.endpoint.type_uris = ['http://openid.net/server/2.0']

Tue Apr  3 13:48:20 PDT 2007  Kevin Turner <kevin@janrain.com>
  * test.test_consumer.TestDiscoveryVerification: fix OPENID2_NS/TYPE confusion
  
  These tests were still passing, but they were passing because 
  failUnlessRaises(ProtocolError) was too broad.

    hunk ./openid/test/test_consumer.py 1623
    -        endpoint.type_uris = [OPENID2_NS]
    +        endpoint.type_uris = [OPENID_2_0_TYPE]
    hunk ./openid/test/test_consumer.py 1628
    -        self.failUnlessRaises(ProtocolError,
    -                              self.consumer._verifyDiscoveryResults,
    -                              self.message, endpoint)
    -
    +        try:
    +            r = self.consumer._verifyDiscoveryResults(self.message, endpoint)
    +        except ProtocolError, e:
    +            # Should we make more ProtocolError subclasses?
    +            self.failUnless('OP Endpoint mismatch' in str(e), e)
    +        else:
    +            self.fail("expected ProtocolError, %r returned." % (r,))
    +            [_$_]
    hunk ./openid/test/test_consumer.py 1640
    -        endpoint.type_uris = [OPENID2_NS]
    +        endpoint.type_uris = [OPENID_2_0_TYPE]
    hunk ./openid/test/test_consumer.py 1644
    -        self.failUnlessRaises(ProtocolError,
    -                              self.consumer._verifyDiscoveryResults,
    -                              self.message, endpoint)
    +        try:
    +            r = self.consumer._verifyDiscoveryResults(self.message, endpoint)
    +        except ProtocolError, e:
    +            self.failUnless('local_id mismatch' in str(e), e)
    +        else:
    +            self.fail("expected ProtocolError, %r returned." % (r,))

Mon Apr  2 17:07:13 PDT 2007  Kevin Turner <kevin@janrain.com>
  * openid.message.OPENID1_NS: 'signon', not 'sso'
  This agrees with spec rev 294, LJ yadis, 2idi XRDS, and PIP.
  Not sure how we got openid.net/sso/1.0 in there.
  

    hunk ./openid/message.py 28
    -OPENID1_NS = 'http://openid.net/sso/1.0'
    +OPENID1_NS = 'http://openid.net/signon/1.0'