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 like findig.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 appropriate scope 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.

authorized_scope

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() and get_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.

get_clientid(grant)[source]

Return the client that sent the request to the grant. (Optional)

get_scopes(grant)[source]

Return a list of scopes that the grant is authorized with. (Optional)

get_username(grant)[source]

Return the username/id of the user that authorized the grant.