Confirmed users
358
edits
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 | * an email address by which they identify themselves; | ||
* a username, formed by taking the sha1 hash of the email address | * 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 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. | ||
= User Backends = | |||
User account information is stored in a generic "backend" with the following interface: | |||
XXX TODO describe the api | |||
= Application API = | |||
The authentication stack is build on the [http://docs.repoze.org/who/2.0/ repoze.who] framework. | |||
== | == Using Pyramid == | ||
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: | |||
Applications built using the new pyramid-based framework | |||
def main(global_config, **settings): | def main(global_config, **settings): | ||
config = get_configurator(global_config, **settings) | config = get_configurator(global_config, **settings) | ||
# | |||
# add Mozilla default views | |||
config.include("mozsvc") | config.include("mozsvc") | ||
# | |||
# 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() | ||
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): | |||
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 | |||
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"],)) | ||
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: | |||
request.registry["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 request.registry["auth"] will be None. | |||
== 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: | |||
def some_view(request): | |||
if not request.user: | |||
return Response("You're not logged in") | |||
return Response("Hello %s" % (request.user["username"],)) | |||
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: | |||
urls = [('GET', '/{username}/info'), "controller_name", "action_name", {"auth": True})] | |||
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 | 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 = | ||