import hashlib import hmac import logging import requests from datetime import timedelta from django.utils import timezone from allauth.socialaccount import app_settings 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.oauth2.views import ( OAuth2Adapter, OAuth2CallbackView, OAuth2LoginView, ) from .forms import FacebookConnectForm from .provider import GRAPH_API_URL, GRAPH_API_VERSION, FacebookProvider logger = logging.getLogger(__name__) def compute_appsecret_proof(app, token): # Generate an appsecret_proof parameter to secure the Graph API call # see https://developers.facebook.com/docs/graph-api/securing-requests msg = token.token.encode("utf-8") key = app.secret.encode("utf-8") appsecret_proof = hmac.new(key, msg, digestmod=hashlib.sha256).hexdigest() return appsecret_proof def fb_complete_login(request, app, token): provider = app.get_provider(request) resp = requests.get( GRAPH_API_URL + "/me", params={ "fields": ",".join(provider.get_fields()), "access_token": token.token, "appsecret_proof": compute_appsecret_proof(app, token), }, ) resp.raise_for_status() extra_data = resp.json() login = provider.sociallogin_from_response(request, extra_data) return login class FacebookOAuth2Adapter(OAuth2Adapter): provider_id = FacebookProvider.id provider_default_auth_url = "https://www.facebook.com/{}/dialog/oauth".format( GRAPH_API_VERSION ) settings = app_settings.PROVIDERS.get(provider_id, {}) scope_delimiter = "," authorize_url = settings.get("AUTHORIZE_URL", provider_default_auth_url) access_token_url = GRAPH_API_URL + "/oauth/access_token" access_token_method = "GET" expires_in_key = "expires_in" def complete_login(self, request, app, access_token, **kwargs): return fb_complete_login(request, app, access_token) oauth2_login = OAuth2LoginView.adapter_view(FacebookOAuth2Adapter) oauth2_callback = OAuth2CallbackView.adapter_view(FacebookOAuth2Adapter) def login_by_token(request): ret = None auth_exception = None if request.method == "POST": form = FacebookConnectForm(request.POST) if form.is_valid(): try: adapter = get_adapter() provider = adapter.get_provider(request, FacebookProvider.id) login_options = provider.get_fb_login_options(request) app = provider.app access_token = form.cleaned_data["access_token"] expires_at = None if login_options.get("auth_type") == "reauthenticate": info = requests.get( GRAPH_API_URL + "/oauth/access_token_info", params={ "client_id": app.client_id, "access_token": access_token, }, ).json() nonce = provider.get_nonce(request, pop=True) ok = nonce and nonce == info.get("auth_nonce") else: ok = True if ok and provider.get_settings().get("EXCHANGE_TOKEN"): resp = requests.get( GRAPH_API_URL + "/oauth/access_token", params={ "grant_type": "fb_exchange_token", "client_id": app.client_id, "client_secret": app.secret, "fb_exchange_token": access_token, }, ).json() access_token = resp["access_token"] expires_in = resp.get("expires_in") if expires_in: expires_at = timezone.now() + timedelta(seconds=int(expires_in)) if ok: token = SocialToken( app=app, token=access_token, expires_at=expires_at ) login = fb_complete_login(request, app, token) login.token = token login.state = SocialLogin.state_from_request(request) ret = complete_social_login(request, login) except requests.RequestException as e: logger.exception("Error accessing FB user profile") auth_exception = e if not ret: ret = render_authentication_error( request, FacebookProvider.id, exception=auth_exception ) return ret