[Python-modules-commits] [python-social-auth] 30/61: first revision

Wolfgang Borgert debacle at moszumanska.debian.org
Sat Dec 24 15:14:04 UTC 2016


This is an automated email from the git hooks/post-receive script.

debacle pushed a commit to tag v0.2.13
in repository python-social-auth.

commit e8a19546db3aff9cf27a834b338c16c94e8e260f
Author: Chris Curvey <chris at chriscurvey.com>
Date:   Fri Jul 31 12:32:03 2015 -0400

    first revision
---
 docs/developer_intro.rst | 164 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)

diff --git a/docs/developer_intro.rst b/docs/developer_intro.rst
new file mode 100644
index 0000000..2242507
--- /dev/null
+++ b/docs/developer_intro.rst
@@ -0,0 +1,164 @@
+Beginners Guide
+===============
+
+This is an attempt to bring together a number of concepts in python-social-auth
+(psa) so that you will understand how it fits into your system.  This definitely
+has a Django flavor to it (because that's how I learned it).
+
+Understanding PSA URLs
+-----------------------
+
+If you have not seen namespaced URLs before, you are about to be introduced.
+When you add the PSA entry to your urls.py, it looks like this:
+
+    url(r'', include('social.apps.django_app.urls', namespace='social'))
+
+that "namespace" part on the end is what keeps the names in the PSA-world from
+colliding with the names in your app, or other 3rd-party apps.  So your login
+link will look like this:
+
+    <a href="{% url 'social:begin' 'provider-name' %}">Login</a>
+
+(See how "social" in the URL mapping matches the value of "namespace" in the
+urls.py entry?)
+
+Understanding Backends
+----------------------
+
+PSA implements a lot of backends.  Find the entry in the docs for your backend,
+and if it's there, follow the steps to enable it, which come down to
+
+    1) Set up SOCIAL_AUTH_{backend} variables in settings.py.  (The settings
+        vary, based on the backends)
+    2) Adding your backend to AUTHENTICATION_BACKENDS in settings.py.
+
+If you need to implement a different backend (for instance, let's say you
+want to use Intuit's OpenID), you can subclass the nearest one and override
+the "name" attribute:
+
+    from social.backends.open_id import OpenIDAuth
+    class IntuitOpenID(OpenIDAuth):
+        name = 'intuit'
+
+And then add your new backend to AUTHENTICATION_BACKENDS in settings.py.
+
+
+A couple notes about the pipeline:
+
+The standard pipeline does not log the user in until after the pipeline has
+completed.  So if you get a value in the user key of the accumulative
+dictionary, that implies that the user was logged in when the process started.
+
+Understanding the Pipeline
+--------------------------
+
+Reversing a URL like {% url 'social:begin' 'github' %} will give you a url like:
+
+http://example.com/login/github
+
+And clicking on that link will cfrom:(seth at hillcountryny.com)ause the "pipeline" to be started.  The pipeline
+is a list of functions that build up data about the user as we go through the
+steps of the authentication process.  (If you really want to understand the
+pipeline, look at the source in social/backends/base.py, and see the
+run_pipeline() function in BaseAuth.)
+
+The design contract for each function in the pipeline is:
+
+1) The pipeline starts with a four-item dictionary (the accumulative dictionary)
+    which is updated with the results of each function in the pipeline. The
+    initial four values are:
+        'strategy' : contains a strategy object
+        'backend' : contains the backend being used during this pipeline run
+        'request' : contains a dictionary of the request keys.  Note to Django
+            users -- this is not an HttpRequest object, it is actually
+            the results of request.REQUEST.
+        'details' : which is an empty dict.
+
+2) If the function returns a dictionary or something False-ish, add the
+    contents of the dictionary to an accumulative dictionary (called "out" in
+    run_pipeline), and call the next step in the pipeline with the accumulative
+    dictionary.
+
+3) If something else is returned (for example, a subclass of HttpResponse),
+    then return that to the browser.
+
+4) If the pipeline completes, *THEN* the user is authenticated (logged in).  So
+    if you are finding an authenticated user object while the pipeline is
+    running, that means that the user was logged in when the pipeline started.
+
+There is one pipeline for your site as a whole -- if you have backend-specific
+logic, you have to make your pipeline steps smart enough to skip the step if it
+is not relevant.  This is as simple as:
+
+    def my_custom_step(strategy, backend, request, details, *args, **kwargs):
+        if backend_name != 'my_custom_backend':
+            return
+        # otherwise, do the special steps for your custom backend
+
+Interrupting the Pipeline (and communicating with views)
+---------------------------------------------------------
+
+Let's say you want to add a custom step in the pipeline -- you want the user
+to establish a password so that they can come directly to your site in the
+future.  We can do that with the @partial decorator, which tells the pipeline
+to keep track of where it is so that it can be restarted.
+
+The first thing we need to do is set up a way for our views to communicate with
+the pipeline. That is done by adding a value to the settings file to tell
+us which values should be passed back and forth between the Django session
+and the pipeline:
+
+    FIELDS_STORED_IN_SESSION = ['local_password',]
+
+In our pipeline code, we would have:
+
+    from django.shortcuts import redirect
+    from django.contrib.auth.models import User
+    from social.pipeline.partial import partial
+
+    # partial says "we may interrupt, but we will come back here again"
+    @partial
+    def collect_password(strategy, backend, request, details, *args, **kwargs):
+        # request['local_password'] is set by the pipeline infrastructure
+        # because it exists in FIELDS_STORED_IN_SESSION
+        if not request.get('local_password', None):
+
+            # if we return something besides a dict or None, then that is
+            # returned to the user -- in this case we will redirect to a
+            # view that can be used to get a password
+            return redirect("myapp.views.collect_password")
+
+        # grab the user object from the database (remember that they may
+        # not be logged in yet) and set their password.  (Assumes that the
+        # email address was captured in an earlier step.)
+        user = User.objects.get(email=kwargs['email'])
+        user.set_password(request['local_password'])
+        user.save()
+
+        # continue the pipeline
+        return
+
+In our view code, we would have something like:
+
+    class PasswordForm(forms.Form):
+        secret_word = forms.CharField(max_length=10)
+
+    def get_user_password(request):
+        if request.method == 'POST':
+            form = PasswordForm(request.POST)
+            if form.is_valid():
+                # because of FIELDS_STORED_IN_SESSION, this will get copied
+                # to the request dictionary when the pipeline is resumed
+                request.session['local_password'] = form.cleaned_data['secret_word']
+
+                # once we have the password stashed in the session, we can
+                # tell the pipeline to resume by using the "complete" endpoint
+                return redirect(reverse('social:complete', args=("backend_name,")))
+        else:
+            form = PasswordForm()
+
+        return render(request, "password_form.html")
+
+Note that the "social:complete" will re-enter the pipeline with the same
+function that interrupted it (in this case, collect_password).
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-social-auth.git



More information about the Python-modules-commits mailing list