[Answer]-Salesforce Oauth with Django

1👍

There were a few things that I was doing wrong. I decided to write my answer here and will update it later when I make additional improvements. Special thanks to Casey Kinsey for his suggestion on using BaseException for troubleshooting.

First off, I was using the wrong grant_type parameter. I had been using the ‘password’ grant_type parameter, when the correct parameter is ‘authorization_code’.

Second, I was creating an overly difficult testing process. I was making updates, deploying to a staging Heroku environment, and troubleshooting. To increase the speed of troubleshooting, I changed the redirect_uri in (1) the the link that the user clicks to authorize their acccount (this is in another file,) (2) the payload variable that is posted to Salesforce, and (3) the redirect_uri in the Salesforce connected app.

from django.views.generic.base import RedirectView, TemplateView, View
from django.http import Http404, HttpResponse
from django.conf import settings
from django.conf.urls import patterns, url, include
from django.contrib import messages
from django.core.urlresolvers import reverse_lazy, reverse
from guardian.mixins import LoginRequiredMixin
from simple_salesforce import Salesforce
import logging, requests, json

from campaigns.views import CampaignOwnerPermission
from . import api, utils, settings
from .models import OauthToken


class SalesforceOauthRedirectView(
    LoginRequiredMixin,
    RedirectView
):
# permanent = False
# query_string = False

    def get_redirect_url(self):
        logger = logging.getLogger(__name__)

        try:
            payload = {
                'grant_type': 'authorization_code',
                'client_id': settings.CONSUMER_KEY,
                'client_secret': settings.CONSUMER_SECRET,
                'code': self.request.GET.get('code'), # get code param from response URL
                # TODO: redirect_uri should NOT be hardcoded
                'redirect_uri': 'https://127.0.0.1:8000/oauth/salesforce/'
                }

            try:
                # Post payload to Salesforce Oauth server and get user
                # token in response.
                r = requests.post(
                    "https://login.salesforce.com/services/oauth2/token",
                    headers={
                        "Content-Type":"application/x-www-form-urlencoded"
                    },
                    data=payload
                )

                try:
                    # Decode the JSON response from Salesforce Oauth server
                    decoded = json.loads(r.content)
                    # Store tokens & Salesforce user info to database
                    creds = OauthToken.objects.get(user=self.request.user)
                    # TODO: Store salesforce_user_id, Thumbnail, etc.
                    creds.access_token = decoded['access_token']
                    # creds.salesforce_organization_id = decoded['refresh_token']
                    # creds.refresh_token = creds['refresh_token']
                    # creds.id_token = creds['id_token']
                    # creds.instance_url = decoded['instance_url']
                    creds.active = True
                    creds.save()

                    messages.add_message(
                        self.request,
                        messages.SUCCESS,
                        _(
                            'Successfully updated Salesforce  \
                            authentication with user credentials: "%s"'
                            %
                            creds.salesforce_user_id
                        )
                    )

                except:
                    logger.error("%s: %s" % (e.__class__, e.args))
                    messages.add_message(
                    self.request,
                    messages.WARNING,
                        ('Error connecting with Salesforce.  \
                        Contact explorro support. [Error 003]')
                        )

                    return reverse_lazy('providers:provider_list')

            except BaseException as e:
                #raise e # Print error to the console
                # or, to print to the error logs
                logger.error("%s: %s" % (e.__class__, e.args))
                messages.add_message(
                    self.request,
                    messages.WARNING,
                    ('Could not get Oauth_token from Salesforce API.\n\n \
                    Salesforce may be experiencing an outage.  Try again \
                    in a few minutes and contact explorro support if the \
                    problem persists. [Error 002]'
                    )
                )
                return reverse_lazy('providers:provider_list')

        except BaseException as e:
            raise e # Print error to console
            logger.error("%s: %s" % (e.__class__, e.args))
            messages.add_message(
                self.request,
                messages.WARNING,
                ('There was a problem authenticating with \
                 Salesforce.  Be sure to enter your Salesforce \
                 username and password before attempting to authorize your\
                 account.  Contact our support team if you need some help. \
                 [Error 003]'
                )
            )
            return reverse_lazy('providers:provider_list')

This solution is not 100% complete; here are the next action items:

(1) there are additional parameters that need to be stored to the database, and the password and all tokens will need to be encrypted in the database (this is what I will work on next.)

(2) When this is pushed to production, the redirect_uri will need to be updated everywhere (in the Salesforce app, in the user authorization link, and in the payload variable)

(3) We need to use the Salesforce ID parameters that are available to us (includes Salesforce username, password, profile picture, etc.) and store those to the database so that this is truly 1-click authentication.

0👍

It looks like the problem is going to be either a syntax or data error on your side of the handshake. The except clause generating your error message is actually outside of the requests.post to salesforce–that is, an exception is being generated by one of these lines of code:

existing_credentials = OauthToken.objects.get(
    user=request.user
)
# get stored Salesforce username and password
username = str(existing_credentials.salesforce_user_id)
password = str(existing_credentials.password)
payload = {
    'grant_type': 'password',
    'client_id': str(settings.CONSUMER_KEY),
    'client_secret': str(settings.CONSUMER_SECRET),
    'username': username,
    # must concatenate password & code before passing to server
    'password': password + str(code)
    }

There is probably a much more helpful error message being silenced here by the broad try/except block that can help you move things forward. I’d advise you actually let the exception be raised and see what you find out (could be that the ORM can’t actually find the OauthToken you are searching for, for example). You can modify the except clause in place:

# Line 96
except BaseException as e:
    raise e # To print to the console
    # or, to print to the error logs
    logger.error("%s: %s" % (e.__class__, e.message))
    messages.add_message(
        self.request,
        messages.WARNING,
        ('There was a problem authenticating with \
         Salesforce.  Be sure to enter your Salesforce \
         username and password before attempting to authorize your\
         account.  Contact our support team if you need some help.'
         )
    )
    return reverse_lazy('providers:provider_list')

This should be enough to help you discover the actual exception being thrown so you can debug further.

Leave a comment