【问题标题】:Salesforce Oauth with Django使用 Django 的 Salesforce Oauth
【发布时间】:2021-11-17 23:23:00
【问题描述】:

我是一名高级初学者,我已经在这个问题上停留了几天。我正在创建的应用程序需要访问每个用户的 Salesforce 帐户。为了建立 API 连接,我们需要存储他们的 Salesforce 用户名、密码和访问令牌。

用户将提供他们的 Salesforce 用户名和密码,然后单击按钮进行授权。此按钮将用户引导至:

https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id=dsf3434&redirect_uri=https%3A%2F%2Fwww.mysite.com/oauth/salesforce/&display=popup

来自 Salesforce Oauth 服务器的响应应该是:

https://www.mysite.com/oauth/salesforce/?display=popup&code=aPrx6RMKPrMpm.SHNCxBmHz3ZiHMc.xZRY9RHLekkzW_G8Uu.KyqmmY0.JGCr5roqPT49vTCbg%3D%3D

这是 views.py 文件

from django.views.generic.base import RedirectView, TemplateView, View
from django.http import Http404, HttpResponse
from django.conf import settings
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

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


class SalesforceOauthRedirectView(
    LoginRequiredMixin,
    RedirectView
):

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

        # Extract 'code' parameter from return URL and set to variable
        if (
            not self.request.GET.get(
                'code', None
            ) is None
        ):
            try:
                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)
                    }

                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
                    )

                    # Decode the JSON response from Salesforce Oauth server
                    decoded = json.loads(r.content)
                    # Store access_token to database
                    existing_credentials.token = decoded['access_token']
                    existing_credentials.active = True
                    existing_credentials.save()

                    messages.add_message(
                        self.request,
                        messages.SUCCESS,
                        _(
                            'Successfully updated Salesforce  \
                            authentication with user credentials: "%s"'
                            %
                            salesforce_user_id
                        )
                    )
                    # Success point
                    return reverse_lazy('providers:provider_list')

                    # except (ValueError, KeyError, TypeError):
                    # logger.error('Could not decode response from Salesforce API')

                    # Not sure how this should be arranged
                except:
                    logger.error(
                        'Could not get Oauth_token from Salesforce API.'
                        )
                    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.'
                            )
                    )
                    return reverse_lazy('providers:provider_list')


            except:
                logger.error('Could not get users Salesforce credentials')
                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')

        else:
            pass
            return reverse_lazy('providers:provider_list')
            messages.add_message(
                self.request,
                messages.WARNING,
                ('Could not retrieve Salesforce Authorization Code\n\n \
                 Contact your Salesforce administrator for assistance.'
                )
            )

'code' 参数(示例值 = randomTextCode)从响应 URL 中解析出来,并作为 'payload' 变量(第 38 行)的一部分传递给https://login.salesforce.com/services/oauth2/token

最后一步应该接收来自https://login.salesforce.com/services/oauth2/token的JSON响应,其中包含access_token

该错误似乎源于第一个 try/except 语句,因为我在第 #103-106 行收到异常错误消息。

在本地运行时,这里是服务器响应 (301)

"GET /oauth/salesforce/?display=popup&code=aPrx6RMKPrMpm.SHNCxBmHz3ZgWIqBWQKmln4Q6TfdI8TbmeWuMw5H..Di.342no15VYNvmgzA%3D%3D HTTP/1.1" 301 0

这里是 models.py

from django.db import models
from django.conf import settings
from django.utils.translation import ugettext_lazy as _


class OauthToken(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        related_name='Salesforce User',
        verbose_name=_('Salesforce User'),
        db_index=True,
    )
    salesforce_user_id = models.EmailField(
        verbose_name=_('Salesforce user id'),
        db_index=True, unique=True
    )
    password = models.CharField(    # TODO: This needs to be encrypted!!
        verbose_name=_('Salesforce password'),
        max_length=256,
    )
    token = models.CharField(
        verbose_name=_('Token'),
        max_length=256,
        null=True,
        blank=True,
    )
    security_token = models.CharField(
        verbose_name=_('Security Token'),
        max_length=256
    )
    active = models.BooleanField(
        verbose_name=_('Active'),
        default=False,
        db_index=True
    )

    def __unicode__(self):
        return str(self.salesforce_user_id)

    class Meta:
        verbose_name = _('Oauth token')
        verbose_name_plural = _('Oauth tokens')

非常感谢任何解决此问题的帮助。

【问题讨论】:

  • 你能粘贴实际的错误吗?
  • 向用户显示的错误是“使用 Salesforce 进行身份验证时出现问题。请务必在尝试授权您的帐户之前输入您的 Salesforce 用户名和密码。如果您需要一些信息,请联系我们的支持团队帮助。”这是从第 98 行到第 108 行的“except:”代码生成的
  • 你能把代码分享给git或site吗?

标签: python django oauth salesforce


【解决方案1】:

我做错了几件事。我决定在这里写下我的答案,稍后我会在进行其他改进时对其进行更新。特别感谢 Casey Kinsey 关于使用 BaseException 进行故障排除的建议。

首先,我使用了错误的 grant_type 参数。当正确的参数是 'authorization_code' 时,我一直在使用 'password' grant_type 参数。

其次,我正在创建一个过于困难的测试过程。我正在进行更新、部署到暂存 Heroku 环境并进行故障排除。为了提高故障排除速度,我更改了 (1) 用户单击以授权其帐户的链接(在另一个文件中)中的 redirect_uri,(2) 发布到 Salesforce 的有效负载变量,以及 (3) Salesforce 连接应用程序中的 redirect_uri。

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')

此解决方案并非 100% 完成;以下是下一步行动:

(1)还有一些额外的参数需要存储到数据库中,并且密码和所有令牌都需要在数据库中加密(这是我接下来要做的工作。)

(2) 当它被推送到生产环境时,redirect_uri 将需要在任何地方进行更新(在 Salesforce 应用程序中、在用户授权链接中以及在负载变量中)

(3) 我们需要使用我们可用的 Salesforce ID 参数(包括 Salesforce 用户名、密码、头像等)并将这些参数存储到数据库中,以便真正实现一键式身份验证。

【讨论】:

  • 可以分享代码吗?
【解决方案2】:

看起来问题可能出在握手一方的语法或数据错误上。生成错误消息的 except 子句实际上在 requests.post 到 salesforce 之外——也就是说,这些代码行之一正在生成异常:

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)
    }

这里可能有一个更有用的错误消息被广泛的 try/except 块静音,它可以帮助您推动事情的发展。我建议您实际上让异常引发并看看您发现了什么(例如,可能是 ORM 实际上无法找到您正在搜索的 OauthToken)。您可以就地修改 except 子句:

# 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')

这应该足以帮助您发现实际抛出的异常,以便您进一步调试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-21
    • 2016-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-07
    • 1970-01-01
    相关资源
    最近更新 更多