Package: talons
# Talons == Falcon Hooks [![Build Status](https://travis-ci.org/talons/talons.png)](https://travis-ci.org/talons/talons)Talons is a library of WSGI middleware that is designed to work withthe [Falcon](http://github.com/racker/falcon) lightweight Python frameworkfor building RESTful APIs. Like Falcon, Talons aims to be fast, light, andflexible.The first middleware in Talons is authentication middleware, enabling oneor more backend identity plugins to handle authentication.# What is `talons.auth`?`talons.auth` is a namespace package that contains utilies forconstructing identifying and authenticating middleware and pluginsdesigned for applications running the Falcon WSGI micro-frameworkfor building REST APIs.## A simple usage exampleA simple Falcon API application is constructed like so:```pythonimport falcon# falcon.API instances are callable WSGI appsapp = falcon.API()```To add middleware to a Falcon API application, we simply instantiate thedesired `talons.auth` middleware and supply it to the `falcon.API()` call:```pythonimport falconfrom talons.auth import middlewarefrom talons.auth import basicauth, httpheader, htpasswd# Assume getappconfig() returns a dictionary of application configuration# options that may have been read from some INI file...config = getappconfig()auth_middleware = middleware.create_middleware(identify_with=[basicauth.Identifier,httpheader.Identifier],authenticate_with=htpasswd.Authenticator,**config)app = falcon.API(before=[auth_middleware])```# DetailsThere are a variety of basic plugins that handle identification of the user makingan API request and authenticating credentials with a number of common backends,including LDAP and SQL data stores.Authentication involves two main tasks:* Identifying the user who wishes to be authenticated* Validating credentials for the identified userClasses that derive from `talons.auth.interfaces.Identifies` implement an `identify`method that takes the `falcon.request.Request` object from the WSGI pipeline andlooks at elements of the request to determine who the requesting user is.The class that stores credential information -- including a login, password/key,a set of roles or groups, as well as other metadata about the requesting user --is the `talons.auth.interfaces.Identity` class. `talons.auth.interfaces.Identifies`subclasses store this `Identity` object in the WSGI environs' "wsgi.identity" bucket.Classes that derive from `talons.auth.interfaces.Authenticates` implement an`authenticate` method that takes a single argument -- a `talons.auth.interfaces.Identity`object -- and attempts to validate that the identity is authentic.To give your Falcon-based WSGI application authentication capabilities, yousimply create middleware that has one or more `talons.auth.identify` modulesand one or more `talons.auth.authenticate` modules. We even give you a helpermethod -- `talons.auth.middleware.create_middleware` -- to create such middlewarein a single call.## IdentifiersEach class that derives from `talons.auth.interfaces.Identifies` is called an "Identifier". Eachclass implements a single method, `identify()`, that takes the incoming `falcon.request.Request`object as its sole parameter. If the identity of the authenticating user can be determined,then the Identifier object stores a `talons.auth.interfaces.Identity` object in the WSGI environ's`wsgi.identity` key and returns True.Multiple Identifier classes can be supplied to the`talons.auth.middleware.create_middleware` method to support a variety of ways ofgleaning identity information from the WSGI request. Each Identifier's`identify()` method checks to see if the `wsgi.identity` key is alreadyset in the WSGI environs. If it is, the method simply returns True and doesnot attempt to process anything further.### `talons.auth.basicauth.Identifier`The most basic identifier, `talons.auth.basicauth.Identifier` has noconfiguration options and simply looks in the[`Authenticate`](http://en.wikipedia.org/wiki/Basic_access_authentication) HTTPheader for credential information. If the `Authenticate` HTTP header is foundand contains valid credential information, then that identity information isstored in the `wsgi.identity` WSGI environs key.### `talons.auth.httpheader.Identifier`Another simple identifier, `talons.auth.httpheader.Identifier` looksfor configurable HTTP headers in the incoming WSGI request, and uses the valuesof the HTTP headers to construct a `talons.auth.Identity` object.A set of configuration options control how this Identifier class behaves:* `httpheader_user`: HTTP header to look for user/loginname (required)* `httpheader_key`: HTTP header to look for password/key(required)* `httpheader_$ATTRIBUTE`: HTTP header that, if found, willbe used to add $ATTRIBUTE to the Identity object stored in the WSGIpipeline. (optional)The above configuration options are supplied to the constructor as keywordarguments.#### ExampleSuppose we wanted to extract identity information from the following HTTPHeaders:* `X-Auth-User` -- The value of this header will be the authenticating user'suser name* `X-Auth-Password` -- The value of this header will be the authenticatinguser's password* `X-Auth-Domain` -- The value of this header should be considered theauthentication domain that will be considered when authenticating theidentity. We want to store this value on the `talons.auth.Identity` object's`domain` attribute.Our configuration options would look like this:```httpheader_user=x-auth-userhttpheader_key=x-auth-passwordhttpheader_domain=x-auth-domain```## AuthenticatorsEach class that derives from `talons.auth.interfaces.Authenticates` iscalled an "Authenticator". Each Authenticator implements a single method,`authenticate()`, that takes a `talons.auth.interfaces.Identity` objectas its sole parameter.The `authenticate` method verifies that the supplied identity can beverified (authenticated). Different implementations will rely on variousbackend storage systems to validate the incoming identity/credentials.If authentication was successful, the method returns True, False otherwise.Talons comes with a few simple examples of Authenticator plugins.### `talons.auth.external.Authenticator`A generic Authenticator plugin that has one main configuration option,`external_authn_callable` which should be the "module.function" or"module.class.method" dotted-import notation for a function or classmethod that accepts a single parameter. This function will be called bythe instance of `talons.auth.authenticate.external.Authenticator` tovalidate the credentials of a request.In addition, there are two other configuration options that indicatewhether the `external_authfn` function may set the roles or groupsattributes on the supplied identity:* `external_sets_roles`: Boolean (defaults to False). A True valueindicates the plugin may set the roles attribute on the identityobject.* `external_sets_groups`: Boolean (defaults to False). A True valueindicates the plugin may set the groups attribute on the identityobject.#### ExampleSuppose we have some application code that looks up a stored passwordfor a user in a [`Redis`](http://redis.io) Key-Value Store. Salted, encryptedpasswords for each user are stored in the Redis KVS, along with acomma-separated list of roles the user belongs to.Our application has a Python file called `/application/auth.py` that lookslike this:```pythonimport hashlibimport redis_AUTH_DB = redis.StrictRedis(host='localhost', port=6379, db=0)def _pass_matches_stored_pass(pass, stored_pass):# Assume that passwords are stored in Redis in the following format:# salt:hashedpass# and that the passwords have been hashed with SHA-256salt, stored_hashed_pass = stored_pass.split(':')hashed_pass = hashlib.sha256(salt.encode() + pass.encode()).hexdigest()return hashed_pass == stored_hashed_passdef authenticate(identity):user = identity.loginpass = identity.key# Assume that user "records" are stored in Redid in the following format:# salt:hashedpass#roles# Where roles is a comma-separated list of rolesuser_record = _AUTH_DB.get(user)if user_record:stored_pass, role_list = user_record.split('#')auth_success = _pass_matches_stored_pass(pass, stored_pass)if auth_success:identity.roles = role_list.split(',')return auth_success```To use the above `application.auth.authenticate` method for authenticatingidentities, we'd supply the following configuration options to the`talons.auth.external.Authenticator` constructor:* `external_authn_callable=application.auth.authenticate`* `external_sets_roles=True`### `talons.auth.htpasswd.Authenticator`An Authenticator plugin that queries an Apache htpasswd file to checkthe credentials of a request. The plugin has a single configuration option:* `htpasswd_path`: The filepath to the Apache htpasswd file touse for authentication checks.## AuthorizersEach class that derives from `talons.auth.interfaces.Authorizes` iscalled an "Authorizer". Each Authorizer implements a single method,`authorize()`, that takes a `talons.auth.interfaces.Identity` object,and a `talons.auth.interfaces.ResourceAction` object.The `ResourceAction` object currently has a single method, `to_string`,that returns a "dotted-notation" string describing the requestedHTTP resource.For instance, let's say the identity made an HTTP request to:POST /users/12345/groupsThe `ResourceAction.to_string` method that is supplied to the `authorize`function would yield the string "users.12345.groups.post". This string isuseful to plugins that compare the string with the supplied identity object.See below for an example that makes this more clear.At present, there is only a single Authorizer built in to Talons: the`talons.auth.external.Authorizer` class. Like its sister, the`talons.auth.external.Authenticator`, it accepts an external callable thataccepts the identity and resource action parameters and returns whetherthe identity is allowed to perform the action on the resource. The singleconfiguration parameter is called `external_authz_callable`.Let's continue the example from above and add an external callable thatwill be used as an authorizer. This callable will compare the result ofthe `ResourceAction`'s `to_string` method against the supplied identityobject and a hashmap of regular expressions in order to determine if theuser is permitted to perform an action.Assuming our application has a Python file called `/application/auth.py` thatcontains the above authenticate code, as well as this:like this:```pythonimport redef self_or_admin(match, identity):"""Returns True if the identity has an admin role or the identitymatches the requesting user."""if "admin" in identity.roles:return Truereturn match.groups(1) == identity.logindef anyone(*args):return True_POLICY_RULES = [(r'^users\.(^\.)+\.get$', self_or_admin),(r'^users\.post$', anyone),]POLICIES = []for regex, fn in _POLICY_RULES:POLICIES.append((re.compile(regex), fn))def authorize(identity, resource_action):user = identity.loginres_string = resource_action.to_string()for p, fn in _POLICIES:m = p.match(res_string)if m:return fn(m, identity)```To use the above `application.auth.authorize` method for authorizing theidentity that was authenticated, we'd supply the following configurationoptions to the `talons.auth.external.Authorizer` constructor:* `external_authz_callable=application.auth.authorize`Why `talons.auth`?==================Why not just use middleware like [repose.who](http://docs.repoze.org/who/2.0/index.html) forauthentication plugins? Why re-invent the wheel here?A few reasons, in no particular order:* Use of the Webob library. I'm not a fan of it, as I've run into numerous issues withthis library over the years.* Use of zope.interfaces. Also not a fan of it. It's a library that seems to be designedfor traditional C++ programmers instead of feeling like it's designed for Python developers.Just use the [abc](http://docs.python.org/2/library/abc.html) module if you absolutely musthave strict interface enforcement.* Trying to override things like logging setup in constructors of middleware.* No Paste.* Wanted something that fit Falcon's app construction paradigm.But hey, there's nothing inherently wrong with repoze.who. If you like it, and it worksfor you, use it.## Contributing[Jay Pipes](http://joinfu.com) maintains the Talons library. You can usually find him on the Freenode IRC #openstack-devchannel. Interested in improving and enhancing Talons? Pull requests are always welcome.## License and CopyrightCopyright 2013-2014, Jay PipesLicensed under the Apache License, Version 2.0 (the "License"); you may not use this fileexcept in compliance with the License. You may obtain a copy of the License athttp://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, software distributed under the Licenseis distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressor implied. See the License for the specific language governing permissions and limitations underthe License.