= OAuth = Most of the OAuth doctests have been converted into unit tests and moved to test_oauth_tokens.py == Nonces and timestamps == A nonce is a random string, generated by the client for each request. A timestamp is also generated by the client, and indicates when the request was generated, according to the client's clock, expressed in the number of seconds since January 1, 1970 00:00:00 GMT. A nonce must be unique for the combination of a client and a timestamp. Launchpad implements this constraint. If a nonce is not unique for a client and a timestamp, the code will raise a NonceAlreadyUsed exception. Here's a quick demonstration of the behavior. >>> login('foo.bar@canonical.com') >>> access_token = factory.makeOAuthAccessToken() >>> logout() - We can use a nonce. >>> from lp.services.oauth.interfaces import IOAuthNonce >>> import time >>> now = time.time() - 1 >>> nonce1 = access_token.checkNonceAndTimestamp('boo', now) >>> verifyObject(IOAuthNonce, nonce1) True - We can use an existing nonce with a new time. >>> now += 1 >>> nonce2 = access_token.checkNonceAndTimestamp('boo', now) >>> IOAuthNonce.providedBy(nonce2) True >>> nonce1 is nonce2 False - We can use a new nonce with the same time. >>> nonce3 = access_token.checkNonceAndTimestamp('surprise!', now) >>> IOAuthNonce.providedBy(nonce3) True >>> nonce1 is nonce3 or nonce2 is nonce3 False - But we cannot use an existing nonce used for the same time. >>> access_token.checkNonceAndTimestamp('boo', now) Traceback (most recent call last): ... NonceAlreadyUsed: ... According to the oauth specification , for a given client, an application should not accept a timestamp older than the most recent timestamp received. For instance, if a client sends a timestamp of 2009-02-24T13:43:08Z, a subsequent request for any earlier time (even one second earlier, 2009-02-24T13:43:07Z) should generate an error. A request with the same timestamp or later is acceptable. These limitations of the nonce and the timestamp are intended to reduce the opportunity for replay attacks, particularly within the context of an insecure channel such as HTTP. Launchpad relaxes the constraint on the timestamp. Rather than accepting any request within the same second, Launchpad accepts any request within the past 60 seconds (or, as defined by the code, within the past number of seconds specified by lp.services.oauth.model.TIMESTAMP_ACCEPTANCE_WINDOW). For instance, in Launchpad, if a client sends a timestamp of 2009-02-24T13:43:08Z, a subsequent request of any time up to 60 seconds prior will be accepted. That means that the next request can specify 2009-02-24T13:42:08Z or later. Otherwise the code will raise a TimestampOrderingError. Here's a quick demonstration of the behavior. - Within the TIMESTAMP_ACCEPTANCE_WINDOW, we can create a nonce with an earlier time. >>> from lp.services.oauth.model import ( ... TIMESTAMP_ACCEPTANCE_WINDOW) >>> TIMESTAMP_ACCEPTANCE_WINDOW 60 >>> nonce4 = access_token.checkNonceAndTimestamp('boo', now-30) >>> IOAuthNonce.providedBy(nonce4) True >>> nonce5 = access_token.checkNonceAndTimestamp('boo', now-60) >>> IOAuthNonce.providedBy(nonce5) True - Once outside of the window (defined by the *latest* timestamp, even if it is not the most recent), we get a TimestampOrderingError. >>> access_token.checkNonceAndTimestamp('boo', now-61) Traceback (most recent call last): ... TimestampOrderingError: ... This change is intended to support pipelining requests, and to acknowledge that network latency, multiple concurrent clients with the same credentials, and other situations can cause timestamps to get out of order (see http://groups.google.com/group/oauth/msg/387fdafcf0be322a and LP bug 319710 for more background). The code also raises a ClockSkew error if the timestamp is more than TIMESTAMP_SKEW_WINDOW seconds away from the server's clock (ahead or behind). This number of seconds is currently equal to one hour (60*60). The primary reason for this behavior is so that a clock skew too far in the future does not essentially make the authentication tokens useless once the client's clock is corrected. In addition, it gives us a constraint within which we can reason about clock-related errors reported by customers. Here's a quick demonstration of the behavior. - We can access the system with a timestamp 55 minutes in the future. >>> from lp.services.oauth.model import ( ... TIMESTAMP_SKEW_WINDOW) >>> TIMESTAMP_SKEW_WINDOW 3600 >>> nonce6 = access_token.checkNonceAndTimestamp('boo', now + 55*60) >>> IOAuthNonce.providedBy(nonce6) True - We cannot access it 65 minutes in the future. >>> access_token.checkNonceAndTimestamp('boo', now + 65*60) Traceback (most recent call last): ... ClockSkew: ... - It's also worth noting that now the TIMESTAMP_ACCEPTANCE_WINDOW is based off of the time 55 minutes in the future. >>> nonce7 = access_token.checkNonceAndTimestamp('boo', now + 54*60 + 30) >>> IOAuthNonce.providedBy(nonce7) True >>> access_token.checkNonceAndTimestamp('boo', now + 60) Traceback (most recent call last): ... TimestampOrderingError: ... >>> access_token.checkNonceAndTimestamp('boo', now + 53*60) Traceback (most recent call last): ... TimestampOrderingError: ...