File: pipeline.rst

package info (click to toggle)
python-social-auth 0.2.1-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 2,828 kB
  • ctags: 3,245
  • sloc: python: 12,867; makefile: 119; sh: 3
file content (344 lines) | stat: -rw-r--r-- 13,955 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
Pipeline
========

python-social-auth_ uses an extendible pipeline mechanism where developers can
introduce their functions during the authentication, association and
disconnection flows.

The functions will receive a variable set of arguments related to the current
process, common arguments are the current ``strategy``, ``user`` (if any) and
``request``. It's recommended that all the function also define an ``**kwargs``
in the parameters to avoid errors for unexpected arguments.

Each pipeline entry can return a ``dict`` or ``None``, any other type of return
value is treated as a response instance and returned directly to the client,
check *Partial Piepeline* below for details.

If a ``dict`` is returned, the value in the set will be merged into the
``kwargs`` argument for the next pipeline entry, ``None`` is taken as if ``{}``
was returned.


Authentication Pipeline
-----------------------

The final process of the authentication workflow is handled by an operations
pipeline where custom functions can be added or default items can be removed to
provide a custom behavior. The default pipeline is a mechanism that creates
user instances and gathers basic data from providers.

The default pipeline is composed by::

    (
        # Get the information we can about the user and return it in a simple
        # format to create the user instance later. On some cases the details are
        # already part of the auth response from the provider, but sometimes this
        # could hit a provider API.
        'social.pipeline.social_auth.social_details',

        # Get the social uid from whichever service we're authing thru. The uid is
        # the unique identifier of the given user in the provider.
        'social.pipeline.social_auth.social_uid',

        # Verifies that the current auth process is valid within the current
        # project, this is were emails and domains whitelists are applied (if
        # defined).
        'social.pipeline.social_auth.auth_allowed',

        # Checks if the current social-account is already associated in the site.
        'social.pipeline.social_auth.social_user',

        # Make up a username for this person, appends a random string at the end if
        # there's any collision.
        'social.pipeline.user.get_username',

        # Send a validation email to the user to verify its email address.
        # Disabled by default.
        # 'social.pipeline.mail.mail_validation',

        # Associates the current social details with another user account with
        # a similar email address. Disabled by default.
        # 'social.pipeline.social_auth.associate_by_email',

        # Create a user account if we haven't found one yet.
        'social.pipeline.user.create_user',

        # Create the record that associated the social account with this user.
        'social.pipeline.social_auth.associate_user',

        # Populate the extra_data field in the social record with the values
        # specified by settings (and the default ones like access_token, etc).
        'social.pipeline.social_auth.load_extra_data',

        # Update the user record with any changed info from the auth service.
        'social.pipeline.user.user_details'
    )


It's possible to override it by defining the setting ``SOCIAL_AUTH_PIPELINE``,
for example a pipeline that won't create users, just accept already registered
ones would look like this::

    SOCIAL_AUTH_PIPELINE = (
        'social.pipeline.social_auth.social_details',
        'social.pipeline.social_auth.social_uid',
        'social.pipeline.social_auth.auth_allowed',
        'social.pipeline.social_auth.social_user',
        'social.pipeline.social_auth.associate_user',
        'social.pipeline.social_auth.load_extra_data',
        'social.pipeline.user.user_details'
    )

Note that this assumes the user is already authenticated, and thus the ``user`` key
in the dict is populated. In cases where the authentication is purely external, a
pipeline method must be provided that populates the ``user`` key. Example::


    SOCIAL_AUTH_PIPELINE = (
        'myapp.pipeline.load_user',
        'social.pipeline.social_auth.social_user',
        'social.pipeline.social_auth.associate_user',
        'social.pipeline.social_auth.load_extra_data',
        'social.pipeline.user.user_details',
    )

Each pipeline function will receive the following parameters:
    * Current strategy (which gives access to current store, backend and request)
    * User ID given by authentication provider
    * User details given by authentication provider
    * ``is_new`` flag (initialized as ``False``)
    * Any arguments passed to ``auth_complete`` backend method, default views
      pass these arguments:
      - current logged in user (if it's logged in, otherwise ``None``)
      - current request


Disconnection Pipeline
----------------------

Like the authentication pipeline, it's possible to define a disconnection
pipeline if needed.

For example, this can be useful on sites where a user that disconnects all the
related social account is required to fill a password to ensure the
authentication process in the future. This can be accomplished by overriding
the default disconnection pipeline and setup a function that checks if the user
has a password, in case it doesn't a redirect to a fill-your-password form can
be returned and later continue the disconnection process, take into account
that disconnection ensures the POST method by default, a simple method to
ensure this, is to make your form POST to ``/disconnect/`` and set the needed
password in your pipeline function. Check *Partial Pipeline* below.

In order to override the disconnection pipeline, just define the setting::

    SOCIAL_AUTH_DISCONNECT_PIPELINE = (
        # Verifies that the social association can be disconnected from the current
        # user (ensure that the user login mechanism is not compromised by this
        # disconnection).
        'social.pipeline.disconnect.allowed_to_disconnect',

        # Collects the social associations to disconnect.
        'social.pipeline.disconnect.get_entries',

        # Revoke any access_token when possible.
        'social.pipeline.disconnect.revoke_tokens',

        # Removes the social associations.
        'social.pipeline.disconnect.disconnect'
    )


Partial Pipeline
----------------

It's possible to cut the pipeline process to return to the user asking for more
data and resume the process later. To accomplish this decorate the function
that will cut the process with the ``@partial`` decorator located at
``social/pipeline/partial.py``.

The old ``social.pipeline.partial.save_status_to_session`` is now deprecated.

When it's time to resume the process just redirect the user to ``/complete/<backend>/``
or ``/disconnect/<backend>/`` view. The pipeline will resume in the same
function that cut the process.

``@partial`` and ``save_status_to_session`` stores needed data into user session
under the key ``partial_pipeline``. To get the backend in order to redirect to
any social view, just do::

    backend = session['partial_pipeline']['backend']

Check the `example applications`_ to check a basic usage.


Email validation
----------------

There's a pipeline to validate email addresses, but it relies a lot on your
project.

The pipeline is at ``social.pipeline.mail.mail_validation`` and it's a partial
pipeline, it will return a redirect to an URL that you can use to tell the
users that an email validation was sent to them. If you want to mention the
email address you can get it from the session under the key ``email_validation_address``.

In order to send the validation python-social-auth_ needs a function that will
take care of it, this function is defined by the developer with the setting
``SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION``. It should be an import path. This
function should take two arguments ``strategy`` and ``code``.  ``code`` is
a model instance used to validate the email address, it contains three fields:

``code = '...'``
    Holds an ``uuid.uuid4()`` value and it's the code used to identify the
    validation process.

``email = '...'``
    Email address trying to be validate.

``verified = True / False``
    Flag marking if the email was verified or not.

You should use the code in this instance the build the link for email
validation which should go to ``/complete/email?verification_code=<code here>``, if using
Django you can do it with::

    from django.core.urlresolvers import reverse
    url = strategy.build_absolute_uri(
        reverse('social:complete', args=(strategy.backend_name,))
    ) + '?verification_code=' + code.code

On Flask::

    from flask import url_for
    url = url_for('social.complete', backend=strategy.backend_name,
                  _external=True) + '?verification_code=' + code

This pipeline can be used globally with any backend if this setting is
defined::

    SOCIAL_AUTH_FORCE_EMAIL_VALIDATION = True

Or individually by defining the setting per backend basis like
``SOCIAL_AUTH_TWITTER_FORCE_EMAIL_VALIDATION = True``.


Extending the Pipeline
======================

The main purpose of the pipeline (either creation or deletion pipelines), is to
allow extensibility for developers, you can jump in the middle of it, do
changes to the data, create other models instances, ask users for data, or even
halt the whole process.

Extending the pipeline implies:

    1. Writing a function
    2. Locate it in a accessible path (accessible in the way that it can be
       imported)
    3. Override the default pipeline definition with one that includes your
       function.

Writing the function is quite simple. Depending on the place you locate it will
determine the arguments it will receive, for example, adding your function
after ``social.pipeline.user.create_user`` ensures that you get the user
instance (created or already existent) instead of a ``None`` value.

The pipeline functions will get quite a lot of arguments, ranging from the
backend in use, different model instances, server requests and provider
responses. To enumerate a few:

``strategy``
    The current strategy instance.

``backend``
    The current backend instance.

``uid``
    User ID in the provider, this ``uid`` should identify the user in the
    current provider.

``response = {} or object()``
    The server user-details response, it depends on the protocol in use (and
    sometimes the provider implementation of such protocol), but usually it's
    just a ``dict`` with the user profile details in such provider. Lots of
    information related to the user is provider here, sometimes the ``scope``
    will increase the amount of information in this response on OAuth
    providers.

``details = {}``
    Basic user details generated by the backend, used to create/update the user
    model details (this ``dict`` will contain values like ``username``,
    ``email``, ``first_name``, ``last_name`` and ``fullname``).

``user = None``
    The user instance (or ``None`` if it wasn't created or retrieved from the
    database yet).

``social = None``
    This is the associated ``UserSocialAuth`` instance for the given user (or
    ``None`` if it wasn't created or retrieved from the DB yet).

Usually when writing your custom pipeline function, you just want to get some
values from the ``response`` parameter. But you can do even more, like call
other APIs endpoints to retrieve even more details about the user, store them
on some other place, etc.

Here's an example of a simple pipeline function that will create a ``Profile``
class related to the current user, this profile will store some simple details
returned by the provider (``Facebook`` in this example). The usual Facebook
``response`` looks like this::

    {
        'username': 'foobar',
        'access_token': 'CAAD...',
        'first_name': 'Foo',
        'last_name': 'Bar',
        'verified': True,
        'name': 'Foo Bar',
        'locale': 'en_US',
        'gender': 'male',
        'expires': '5183999',
        'email': 'foo@bar.com',
        'updated_time': '2014-01-14T15:58:35+0000',
        'link': 'https://www.facebook.com/foobar',
        'timezone': -3,
        'id': '100000126636010'
    }

Let's say we are interested in storing the user profile link, the gender and
the timezone in our ``Profile`` model::

    def save_profile(backend, user, response, *args, **kwargs):
        if backend.name == 'facebook':
            profile = user.get_profile()
            if profile is None:
                profile = Profile(user_id=user.id)
            profile.gender = response.get('gender')
            profile.link = response.get('link')
            profile.timezone = response.get('timezone')
            profile.save()

Now all that's needed is to tell ``python-social-auth`` to use this function in
the pipeline, since it needs the user instance, it needs to be put after
``create_user`` function::

    SOCIAL_AUTH_PIPELINE = (
        'social.pipeline.social_auth.social_details',
        'social.pipeline.social_auth.social_uid',
        'social.pipeline.social_auth.auth_allowed',
        'social.pipeline.social_auth.social_user',
        'social.pipeline.user.get_username',
        'social.pipeline.user.create_user',
        'import.path.to.save_profile',  # <--- set the import-path to the function
        'social.pipeline.social_auth.associate_user',
        'social.pipeline.social_auth.load_extra_data',
        'social.pipeline.user.user_details'
    )

If the return value of the function is a ``dict``, the values will be merged
into the next pipeline function parameters, so, for instance, if you want the
``profile`` instance to be available to the next function, all that it needs to
do is return ``{'profile': profile}``.

.. _python-social-auth: https://github.com/omab/python-social-auth
.. _example applications: https://github.com/omab/python-social-auth/tree/master/examples