Проблема с классом социальной аутентификации Flask
Я работаю над учебным пособием Мигеля Гринберга по социальной аутентификации.
На шаблоне домашней страницы у меня есть этот код, и я удалил часть твиттера из учебника:
<h2>I don't know you!</h2>
<p><a href="{{ url_for('oauth_authorize', provider='facebook') }}">Login with Facebook</a></p>
{% endif %}
Поэтому, когда вы нажимаете эту ссылку, вы передаете Facebook в качестве поставщика через эту функцию просмотра:
@app.route('/authorize/<provider>')
def oauth_authorize(provider):
if not current_user.is_anonymous():
return redirect(url_for('index'))
oauth = OAuthSignIn.get_provider(provider)
return oauth.authorize()
Теперь, в другом файле, oauth.py, у меня есть следующее, и моя проблема заключается в следующем. Я продолжаю получать сообщение об ошибке, когда нажимаю на ссылку Facebook, ЕСЛИ УДАЛЕН класс TwitterSignIn. Я думаю, мне любопытно, почему класс TwitterSignIn должен быть удален, чтобы это работало, потому что данные ему не передаются, верно? Даже если Facebook был не единственным вариантом, зачем щелкать ссылку входа в Facebook, чтобы передавать какие-либо данные классу TwitterSignIn?
from rauth import OAuth1Service, OAuth2Service
from flask import current_app, url_for, request, redirect, session
class OAuthSignIn(object):
providers = None
def __init__(self, provider_name):
self.provider_name = provider_name
credentials = current_app.config['OAUTH_CREDENTIALS'][provider_name]
self.consumer_id = credentials['id']
self.consumer_secret = credentials['secret']
def authorize(self):
pass
def callback(self):
pass
def get_callback_url(self):
return url_for('oauth_callback', provider=self.provider_name,
_external=True)
@classmethod
def get_provider(self, provider_name):
if self.providers is None:
self.providers = {}
for provider_class in self.__subclasses__():
provider = provider_class()
self.providers[provider.provider_name] = provider
return self.providers[provider_name]
class FacebookSignIn(OAuthSignIn):
def __init__(self):
super(FacebookSignIn, self).__init__('facebook')
self.service = OAuth2Service(
name='facebook',
client_id=self.consumer_id,
client_secret=self.consumer_secret,
authorize_url='https://graph.facebook.com/oauth/authorize',
access_token_url='https://graph.facebook.com/oauth/access_token',
base_url='https://graph.facebook.com/'
)
def authorize(self):
return redirect(self.service.get_authorize_url(
scope='email',
response_type='code',
redirect_uri=self.get_callback_url())
)
def callback(self):
if 'code' not in request.args:
return None, None, None
oauth_session = self.service.get_auth_session(
data={'code': request.args['code'],
'grant_type': 'authorization_code',
'redirect_uri': self.get_callback_url()}
)
me = oauth_session.get('me').json()
return (
'facebook$' + me['id'],
me.get('email').split('@')[0], # Facebook does not provide
# username, so the email's user
# is used instead
me.get('email')
)
class TwitterSignIn(OAuthSignIn):
def __init__(self):
super(TwitterSignIn, self).__init__('twitter')
self.service = OAuth1Service(
name='twitter',
consumer_key=self.consumer_id,
consumer_secret=self.consumer_secret,
request_token_url='https://api.twitter.com/oauth/request_token',
authorize_url='https://api.twitter.com/oauth/authorize',
access_token_url='https://api.twitter.com/oauth/access_token',
base_url='https://api.twitter.com/1.1/'
)
def authorize(self):
request_token = self.service.get_request_token(
params={'oauth_callback': self.get_callback_url()}
)
session['request_token'] = request_token
return redirect(self.service.get_authorize_url(request_token[0]))
def callback(self):
request_token = session.pop('request_token')
if 'oauth_verifier' not in request.args:
return None, None, None
oauth_session = self.service.get_auth_session(
request_token[0],
request_token[1],
data={'oauth_verifier': request.args['oauth_verifier']}
)
me = oauth_session.get('account/verify_credentials.json').json()
social_id = 'twitter$' + str(me.get('id'))
username = me.get('screen_name')
return social_id, username, None # Twitter does not provide email
Некоторая дополнительная информация
Конкретная ошибка заключается в следующем:
File "/Users/metersky/code/mylastapt/app/oauth.py", line 29, in get_provider
provider = provider_class()
File "/Users/metersky/code/mylastapt/app/oauth.py", line 73, in __init__
super(TwitterSignIn, self).__init__('twitter')
File "/Users/metersky/code/mylastapt/app/oauth.py", line 10, in __init__
credentials = current_app.config['OAUTH_CREDENTIALS'][provider_name]
KeyError: 'twitter'
И вот где я думаю, что проблема может происходить:
app.config['OAUTH_CREDENTIALS'] = {
'facebook': {
'id': 'XXX',
'secret': 'XXXX'
}
}
1 ответ
Проблема в OAuthSignIn.get_provider
,
@classmethod
def get_provider(self, provider_name):
if self.providers is None:
self.providers = {}
for provider_class in self.__subclasses__():
provider = provider_class()
self.providers[provider.provider_name] = provider
return self.providers[provider_name]
Первый раз, когда вы звоните из своего поля зрения
oauth = OAuthSignIn.get_provider(provider)
метод кэширует провайдеров, которых вы определили. Это делается путем проверки всех OAuthSignIn
Подклассы.
for provider_class in self.__subclasses__():
Когда вы включаете TwitterSignIn
, он будет включен в качестве подкласса. Затем вы создадите экземпляр класса
provider = provider_class()
внутри OAuthSignIn.__init__
Вы загружаете настройки провайдера с current_app.config['OAUTH_CREDENTIALS'][provider_name]
, Так как Twitter не включен, вы получаете KeyError
,
Если вы не хотите поддерживать Twitter, просто удалите класс. Если вы хотите еще немного защитить свое приложение, чтобы поставщики могли быть удалены из ваших настроек без обновления кода, вам необходимо проверить исключение. Вы могли бы сделать проверку внутри OAuthSignIn.__init__
, но, вероятно, нет особого смысла включать неподдерживаемого провайдера в OAuthSignIn.providers
, Вам лучше поставить чек в OAuthSignIn.get_provider
,
@classmethod
def get_provider(cls, provider_name):
if cls.providers is None:
cls.providers = {}
for provider_class in cls.__subclassess__():
try:
provider = provider_class()
except KeyError:
pass # unsupported provider
else:
cls.providers[provider.provider_name] = provider
return cls.providers[provider_name]