gpt4 book ai didi

python - 即使 access_type 为 ='offline',Google OAuth2 也不会发布刷新 token ?

转载 作者:太空宇宙 更新时间:2023-11-03 13:58:46 24 4
gpt4 key购买 nike

这个问题类似于Not receiving Google OAuth refresh token ,但我已经按照已接受解决方案的评论中的建议指定了 access_type='offline'

我正在编写一个 Django 应用程序来使用 Google API 发送日历邀请,这基本上是对 https://developers.google.com/api-client-library/python/auth/web-app 中给出的 Flask 示例的改编。 ,我在其中创建了一个模型 GoogleCredentials 以将凭据持久存储在数据库中而不是 session 中。

观点如下:

import logging
from django.conf import settings
from django.shortcuts import redirect
from django.http import JsonResponse
from django.urls import reverse
from django.contrib.auth.decorators import login_required
import google.oauth2.credentials
import google_auth_oauthlib.flow
import googleapiclient.discovery
from lucy_web.models import GoogleCredentials

logger = logging.getLogger(__name__)


# Client configuration for an OAuth 2.0 web server application
# (cf. https://developers.google.com/identity/protocols/OAuth2WebServer)
# This is constructed from environment variables rather than from a
# client_secret.json file, since the Aptible deployment process would
# require us to check that into version control, which is not in accordance
# with the 12-factor principles.
# The client_secret.json containing this information can be downloaded from
# https://console.cloud.google.com/apis/credentials?organizationId=22827866999&project=cleo-212520
CLIENT_CONFIG = {'web': {
'client_id': settings.GOOGLE_CLIENT_ID,
'project_id': settings.GOOGLE_PROJECT_ID,
'auth_uri': 'https://accounts.google.com/o/oauth2/auth',
'token_uri': 'https://www.googleapis.com/oauth2/v3/token',
'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs',
'client_secret': settings.GOOGLE_CLIENT_SECRET,
'redirect_uris': settings.GOOGLE_REDIRECT_URIS,
'javascript_origins': settings.GOOGLE_JAVASCRIPT_ORIGINS}}

# This scope will allow the application to manage the user's calendars
SCOPES = ['https://www.googleapis.com/auth/calendar']
API_SERVICE_NAME = 'calendar'
API_VERSION = 'v3'


@login_required
def authorize(request):
authorization_url, state = _get_authorization_url(request)
request.session['state'] = state
return redirect(to=authorization_url)


@login_required
def oauth2callback(request):
flow = _get_flow(request, state=request.session['state'])

# Note: to test this locally, set OAUTHLIB_INSECURE_TRANSPORT=1 in your .env file
# (cf. https://stackoverflow.com/questions/27785375/testing-flask-oauthlib-locally-without-https)
flow.fetch_token(authorization_response=request.get_raw_uri())
_save_credentials(user=request.user, credentials=flow.credentials)
return redirect(to=reverse('create-meeting'))


@login_required
def create_meeting(request):
# Retrieve the user's credentials from the database, redirecting
# to the authorization page if none are found
credentials = _get_credentials(user=request.user)
if not credentials:
return redirect(to=reverse('authorize'))

calendar = googleapiclient.discovery.build(
API_SERVICE_NAME, API_VERSION, credentials=credentials)

calendars = calendar.calendarList().list().execute()

return JsonResponse(calendars)


def _get_credentials(user):
"""
Retrieve a user's google.oauth2.credentials.Credentials from the database.
"""
try:
_credentials = GoogleCredentials.objects.get(user=user)
except GoogleCredentials.DoesNotExist:
return

return google.oauth2.credentials.Credentials(**_credentials.to_dict())


def _save_credentials(user, credentials):
"""
Store a user's google.oauth2.credentials.Credentials in the database.
"""
gc, _ = GoogleCredentials.objects.get_or_create(user=user)
gc.update_from_credentials(credentials)


def _get_authorization_url(request):
flow = _get_flow(request)

# Generate URL for request to Google's OAuth 2.0 server
return flow.authorization_url(
# Enable offline access so that you can refresh an access token without
# re-prompting the user for permission. Recommended for web server apps.
access_type='offline',
login_hint=settings.SCHEDULING_EMAIL,
# Enable incremental authorization. Recommended as a best practice.
include_granted_scopes='true')


def _get_flow(request, **kwargs):
# Use the information in the client_secret.json to identify
# the application requesting authorization.
flow = google_auth_oauthlib.flow.Flow.from_client_config(
client_config=CLIENT_CONFIG,
scopes=SCOPES,
**kwargs)

# Indicate where the API server will redirect the user after the user completes
# the authorization flow. The redirect URI is required.
flow.redirect_uri = request.build_absolute_uri(reverse('oauth2callback'))
return flow

请注意,我已将 access_type='offline' 传递给 flow.authorization_url()。这是 GoogleCredentials 模型:

from django.db import models
from django.contrib.postgres.fields import ArrayField
from .timestamped_model import TimeStampedModel
from .user import User


class GoogleCredentials(TimeStampedModel):
"""
Model for saving Google credentials to a persistent database (cf. https://developers.google.com/api-client-library/python/auth/web-app)
The user's ID is used as the primary key, following https://github.com/google/google-api-python-client/blob/master/samples/django_sample/plus/models.py.
(Note that we don't use oauth2client's CredentialsField as that library is deprecated).
"""
user = models.OneToOneField(
User,
primary_key=True,
limit_choices_to={'is_staff': True},
# Deleting a user will automatically delete his/her Google credentials
on_delete=models.CASCADE)
token = models.CharField(max_length=255, null=True)
refresh_token = models.CharField(max_length=255, null=True)
token_uri = models.CharField(max_length=255, null=True)
client_id = models.CharField(max_length=255, null=True)
client_secret = models.CharField(max_length=255, null=True)
scopes = ArrayField(models.CharField(max_length=255), null=True)

def to_dict(self):
"""
Return a dictionary of the fields required to construct
a google.oauth2.credentials.Credentials object
"""
return dict(
token=self.token,
refresh_token=self.refresh_token,
token_uri=self.token_uri,
client_id=self.client_id,
client_secret=self.client_secret,
scopes=self.scopes)

def update_from_credentials(self, credentials):
self.token = credentials.token
self.refresh_token = credentials.refresh_token
self.token_uri = credentials.token_uri
self.client_id = credentials.client_id
self.client_secret = credentials.client_secret
self.scopes = credentials.scopes
self.save()

在开发服务器运行的情况下,如果我转到 localhost:8000/authorize(连接到 authorize() View )然后我检查第一个凭据,我看到 refresh_tokenNone:

(lucy-web-CVxkrCFK) bash-3.2$ python manage.py shell
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from lucy_web.models import *

In [2]: GoogleCredentials.objects.all()
Out[2]: <QuerySet [<GoogleCredentials: GoogleCredentials object (2154)>]>

In [3]: gc = GoogleCredentials.objects.first()

In [4]: gc.__dict__
Out[4]:
{'_state': <django.db.models.base.ModelState at 0x111f91630>,
'created_at': datetime.datetime(2018, 8, 15, 17, 58, 33, 626971, tzinfo=<UTC>),
'updated_at': datetime.datetime(2018, 8, 15, 23, 8, 38, 634449, tzinfo=<UTC>),
'user_id': 2154,
'token': 'ya29foobar6tA',
'refresh_token': None,
'token_uri': 'https://www.googleapis.com/oauth2/v3/token',
'client_id': '8214foobar13-unernto9l5ievs2pi0l6fir12fus1o46.apps.googleusercontent.com',
'client_secret': 'bZt6foobarQj10y',
'scopes': ['https://www.googleapis.com/auth/calendar']}

最初,这不是问题,但过了一会儿,如果我转到 create_meeting() View ,我会得到一个我跟踪到的 RefreshError google.oauth2.credentials 中的这段源代码:

@_helpers.copy_docstring(credentials.Credentials)
def refresh(self, request):
if (self._refresh_token is None or
self._token_uri is None or
self._client_id is None or
self._client_secret is None):
raise exceptions.RefreshError(
'The credentials do not contain the necessary fields need to '
'refresh the access token. You must specify refresh_token, '
'token_uri, client_id, and client_secret.')

换句话说,我需要一个 refresh_token 来防止这个错误。在这种情况下,为什么 Google API 不返回一个?

最佳答案

更仔细地按照接受的答案,我发现我可以通过删除网络应用程序对我的帐户的访问并再次添加它来获取刷新 token 。我导航到 https://myaccount.google.com/permissions并删除了“Cleo”应用程序的访问权限:

enter image description here

然后我去localhost:8000/authorize(链接到authorize() View )再次查找保存的凭据,他们有一个刷新 token :

In [24]: from lucy_web.models import *

In [25]: gc = GoogleCredentials.objects.first()

In [26]: gc.__dict__
Out[26]:
{'_state': <django.db.models.base.ModelState at 0x109133e10>,
'created_at': datetime.datetime(2018, 8, 15, 17, 58, 33, 626971, tzinfo=<UTC>),
'updated_at': datetime.datetime(2018, 8, 16, 22, 37, 48, 108105, tzinfo=<UTC>),
'user_id': 2154,
'token': 'ya29.Glv6BbcPkVoFfoobarHGifJUlEKP7kvwO5G1myTDOw9UYfl1LKAGxt',
'refresh_token': '1/iafoobar4z1OxFtNljiLrmS0',
'token_uri': 'https://www.googleapis.com/oauth2/v3/token',
'client_id': '821409068013-unerntfoobarir12fus1o46.apps.googleusercontent.com',
'client_secret': 'bZt6lfoobarpI8Qj10y',
'scopes': ['https://www.googleapis.com/auth/calendar']}

关于python - 即使 access_type 为 ='offline',Google OAuth2 也不会发布刷新 token ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51883184/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com