This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,850 @@
<?xml version="1.0" encoding="UTF-8"?>
<locales>
<locale>
<englishName>Afrikaans</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>af_ZA</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Arabic</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ar_AR</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Azerbaijani</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>az_AZ</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Belarusian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>be_BY</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Bulgarian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>bg_BG</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Bengali</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>bn_IN</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Bosnian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>bs_BA</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Catalan</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ca_ES</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Czech</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>cs_CZ</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Welsh</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>cy_GB</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Danish</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>da_DK</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>German</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>de_DE</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Greek</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>el_GR</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>English (UK)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>en_GB</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>English (Pirate)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>en_PI</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>English (Upside Down)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>en_UD</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>English (US)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>en_US</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Esperanto</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>eo_EO</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Spanish (Spain)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>es_ES</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Spanish</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>es_LA</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Estonian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>et_EE</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Basque</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>eu_ES</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Persian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>fa_IR</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Leet Speak</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>fb_LT</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Finnish</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>fi_FI</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Faroese</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>fo_FO</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>French (Canada)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>fr_CA</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>French (France)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>fr_FR</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Frisian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>fy_NL</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Irish</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ga_IE</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Galician</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>gl_ES</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Hebrew</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>he_IL</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Hindi</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>hi_IN</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Croatian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>hr_HR</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Hungarian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>hu_HU</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Armenian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>hy_AM</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Indonesian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>id_ID</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Icelandic</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>is_IS</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Italian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>it_IT</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Japanese</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ja_JP</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Georgian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ka_GE</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Khmer</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>km_KH</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Korean</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ko_KR</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Kurdish</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ku_TR</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Latin</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>la_VA</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Lithuanian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>lt_LT</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Latvian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>lv_LV</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Macedonian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>mk_MK</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Malayalam</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ml_IN</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Malay</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ms_MY</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Norwegian (bokmal)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>nb_NO</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Nepali</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ne_NP</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Dutch</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>nl_NL</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Norwegian (nynorsk)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>nn_NO</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Punjabi</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>pa_IN</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Polish</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>pl_PL</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Pashto</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ps_AF</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Portuguese (Brazil)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>pt_BR</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Portuguese (Portugal)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>pt_PT</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Romanian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ro_RO</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Russian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ru_RU</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Slovak</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>sk_SK</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Slovenian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>sl_SI</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Albanian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>sq_AL</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Serbian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>sr_RS</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Swedish</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>sv_SE</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Swahili</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>sw_KE</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Tamil</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>ta_IN</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Telugu</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>te_IN</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Thai</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>th_TH</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Filipino</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>tl_PH</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Turkish</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>tr_TR</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Ukrainian</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>uk_UA</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Vietnamese</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>vi_VN</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Simplified Chinese (China)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>zh_CN</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Traditional Chinese (Hong Kong)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>zh_HK</representation>
</standard>
</code>
</codes>
</locale>
<locale>
<englishName>Traditional Chinese (Taiwan)</englishName>
<codes>
<code>
<standard>
<name>FB</name>
<representation>zh_TW</representation>
</standard>
</code>
</codes>
</locale>
</locales>

View File

@@ -0,0 +1,5 @@
from django import forms
class FacebookConnectForm(forms.Form):
access_token = forms.CharField(required=True)

View File

@@ -0,0 +1,70 @@
# Default locale mapping for the Facebook JS SDK
# The list of supported locales is at
# https://www.facebook.com/translations/FacebookLocales.xml
import os
from django.utils.translation import get_language, to_locale
def _build_locale_table(filename_or_file):
"""
Parses the FacebookLocales.xml file and builds a dict relating every
available language ('en, 'es, 'zh', ...) with a list of available regions
for that language ('en' -> 'US', 'EN') and an (arbitrary) default region.
"""
# Require the XML parser module only if we want the default mapping
from xml.dom.minidom import parse
dom = parse(filename_or_file)
reps = dom.getElementsByTagName("representation")
locs = map(lambda r: r.childNodes[0].data, reps)
locale_map = {}
for loc in locs:
lang, _, reg = loc.partition("_")
lang_map = locale_map.setdefault(lang, {"regs": [], "default": reg})
lang_map["regs"].append(reg)
# Default region overrides (arbitrary)
locale_map["en"]["default"] = "US"
# Special case: Use es_ES for Spain and es_LA for everything else
locale_map["es"]["default"] = "LA"
locale_map["zh"]["default"] = "CN"
locale_map["fr"]["default"] = "FR"
locale_map["pt"]["default"] = "PT"
return locale_map
def get_default_locale_callable():
"""
Wrapper function so that the default mapping is only built when needed
"""
exec_dir = os.path.dirname(os.path.realpath(__file__))
xml_path = os.path.join(exec_dir, "data", "FacebookLocales.xml")
fb_locales = _build_locale_table(xml_path)
def default_locale(request):
"""
Guess an appropriate FB locale based on the active Django locale.
If the active locale is available, it is returned. Otherwise,
it tries to return another locale with the same language. If there
isn't one available, 'en_US' is returned.
"""
chosen = "en_US"
language = get_language()
if language:
locale = to_locale(language)
lang, _, reg = locale.partition("_")
lang_map = fb_locales.get(lang)
if lang_map is not None:
if reg in lang_map["regs"]:
chosen = lang + "_" + reg
else:
chosen = lang + "_" + lang_map["default"]
return chosen
return default_locale

View File

@@ -0,0 +1,213 @@
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]

View File

@@ -0,0 +1,125 @@
/* global document, window, FB */
(function () {
'use strict'
function postForm (action, data) {
var f = document.createElement('form')
f.method = 'POST'
f.action = action
for (var key in data) {
var d = document.createElement('input')
d.type = 'hidden'
d.name = key
d.value = data[key]
f.appendChild(d)
}
document.body.appendChild(f)
f.submit()
}
function setLocationHref (url) {
if (typeof (url) === 'function') {
// Deprecated -- instead, override
// allauth.facebook.onLoginError et al directly.
url()
} else {
window.location.href = url
}
}
var allauth = window.allauth = window.allauth || {}
var fbSettings = JSON.parse(document.getElementById('allauth-facebook-settings').innerHTML)
var fbInitialized = false
allauth.facebook = {
init: function (opts) {
this.opts = opts
window.fbAsyncInit = function () {
FB.init(opts.initParams)
fbInitialized = true
allauth.facebook.onInit()
};
(function (d) {
var js
var id = 'facebook-jssdk'
if (d.getElementById(id)) { return }
js = d.createElement('script'); js.id = id; js.async = true
js.src = opts.sdkUrl
d.getElementsByTagName('head')[0].appendChild(js)
}(document))
},
onInit: function () {
},
login: function (nextUrl, action, process, scope) {
var self = this
if (!fbInitialized) {
var url = this.opts.loginUrl + '?next=' + encodeURIComponent(nextUrl) + '&action=' + encodeURIComponent(action) + '&process=' + encodeURIComponent(process) + '&scope=' + encodeURIComponent(scope)
setLocationHref(url)
return
}
if (action === 'reauthenticate' || action === 'rerequest') {
this.opts.loginOptions.auth_type = action
}
if (scope !== '') {
this.opts.loginOptions.scope = scope
}
FB.login(function (response) {
if (response.authResponse) {
self.onLoginSuccess(response, nextUrl, process)
} else if (response && response.status && ['not_authorized', 'unknown'].indexOf(response.status) > -1) {
self.onLoginCanceled(response)
} else {
self.onLoginError(response)
}
}, self.opts.loginOptions)
},
onLoginCanceled: function (/* response */) {
setLocationHref(this.opts.cancelUrl)
},
onLoginError: function (/* response */) {
setLocationHref(this.opts.errorUrl)
},
onLoginSuccess: function (response, nextUrl, process) {
var data = {
next: nextUrl || '',
process: process,
access_token: response.authResponse.accessToken,
expires_in: response.authResponse.expiresIn,
csrfmiddlewaretoken: this.opts.csrfToken
}
postForm(this.opts.loginByTokenUrl, data)
},
logout: function (nextUrl) {
var self = this
if (!fbInitialized) {
return
}
FB.logout(function (response) {
self.onLogoutSuccess(response, nextUrl)
})
},
onLogoutSuccess: function (response, nextUrl) {
var data = {
next: nextUrl || '',
csrfmiddlewaretoken: this.opts.csrfToken
}
postForm(this.opts.logoutUrl, data)
}
}
allauth.facebook.init(fbSettings)
})()

View File

@@ -0,0 +1,6 @@
{% load static %}
<div id="fb-root"></div>
<script id="allauth-facebook-settings" type="application/json">
{{ fb_data }}
</script>
<script type="text/javascript" src="{% static 'facebook/js/fbconnect.js' %}"></script>

View File

@@ -0,0 +1,138 @@
import json
from django.test.client import RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
from allauth.account import app_settings as account_settings
from allauth.account.models import EmailAddress
from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase, patch
from allauth.utils import get_user_model
from .provider import FacebookProvider
@override_settings(
SOCIALACCOUNT_AUTO_SIGNUP=True,
ACCOUNT_SIGNUP_FORM_CLASS=None,
LOGIN_REDIRECT_URL="/accounts/profile/",
ACCOUNT_EMAIL_VERIFICATION=account_settings.EmailVerificationMethod.NONE,
SOCIALACCOUNT_PROVIDERS={"facebook": {"AUTH_PARAMS": {}, "VERIFIED_EMAIL": False}},
)
class FacebookTests(OAuth2TestsMixin, TestCase):
provider_id = FacebookProvider.id
facebook_data = """
{
"id": "630595557",
"name": "Raymond Penners",
"first_name": "Raymond",
"last_name": "Penners",
"email": "raymond.penners@example.com",
"link": "https://www.facebook.com/raymond.penners",
"username": "raymond.penners",
"birthday": "07/17/1973",
"work": [
{
"employer": {
"id": "204953799537777",
"name": "IntenCT"
}
}
],
"timezone": 1,
"locale": "nl_NL",
"verified": true,
"updated_time": "2012-11-30T20:40:33+0000"
}"""
def get_mocked_response(self, data=None):
if data is None:
data = self.facebook_data
return MockedResponse(200, data)
def test_username_conflict(self):
User = get_user_model()
User.objects.create(username="raymond.penners")
self.login(self.get_mocked_response())
socialaccount = SocialAccount.objects.get(uid="630595557")
self.assertEqual(socialaccount.user.username, "raymond")
def test_username_based_on_provider(self):
self.login(self.get_mocked_response())
socialaccount = SocialAccount.objects.get(uid="630595557")
self.assertEqual(socialaccount.user.username, "raymond.penners")
def test_username_based_on_provider_with_simple_name(self):
data = '{"id": "1234567", "name": "Harvey McGillicuddy"}'
self.login(self.get_mocked_response(data=data))
socialaccount = SocialAccount.objects.get(uid="1234567")
self.assertEqual(socialaccount.user.username, "harvey")
@override_settings(
SOCIALACCOUNT_PROVIDERS={
"facebook": {
"METHOD": "js_sdk",
}
},
)
def test_media_js(self):
request = RequestFactory().get(reverse("account_login"))
request.session = {}
script = self.provider.media_js(request)
self.assertTrue('"appId": "app123id"' in script)
def test_login_by_token(self):
resp = self.client.get(reverse("account_login"))
with patch(
"allauth.socialaccount.providers.facebook.views.requests"
) as requests_mock:
mocks = [self.get_mocked_response().json()]
requests_mock.get.return_value.json = lambda: mocks.pop()
resp = self.client.post(
reverse("facebook_login_by_token"),
data={"access_token": "dummy"},
)
self.assertRedirects(
resp, "/accounts/profile/", fetch_redirect_response=False
)
@override_settings(
SOCIALACCOUNT_PROVIDERS={
"facebook": {
"METHOD": "js_sdk",
"AUTH_PARAMS": {"auth_type": "reauthenticate"},
"VERIFIED_EMAIL": False,
}
}
)
def test_login_by_token_reauthenticate(self):
resp = self.client.get(reverse("account_login"))
nonce = json.loads(resp.context["fb_data"])["loginOptions"]["auth_nonce"]
with patch(
"allauth.socialaccount.providers.facebook.views.requests"
) as requests_mock:
mocks = [self.get_mocked_response().json(), {"auth_nonce": nonce}]
requests_mock.get.return_value.json = lambda: mocks.pop()
resp = self.client.post(
reverse("facebook_login_by_token"),
data={"access_token": "dummy"},
)
self.assertRedirects(
resp, "/accounts/profile/", fetch_redirect_response=False
)
@override_settings(SOCIALACCOUNT_PROVIDERS={"facebook": {"VERIFIED_EMAIL": True}})
def test_login_verified(self):
emailaddress = self._login_verified()
self.assertTrue(emailaddress.verified)
def test_login_unverified(self):
emailaddress = self._login_verified()
self.assertFalse(emailaddress.verified)
def _login_verified(self):
self.login(self.get_mocked_response())
return EmailAddress.objects.get(email="raymond.penners@example.com")

View File

@@ -0,0 +1,17 @@
from django.urls import path
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
from . import views
from .provider import FacebookProvider
urlpatterns = default_urlpatterns(FacebookProvider)
urlpatterns += [
path(
"facebook/login/token/",
views.login_by_token,
name="facebook_login_by_token",
),
]

View File

@@ -0,0 +1,129 @@
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