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')

    >>> def dummy_application(environ, start_response):
    ...     start_response(
    ...         '200', [('Content-type','application/vnd.sun.wadl+xml')])
    ...     return [wadl_string]


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):
    ...
    HTTPError: 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):
    ...
    HTTPError: 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--it doesn't serve any JSON resources--but we're able to
make HTTP requests without getting 401 errors.

    >>> authorizer = BasicHttpAuthorizer("user", "password")
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")
    Traceback (most recent call last):
    ...
    ValueError: No JSON object could be decoded

Teardown.

    >>> 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):
    ...
    HTTPError: 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):
    ...
    HTTPError: 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).

    >>> authorizer = OAuthAuthorizer(
    ...     valid_consumer.key, access_token=valid_token)
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")
    Traceback (most recent call last):
    ...
    ValueError: No JSON object could be decoded

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

    >>> authorizer = OAuthAuthorizer(
    ...     valid_consumer, access_token=empty_token)
    >>> client = ServiceRoot(authorizer, "http://api.launchpad.dev/")
    Traceback (most recent call last):
    ...
    ValueError: No JSON object could be decoded

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/")
    Traceback (most recent call last):
    ...
    ValueError: No JSON object could be decoded

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):
    ...
    HTTPError: 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):
    ...
    HTTPError: HTTP Error 401: Unauthorized
    ...

Teardown.

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

Accessing the authorizer object after the fact
----------------------------------------------

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://cookbooks.dev/1.0/")
    >>> root.credentials
    <lazr.restfulclient.authorize.oauth.OAuthAuthorizer object...>

Server-side permissions
-----------------------

The server may hide some data from you because you lack the permission
to see it. To avoid objects that are mysteriously missing fields, the
server will serve a special "redacted" value that lets you know you
don't have permission to see the data.

    >>> from lazr.restfulclient.tests.example import CookbookWebServiceClient
    >>> service = CookbookWebServiceClient()

    >>> cookbook = service.recipes[1].cookbook
    >>> print cookbook.confirmed
    tag:launchpad.net:2008:redacted

If you try to make an HTTP request for the "redacted" value (usually
by following a link that you don't know is redacted), you'll get a
helpful error.

    >>> service.load("tag:launchpad.net:2008:redacted")
    Traceback (most recent call last):
    ...
    ValueError: You tried to access a resource that you don't have the
    server-side permission to see.
