Authorizers
===========

Authorizers are objects that encapsulate knowledge about a particular
web service's authentication scheme. lazr.restfulclient includes
authorizers for common HTTP authentication schemes.

The BasicHttpAuthorizer
-----------------------

This authorizer handles HTTP Basic Auth. To test it, we'll create a
fake web service that serves some dummy WADL.

    >>> import pkg_resources
    >>> wadl_string = pkg_resources.resource_string(
    ...     'wadllib.tests.data', 'launchpad-wadl.xml')

    >>> responses = { 'application/vnd.sun.wadl+xml' : wadl_string,
    ...               'application/json' : '{}' }

    >>> def dummy_application(environ, start_response):
    ...     media_type = environ['HTTP_ACCEPT']
    ...     content = responses[media_type]
    ...     start_response(
    ...         '200', [('Content-type', media_type)])
    ...     return [content]


The WADL file will be protected with HTTP Basic Auth. To access it,
you'll need to provide a username of "user" and a password of
"password".

    >>> def authenticate(username, password):
    ...     """Accepts "user/password", rejects everything else.
    ...
    ...     :return: The username, if the credentials are valid.
    ...              None, otherwise.
    ...     """
    ...     if username == "user" and password == "password":
    ...         return username
    ...     return None

    >>> from lazr.authentication.wsgi import BasicAuthMiddleware
    >>> def protected_application():
    ...     return BasicAuthMiddleware(
    ...         dummy_application, authenticate_with=authenticate)

Finally, we'll set up a WSGI intercept so that we can test the web
service by making HTTP requests to http://api.launchpad.dev/. (This is
the hostname mentioned in the WADL file.)

    >>> import wsgi_intercept
    >>> from wsgi_intercept.httplib2_intercept import install
    >>> install()
    >>> wsgi_intercept.add_wsgi_intercept(
    ...     'api.launchpad.dev', 80, protected_application)

With no HttpAuthorizer, a ServiceRoot can't get access to the web service.

    >>> from lazr.restfulclient.resource import ServiceRoot
    >>> client = ServiceRoot(None, "http://api.launchpad.dev/")
    Traceback (most recent call last):
    ...
    Unauthorized: HTTP Error 401: Unauthorized
    ...

We can't get access if the authorizer doesn't have the right
credentials.

    >>> from lazr.restfulclient.authorize import BasicHttpAuthorizer

    >>> bad_authorizer = BasicHttpAuthorizer("baduser", "badpassword")
    >>> client = ServiceRoot(bad_authorizer, "http://api.launchpad.dev/")
    Traceback (most recent call last):
    ...
    Unauthorized: HTTP Error 401: Unauthorized
    ...

If we provide the right credentials, we can retrieve the WADL. We'll
still get an exception, because our fake web service is too fake for
ServiceRoot--its 'service root' resource doesn't match the WADL--but
we're able to make HTTP requests without getting 401 errors.

Note that the HTTP request includes the User-Agent header, but that
that header contains no special information about the authorization
method. This will change when the authorization method is OAuth.

    >>> import httplib2
    >>> httplib2.debuglevel = 1

    >>> authorizer = BasicHttpAuthorizer("user", "password")
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")
    send: 'GET / ...user-agent: lazr.restfulclient ...'
    ...


The BasicHttpAuthorizer allows you to adds proper basic auth headers to the
request, when asked to, using the username and password information it already
knows about.

    >>> headers = {}
    >>> authorizer.authorizeRequest('/', 'GET', '', headers)
    >>> headers.get('authorization')
    'Basic dXNlcjpwYXNzd29yZA=='

Teardown.

    >>> httplib2.debuglevel = 0
    >>> wsgi_intercept.remove_wsgi_intercept("api.launchpad.dev", 80)


The OAuthAuthorizer
-------------------

This authorizer handles OAuth authorization. To test it, we'll protect
the dummy application with a piece of OAuth middleware. The middleware
will accept only one consumer/token combination, though it will also
allow anonymous access: if you pass in an empty token and secret,
you'll get a lower level of access.

    >>> from oauth.oauth import OAuthConsumer, OAuthToken
    >>> valid_consumer = OAuthConsumer("consumer", '')
    >>> valid_token = OAuthToken("token", "secret")
    >>> empty_token = OAuthToken("", "")

Our authenticate() implementation checks against the one valid
consumer and token.

    >>> def authenticate(consumer, token, parameters):
    ...     """Accepts the valid consumer and token, rejects everything else.
    ...
    ...     :return: The consumer, if the credentials are valid.
    ...              None, otherwise.
    ...     """
    ...     if token.key == '' and token.secret == '':
    ...         # Anonymous access.
    ...         return consumer
    ...     if consumer == valid_consumer and token == valid_token:
    ...         return consumer
    ...     return None

Our data store helps the middleware look up consumer and token objects
from the information provided in a signed OAuth request.

    >>> from lazr.authentication.testing.oauth import SimpleOAuthDataStore

    >>> class AnonymousAccessDataStore(SimpleOAuthDataStore):
    ...     """A data store that will accept any consumer."""
    ...     def lookup_consumer(self, consumer):
    ...         """If there's no matching consumer, just create one.
    ...
    ...         This will let anonymous requests succeed with any
    ...         consumer key."""
    ...         consumer = super(
    ...             AnonymousAccessDataStore, self).lookup_consumer(
    ...             consumer)
    ...         if consumer is None:
    ...             consumer = OAuthConsumer(consumer, '')
    ...         return consumer

    >>> data_store = AnonymousAccessDataStore(
    ...     {valid_consumer.key : valid_consumer},
    ...     {valid_token.key : valid_token,
    ...      empty_token.key : empty_token})

Now we're ready to protect the dummy_application with OAuthMiddleware,
using our authenticate() implementation and our data store.

    >>> from lazr.authentication.wsgi import OAuthMiddleware
    >>> def protected_application():
    ...     return OAuthMiddleware(
    ...         dummy_application, realm="OAuth test",
    ...         authenticate_with=authenticate, data_store=data_store)
    >>> wsgi_intercept.add_wsgi_intercept(
    ...     'api.launchpad.dev', 80, protected_application)

Let's try out some clients. As you'd expect, you can't get through the
middleware with no HTTPAuthorizer at all.

    >>> from lazr.restfulclient.authorize.oauth import OAuthAuthorizer
    >>> client = ServiceRoot(None, "http://api.launchpad.dev/")
    Traceback (most recent call last):
    ...
    Unauthorized: HTTP Error 401: Unauthorized
    ...

Invalid credentials are also no help.

    >>> authorizer = OAuthAuthorizer(
    ...     valid_consumer.key, access_token=OAuthToken("invalid", "token"))
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")
    Traceback (most recent call last):
    ...
    Unauthorized: HTTP Error 401: Unauthorized
    ...

But valid credentials work fine (again, up to the point at which
lazr.restfulclient runs against the limits of this simple web
service). Note that the User-Agent header mentions the consumer key.

    >>> httplib2.debuglevel = 1
    >>> authorizer = OAuthAuthorizer(
    ...     valid_consumer.key, access_token=valid_token)
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")
    send: 'GET /...user-agent: lazr.restfulclient...; oauth_consumer="consumer"...'
    ...

If the OAuthAuthorizer is created with an application name as well as
a consumer key, the application name is mentioned in the User-Agent
header as well.

    >>> authorizer = OAuthAuthorizer(
    ...     valid_consumer.key, access_token=valid_token,
    ...     application_name="the app")
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")
    send: 'GET /...user-agent: lazr.restfulclient...; application="the app"; oauth_consumer="consumer"...'
    ...

    >>> httplib2.debuglevel = 0

It's even possible to get anonymous access by providing an empty
access token.

    >>> authorizer = OAuthAuthorizer(
    ...     valid_consumer.key, access_token=empty_token)
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")

Because of the way the AnonymousAccessDataStore (defined
earlier in the test) works, you can even get anonymous access by
specifying an OAuth consumer that's not in the server-side list of
valid consumers.

    >>> authorizer = OAuthAuthorizer(
    ...     "random consumer", access_token=empty_token)
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")

A ServiceRoot object has a 'credentials' attribute which contains the
Authorizer used to authorize outgoing requests.

    >>> from lazr.restfulclient.resource import ServiceRoot
    >>> root = ServiceRoot(authorizer, "http://api.launchpad.dev/")
    >>> root.credentials
    <lazr.restfulclient.authorize.oauth.OAuthAuthorizer object...>

If you try to provide credentials with an unrecognized OAuth consumer,
you'll get an error--even if the credentials are valid. The data store
used in this test only lets unrecognized OAuth consumers through when
they request anonymous access.

    >>> authorizer = OAuthAuthorizer(
    ...     'random consumer', access_token=valid_token)
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")
    Traceback (most recent call last):
    ...
    Unauthorized: HTTP Error 401: Unauthorized
    ...

    >>> authorizer = OAuthAuthorizer(
    ...     'random consumer', access_token=OAuthToken("invalid", "token"))
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")
    Traceback (most recent call last):
    ...
    Unauthorized: HTTP Error 401: Unauthorized
    ...

Teardown.

    >>> wsgi_intercept.remove_wsgi_intercept("api.launchpad.dev", 80)

