Endpoint Resource

An endpoint resource maps to a single URL endpoint in a Django app’s urls.py. Each endpoint resource can have one or more EndpointDefinition that implements the handler code. HTTP verbs such as POST, GET, along with parameters present in the request determine which EndpointDefinition is used.

Set Up Resource Adapter

The default resource adapter for Django Declarative APIs is django_declarative_apis.adapters.EndpointResource defined in settings.py. To use a custom resource adapter, set DECLARATIVE_ENDPOINT_RESOURCE_ADAPTER to your own definition of resource adapter.

DECLARATIVE_ENDPOINT_RESOURCE_ADAPTER = (
    "django_declarative_apis.adapters.EndpointResource")

Helper Function

django_declarative_apis.adapters.resource_adapter(*args, **kwargs)[source]

resource_adapter() is a helper function that finds the endpoint resource adapter from settings.py and calls that resource adapter.

resource_adapter takes two arguments:

Handler/Resource

Required | The EndpointDefinition implementation along with an HTTP verb.

Authentication Handler

Optional | If not specified, OAuth1.0a will be used by default.

Example: Handler defined in a separate file named handlers.py.

TodoEndpoint = resource_adapter(
    post=resources.TodoUpdateDefinition,
    get=resources.TodoDefinition,
    authentication={None: (NoAuth(),)},
)

Django app’s urls.py.

url(
    r"^tasks/$",
    handlers.TodoEndpoint,
)

EndpointResource

class django_declarative_apis.adapters.EndpointResource(authentication=None, **kwargs)[source]

EndpointResource is the DDA default resource adapter. It validates the configuration of the authentication handler, and in combination with Django’s native urls.py routes requests (through behavioral routing) to the same URL but to different handlers based on request attributes.

Authentication

Authentication is tied to the resource adapter. It is the first step that a request needs to pass through. Requests will not be processed if authentication fails at this stage.

Authentication can be set up in two places

settings.py

Required | Sets the default authenticator for the entire application. In other words, all endpoint definitions will use the authenticator defined here.

Set DECLARATIVE_ENDPOINT_AUTHENTICATION_HANDLERS to point to the authentication handler.

resource_adapter()

Optional | Defines authentication handler for a specific endpoint definition. To implement, set authentication=<{<AuthenticatorHint>: [Authenticator]}> in an argument to the resource_adapter().

Once specified, it will override the default authentication setup in settings.py.

Default Value | None

Example

# Define a custom NoAuth class.

TodoEndpoint = resource_adapter(
    post=resources.TodoUpdateDefinition,
    authentication={None: (NoAuth(),)},
)

The resource_adapter expects the authentication handler to conform to the following configuration schema.

{
    <AuthenticatorHint>: [<Authenticator>, <Authenticator>...],
    <AuthenticatorHint>: [<Authenticator>, <Authenticator>...],
}
  • <AuthenticatorHint>

    Required | It is used to match Authorization headers for quick handler lookup.

    Properties

    • Should be defined as a <(tuple of header hints)>

    • Must be an instance of authentication.AuthenticatorHint or None

    Example

    from django_declarative_apis.authentication import AuthenticatorHint
    
    SampleAuthenticatorHint = AuthenticatorHint("OAuth ")
    

    If there are more complexities to the authenticator, catch-alls are allowed by using a key of None. The catch-all authenticators are always executed after matched authenticators.

    {
        None: [<Authenticator>]
    }
    
  • <Authenticator>

    Required | Responsible for looking at the request and determining whether it can validate the requester.

    Properties

    • Should point to the implementation of the authenticator → <implementation>

    • Must be an instance of authentication.Authenticator

    Example

    from django_declarative_apis.authentication import Authenticator
    
    class SampleAuthenticator(Authenticator):
        # your code goes here
    

Example

For instance, if we want to use OAuth1.0a, we could use an AuthenticatorHint.header value of “OAuth” as a key, and [django_declarative_apis.authentication.oauthilib.oauth1.TwoLeggedOauth1()] as value. This will ensure that any time an ‘Authorization: OAuth …’ header is seen, the appropriate authenticator is used. In this case, the DDA built-in TwoLeggedOauth1() will be used.

DECLARATIVE_ENDPOINT_AUTHENTICATION_HANDLERS = [
(
 (None, 'django_declarative_apis.authentication.oauthlib.oauth1.TwoLeggedOauth1Hint'),
 'django_declarative_apis.authentication.oauthlib.oauth1.TwoLeggedOauth'
 ),
]

Features of DDA Authentication

class django_declarative_apis.authentication.Authenticator[source]

The base class for constructing an authenticator.

The Authenticator class has two methods: is_authenticated and challenge. Both of these need to be overridden by the authenticator implementation that inherits from Authenticator class. Otherwise, it will throw a NotImplementedError.

Example

from django_declarative_apis.authentication import Authenticator

class SampleAuthenticator(Authenticator):
    def is_authenticated(request):
        # authentication code

    def challenge(self, error):
        # challenge code
abstract challenge(error)[source]

Results in the challenge response sent to the user

This should result in a django.http.HttpResponse that should include information through the WWW-Authenticate header around expectations.

abstract is_authenticated(request)[source]

Takes in the request as an argument and identifies whether the requester is valid.

class django_declarative_apis.authentication.AuthenticatorHint(header: str)[source]

Tuple to provide hints for authentication implementations.

header_hint: a string used to match the Authentication: header.

header: str

Alias for field number 0

class django_declarative_apis.authentication.AuthenticationResult(detail=None, auth_header=None)[source]

A class definition that takes in and stores the authentication header and details of the result.

class django_declarative_apis.authentication.AuthenticationSuccess(detail=None, auth_header=None)[source]

An instance of AuthenticationResult that returns True.

It can be used as a return response in an authenticator implementation.

class django_declarative_apis.authentication.AuthenticationFailure(detail=None, auth_header=None)[source]

An instance of AuthenticationResult that returns False.

It can be used as a return response in an authenticator implementation.

class django_declarative_apis.authentication.NoAuthentication(detail=None, auth_header=None)[source]

Authentication handler that always returns True, so no authentication is needed, nor initiated.

Note

Important: In this implementation the challenge method is missing and must be implemented by the user. Otherwise, it will raise NotImplementedError.

is_authenticated(request)[source]

Takes in the request as an argument and identifies whether the requester is valid.

Example

SampleHint = authentication.AuthenticatorHint("SampleHint")


class SampleAuthenticator(authentication.Authenticator):
    def is_authenticated(self, request):
        try:
            # code for authentication of the requester
            return authentication.AuthenticationSuccess()
        except Exception as error:
            # more code
            return authentication.AuthenticationFailure()

Custom Authenticator Class

Any authenticator class must be an instance of authentication.Authenticator.

The built-in Authenticator class requires the user to override the built in is_authenticated and challenge methods, and write their own authentication methods. If not implemented, it will raise a NotImplementedError.

Example

The NoAuth authentication handler is the minimal implementation of that interface.

class NoAuth(authentication.Authenticator):
    @staticmethod
    def is_authenticated(request):
        return True

    def challenge(self, error):
        super().challenge(error)

Built-in DDA Authenticator Based on OAuth1.0a

The current authentication implementation is OAuth 1.0a. This works well for cases where keys and secrets can be treated as secret, such as in server-to-server communication. If other methods of authentication are required, custom methods must be implemented and specified when defining URLs and their corresponding endpoints.

TwoLeggedOauth1

class django_declarative_apis.authentication.oauthlib.oauth1.TwoLeggedOauth1[source]
authenticate_header(request)[source]

Returns the authentication header. If it does not exist, returns “Unknown OAuth Error

challenge(oauth_error=None)[source]

Returns a 401 response with a small bit on what OAuth is, and where to learn more about it.

When this was written, browsers did not understand OAuth authentication on the browser side, and hence the helpful template we render. Maybe some day in the future, browsers will take care of this stuff for us and understand the 401 with the realm we give it.

is_authenticated(request)[source]

Authenticates the requester using OAuth1.0a.

Behavioral Routing

In DDA a single HTTP request can take multiple endpoint definitions. DDA will determine which EndpointDefinition to use depending on parameters present in the request and parameters accepted by the different endpoint definitions.

Determining whether an EndpointDefinition can handle the request happens through an EndpointBinder.

EndpointBinder

class django_declarative_apis.machinery.EndpointBinder(endpoint_definition)[source]

The EndpointBinder performs three important roles.

  1. It checks whether all the required fields for an EndpointDefinition are present. If everything binds successfully and all the required fields are present, the EndpointDefinition is going to handle the request. If there are errors and other endpoint definitions are present, then the endpoint binder will try the next endpoint definition. If all the endpoint definitions present give error then the endpoint binder will raise an error.

  2. It runs the three standard validators required by the endpoint definition that checks whether the requester should have access to <endpoint>.resource().

    1. is_authorized()

    2. is_permitted()

    3. is_valid()

  3. It checks the rate limits defined in the endpoint definition, which are:

    1. rate_limit_key()

    2. rate_limit_period()

Example

post=(
        FooCreationEndpoint,
        BarCreationEndpoint,
        FooBarCreationEndpoint,
    )

Helper Functions

class django_declarative_apis.machinery.EndpointResourceAttribute(type=None, filter=None, returns_list=False, **kwargs)[source]

Used as a decorator on a resource function. Specifies the attributes of that resource.

Parameters
  • type (required) – Specifies the model type. It is used only for documentation generation purposes.

  • filter (optional) – Defines the class filters. Overrides the default filters. Defaults to None.

  • returns_list (optional) – It is used for documentation generation purposes. Defaults to False

Example

from django_declarative_apis.machinery import endpoint_resource

class TodoSingleTaskDefinition(
    TodoResourceMixin,
    machinery.ResourceEndpointDefinition
):
    resource_id = url_field(name='id')  # grabs the id from url

    @endpoint_resource(type=Todo)
    def resource(self):
        return Todo.objects.get(id=self.resource_id)
class django_declarative_apis.machinery.EndpointResponseAttribute(type, filter=None, **kwargs)[source]

Used as a decorator on a response function. Specifies the attributes of the response.

Parameters
  • type (required) – Specifies the response type, which can be dictionary, list, or type. It is used only for documentation generation purposes.

  • filter (optional) – Defines the class filters. Overrides the default filters. Defaults to None.

Example

from django_declarative_apis.machinery import endpoint_response

@endpoint_response(type=dict)
def response(self):
    return http.status.OK