from __future__ import absolute_import import logging from django.urls import reverse from allauth.socialaccount.adapter import get_adapter from allauth.socialaccount.helpers import ( complete_social_login, render_authentication_error, ) from allauth.socialaccount.models import SocialLogin, SocialToken from allauth.socialaccount.providers.base.constants import ( AuthAction, AuthError, ) from allauth.socialaccount.providers.base.mixins import OAuthLoginMixin from allauth.socialaccount.providers.oauth.client import ( OAuthClient, OAuthError, ) logger = logging.getLogger(__name__) class OAuthAdapter(object): def __init__(self, request): self.request = request def complete_login(self, request, app): """ Returns a SocialLogin instance """ raise NotImplementedError def get_provider(self): adapter = get_adapter(self.request) app = adapter.get_app(self.request, provider=self.provider_id) return app.get_provider(self.request) class OAuthView(object): @classmethod def adapter_view(cls, adapter): def view(request, *args, **kwargs): self = cls() self.request = request self.adapter = adapter(request) return self.dispatch(request, *args, **kwargs) return view def _get_client(self, request, callback_url): provider = self.adapter.get_provider() app = provider.app scope = " ".join(provider.get_scope(request)) parameters = {} if scope: parameters["scope"] = scope client = OAuthClient( request, app.client_id, app.secret, self.adapter.request_token_url, self.adapter.access_token_url, callback_url, parameters=parameters, provider=provider, ) return client class OAuthLoginView(OAuthLoginMixin, OAuthView): def login(self, request, *args, **kwargs): callback_url = reverse(self.adapter.provider_id + "_callback") SocialLogin.stash_state(request) action = request.GET.get("action", AuthAction.AUTHENTICATE) provider = self.adapter.get_provider() auth_url = provider.get_auth_url(request, action) or self.adapter.authorize_url auth_params = provider.get_auth_params(request, action) client = self._get_client(request, callback_url) try: return client.get_redirect(auth_url, auth_params) except OAuthError as e: logger.error("OAuth authentication error", exc_info=True) return render_authentication_error( request, self.adapter.provider_id, exception=e ) class OAuthCallbackView(OAuthView): def dispatch(self, request): """ View to handle final steps of OAuth based authentication where the user gets redirected back to from the service provider """ login_done_url = reverse(self.adapter.provider_id + "_callback") client = self._get_client(request, login_done_url) if not client.is_valid(): if "denied" in request.GET: error = AuthError.CANCELLED else: error = AuthError.UNKNOWN extra_context = dict(oauth_client=client) return render_authentication_error( request, self.adapter.provider_id, error=error, extra_context=extra_context, ) app = self.adapter.get_provider().app try: access_token = client.get_access_token() token = SocialToken( app=app, token=access_token["oauth_token"], # .get() -- e.g. Evernote does not feature a secret token_secret=access_token.get("oauth_token_secret", ""), ) login = self.adapter.complete_login( request, app, token, response=access_token ) login.token = token login.state = SocialLogin.unstash_state(request) return complete_social_login(request, login) except OAuthError as e: return render_authentication_error( request, self.adapter.provider_id, exception=e )