diff options
Diffstat (limited to 'docs/api-guide')
| -rw-r--r-- | docs/api-guide/authentication.md | 36 | ||||
| -rw-r--r-- | docs/api-guide/content-negotiation.md | 7 | ||||
| -rw-r--r-- | docs/api-guide/contentnegotiation.md | 1 | ||||
| -rw-r--r-- | docs/api-guide/exceptions.md | 79 | ||||
| -rw-r--r-- | docs/api-guide/format-suffixes.md | 11 | ||||
| -rw-r--r-- | docs/api-guide/generic-views.md | 10 | ||||
| -rw-r--r-- | docs/api-guide/permissions.md | 99 | ||||
| -rw-r--r-- | docs/api-guide/reverse.md | 4 | ||||
| -rw-r--r-- | docs/api-guide/settings.md | 10 | ||||
| -rw-r--r-- | docs/api-guide/throttling.md | 148 |
10 files changed, 388 insertions, 17 deletions
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index ca29bc4d..79950946 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -2,6 +2,10 @@ # Authentication +> Auth needs to be pluggable. +> +> — Jacob Kaplan-Moss, ["REST worst practices"][cite] + Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The [permission] and [throttling] policies can then use those credentials to determine if the request should be permitted. REST framework provides a number of authentication policies out of the box, and also allows you to implement custom policies. @@ -14,7 +18,7 @@ The `request.auth` property is used for any additional authentication informatio ## How authentication is determined -Authentication is always set as a list of classes. REST framework will attempt to authenticate with each class in the list, and will set `request.user` and `request.auth` using the return value of the first class that successfully authenticates. +The authentication policy is always defined as a list of classes. REST framework will attempt to authenticate with each class in the list, and will set `request.user` and `request.auth` using the return value of the first class that successfully authenticates. If no class authenticates, `request.user` will be set to an instance of `django.contrib.auth.models.AnonymousUser`, and `request.auth` will be set to `None`. @@ -56,31 +60,40 @@ Or, if you're using the `@api_view` decorator with function based views. } return Response(content) -## UserBasicAuthentication - -This policy uses [HTTP Basic Authentication][basicauth], signed against a user's username and password. User basic authentication is generally only appropriate for testing. +## BasicAuthentication -**Note:** If you run `UserBasicAuthentication` in production your API must be `https` only, or it will be completely insecure. You should also ensure that your API clients will always re-request the username and password at login, and will never store those details to persistent storage. +This policy uses [HTTP Basic Authentication][basicauth], signed against a user's username and password. Basic authentication is generally only appropriate for testing. -If successfully authenticated, `UserBasicAuthentication` provides the following credentials. +If successfully authenticated, `BasicAuthentication` provides the following credentials. * `request.user` will be a `django.contrib.auth.models.User` instance. * `request.auth` will be `None`. +**Note:** If you use `BasicAuthentication` in production you must ensure that your API is only available over `https` only. You should also ensure that your API clients will always re-request the username and password at login, and will never store those details to persistent storage. + ## TokenAuthentication -This policy uses [HTTP Authentication][basicauth] with no authentication scheme. Token basic authentication is appropriate for client-server setups, such as native desktop and mobile clients. The token key should be passed in as a string to the "Authorization" HTTP header. For example: +This policy uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients. + +To use the `TokenAuthentication` policy, include `djangorestframework.authtoken` in your `INSTALLED_APPS` setting. + +You'll also need to create tokens for your users. + + from djangorestframework.authtoken.models import Token + + token = Token.objects.create(user=...) + print token.key - curl http://my.api.org/ -X POST -H "Authorization: 0123456789abcdef0123456789abcdef" +For clients to authenticate, the token key should be included in the `Authorization` HTTP header. The key should be prefixed by the string literal "Token", with whitespace seperating the two strings. For example: -**Note:** If you run `TokenAuthentication` in production your API must be `https` only, or it will be completely insecure. + Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b If successfully authenticated, `TokenAuthentication` provides the following credentials. * `request.user` will be a `django.contrib.auth.models.User` instance. * `request.auth` will be a `djangorestframework.tokenauth.models.BasicToken` instance. -To use the `TokenAuthentication` policy, you must have a token model. Django REST Framework comes with a minimal default token model. To use it, include `djangorestframework.tokenauth` in your installed applications and sync your database. To use your own token model, subclass the `djangorestframework.tokenauth.TokenAuthentication` class and specify a `model` attribute that references your custom token model. The token model must provide `user`, `key`, and `revoked` attributes. Refer to the `djangorestframework.tokenauth.models.BasicToken` model as an example. +**Note:** If you use `TokenAuthentication` in production you must ensure that your API is only available over `https` only. ## OAuthAuthentication @@ -102,8 +115,9 @@ If successfully authenticated, `SessionAuthentication` provides the following cr ## Custom authentication policies -To implement a custom authentication policy, subclass `BaseAuthentication` and override the `authenticate(self, request)` method. The method should return a two-tuple of `(user, auth)` if authentication succeeds, or `None` otherwise. +To implement a custom authentication policy, subclass `BaseAuthentication` and override the `.authenticate(self, request)` method. The method should return a two-tuple of `(user, auth)` if authentication succeeds, or `None` otherwise. +[cite]: http://jacobian.org/writing/rest-worst-practices/ [basicauth]: http://tools.ietf.org/html/rfc2617 [oauth]: http://oauth.net/2/ [permission]: permissions.md diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md new file mode 100644 index 00000000..01895a4b --- /dev/null +++ b/docs/api-guide/content-negotiation.md @@ -0,0 +1,7 @@ +# Content negotiation + +> HTTP has provisions for several mechanisms for "content negotiation" - the process of selecting the best representation for a given response when there are multiple representations available. +> +> — [RFC 2616][cite], Fielding et al. + +[cite]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html diff --git a/docs/api-guide/contentnegotiation.md b/docs/api-guide/contentnegotiation.md deleted file mode 100644 index f01627d8..00000000 --- a/docs/api-guide/contentnegotiation.md +++ /dev/null @@ -1 +0,0 @@ -> HTTP has provisions for several mechanisms for "content negotiation" -- the process of selecting the best representation for a given response when there are multiple representations available. -- RFC 2616, Fielding et al. diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index bb3ed56e..c22d6d8b 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -2,4 +2,83 @@ # Exceptions +> Exceptions… allow error handling to be organized cleanly in a central or high-level place within the program structure. +> +> — Doug Hellmann, [Python Exception Handling Techniques][cite] +## Exception handling in REST framework views + +REST framework's views handle various exceptions, and deal with returning appropriate error responses. + +The handled exceptions are: + +* Subclasses of `APIException` raised inside REST framework. +* Django's `Http404` exception. +* Django's `PermissionDenied` exception. + +In each case, REST framework will return a response with an appropriate status code and content-type. The body of the response will include any additional details regarding the nature of the error. + +By default all error responses will include a key `details` in the body of the response, but other keys may also be included. + +For example, the following request: + + DELETE http://api.example.com/foo/bar HTTP/1.1 + Accept: application/json + +Might recieve an error response indicating that the `DELETE` method is not allowed on that resource: + + HTTP/1.1 405 Method Not Allowed + Content-Type: application/json; charset=utf-8 + Content-Length: 42 + + {"detail": "Method 'DELETE' not allowed."} + +## APIException + +**Signature:** `APIException(detail=None)` + +The base class for all exceptions raised inside REST framework. + +To provide a custom exception, subclass `APIException` and set the `.status_code` and `.detail` properties on the class. + +## ParseError + +**Signature:** `ParseError(detail=None)` + +Raised if the request contains malformed data when accessing `request.DATA` or `request.FILES`. + +By default this exception results in a response with the HTTP status code "400 Bad Request". + +## PermissionDenied + +**Signature:** `PermissionDenied(detail=None)` + +Raised when an incoming request fails the permission checks. + +By default this exception results in a response with the HTTP status code "403 Forbidden". + +## MethodNotAllowed + +**Signature:** `MethodNotAllowed(method, detail=None)` + +Raised when an incoming request occurs that does not map to a handler method on the view. + +By default this exception results in a response with the HTTP status code "405 Method Not Allowed". + +## UnsupportedMediaType + +**Signature:** `UnsupportedMediaType(media_type, detail=None)` + +Raised if there are no parsers that can handle the content type of the request data when accessing `request.DATA` or `request.FILES`. + +By default this exception results in a response with the HTTP status code "415 Unsupported Media Type". + +## Throttled + +**Signature:** `Throttled(wait=None, detail=None)` + +Raised when an incoming request fails the throttling checks. + +By default this exception results in a response with the HTTP status code "429 Too Many Requests". + +[cite]: http://www.doughellmann.com/articles/how-tos/python-exception-handling/index.html
\ No newline at end of file diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md new file mode 100644 index 00000000..7d72d9f8 --- /dev/null +++ b/docs/api-guide/format-suffixes.md @@ -0,0 +1,11 @@ +<a class="github" href="urlpatterns.py"></a> + +# Format suffixes + +> Section 6.2.1 does not say that content negotiation should be +used all the time. +> +> — Roy Fielding, [REST discuss mailing list][cite] + +[cite]: http://tech.groups.yahoo.com/group/rest-discuss/message/5857 + diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md new file mode 100644 index 00000000..202875a4 --- /dev/null +++ b/docs/api-guide/generic-views.md @@ -0,0 +1,10 @@ +<a class="github" href="mixins.py"></a> +<a class="github" href="generics.py"></a> + +# Generic views + +> Django’s generic views... were developed as a shortcut for common usage patterns... They take certain common idioms and patterns found in view development and abstract them so that you can quickly write common views of data without having to repeat yourself. +> +> — [Django Documentation][cite] + +[cite]: https://docs.djangoproject.com/en/dev/ref/class-based-views/#base-vs-generic-views diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 2e15107c..fafef305 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -1,3 +1,100 @@ <a class="github" href="permissions.py"></a> -# Permissions
\ No newline at end of file +# Permissions + +> Authentication or identification by itself is not usually sufficient to gain access to information or code. For that, the entity requesting access must have authorization. +> +> — [Apple Developer Documentation][cite] + +Together with [authentication] and [throttling], permissions determine wheter a request should be granted or denied access. + +Permission checks are always run at the very start of the view, before any other code is allowed to proceed. Permission checks will typically use the authentication information in the `request.user` and `request.auth` properties to determine if the incoming request should be permitted. + +## How permissions are determined + +Permissions in REST framework are always defined as a list of permission classes. + +Before running the main body of the view each permission in the list is checked. +If any permission check fails an `exceptions.PermissionDenied` exception will be raised, and the main body of the view will not run. + +## Object level permissions + +REST framework permissions also support object-level permissioning. Object level permissions are used to determine if a user should be allowed to act on a particular object, which will typically be a model instance. + +Object level permissions are run by REST framework's generic views when `.get_object()` is called. As with view level permissions, an `exceptions.PermissionDenied` exception will be raised if the user is not allowed to act on the given object. + +## Setting the permission policy + +The default permission policy may be set globally, using the `DEFAULT_PERMISSIONS` setting. For example. + + API_SETTINGS = { + 'DEFAULT_PERMISSIONS': ( + 'djangorestframework.permissions.IsAuthenticated', + ) + } + +You can also set the authentication policy on a per-view basis, using the `APIView` class based views. + + class ExampleView(APIView): + permission_classes = (IsAuthenticated,) + + def get(self, request, format=None): + content = { + 'status': 'request was permitted' + } + return Response(content) + +Or, if you're using the `@api_view` decorator with function based views. + + @api_view('GET') + @permission_classes(IsAuthenticated) + def example_view(request, format=None): + content = { + 'status': 'request was permitted' + } + return Response(content) + +## IsAuthenticated + +The `IsAuthenticated` permission class will deny permission to any unauthenticated user, and allow permission otherwise. + +This permission is suitable if you want your API to only be accessible to registered users. + +## IsAdminUser + +The `IsAdminUser` permission class will deny permission to any user, unless `user.is_staff`is `True` in which case permission will be allowed. + +This permission is suitable is you want your API to only be accessible to a subset of trusted administrators. + +## IsAuthenticatedOrReadOnly + +The `IsAuthenticatedOrReadOnly` will allow authenticated users to perform any request. Requests for unauthorised users will only be permitted if the request method is one of the "safe" methods; `GET`, `HEAD` or `OPTIONS`. + +This permission is suitable if you want to your API to allow read permissions to anonymous users, and only allow write permissions to authenticated users. + +## DjangoModelPermissions + +This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. When applied to a view that has a `.model` property, authorization will only be granted if the user has the relevant model permissions assigned. + +* `POST` requests require the user to have the `add` permission on the model. +* `PUT` and `PATCH` requests require the user to have the `change` permission on the model. +* `DELETE` requests require the user to have the `delete` permission on the model. + +The default behaviour can also be overridden to support custom model permissions. For example, you might want to include a `view` model permission for `GET` requests. + +To use custom model permissions, override `DjangoModelPermissions` and set the `.perms_map` property. Refer to the source code for details. + +The `DjangoModelPermissions` class also supports object-level permissions. Third-party authorization backends such as [django-guardian][guardian] that provide object-level permissions should work just fine with `DjangoModelPermissions` without any custom configuration required. + +## Custom permissions + +To implement a custom permission, override `BasePermission` and implement the `.has_permission(self, request, obj=None)` method. + +The method should return `True` if the request should be granted access, and `False` otherwise. + + +[cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html +[authentication]: authentication.md +[throttling]: throttling.md +[contribauth]: https://docs.djangoproject.com/en/1.0/topics/auth/#permissions +[guardian]: https://github.com/lukaszb/django-guardian
\ No newline at end of file diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md index 6e42b68e..f3cb0c64 100644 --- a/docs/api-guide/reverse.md +++ b/docs/api-guide/reverse.md @@ -1,12 +1,12 @@ <a class="github" href="reverse.py"></a> -# Returning URIs from your Web APIs +# Returning URLs > The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components. > > — Roy Fielding, [Architectural Styles and the Design of Network-based Software Architectures][cite] -As a rule, it's probably better practice to return absolute URIs from you web APIs, such as `http://example.com/foobar`, rather than returning relative URIs, such as `/foobar`. +As a rule, it's probably better practice to return absolute URIs from you Web APIs, such as `http://example.com/foobar`, rather than returning relative URIs, such as `/foobar`. The advantages of doing so are: diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index ae8dce76..2513928c 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -2,9 +2,13 @@ # Settings -Configuration for REST framework is all namespaced inside the `API_SETTINGS` setting. +> Namespaces are one honking great idea - let's do more of those! +> +> — [The Zen of Python][cite] -For example your project's `settings.py` file might look like this: +Configuration for REST framework is all namespaced inside a single Django setting, named `API_SETTINGS`. + +For example your project's `settings.py` file might include something like this: API_SETTINGS = { 'DEFAULT_RENDERERS': ( @@ -133,3 +137,5 @@ The name of a URL parameter that may be used to override the HTTP `Accept` heade If the value of this setting is `None` then URL accept overloading will be disabled. Default: `'_accept'` + +[cite]: http://www.python.org/dev/peps/pep-0020/ diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index e3a66c83..10997801 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -1,3 +1,151 @@ <a class="github" href="throttling.py"></a> # Throttling + +> HTTP/1.1 420 Enhance Your Calm +> +> [Twitter API rate limiting response][cite] + +[cite]: https://dev.twitter.com/docs/error-codes-responses + +Throttling is similar to [permissions], in that it determines if a request should be authorized. Throttles indicate a temporary state, and are used to control the rate of requests that clients can make to an API. + +As with permissions, multiple throttles may be used. Your API might have a restrictive throttle for unauthenticated requests, and a less restrictive throttle for authenticated requests. + +Another scenario where you might want to use multiple throttles would be if you need to impose different constraints on different parts of the API, due to some services being particularly resource-intensive. + +Multiple throttles can also be used if you want to impose both burst throttling rates, and sustained throttling rates. For example, you might want to limit a user to a maximum of 60 requests per minute, and 1000 requests per day. + +Throttles do not necessarily only refer to rate-limiting requests. For example a storage service might also need to throttle against bandwidth, and a paid data service might want to throttle against a certain number of a records being accessed. + +## How throttling is determined + +As with permissions and authentication, throttling in REST framework is always defined as a list of classes. + +Before running the main body of the view each throttle in the list is checked. +If any throttle check fails an `exceptions.Throttled` exception will be raised, and the main body of the view will not run. + +## Setting the throttling policy + +The default throttling policy may be set globally, using the `DEFAULT_THROTTLES` and `DEFAULT_THROTTLE_RATES` settings. For example. + + API_SETTINGS = { + 'DEFAULT_THROTTLES': ( + 'djangorestframework.throttles.AnonThrottle', + 'djangorestframework.throttles.UserThrottle', + ) + 'DEFAULT_THROTTLE_RATES': { + 'anon': '100/day', + 'user': '1000/day' + } + } + +The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `minute`, `hour` or `day` as the throttle period. + +You can also set the throttling policy on a per-view basis, using the `APIView` class based views. + + class ExampleView(APIView): + throttle_classes = (UserThrottle,) + + def get(self, request, format=None): + content = { + 'status': 'request was permitted' + } + return Response(content) + +Or, if you're using the `@api_view` decorator with function based views. + + @api_view('GET') + @throttle_classes(UserThrottle) + def example_view(request, format=None): + content = { + 'status': 'request was permitted' + } + return Response(content) + +## AnonRateThrottle + +The `AnonThrottle` will only ever throttle unauthenticated users. The IP address of the incoming request is used to generate a unique key to throttle against. + +The allowed request rate is determined from one of the following (in order of preference). + +* The `rate` property on the class, which may be provided by overriding `AnonThrottle` and setting the property. +* The `DEFAULT_THROTTLE_RATES['anon']` setting. + +`AnonThrottle` is suitable if you want to restrict the rate of requests from unknown sources. + +## UserRateThrottle + +The `UserThrottle` will throttle users to a given rate of requests across the API. The user id is used to generate a unique key to throttle against. Unauthenticted requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against. + +The allowed request rate is determined from one of the following (in order of preference). + +* The `rate` property on the class, which may be provided by overriding `UserThrottle` and setting the property. +* The `DEFAULT_THROTTLE_RATES['user']` setting. + +An API may have multiple `UserRateThrottles` in place at the same time. To do so, override `UserRateThrottle` and set a unique "scope" for each class. + +For example, multiple user throttle rates could be implemented by using the following classes... + + class BurstRateThrottle(UserRateThrottle): + scope = 'burst' + + class SustainedRateThrottle(UserRateThrottle): + scope = 'sustained' + +...and the following settings. + + API_SETTINGS = { + 'DEFAULT_THROTTLES': ( + 'example.throttles.BurstRateThrottle', + 'example.throttles.SustainedRateThrottle', + ) + 'DEFAULT_THROTTLE_RATES': { + 'burst': '60/min', + 'sustained': '1000/day' + } + } + +`UserThrottle` is suitable if you want simple global rate restrictions per-user. + +## ScopedRateThrottle + +The `ScopedThrottle` class can be used to restrict access to specific parts of the API. This throttle will only be applied if the view that is being accessed includes a `.throttle_scope` property. The unique throttle key will then be formed by concatenating the "scope" of the request with the unqiue user id or IP address. + +The allowed request rate is determined by the `DEFAULT_THROTTLE_RATES` setting using a key from the request "scope". + +For example, given the following views... + + class ContactListView(APIView): + throttle_scope = 'contacts' + ... + + class ContactDetailView(ApiView): + throttle_scope = 'contacts' + ... + + class UploadView(APIView): + throttle_scope = 'uploads' + ... + +...and the following settings. + + API_SETTINGS = { + 'DEFAULT_THROTTLES': ( + 'djangorestframework.throttles.ScopedRateThrottle', + ) + 'DEFAULT_THROTTLE_RATES': { + 'contacts': '1000/day', + 'uploads': '20/day' + } + } + +User requests to either `ContactListView` or `ContactDetailView` would be restricted to a total of 1000 requests per-day. User requests to `UploadView` would be restricted to 20 requests per day. + +## Custom throttles + +To create a custom throttle, override `BaseThrottle` and implement `.allow_request(request)`. The method should return `True` if the request should be allowed, and `False` otherwise. + +Optionally you may also override the `.wait()` method. If implemented, `.wait()` should return a recomended number of seconds to wait before attempting the next request, or `None`. The `.wait()` method will only be called if `.check_throttle()` has previously returned `False`. + +[permissions]: permissions.md
\ No newline at end of file |
