import json import string from urllib.parse import quote from django.conf import settings from django.middleware.csrf import get_token from django.template.loader import render_to_string from django.urls import reverse from django.utils.crypto import get_random_string from django.utils.html import escapejs, mark_safe from allauth.account.models import EmailAddress from allauth.socialaccount.app_settings import QUERY_EMAIL from allauth.socialaccount.providers.base import ( AuthAction, AuthProcess, ProviderAccount, ) from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider from allauth.utils import import_callable from .locale import get_default_locale_callable GRAPH_API_VERSION = ( getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}) .get("facebook", {}) .get("VERSION", "v13.0") ) GRAPH_API_URL = ( getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}) .get("facebook", {}) .get("GRAPH_API_URL", "https://graph.facebook.com/{}".format(GRAPH_API_VERSION)) ) NONCE_SESSION_KEY = "allauth_facebook_nonce" NONCE_LENGTH = 32 class FacebookAccount(ProviderAccount): def get_profile_url(self): return self.account.extra_data.get("link") def get_avatar_url(self): uid = self.account.uid # ask for a 600x600 pixel image. We might get smaller but # image will always be highest res possible and square return ( GRAPH_API_URL + "/%s/picture?type=square&height=600&width=600&return_ssl_resources=1" % uid ) # noqa def to_str(self): dflt = super(FacebookAccount, self).to_str() return self.account.extra_data.get("name", dflt) class FacebookProvider(OAuth2Provider): id = "facebook" name = "Facebook" account_class = FacebookAccount def __init__(self, *args, **kwargs): self._locale_callable_cache = None super().__init__(*args, **kwargs) def get_method(self): return self.get_settings().get("METHOD", "oauth2") def get_login_url(self, request, **kwargs): method = kwargs.pop("method", self.get_method()) if method == "js_sdk": next = "'%s'" % escapejs(kwargs.get("next") or "") process = "'%s'" % escapejs(kwargs.get("process") or AuthProcess.LOGIN) action = "'%s'" % escapejs(kwargs.get("action") or AuthAction.AUTHENTICATE) scope = "'%s'" % escapejs(kwargs.get("scope", "")) js = "allauth.facebook.login(%s, %s, %s, %s)" % ( next, action, process, scope, ) ret = "javascript:%s" % (quote(js),) elif method == "oauth2": ret = super(FacebookProvider, self).get_login_url(request, **kwargs) else: raise RuntimeError("Invalid method specified: %s" % method) return ret def _get_locale_callable(self): settings = self.get_settings() func = settings.get("LOCALE_FUNC") return import_callable(func) if func else get_default_locale_callable() def get_locale_for_request(self, request): if not self._locale_callable_cache: self._locale_callable_cache = self._get_locale_callable() return self._locale_callable_cache(request) def get_default_scope(self): scope = [] if QUERY_EMAIL: scope.append("email") return scope def get_fields(self): settings = self.get_settings() default_fields = [ "id", "email", "name", "first_name", "last_name", "verified", "locale", "timezone", "link", "gender", "updated_time", ] return settings.get("FIELDS", default_fields) def get_auth_params(self, request, action): ret = super(FacebookProvider, self).get_auth_params(request, action) if action == AuthAction.REAUTHENTICATE: ret["auth_type"] = "reauthenticate" elif action == AuthAction.REREQUEST: ret["auth_type"] = "rerequest" return ret def get_init_params(self, request, app): init_params = {"appId": app.client_id, "version": GRAPH_API_VERSION} settings = self.get_settings() init_params.update(settings.get("INIT_PARAMS", {})) return init_params def get_fb_login_options(self, request): ret = self.get_auth_params(request, "authenticate") ret["scope"] = ",".join(self.get_scope(request)) if ret.get("auth_type") == "reauthenticate": ret["auth_nonce"] = self.get_nonce(request, or_create=True) return ret def get_sdk_url(self, request): settings = self.get_settings() sdk_url = settings.get("SDK_URL", "//connect.facebook.net/{locale}/sdk.js") field_names = [ tup[1] for tup in string.Formatter().parse(sdk_url) if tup[1] is not None ] if "locale" in field_names: locale = self.get_locale_for_request(request) sdk_url = sdk_url.format(locale=locale) return sdk_url def media_js(self, request): if self.get_method() != "js_sdk": return "" def abs_uri(name): return request.build_absolute_uri(reverse(name)) fb_data = { "appId": self.app.client_id, "version": GRAPH_API_VERSION, "sdkUrl": self.get_sdk_url(request), "initParams": self.get_init_params(request, self.app), "loginOptions": self.get_fb_login_options(request), "loginByTokenUrl": abs_uri("facebook_login_by_token"), "cancelUrl": abs_uri("socialaccount_login_cancelled"), "logoutUrl": abs_uri("account_logout"), "loginUrl": request.build_absolute_uri( self.get_login_url(request, method="oauth2") ), "errorUrl": abs_uri("socialaccount_login_error"), "csrfToken": get_token(request), } ctx = {"fb_data": mark_safe(json.dumps(fb_data))} return render_to_string("facebook/fbconnect.html", ctx, request=request) def get_nonce(self, request, or_create=False, pop=False): if pop: nonce = request.session.pop(NONCE_SESSION_KEY, None) else: nonce = request.session.get(NONCE_SESSION_KEY) if not nonce and or_create: nonce = get_random_string(NONCE_LENGTH) request.session[NONCE_SESSION_KEY] = nonce return nonce def extract_uid(self, data): return data["id"] def extract_common_fields(self, data): return dict( email=data.get("email"), username=data.get("username"), first_name=data.get("first_name"), last_name=data.get("last_name"), name=data.get("name"), ) def extract_email_addresses(self, data): ret = [] email = data.get("email") if email: # data['verified'] does not imply the email address is # verified. ret.append(EmailAddress(email=email, verified=False, primary=True)) return ret provider_classes = [FacebookProvider]