/usr/share/pyshared/social_auth/backends/facebook.py is in python-django-social-auth 0.7.23-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | """
Facebook OAuth support.
This contribution adds support for Facebook OAuth service. The settings
FACEBOOK_APP_ID and FACEBOOK_API_SECRET must be defined with the values
given by Facebook application registration process.
Extended permissions are supported by defining FACEBOOK_EXTENDED_PERMISSIONS
setting, it must be a list of values to request.
By default account id and token expiration time are stored in extra_data
field, check OAuthBackend class for details on how to extend it.
"""
import cgi
import base64
import hmac
import hashlib
import time
from urllib import urlencode
from urllib2 import HTTPError
from django.utils import simplejson
from django.contrib.auth import authenticate
from django.http import HttpResponse
from django.template import TemplateDoesNotExist, RequestContext, loader
from social_auth.backends import BaseOAuth2, OAuthBackend
from social_auth.utils import sanitize_log_data, backend_setting, setting,\
log, dsa_urlopen
from social_auth.exceptions import AuthException, AuthCanceled, AuthFailed,\
AuthTokenError, AuthUnknownError
# Facebook configuration
FACEBOOK_ME = 'https://graph.facebook.com/me?'
ACCESS_TOKEN = 'https://graph.facebook.com/oauth/access_token?'
USE_APP_AUTH = setting('FACEBOOK_APP_AUTH', False)
LOCAL_HTML = setting('FACEBOOK_LOCAL_HTML', 'facebook.html')
APP_NAMESPACE = setting('FACEBOOK_APP_NAMESPACE', None)
REDIRECT_HTML = """
<script type="text/javascript">
var domain = 'https://apps.facebook.com/',
redirectURI = domain + {{ FACEBOOK_APP_NAMESPACE }} + '/';
window.top.location = 'https://www.facebook.com/dialog/oauth/' +
'?client_id={{ FACEBOOK_APP_ID }}' +
'&redirect_uri=' + encodeURIComponent(redirectURI) +
'&scope={{ FACEBOOK_EXTENDED_PERMISSIONS }}';
</script>
"""
class FacebookBackend(OAuthBackend):
"""Facebook OAuth2 authentication backend"""
name = 'facebook'
# Default extra data to store
EXTRA_DATA = [
('id', 'id'),
('expires', 'expires')
]
def get_user_details(self, response):
"""Return user details from Facebook account"""
return {'username': response.get('username', response.get('name')),
'email': response.get('email', ''),
'fullname': response.get('name', ''),
'first_name': response.get('first_name', ''),
'last_name': response.get('last_name', '')}
class FacebookAuth(BaseOAuth2):
"""Facebook OAuth2 support"""
AUTH_BACKEND = FacebookBackend
RESPONSE_TYPE = None
SCOPE_SEPARATOR = ','
AUTHORIZATION_URL = 'https://www.facebook.com/dialog/oauth'
ACCESS_TOKEN_URL = ACCESS_TOKEN
SETTINGS_KEY_NAME = 'FACEBOOK_APP_ID'
SETTINGS_SECRET_NAME = 'FACEBOOK_API_SECRET'
SCOPE_VAR_NAME = 'FACEBOOK_EXTENDED_PERMISSIONS'
EXTRA_PARAMS_VAR_NAME = 'FACEBOOK_PROFILE_EXTRA_PARAMS'
def user_data(self, access_token, *args, **kwargs):
"""
Grab user profile information from facebook.
returns: dict or None
"""
data = None
params = backend_setting(self, self.EXTRA_PARAMS_VAR_NAME, {})
params['access_token'] = access_token
url = FACEBOOK_ME + urlencode(params)
try:
response = dsa_urlopen(url)
data = simplejson.load(response)
except ValueError:
extra = {'access_token': sanitize_log_data(access_token)}
log('error', 'Could not load user data from Facebook.',
exc_info=True, extra=extra)
except HTTPError:
extra = {'access_token': sanitize_log_data(access_token)}
log('error', 'Error validating access token.',
exc_info=True, extra=extra)
raise AuthTokenError(self)
else:
log('debug', 'Found user data for token %s',
sanitize_log_data(access_token), extra={'data': data})
return data
def auth_complete(self, *args, **kwargs):
"""Completes loging process, must return user instance"""
access_token = None
expires = None
if 'code' in self.data:
state = self.validate_state()
url = ACCESS_TOKEN + urlencode({
'client_id': backend_setting(self, self.SETTINGS_KEY_NAME),
'redirect_uri': self.get_redirect_uri(state),
'client_secret': backend_setting(
self,
self.SETTINGS_SECRET_NAME
),
'code': self.data['code']
})
try:
payload = dsa_urlopen(url)
except HTTPError:
raise AuthFailed(self, 'There was an error authenticating '
'the app')
response = payload.read()
parsed_response = cgi.parse_qs(response)
access_token = parsed_response['access_token'][0]
if 'expires' in parsed_response:
expires = parsed_response['expires'][0]
if 'signed_request' in self.data:
response = load_signed_request(
self.data.get('signed_request'),
backend_setting(self, self.SETTINGS_SECRET_NAME)
)
if response is not None:
access_token = response.get('access_token') or\
response.get('oauth_token') or\
self.data.get('access_token')
if 'expires' in response:
expires = response['expires']
if access_token:
return self.do_auth(access_token, expires=expires, *args, **kwargs)
else:
if self.data.get('error') == 'access_denied':
raise AuthCanceled(self)
else:
raise AuthException(self)
@classmethod
def process_refresh_token_response(cls, response):
return dict((key, val[0])
for key, val in cgi.parse_qs(response).iteritems())
@classmethod
def refresh_token_params(cls, token):
client_id, client_secret = cls.get_key_and_secret()
return {
'fb_exchange_token': token,
'grant_type': 'fb_exchange_token',
'client_id': client_id,
'client_secret': client_secret
}
def do_auth(self, access_token, expires=None, *args, **kwargs):
data = self.user_data(access_token)
if not isinstance(data, dict):
# From time to time Facebook responds back a JSON with just
# False as value, the reason is still unknown, but since the
# data is needed (it contains the user ID used to identify the
# account on further logins), this app cannot allow it to
# continue with the auth process.
raise AuthUnknownError(self, 'An error ocurred while '
'retrieving users Facebook '
'data')
data['access_token'] = access_token
if expires: # expires is None on offline access
data['expires'] = expires
kwargs.update({'auth': self,
'response': data,
self.AUTH_BACKEND.name: True})
return authenticate(*args, **kwargs)
@classmethod
def enabled(cls):
"""Return backend enabled status by checking basic settings"""
return backend_setting(cls, cls.SETTINGS_KEY_NAME) and\
backend_setting(cls, cls.SETTINGS_SECRET_NAME)
def base64_url_decode(data):
data = data.encode(u'ascii')
data += '=' * (4 - (len(data) % 4))
return base64.urlsafe_b64decode(data)
def base64_url_encode(data):
return base64.urlsafe_b64encode(data).rstrip('=')
def load_signed_request(signed_request, api_secret=None):
try:
sig, payload = signed_request.split(u'.', 1)
sig = base64_url_decode(sig)
data = simplejson.loads(base64_url_decode(payload))
expected_sig = hmac.new(api_secret or setting('FACEBOOK_API_SECRET'),
msg=payload,
digestmod=hashlib.sha256).digest()
# allow the signed_request to function for upto 1 day
if sig == expected_sig and \
data[u'issued_at'] > (time.time() - 86400):
return data
except ValueError:
pass # ignore if can't split on dot
class FacebookAppAuth(FacebookAuth):
"""Facebook Application Authentication support"""
uses_redirect = False
def auth_complete(self, *args, **kwargs):
if not self.application_auth():
return HttpResponse(self.auth_html())
access_token = None
expires = None
if 'signed_request' in self.data:
response = load_signed_request(
self.data.get('signed_request'),
backend_setting(self, self.SETTINGS_SECRET_NAME)
)
if response is not None:
access_token = response.get('access_token') or\
response.get('oauth_token') or\
self.data.get('access_token')
if 'expires' in response:
expires = response['expires']
if access_token:
return self.do_auth(access_token, expires=expires, *args, **kwargs)
else:
if self.data.get('error') == 'access_denied':
raise AuthCanceled(self)
else:
raise AuthException(self)
def application_auth(self):
required_params = ('user_id', 'oauth_token')
data = load_signed_request(
self.data.get('signed_request'),
backend_setting(self, self.SETTINGS_SECRET_NAME)
)
for param in required_params:
if not param in data:
return False
return True
def auth_html(self):
app_id = backend_setting(self, self.SETTINGS_KEY_NAME)
ctx = {
'FACEBOOK_APP_ID': app_id,
'FACEBOOK_EXTENDED_PERMISSIONS': ','.join(
backend_setting(self, self.SCOPE_VAR_NAME)
),
'FACEBOOK_COMPLETE_URI': self.redirect_uri,
'FACEBOOK_APP_NAMESPACE': APP_NAMESPACE or app_id
}
try:
fb_template = loader.get_template(LOCAL_HTML)
except TemplateDoesNotExist:
fb_template = loader.get_template_from_string(REDIRECT_HTML)
context = RequestContext(self.request, ctx)
return fb_template.render(context)
# Backend definition
BACKENDS = {
'facebook': FacebookAppAuth if USE_APP_AUTH else FacebookAuth,
}
|