5.2. HTTP Authentication

Pyslet’s http sub-package contains an auth module that supports RFC2617. This module adds core support for HTTP’s simple challenge-response authentication.

5.2.1. Adding Credentials to an HTTP Request

The simplest way to do basic authentication is to simply add a preformatted Authorization header to each request. For example, if you need to send a request to a website and you know that it requires you to pass your basic auth credentials you could just do something like this:

import pyslet.http.client as http
c = http.Client()
r = http.ClientRequest('https://www.example.com/mypage')
r.set_header("Authorization", 'Basic Sm9oblNtaXRoOnNlY3JldA==')
c.process_request(r)

Calculating the correctly formatted value for the Authorization header can be simplified by creating a BasicCredentials object:

import pyslet.http.auth as auth
credentials = auth.BasicCredentials()
credentials.userid = "JohnSmith"
credentials.password = "secret"
str(credentials)

'Basic Sm9oblNtaXRoOnNlY3JldA=='

As you can see, the credentials object takes care of the syntax for you. The userid and password are character strings but you should be aware that only characters in the ISO-8859-1 character set can be used in user names and passwords.

If you don’t want to add the Authorization header yourself you can delegate responsibility to the http client itself. Before you do that though you have to add an additional piece of information to your credentials objects: the protection space. A protection space is simply the combination of the http scheme (http/https), the host and any optional port information. You can calculate the protection space associated with a URL using Pyslet’s URI object:

from pyslet.rfc2396 import URI
uri = URI.from_octets(
    'https://www.example.com:443/mypage').get_canonical_root()
str(uri)

'https://www.example.com'

Notice that the get_canonical_root() method takes care of figuring out default ports and removing the path for you so you can get the protection space for any http-based URL. By setting the protectionSpace attribute on the BasicCredentials object you tell the client which sites it should offer the credentials to:

credentials.protectionSpace = uri
c.add_credentials(credentials)
r = http.ClientRequest('https://www.example.com/mypage')
c.process_request(r)

The HTTP client has a credential store and an add_credentials method. Once added, the following happens when a 401 response is received:

  1. The client iterates through any received challenges
  2. Each challenge is matched against the stored credentials
  3. If matching credentials are found then an Authorization header is added and the request resent
  4. If the request receives another 401 response indicating that the attempt to authenticate failed then the credentials are removed from the store and we go back to (1)

This process terminates when there are no more credentials that match any of the challenges or when a code other than 401 is received.

If the matching credentials are BasicCredentials (and that’s the only type Pyslet supports out of the box), then some additional logic gets activated on success. RFC 2617 says that for basic authentication, a challenge implies that all paths “at or deeper than the depth of the last symbolic element in the path field” fall into the same protection space. Therefore, when credentials are used successfully, Pyslet adds the path to the credentials using BasicCredentials.add_success_path. Next time a request is sent to a URL on the same server with a path that meets this criterium the Authorization header will be added automatically without waiting for a 401 challenge response.

You can simulate this behaviour yourself if you want to pre-empt a 401 response completely. You just need to add a suitable path to the credentials before you add them to the client. So if you know your credentials are good for everything in /website/~user/ you could continue the above code like this:

credentials.add_success_path('/website/~user/')

That last slash is really important, if you leave it off it will add everything in ‘/website/’ to your protection space which is probably not what you want.

5.2.2. Class Reference

class pyslet.http.auth.Credentials

Bases: pyslet.http.params.Parameter

An abstract class that represents a set of HTTP authentication credentials.

Instances are typically created and then added to a request manager object using add_credentials() for matching against HTTP authorization challenges.

The built-in str function can be used to format instances according to the grammar defined in the specification.

scheme_class = {'basic': <class 'pyslet.http.auth.BasicCredentials'>}

A dictionary mapping lower-case auth schemes onto the special classes used to represent their credential messages

classmethod register(scheme, credential_class)

Registers a class to represent credentials in scheme

scheme
A string representing an auth scheme, e.g., ‘Basic’. The string is converted to lower-case before it is registered to allow for case insensitive look-up.
credential_class
A class derived from Credentials that is used to represent a set of base Credentials in that scheme. The class must have a from_words() class method.

If a class has already been registered for the scheme it is replaced. The mapping is kept in the scheme_class dictionary.

base = None

The base credentials

By default, new instances are ‘base’ credentials and this attribute will be None. When credentials are traded in response to a challenge they become session credentials and base contains the original ‘base’ credentials object from which the proffered credentials were derived.

scheme = None

the authentication scheme

protectionSpace = None

the protection space in which these credentials should be used.

The protection space is a pyslet.rfc2396.URI instance reduced to just the the URL scheme, hostname and port.

realm = None

the realm in which these credentials should be used.

The realm is a simple string as returned by the HTTP server. If None then these credentials will be used for any realm within the protection space.

match_challenge(challenge)

Returns True if these credentials can be used in response to challenge.

challenge
A Challenge instance

The match is successful if the authentication scheme, the protection space and the realms match the corresponding values in the challenge.

test_url(url)

Returns True if these credentials can be used peremptorily when making a request to url.

url
A pyslet.rfc2396.URI instance.

The default implementation always returns False.

classmethod from_str(source)

Constructs a Credentials instance from an HTTP formatted string.

get_response(challenge=None)

Creates a new set of credentials from a challenge

challenge (None)
A Challenge instance containing a challenge that has been received from the server. If these credentials are being used pre-emptively (based on the target URL) then challenge will be None.

This is an abstract method, by default None is returned.

For base credentials, this method must return a new credentials object to be used in a new authentication session or None if these credentials cannot be used in response to challenge. If there is no challenge, return a new credentials object that can be used pre-emptively.

For session credentials (instances returned by a previous call) this method must return a new credentials object, the same credentials object with updated state or None if the challenge indicates that the authentication session has failed. (If there is no challenge, the credentials should be returned unchanged.)

In all cases, the returned object must have base set to the original base credentials used to initiate the authentication session.

class pyslet.http.auth.BasicCredentials

Bases: pyslet.http.auth.Credentials

test_url(url)

Given a URI instance representing an absolute URI, checks if these credentials contain a matching protection space and path prefix.

test_path(path)

Returns True if there is a path prefix that matches path

add_success_path(path)

Updates credentials based on success at path

path
A string of octets representing the path that these credentials have been used for with a successful result.

This method implements the requirement that paths “at or deeper than the depth of the last symbolic element in the path field” should be treated as being part of the same protection space.

The path is reduced to a path prefix by removing the last symbolic element and then it is tested against existing prefixes to ensure that the most general prefix is being stored, for example, if path is “/website/document” it will replace any existing prefixes of the form “/website/folder.” with the common prefix “/website”.

class pyslet.http.auth.Challenge(scheme, *params)

Bases: pyslet.http.params.Parameter

Represents an HTTP authentication challenge.

Instances are created from a scheme and a variable length list of 3-tuples containing parameter (name, value, qflag) values. The types of the items are as follows:

name
A character string containing the token that names the parameter
value
A binary string representing the parameter value.
qflag
a boolean indicating that the value must always be quoted, even if it is a valid token.

All Challenges require a realm parameter, if omitted a realm of “Default” is used.

Instances behave like read-only lists of (name,value) pairs implementing len, indexing and iteration in the usual way. Instances also support basic key lookup of parameter names by implementing __contains__ and __getitem__ (which returns the parameter value and raises KeyError for undefined parameters). Name look-up handles case sensitivity by looking first for a case-sensitive match and then for a case insensitive match. Instances are not truly dictionary like.

scheme_class = {'basic': <class 'pyslet.http.auth.BasicChallenge'>}

A dictionary mapping lower-case auth schemes onto the special classes used to represent their challenge messages

can_parse = True

a scheme-specific flag indicating whether of not the scheme is parsable. Some schemes use the WWW-Authenticate header but do not adhere to the syntax past the scheme name.

classmethod register(scheme, challenge_class)

Registers a class to represent a challenge in scheme

scheme
A string representing an auth scheme, e.g., ‘Basic’. The string is converted to lower-case before it is registered to allow for case insensitive look-up.
challenge_class
A class derived from Challenge that is used to represent a Challenge issued using that scheme

If a class has already been registered for the scheme it is replaced. The mapping is kept in the scheme_class dictionary.

scheme = None

the name of the schema

protectionSpace = None

an optional protection space indicating the scope of this challenge. When the HTTP client receives a challenge this is set to the URL representing the scheme/host/port of the server.

classmethod from_str(source)

Creates a Challenge from a source string.

classmethod list_from_str(source)

Creates a list of Challenges from a source string.

class pyslet.http.auth.BasicChallenge(*params)

Bases: pyslet.http.auth.Challenge

Represents an HTTP Basic authentication challenge.