findig.tools.protector
— Authorization tools¶
For many web API resources, it is desirable to restrict access to only specific users or clients that have been authorized to use them. The tools in this module provide one mechanism for putting such restrictions in place.
The Protector
is the core tool provided here. Its instances collect
information about resources that should be guarded against authorized access,
and on each request, it checks that requests to those resources present a valid
authorization.
The precise authorization mechanism used by the protector is controlled by
the GateKeeper
abstract class for which an application developer may
supply their own concrete instance. Alternatively, some protectors
supply their own gatekeepers (example: BasicProtector
uses RFC 2617#section-2 as its authorization mechanism).
Scopes¶
Protectors provide implicit support for ‘scopes’ (an idea borrowed from OAuth 2, with some enhancements). While completely optional, their use provides a way allow access to only portions of an API while denying access to others.
An authorization (commonly represented by a token) may have some scopes (identified by application chosen strings) associated with it which define what portions of the API that a request using the authorization can access; only resources belong to one of the scopes associated with the authorization, or resources that belong to no scopes are accessible. Protectors provide a mechanism for marking a guarded resource as belonging to a scope.
For example, an application may provide a resource guarded under the scope
foobar
. In order to access the resource, then a request must present
authorization that encapsulates the foobar
scope, otherwise the request
is denied.
Tip
While recommended, using scopes is optional. In fact, some
authorization mechanisms do not provide a way to encapsulate
scopes. To provide scope support for a custom authentication
mechanism that encapsulates scopes, see GateKeeper
.
Findig extends authorization scopes with special semantics that can affect the way they are used by a protector. The grammar for authorization scopes is given below:
auth_scope ::= scope_name["+"permissions] scope_name ::= scope_fragment{"/"scope_fragment} permissions ::= permission{permission} permission ::= "c" | "r" | "u" | "d"
scope_fragment
is token that does not include the ‘+’ or ‘/’ characters.
Whenever a permission is omitted, the ‘r’ is permission is implied.
Permissions are used to control which actions an authorization permits on the resources falling into its scope, according to this table:
Action | Permission |
---|---|
HEAD | r |
GET | r |
POST | c |
PUT | c and u |
DELETE | d |
So for example, an authorization with the scope foobar+rd
can read
and delete resources under the foobar
scope.
The ‘/’ character is used to denote sub-scopes (and super-scopes). foo/bar
is considered a sub-scope of foo
(and foo
a super-scope of
foo/bar
), and so on. This is useful, because by default if a request
possesses authorization for a super-scope, then this implicitly authorizes
its sub-scopes as well.
Scopes attached to a resource follow a simpler grammar:
resource_scope ::= scope_name
In other words, the permissions are omitted (because the protector multiplexes which permission is required from the request method).
The findig.tools.protector.scopeutil
module provides some functions
for working with scopes.
Protectors¶
-
class
findig.tools.protector.
Protector
(app=None, subscope_separator="/", gatekeeper=None)[source]¶ A protector is responsible for guarding access to a restricted resource:
from findig import App app = App() protector = Protector(app) protector.guard(resource)
Parameters: - app – A findig application instance.
- subscope_separator – A separator used to denote sub-scopes.
- gatekeeper – A concrete implementation of
GateKeeper
. If not provided, the protector will deny all requests to its guarded resources.
-
attach_to
(app)[source]¶ Attach the protector to a findig application.
Note
This is called automatically for any app that is passed to the protector’s constructor.
By attaching the protector to a findig application, the protector is enabled to intercept requests made to the application, performing authorization checks as needed.
Parameters: app ( findig.App
, or a subclass likefindig.json.App
.) – A findig application whose requests the protector will intercept.
-
guard
(resource[, scope[, scope[, ...]]])[source]¶ Guard a resource against unauthorized access. If given, the
scopes
will be used to protect the resource (similar to oauth) such that only requests with the appropriatescope
will be allowed through.If this function is called more than once, then a grant by any of the specifications will allow the request to access the resource. For example:
# This protector will allow requests to res with BOTH # "user" and "friends" scope, but it will also allow # requests with only "foo" scope. protector.guard(res, "user", "friends") protector.guard(res, "foo")
A protector can also be used to decorate resources for guarding:
@protector.guard @app.route("/foo") def foo(): # This resource is guarded with no scopes; any authenticated # request will be allowed through. pass @protector.guard("user/email_addresses") @app.route("/bar") def bar(): # This resource is guarded with "user/email_addresses" scope, # so that only requests authorized with that scope will be # allowed to access the resource. pass @protector.guard("user/phone_numbers", "user/contact") @app.route("/baz") def baz(): # This resource is guarded with both "user/phone_numbers" and # "user/contact" scope, so requests must be authorized with both # to access this resource. pass # NOTE: Depending on the value passed for 'subscope_separator' to the # protector's constructor, authenticated requests authorized with "user" scope # will also be allowed to access all of these resources (default behavior).
-
authenticated_client
¶ Get the client id of the authenticated client for the current request, or None.
-
authenticated_user
¶ Get the username/id of the authenticated user for the current request.
Get the a list of authorized scopes for the current request.
-
class
findig.tools.protector.
BasicProtector
(app=None, subscope_separator='/', auth_func=None, realm='guarded')[source]¶ A
Protector
that implements HTTP Basic Auth.While straightforward, this protector has a few security considerations:
- Credentials are transmitted in plain-text. If you must use this protector, then at the very least the HTTPS protocol should be used.
- Credentials are transmitted with each request. It requires that clients either store user credentials, or prompt the user for their credentials at frequent intervals (possibly every request).
- This protector offers no scoping support; a grant from this protector allows unlimited access to any resource that it guards.
-
auth_func
(fauth)[source]¶ Supply an application-defined function that performs authentication.
The function has the signature
fauth(username:str, password:str) -> bool
and should return whether or not the credentials given authenticate successfully.auth_func is usable as a decorator:
@protector.auth_func def check_credentials(usn, pwd): user = db.get_obj(usn) return user.password == pwd
GateKeepers¶
Each Protector
should be supplied with a GateKeeper
that extracts any authorization information embedded in
a request. Protector
uses a default gatekeeper which denies all
requests made to its guarded resources.
An application may provide its own gatekeeper that implements the
desired authorization mechanism. That’s done by implementing the
GateKeeper
abstract base class.
-
class
findig.tools.protector.
GateKeeper
[source]¶ To implement a gatekeeper, implement at least
check_auth()
andget_username()
.-
check_auth
()[source]¶ Try to perform an authorization check using the request context variables.
Perform the authorization check using whatever mechanism that the gatekeeper’s authorization is handled. If authorization fails, then an
Unauthorized
error should be raised.Return a ‘grant’ that will be used to query the gatekeeper about the authorization.
-