Services/Authentication: Difference between revisions

Jump to navigation Jump to search
no edit summary
No edit summary
No edit summary
Line 13: Line 13:
Every user of a services app has the following unique identifiers:
Every user of a services app has the following unique identifiers:


* an email address by which they identify themselves, and
* an email address by which they identify themselves;
* a username, formed by taking the sha1 hash of the email address, and
* a username, formed by taking the sha1 hash of the email address; and
* a numeric userid by which they are identified in persistent storage.
* a numeric userid by which they are identified in persistent storage.


The email address may be mutable and is selected by the user; the userid is immutable and is generated automatically.
The email address may be mutable and is selected by the user, while the userid is immutable and is generated automatically by the registration system.


In code, the user is represented by a dict of their user data.  It will always include the keys "username" and "userid".  Applications may also arrange for other keys to be loaded into this dict.
In code, the user is represented by a dict of their user data.  It will always include the keys "username" and "userid".  Applications may also arrange for other keys to be loaded into this dict.


= Workflow =


Authentication in services apps is loosely based on the workflow of the [http://docs.repoze.org/who/2.0/ repoze.who] framework, and is explicitly divided into four stages: identify, authenticate, challenge and acknowledge.
= User Backends =


The details of each stage in this workflow are hidden from the application code, but they exist as distinct extension and configuration points as described below.
User account information is stored in a generic "backend" with the following interface:


== Identify ==
XXX TODO describe the api


The first step is to extract credentials from an incoming request.  For an interactive application this might mean gathering the information that was submitted from a login form.  For automation APIs this will most likely mean parsing credentials from the HTTP "Authorization" header in conformance with [http://www.ietf.org/rfc/rfc2617.txt RFC 2617].


The outcome of this stage is a dict of authentication credentials.  The precise contents of the dict will depend on the authentication scheme being used, but it must contain at least the following:
= Application API =
 
* username:  the string username by which the user identifies themselves
* scheme:  a string naming the authentication scheme to use
 
== Authenticate ==
 
The extracted credentials dict is then checked for authenticity according to the particular authentication scheme in use.  If successfully authenticated, this produces the dict of user information for use by the application.
 
== Challenge ==


If the client provides invalid credentials (or no credentials) then the application will respond with one or more authentication challenges. For interactive applications a challenge would normally be a HTML form for them to submit their credentials. For automation APIs the challenge would be a "401 Authorization Required" response.
The authentication stack is build on the [http://docs.repoze.org/who/2.0/ repoze.who] framework.


== Acknowledge ==
== Using Pyramid ==


If the client provides valid credentials, the server may include headers in its response to acknowledge the successful authentication.  For example, it may set a session cookie.
Applications built using the new pyramid-based framework should activate authentication by including the "mozsvc.user" package into their pyramid config.  This will set up authentication via repoze.who and the internal users database, as well as providing some handy shortcuts:
 
= Application API =
 
== Loading the Framework ==
 
Applications built using the new pyramid-based framework can hook into this system by including the "mozsvc.user" package into their pyramid config:


     def main(global_config, **settings):
     def main(global_config, **settings):
         config = get_configurator(global_config, **settings)
         config = get_configurator(global_config, **settings)
         # adds Mozilla default views
       
         # add Mozilla default views
         config.include("mozsvc")
         config.include("mozsvc")
         # adds the Mozilla auth framework
       
         # add the Mozilla users database
         config.include("mozsvc.user")
         config.include("mozsvc.user")
       
         ...etc...
         ...etc...
         return config.make_wsgi_app()
         return config.make_wsgi_app()


Applications using the old server-core framework will have authentication loaded automatically.
Each request object will then have a "user" attribute that provides the identity dict for a successfully authenticated user.  If the user is not authenticated then request.user will be something falsey (currently an empty dict, possibly None in the future):
 
== Getting the User ==
 
Each request object will have a "user" attribute that provides the identity dict for a successfully authenticated user.  If the user is not authenticated then request.user will be false.


     def some_view(request):
     def some_view(request):
Line 74: Line 56:
         return Response("Hello %s" % (request.user["username"],))
         return Response("Hello %s" % (request.user["username"],))


== Demanding Credentials ==
Permission checking is performed via the standard pyramid permissions system. To force the user to provide credentials, you can either [http://readthedocs.org/docs/pyramid/en/latest/narr/security.html#protecting-views-with-permissions assign permissions to your views] or explicitly raise a [http://readthedocs.org/docs/pyramid/en/latest/api/exceptions.html#pyramid.exceptions.Forbidden Forbidden exception].
 
The active user backend is available in the pyramid registry under the key "auth", and can be used to perform user-maintenance tasks like so:


For pyramid applications, you can force the user to provide valid credentials using the standard permission system, either by assigning permissions to your views or by explicitly raising Forbidden when the user is not authenticated.
    request.registry["auth"].create_user("testuser", "testpass", "test@example.com")


For server-core applications, you must configure your route match to include the key "auth" with value "True".
If no backend is in use (e.g. because users are being authenticated against a third-party service) then request.registry["auth"] will be None.


== User Backends ==


User account information is stored in a generic "backend" with the following interface:
== Using server-core ==
 
Applications using the server-core framework should use services.whoauth.WhoAuthentication as their auth management class.  In the wsgiapp.py file use something like:
 
    from services.baseapp import set_app, SyncServerApp
    from services.whoauth import WhoAuthentication
 
    #  ...define urls and controllers here...
 
    make_app = set_app(urls, controllers,
                      klass=MyCustomServerApp,
                      auth_class=WhoAuthentication)
 
Each request object will then have a "user" attribute that provides the identity dict for a successfully authenticated user.  If the user is not authenticated then request.user will be None:


XXX TODO describe the api
    def some_view(request):
        if not request.user:
            return Response("You're not logged in")
        return Response("Hello %s" % (request.user["username"],))


For pyramid applications, the active user backend can be accessed through the pyramid registry:
To force authentication for a particular URL, configure [http://routes.groovie.org/ Routes] to add {"auth": True} into the matched variables.  Typically this would look something like:


     request.registry["auth"].create_user("testuser", "testpass", "test@example.com")
     urls = [('GET', '/{username}/info'), "controller_name", "action_name", {"auth": True})]


For server-core applications, the active user backend is available as an attribute of the controller:
The active user backend is available as the "auth" attribute on the application object, and can be used to perform user-maintenance tasks like so:


     self.auth.create_user("testuser", "testpass", "test@example.com")
     self.auth.create_user("testuser", "testpass", "test@example.com")


If no backend is in use (e.g. because users are being authenticated against a third-party service) then these objects will be None.
If no backend is in use (e.g. because users are being authenticated against a third-party service) then self.auth will be None.
 


= Configuration =
= Configuration =
Line 124: Line 124:
     plugins = backend
     plugins = backend


= Workflow =
Authentication in services apps is loosely based on the workflow of the [http://docs.repoze.org/who/2.0/ repoze.who] framework, and is explicitly divided into four stages: identify, authenticate, challenge and acknowledge.
The details of each stage in this workflow are hidden from the application code, but they exist as distinct extension and configuration points as described below.
== Identify ==
The first step is to extract credentials from an incoming request.  For an interactive application this might mean gathering the information that was submitted from a login form.  For automation APIs this will most likely mean parsing credentials from the HTTP "Authorization" header in conformance with [http://www.ietf.org/rfc/rfc2617.txt RFC 2617].
The outcome of this stage is a dict of authentication credentials.  The precise contents of the dict will depend on the authentication scheme being used, but it must contain at least the following:
* username:  the string username by which the user identifies themselves
* scheme:  a string naming the authentication scheme to use
== Authenticate ==
The extracted credentials dict is then checked for authenticity according to the particular authentication scheme in use.  If successfully authenticated, this produces the dict of user information for use by the application.
== Challenge ==
If the client provides invalid credentials (or no credentials) then the application will respond with one or more authentication challenges.  For interactive applications a challenge would normally be a HTML form for them to submit their credentials.  For automation APIs the challenge would be a "401 Authorization Required" response.
== Acknowledge ==
If the client provides valid credentials, the server may include headers in its response to acknowledge the successful authentication.  For example, it may set a session cookie.
= Authentication Schemes =
= Authentication Schemes =


Confirmed users
358

edits

Navigation menu