Integracja Facebook Connect z kontami Django na filmaster.pl
W artykule Integracja Facebook Connect z Django opisałem jak skonfigurować aplikację Facebook Connect, oraz jak wstawić button logowania w XFBML na własnej stronie. Dla Filmastera wykonałem integrację Facebook Connect z kontami użytkowników Django, dzięki której użytkownik po zalogowaniu przez FB Connect jest automatycznie rejestrowany/logowany w serwisie.
Podstawą jest middleware, które sprawdza czy są cookies ustawione przez Facebook Connect i jeżeli są to próbuje zalogować, zarejestrować użytkownika Django. FBC tworzy cookie o nazwie takiej jak klucz naszej aplikacji na Facebooku (plus kilka dodatkowych KLUCZ_*). Sprawdzanie poprawności cookie zawarte jest w kodzie middleware:
class fbMiddleware(object):
"""
Handle Facebook association, autologin
"""
def process_request(self, request):
request.facebookconn = False
request.facebookconn_new = False
f_name = False
l_name = False
# Check if we have the FBConnect cookie
if settings.FACEBOOK_CONNECT_KEY in request.COOKIES:
signature_hash = self.get_facebook_signature(request.COOKIES, True)
# check if cookie is valud
if signature_hash == request.COOKIES[settings.FACEBOOK_CONNECT_KEY]:
# check if it didn't expired
if datetime.fromtimestamp(float(request.COOKIES[settings.FACEBOOK_CONNECT_KEY+'_expires'])) > datetime.now():
# get the FB user ID from cookie
uid = '%s_user' % settings.FACEBOOK_CONNECT_KEY
cookie_uid = request.COOKIES[uid]
request.facebookconn = cookie_uid
# check if the FB user id is associated to a Django User account
try:
f = FBAssociation.objects.get(fb_uid=cookie_uid)
except Exception, e:
logging.debug("new Facebook association")
# no account, so we make a new one
password = ''.join([choice('1234567890qwertyuiopasdfghjklzxcvbnm') for i in range(10)])
if 'fb_name' in request.POST:
username = slughifi(request.POST['fb_name'])
else:
logging.error("NO Facebook username")
return None
if 'fb_mail' in request.POST:
email = request.POST['fb_mail']
if len(email) >= 255:
# too long
email = str(cookie_uid)
else:
logging.error("No Facebook proxy mail")
return None
username = username.replace('-', '')
# check if username taken
try:
u = User.objects.get(username=username)
except:
pass
else:
username = '%s%s' % (username, str(cookie_uid))
# this shouldn't exist
try:
u = User.objects.get(username=username)
except:
pass
else:
logging.error("Existing Facebook usernames %s" % username)
return None
# check if mail is taken
try:
u = User.objects.get(email=email)
except:
pass
else:
email = str(cookie_uid)
try:
u = User.objects.get(email=email)
except:
pass
else:
logging.error("Existing proxy email %s" % email)
return None
#make the user
user = None
try:
user = User.objects.create_user(username, email, password)
user.save()
user = authenticate(username=username, password=password)
except Exception, e:
try:
user.delete()
except:
pass
logging.error("Facebook User Creation Exception")
logging.error(e)
return None
else:
# save the Facebook association
o = FBAssociation(user=user, fb_uid=request.facebookconn, is_new=True, is_from_facebook=True)
o.save()
if user is not None:
# login user and make the association
try:
login(request, user)
except Exception, e:
logging.error("Facebook User Login Exception")
logging.error(e)
return None
if 'fb_pic' in request.POST:
try:
path = settings.MEDIA_ROOT + date.today().strftime("avatars/%Y/%b/%d")
if not os.path.isdir(path):
os.makedirs(path)
image = '%s/%s.jpg' % (path, str(user.username))
img = urllib2.urlopen(request.POST['fb_pic']).read()
tmp = open('%s/%s.jpg' % (path, str(user.username)), 'wb')
tmp.write(img)
tmp.close()
i = Image.open(image)
i.thumbnail((480, 480), Image.ANTIALIAS)
i.convert("RGB").save(image, "JPEG")
image = '%s/%s.jpg' % (date.today().strftime("avatars/%Y/%b/%d"), str(user.username))
avatar = Avatar(user=user, image=image, valid=True)
avatar.save()
except Exception, e:
logging.error("Could not save avatar from Facebook")
logging.error(e)
return None
else:
# FB ID is assigned to a Django User account. Login user
if not request.user.is_authenticated():
user = authenticate(user_id = f.user.id, fb_uid=cookie_uid)
if user is not None:
login(request, user)
else:
if request.user.is_authenticated():
# not FB cookie but user from FB logged in? logout
try:
f = FBAssociation.objects.get(user=request.user)
except:
pass
else:
if f.is_from_facebook:
logout(request)
def get_facebook_signature(self, values_dict, is_cookie_check=False):
"""
Generates signatures for FB requests/cookies
"""
signature_keys = []
for key in sorted(values_dict.keys()):
if (is_cookie_check and key.startswith(settings.FACEBOOK_CONNECT_KEY + '_')):
signature_keys.append(key)
elif (is_cookie_check is False):
signature_keys.append(key)
if (is_cookie_check):
signature_string = ''.join(['%s=%s' % (x.replace(settings.FACEBOOK_CONNECT_KEY + '_',''), values_dict[x]) for x in signature_keys])
else:
signature_string = ''.join(['%s=%s' % (x, values_dict[x]) for x in signature_keys])
signature_string = signature_string + settings.FACEBOOK_CONNECT_SECRET
return md5.new(signature_string).hexdigest()
Middleware rozpoznaje użytkownika z Facebook Connect po jego ID zawartym w cookie KLUCZ_user. Po tym ID tworzone są relacje z kontem w Django. Jeżeli middleware znajdzie powiązanie - to zaloguje na Djangowskie konto. Jeżeli nie ma powiązanego konta to będzie oczekiwało danych o użytkowniku (login, awatar, email), które można pobrać poprzez API Facebook Connect, ale za pomocą JavaScriptu. Dane te pobierane są po zalogowaniu przez FB Connect, przez kod w szablonie:
<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script>
<script type="text/javascript">
FB_RequireFeatures(["XFBML"], function()
{
FB.Facebook.init("{{ connect_key }}", "/fb/xd_receiver.htm");
FB.Facebook.get_sessionState().waitUntilReady(function()
{
var uid = FB.Facebook.apiClient.get_session().uid;
if (uid)
{
{% if not user.is_authenticated %}
var viewer = FB.Facebook.apiClient.fql_query('SELECT name, pic_small, proxied_email FROM user WHERE uid='+uid,
function(results) {
$.post("/", {fb_name: results[0].name, fb_pic: results[0].pic_small , fb_mail: results[0].proxied_email} , function(data){
{% ifequal request.path request.login_url %}
location.assign("/dashboard/");
{% else %}
location.assign("http://{{ request.META.HTTP_HOST }}{{request.path }}");
{% endifequal %}
});
}
);
{% endif %}
}
});
});
</script>
W przypadku API Facebooka - XFBML pewien problem stanowi konieczność stosowania API działającego po stronie klienta, co komplikuje działanie komponentu. Trzeba także stosować buttona Facebooka żeby skutecznie wylogować użytkownika (jeżeli usuniemy tylko cookie w serwisie to ponowne wyświetlenie buttona na jakiejś stronie spowoduje automatyczne zalogowanie użytkownika przez ten button)
<fb:login-button autologoutlink="true"></fb:login-button>
Comment article