def send(self, graph=None, shared_explicitly=False): result = None # update the last attempt self.last_attempt = datetime.now() self.save() # see if the graph is enabled profile = try_get_profile(self.user) user_or_profile = get_instance_for_attribute( self.user, profile, 'access_token') graph = graph or user_or_profile.get_offline_graph() user_enabled = shared_explicitly or \ (user_or_profile.facebook_open_graph and self.facebook_user_id) # start sharing if graph and user_enabled: graph_location = '%s/%s' % ( self.facebook_user_id, self.action_domain) share_dict = self.get_share_dict() from open_facebook.exceptions import OpenFacebookException try: result = graph.set(graph_location, **share_dict) share_id = result.get('id') if not share_id: error_message = 'No id in Facebook response, found %s for url %s with data %s' % ( result, graph_location, share_dict) logger.error(error_message) raise OpenFacebookException(error_message) self.share_id = share_id self.error_message = None self.completed_at = datetime.now() self.save() except OpenFacebookException as e: logger.warn( 'Open graph share failed, writing message %s' % str(e)) self.error_message = repr(e) self.save() # maybe we need a new access token new_token_required = self.exception_requires_new_token( e, graph) # verify that the token didnt change in the mean time user_or_profile = user_or_profile.__class__.objects.get( id=user_or_profile.id) token_changed = graph.access_token != user_or_profile.access_token logger.info('new token required is %s and token_changed is %s', new_token_required, token_changed) if new_token_required and not token_changed: logger.info( 'a new token is required, setting the flag on the user or profile') # time to ask the user for a new token update_user_attributes(self.user, profile, dict( new_token_required=True), save=True) elif not graph: self.error_message = 'no graph available' self.save() elif not user_enabled: self.error_message = 'user not enabled' self.save() return result
def test_follow_og_share_error(self): from django_facebook.models import OpenGraphShare user_url = 'http://www.fashiolista.com/style/neni/' kwargs = dict(item=user_url) user = get_user_model().objects.all()[:1][0] profile = try_get_profile(user) user_or_profile = get_instance_for_attribute( user, profile, 'facebook_open_graph') user_or_profile.facebook_open_graph = True user_or_profile.save() from django.contrib.contenttypes.models import ContentType some_content_type = ContentType.objects.all()[:1][0] share = OpenGraphShare.objects.create( user_id=user.id, facebook_user_id=13123123, action_domain='fashiolista:follow', content_type=some_content_type, object_id=user.id, ) share.set_share_dict(kwargs) share.save() from open_facebook.exceptions import FacebookUnreachable with mock.patch('open_facebook.api.OpenFacebook') as mocked: instance = mocked.return_value instance.set = Mock(side_effect=FacebookUnreachable('broken')) share.send(graph=instance) self.assertEqual(share.error_message, 'broken') self.assertFalse(share.completed_at)
def profile_or_self(self): user_or_profile_model = get_model_for_attribute('facebook_id') user_model = get_user_model() if user_or_profile_model == user_model: return self else: return try_get_profile(self)
def profile_authenticate(self, facebook_id=None, facebook_email=None): ''' Authenticate the facebook user by id OR facebook_email We filter using an OR to allow existing members to connect with their facebook ID using email. :param facebook_id: Optional string representing the facebook id :param facebook_email: Optional string with the facebook email :return: The signed in :class:`User`. ''' logger.info("PA01 Inside profile authentication") if facebook_id or facebook_email: profile_class = get_profile_model() logger.info("PA02 Profile class %s" % profile_class) profile_query = profile_class.objects.all().order_by('user') profile_query = profile_query.select_related('user') logger.info("PA03 Profile query %s" % profile_query) profile = None # filter on email or facebook id, two queries for better # queryplan with large data sets if facebook_id: profiles = profile_query.filter(facebook_id=facebook_id)[:1] logger.info("PA04 Profiles from facebook_id %s" % profiles) profile = profiles[0] if profiles else None logger.info("PA05 Profile %s" % profile) if profile is None and facebook_email: try: profiles = profile_query.filter( user__email__iexact=facebook_email)[:1] logger.info("PA06 Profiles from email %s" % profiles) profile = profiles[0] if profiles else None logger.info("PA07 Profile %s" % profile) except DatabaseError: logger.info("PA08 Database error") try: user = get_user_model( ).objects.get(email=facebook_email) logger.info("PA09 User from model %s" % user) except get_user_model().DoesNotExist: logger.info("PA10 No model") user = None profile = try_get_profile(user) if user else None logger.info("PA11 Profile %s" % profile) if profile: # populate the profile cache while we're getting it anyway user = profile.user logger.info("PA07 User from profile %s" % user) user._profile = profile if facebook_settings.FACEBOOK_FORCE_PROFILE_UPDATE_ON_LOGIN: logger.info("UA10 Require user update") user.fb_update_required = True return user
def remove(self, graph=None): if not self.share_id: raise ValueError('Can only delete shares which have an id') # see if the graph is enabled profile = try_get_profile(self.user) graph = graph or profile.get_offline_graph() response = None if graph: response = graph.delete(self.share_id) self.removed_at = datetime.now() self.save() return response
def _add_current_user_id(graph, user): ''' set the current user id, convenient if you want to make sure you fb session and user belong together ''' if graph: graph.current_user_id = None if user.is_authenticated(): profile = try_get_profile(user) facebook_id = get_user_attribute(user, profile, 'facebook_id') if facebook_id: graph.current_user_id = facebook_id
def test_gender_matching(self): request = RequestMock().get('/') request.session = {} request.user = AnonymousUser() graph = get_persistent_graph(request, access_token='paul') converter = FacebookUserConverter(graph) base_data = converter.facebook_profile_data() self.assertEqual(base_data['gender'], 'male') data = converter.facebook_registration_data() self.assertEqual(data['gender'], 'm') action, user = connect_user(self.request, facebook_graph=graph) profile = try_get_profile(user) gender = get_user_attribute(user, profile, 'gender') self.assertEqual(gender, 'm')
def test_follow_og_share_error(self): user_url = 'http://www.fashiolista.com/style/neni/' kwargs = dict(item=user_url) user = get_user_model().objects.all()[:1][0] profile = try_get_profile(user) user_or_profile = get_instance_for_attribute( user, profile, 'facebook_open_graph') user_or_profile.facebook_open_graph = True user_or_profile.save() some_content_type = ContentType.objects.all()[:1][0] share = OpenGraphShare.objects.create( user_id=user.id, facebook_user_id=13123123, action_domain='fashiolista:follow', content_type=some_content_type, object_id=user.id, ) share.set_share_dict(kwargs) share.save() update_user_attributes(user, profile, dict(new_token_required=False)) from open_facebook.exceptions import FacebookUnreachable, OAuthException with mock.patch('open_facebook.api.OpenFacebook') as mocked: instance = mocked.return_value instance.set = Mock(side_effect=FacebookUnreachable('broken')) share.send(graph=instance) self.assertEqual(share.error_message, 'broken') self.assertFalse(share.completed_at) user = get_user_model().objects.get(id=user.id) if profile: profile = get_profile_model().objects.get(id=profile.id) new_token_required = get_user_attribute( user, profile, 'new_token_required') self.assertEqual(new_token_required, False) # now try with an oauth exception return # Can't figure out how to test this with mock.patch('open_facebook.api.OpenFacebook') as mocked: instance = mocked.return_value instance.set = Mock(side_effect=OAuthException('permissions')) share.send(graph=instance) self.assertEqual(share.error_message, 'permissions') self.assertFalse(share.completed_at) user = get_user_model().objects.get(id=user.id) if profile: profile = get_profile_model().objects.get(id=profile.id) new_token_required = get_user_attribute( user, profile, 'new_token_required') self.assertEqual(new_token_required, True)
def disconnect(request): ''' Removes Facebook from the users profile And redirects to the specified next page ''' if request.method == 'POST': logger.info("You have disconnected your Facebook profile.") messages.info( request, _("You have disconnected your Facebook profile.")) profile = try_get_profile(request.user) profile.disconnect_facebook() profile.save() response = next_redirect(request) return response
def remove(self, graph=None): if not self.share_id: raise ValueError('Can only delete shares which have an id') # see if the graph is enabled profile = try_get_profile(self.user) user_or_profile = get_instance_for_attribute( self.user, profile, 'access_token') graph = graph or user_or_profile.get_offline_graph() response = None if graph: response = graph.delete(self.share_id) self.removed_at = datetime.now() self.save() return response
def update(self, data, graph=None): ''' Update the share with the given data ''' profile = try_get_profile(self.user) user_or_profile = get_instance_for_attribute( self.user, profile, 'access_token') graph = graph or user_or_profile.get_offline_graph() # update the share dict so a retry will do the right thing # just in case we fail the first time shared = self.update_share_dict(data) self.save() # broadcast the change to facebook if self.share_id: return graph.set(self.share_id, **shared)
def _add_current_user_id(graph, user): ''' set the current user id, convenient if you want to make sure you fb session and user belong together ''' logger.info("ACUI01 add user id") if graph: logger.info("ACUI02 graph") graph.current_user_id = None if user.is_authenticated(): logger.info("ACUI03 user is authenticated, try get profile") profile = try_get_profile(user) facebook_id = get_user_attribute(user, profile, 'facebook_id') logger.info("ACUI04 facebook_id %s" % facebook_id) if facebook_id: graph.current_user_id = facebook_id
def profile_authenticate(self, facebook_id=None, facebook_email=None): ''' Authenticate the facebook user by id OR facebook_email We filter using an OR to allow existing members to connect with their facebook ID using email. :param facebook_id: Optional string representing the facebook id :param facebook_email: Optional string with the facebook email :return: The signed in :class:`User`. ''' if facebook_id or facebook_email: profile_class = get_profile_model() profile_query = profile_class.objects.all().order_by('user') profile_query = profile_query.select_related('user') profile = None # filter on email or facebook id, two queries for better # queryplan with large data sets if facebook_id: profiles = profile_query.filter(facebook_id=facebook_id)[:1] profile = profiles[0] if profiles else None if profile is None and facebook_email: try: profiles = profile_query.filter( user__email__iexact=facebook_email)[:1] profile = profiles[0] if profiles else None except DatabaseError: try: user = get_user_model( ).objects.get(email=facebook_email) except get_user_model().DoesNotExist: user = None profile = try_get_profile(user) if user else None if profile: # populate the profile cache while we're getting it anyway user = profile.user user._profile = profile if facebook_settings.FACEBOOK_FORCE_PROFILE_UPDATE_ON_LOGIN: user.fb_update_required = True return user
def update(self, data, graph=None): ''' Update the share with the given data ''' result = None profile = try_get_profile(self.user) graph = graph or profile.get_offline_graph() # update the share dict so a retry will do the right thing # just in case we fail the first time shared = self.update_share_dict(data) self.save() # broadcast the change to facebook if self.share_id: result = graph.set(self.share_id, **shared) return result
def disconnect(request): ''' Removes Facebook from the users profile And redirects to the specified next page ''' if request.method == 'POST': messages.info( request, _("You have disconnected your Facebook profile.")) user = request.user profile = try_get_profile(user) user_or_profile = get_instance_for_attribute(user, profile, 'access_token') user_or_profile.disconnect_facebook() user_or_profile.save() response = next_redirect(request) return response
def _update_access_token(user, graph): """ Conditionally updates the access token in the database """ profile = try_get_profile(user) model_or_profile = get_instance_for_attribute(user, profile, "access_token") # store the access token for later usage if the profile model supports it if model_or_profile: # update if not equal to the current token new_token = graph.access_token != model_or_profile.access_token token_message = "a new" if new_token else "the same" logger.info("found %s token %s", token_message, graph.access_token[:10]) if new_token: logger.info("access token changed, updating now") model_or_profile.update_access_token(graph.access_token) model_or_profile.save() # see if we can extend the access token # this runs in a task, after extending the token we fire an event model_or_profile.extend_access_token()
def _update_access_token(user, graph): ''' Conditionally updates the access token in the database ''' profile = try_get_profile(user) model_or_profile = get_instance_for_attribute( user, profile, 'access_token') # store the access token for later usage if the profile model supports it if model_or_profile: # update if not equal to the current token new_token = graph.access_token != model_or_profile.access_token token_message = 'a new' if new_token else 'the same' logger.info('found %s token', token_message) if new_token: logger.info('access token changed, updating now') model_or_profile.access_token = graph.access_token model_or_profile.save() # see if we can extend the access token # this runs in a task, after extending the token we fire an event model_or_profile.extend_access_token()
def check_django_facebook_user(self, request, facebook_id, access_token): logger.info("CDFU01 Checking django facebook user...") try: current_user = try_get_profile(request.user) logger.info("CDFU02 got user profile %s" % current_user) except: logger.info("CDFU03 failed to get user profile") if request.user.is_authenticated(): logger.info("CDFU04 user is authenticated user_id = %s" % request.user.id) if FacebookUserProfile.objects.filter(facebook_id=facebook_id).exists(): logger.info("CDFU05 FacebookUserProfile exists facebook_id = %s" % facebook_id) messages.info(request, "There is already an account on this site using that login") request.session["facebook_share_not_allowed"] = True return False else: logger.info("CDFU06 create new FacebookUserProfile for user_id = %s" % request.user.id) current_user = FacebookUserProfile.objects.create(user=request.user) current_user.facebook_id = facebook_id current_facebook_id = facebook_id else: logger.info("CDFU07 user is not authenticated") current_facebook_id = None else: current_facebook_id = current_user.facebook_id logger.info("CDFU08 current user is user with facebook_id = %s" % current_facebook_id) if not current_facebook_id or current_facebook_id != facebook_id: logger.info("CDFU09 Wrong or no facebook_id") logout(request) # clear possible caches if hasattr(request, "facebook"): del request.facebook logger.info("CDFU10 Clear request facebook") if request.session.get("graph", None): del request.session["graph"] logger.info("CDFU11 Clear graph in session") else: # save last access_token to make sure we always have the most # recent one current_user.access_token = access_token current_user.save() logger.info("CDFU12 Save last access_token")
def test_auth_backend(self): # the auth backend backend = FacebookBackend() facebook = get_facebook_graph(access_token='new_user') action, user = connect_user(self.request, facebook_graph=facebook) facebook_email = user.email profile = try_get_profile(user) user_or_profile = get_instance_for_attribute( user, profile, 'facebook_id') facebook_id = user_or_profile.facebook_id auth_user = backend.authenticate(facebook_email=facebook_email) #I GOT IT WORKING!!!!!!!!!! self.client.login(facebook_email=facebook_email) # response = self.client.get('/login/?testing=True') response = self.client.get('/login/') self.assertTemplateUsed(response, ) template_names = [] for template in response.templates: template_names.append(template.name) print template_names # logger.info('%s %s %s', auth_user.email, user.email, facebook_email) self.assertEqual(auth_user, user) auth_user = backend.authenticate(facebook_id=facebook_id) self.assertEqual(auth_user, user) auth_user = backend.authenticate(facebook_id=facebook_id, facebook_email=facebook_email) self.assertEqual(auth_user, user) auth_user = backend.authenticate() self.assertIsNone(auth_user)
def setUp(self): FacebookTest.setUp(self) user_url = 'http://www.fashiolista.com/style/neni/' kwargs = dict(item=user_url) user = get_user_model().objects.all()[:1][0] profile = try_get_profile(user) user_or_profile = get_instance_for_attribute( user, profile, 'facebook_open_graph') user_or_profile.facebook_open_graph = True user_or_profile.save() some_content_type = ContentType.objects.all()[:1][0] share = OpenGraphShare.objects.create( user_id=user.id, facebook_user_id=13123123, action_domain='fashiolista:follow', content_type=some_content_type, object_id=user.id, ) share.set_share_dict(kwargs) share.save() self.share = share self.share_details = user, profile, share
def test_auth_backend(self): # the auth backend backend = FacebookBackend() facebook = get_facebook_graph(access_token='new_user') action, user = connect_user(self.request, facebook_graph=facebook) facebook_email = user.email profile = try_get_profile(user) user_or_profile = get_instance_for_attribute( user, profile, 'facebook_id') facebook_id = user_or_profile.facebook_id auth_user = backend.authenticate(facebook_email=facebook_email) logger.info('%s %s %s', auth_user.email, user.email, facebook_email) self.assertEqual(auth_user, user) auth_user = backend.authenticate(facebook_id=facebook_id) self.assertEqual(auth_user, user) auth_user = backend.authenticate(facebook_id=facebook_id, facebook_email=facebook_email) self.assertEqual(auth_user, user) auth_user = backend.authenticate() self.assertIsNone(auth_user)
def test_update_access_token(self): request = RequestMock().get('/') request.session = {} request.user = AnonymousUser() graph = get_persistent_graph(request, access_token='paul') action, user = connect_user(self.request, facebook_graph=graph) first_user_id = user.id # new token required should start out as False profile = try_get_profile(user) new_token_required = get_user_attribute( user, profile, 'new_token_required') self.assertEqual(new_token_required, False) # we manually set it to true update_user_attributes( user, profile, dict(new_token_required=True), save=True) if profile: profile = get_profile_model().objects.get(id=profile.id) user = get_user_model().objects.get(id=user.id) new_token_required = get_user_attribute( user, profile, 'new_token_required') self.assertEqual(new_token_required, True) # another update should however set it back to False request.facebook = None graph = get_facebook_graph(request, access_token='paul2') logger.info('and the token is %s', graph.access_token) action, user = connect_user(self.request, facebook_graph=graph) user = get_user_model().objects.get(id=user.id) self.assertEqual(user.id, first_user_id) if profile: profile = get_profile_model().objects.get(id=profile.id) user = get_user_model().objects.get(id=user.id) new_token_required = get_user_attribute( user, profile, 'new_token_required') self.assertEqual(new_token_required, False)
def send(self, graph=None): result = None # update the last attempt self.last_attempt = datetime.now() self.save() # see if the graph is enabled profile = try_get_profile(self.user) user_or_profile = get_instance_for_attribute( self.user, profile, 'access_token') graph = graph or user_or_profile.get_offline_graph() user_enabled = user_or_profile.facebook_open_graph and self.facebook_user_id # start sharing if graph and user_enabled: graph_location = '%s/%s' % ( self.facebook_user_id, self.action_domain) share_dict = self.get_share_dict() from open_facebook.exceptions import OpenFacebookException try: result = graph.set(graph_location, **share_dict) share_id = result.get('id') if not share_id: error_message = 'No id in Facebook response, found %s for url %s with data %s' % ( result, graph_location, share_dict) logger.error(error_message) raise OpenFacebookException(error_message) self.share_id = share_id self.error_message = None self.completed_at = datetime.now() self.save() except OpenFacebookException, e: logger.warn( 'Open graph share failed, writing message %s' % e.message) self.error_message = unicode(e) self.save()
def connect_user(request, access_token=None, facebook_graph=None, connect_facebook=False): ''' Given a request either - (if authenticated) connect the user - login - register ''' user = None graph = facebook_graph or get_facebook_graph(request, access_token) converter = get_instance_for('user_conversion', graph) assert converter.is_authenticated() facebook_data = converter.facebook_profile_data() force_registration = request.POST.get('force_registration') or \ request.GET.get('force_registration') or \ request.POST.get('force_registration_hard') or \ request.GET.get('force_registration_hard') logger.debug('force registration is set to %s', force_registration) if connect_facebook and request.user.is_authenticated() and not force_registration: # we should only allow connect if users indicate they really want to connect # only when the request.CONNECT_FACEBOOK = 1 # if this isn't present we just do a login action = CONNECT_ACTIONS.CONNECT # default behaviour is not to overwrite old data user = _connect_user(request, converter, overwrite=True) else: email = facebook_data.get('email', False) email_verified = facebook_data.get('verified', False) kwargs = {} if email and email_verified: kwargs = {'facebook_email': email} auth_user = authenticate(facebook_id=facebook_data['id'], **kwargs) if auth_user and not force_registration: action = CONNECT_ACTIONS.LOGIN # Has the user registered without Facebook, using the verified FB # email address? # It is after all quite common to use email addresses for usernames update = getattr(auth_user, 'fb_update_required', False) profile = try_get_profile(auth_user) current_facebook_id = get_user_attribute( auth_user, profile, 'facebook_id') if not current_facebook_id: update = True # login the user user = _login_user(request, converter, auth_user, update=update) else: action = CONNECT_ACTIONS.REGISTER # when force registration is active we should remove the old # profile try: user = _register_user(request, converter, remove_old_connections=force_registration) except facebook_exceptions.AlreadyRegistered as e: # in Multithreaded environments it's possible someone beats us to # the punch, in that case just login logger.info( 'parallel register encountered, slower thread is doing a login') auth_user = authenticate( facebook_id=facebook_data['id'], **kwargs) if not auth_user: # We don't have a valid user so raise raise e action = CONNECT_ACTIONS.LOGIN user = _login_user(request, converter, auth_user, update=False) _update_likes_and_friends(request, user, converter) _update_access_token(user, graph) logger.info('connect finished with action %s', action) return action, user
def _update_user(user, facebook, overwrite=True): ''' Updates the user and his/her profile with the data from facebook ''' # if you want to add fields to ur user model instead of the # profile thats fine # partial support (everything except raw_data and facebook_id is included) facebook_data = facebook.facebook_registration_data(username=False) facebook_fields = ['facebook_name', 'facebook_profile_url', 'gender', 'date_of_birth', 'about_me', 'website_url', 'first_name', 'last_name'] profile = try_get_profile(user) # which attributes to update attributes_dict = {} # send the signal that we're updating signals.facebook_pre_update.send(sender=get_user_model(), user=user, profile=profile, facebook_data=facebook_data) # set the facebook id and make sure we are the only user with this id current_facebook_id = get_user_attribute(user, profile, 'facebook_id') facebook_id_changed = facebook_data['facebook_id'] != current_facebook_id overwrite_allowed = overwrite or not current_facebook_id # update the facebook id and access token facebook_id_overwritten = False if facebook_id_changed and overwrite_allowed: # when not overwriting we only update if there is no # profile.facebook_id logger.info('profile facebook id changed from %s to %s', repr(facebook_data['facebook_id']), repr(current_facebook_id)) attributes_dict['facebook_id'] = facebook_data['facebook_id'] facebook_id_overwritten = True if facebook_id_overwritten: _remove_old_connections(facebook_data['facebook_id'], user.id) # update all fields on both user and profile for f in facebook_fields: facebook_value = facebook_data.get(f, False) current_value = get_user_attribute(user, profile, f, None) if facebook_value and not current_value: attributes_dict[f] = facebook_value # write the raw data in case we missed something serialized_fb_data = json.dumps(facebook.facebook_profile_data()) current_raw_data = get_user_attribute(user, profile, 'raw_data') if current_raw_data != serialized_fb_data: attributes_dict['raw_data'] = serialized_fb_data image_url = facebook_data['image'] # update the image if we are allowed and have to if facebook_settings.FACEBOOK_STORE_LOCAL_IMAGE: image_field = get_user_attribute(user, profile, 'image', True) if not image_field: image_name, image_file = _update_image( facebook_data['facebook_id'], image_url) image_field.save(image_name, image_file) # save both models if they changed update_user_attributes(user, profile, attributes_dict) if getattr(user, '_fb_is_dirty', False): user.save() if getattr(profile, '_fb_is_dirty', False): profile.save() signals.facebook_post_update.send(sender=get_user_model(), user=user, profile=profile, facebook_data=facebook_data) return user
def connect_user(request, access_token=None, facebook_graph=None, connect_facebook=False): ''' Given a request either - (if authenticated) connect the user - login - register ''' user = None graph = facebook_graph or get_facebook_graph(request, access_token) converter = get_instance_for('user_conversion', graph) assert converter.is_authenticated() facebook_data = converter.facebook_profile_data() force_registration = request.REQUEST.get('force_registration') or\ request.REQUEST.get('force_registration_hard') logger.debug('force registration is set to %s', force_registration) if connect_facebook and request.user.is_authenticated( ) and not force_registration: # we should only allow connect if users indicate they really want to connect # only when the request.CONNECT_FACEBOOK = 1 # if this isn't present we just do a login action = CONNECT_ACTIONS.CONNECT # default behaviour is not to overwrite old data user = _connect_user(request, converter, overwrite=True) else: email = facebook_data.get('email', False) email_verified = facebook_data.get('verified', False) kwargs = {} if email and email_verified: kwargs = {'facebook_email': email} auth_user = authenticate(facebook_id=facebook_data['id'], **kwargs) if auth_user and not force_registration: action = CONNECT_ACTIONS.LOGIN # Has the user registered without Facebook, using the verified FB # email address? # It is after all quite common to use email addresses for usernames update = getattr(auth_user, 'fb_update_required', False) profile = try_get_profile(auth_user) current_facebook_id = get_user_attribute(auth_user, profile, 'facebook_id') if not current_facebook_id: update = True # login the user user = _login_user(request, converter, auth_user, update=update) else: action = CONNECT_ACTIONS.REGISTER # when force registration is active we should remove the old # profile try: user = _register_user( request, converter, remove_old_connections=force_registration) except facebook_exceptions.AlreadyRegistered, e: # in Multithreaded environments it's possible someone beats us to # the punch, in that case just login logger.info( 'parallel register encountered, slower thread is doing a login' ) auth_user = authenticate(facebook_id=facebook_data['id'], **kwargs) action = CONNECT_ACTIONS.LOGIN user = _login_user(request, converter, auth_user, update=False)
def connect_user(request, access_token=None, facebook_graph=None): """ Given a request either - (if authenticated) connect the user - login - register """ user = None graph = facebook_graph or get_facebook_graph(request, access_token) converter = get_instance_for("user_conversion", graph) assert converter.is_authenticated() facebook_data = converter.facebook_profile_data() force_registration = request.REQUEST.get("force_registration") or request.REQUEST.get("force_registration_hard") connect_facebook = to_bool(request.REQUEST.get("connect_facebook")) backend = get_registration_backend() logger.debug("force registration is set to %s", force_registration) if connect_facebook and request.user.is_authenticated() and not force_registration: # we should only allow connect if users indicate they really want to connect # only when the request.CONNECT_FACEBOOK = 1 # if this isn't present we just do a login action = CONNECT_ACTIONS.CONNECT user = _connect_user(request, converter) if backend.is_user_banned(user): raise PermissionDenied() else: email = facebook_data.get("email", False) email_verified = facebook_data.get("verified", False) kwargs = {} if email and email_verified: kwargs = {"facebook_email": email} auth_user = authenticate(facebook_id=facebook_data["id"], **kwargs) if backend.is_user_banned(auth_user): raise PermissionDenied() if auth_user and not force_registration: action = CONNECT_ACTIONS.LOGIN # Has the user registered without Facebook, using the verified FB # email address? # It is after all quite common to use email addresses for usernames update = getattr(auth_user, "fb_update_required", False) profile = try_get_profile(auth_user) current_facebook_id = get_user_attribute(auth_user, profile, "facebook_id") if not current_facebook_id: update = True # login the user user = _login_user(request, converter, auth_user, update=update) else: action = CONNECT_ACTIONS.REGISTER # when force registration is active we should remove the old # profile try: user = _register_user(request, converter, remove_old_connections=force_registration) except facebook_exceptions.AlreadyRegistered, e: # in Multithreaded environments it's possible someone beats us to # the punch, in that case just login logger.info("parallel register encountered, slower thread is doing a login") auth_user = authenticate(facebook_id=facebook_data["id"], **kwargs) action = CONNECT_ACTIONS.LOGIN user = _login_user(request, converter, auth_user, update=False)
def get_facebook_graph(request=None, access_token=None, redirect_uri=None, raise_=False): ''' given a request from one of these - js authentication flow (signed cookie) - facebook app authentication flow (signed cookie) - facebook oauth redirect (code param in url) - mobile authentication flow (direct access_token) - offline access token stored in user profile returns a graph object redirect path is the path from which you requested the token for some reason facebook needs exactly this uri when converting the code to a token falls back to the current page without code in the request params specify redirect_uri if you are not posting and recieving the code on the same page ''' # this is not a production flow, but very handy for testing if not access_token and request.REQUEST.get('access_token'): access_token = request.REQUEST['access_token'] # should drop query params be included in the open facebook api, # maybe, weird this... from open_facebook import OpenFacebook, FacebookAuthorization from django.core.cache import cache expires = None if hasattr(request, 'facebook') and request.facebook: graph = request.facebook _add_current_user_id(graph, request.user) return graph # parse the signed request if we have it signed_data = None if request: signed_request_string = request.REQUEST.get('signed_data') if signed_request_string: logger.info('Got signed data from facebook') signed_data = parse_signed_request(signed_request_string) if signed_data: logger.info('We were able to parse the signed data') # the easy case, we have an access token in the signed data if signed_data and 'oauth_token' in signed_data: access_token = signed_data['oauth_token'] if not access_token: # easy case, code is in the get code = request.REQUEST.get('code') if code: logger.info('Got code from the request data') if not code: # signed request or cookie leading, base 64 decoding needed cookie_name = 'fbsr_%s' % facebook_settings.FACEBOOK_APP_ID cookie_data = request.COOKIES.get(cookie_name) if cookie_data: signed_request_string = cookie_data if signed_request_string: logger.info('Got signed data from cookie') signed_data = parse_signed_request(signed_request_string) if signed_data: logger.info('Parsed the cookie data') # the javascript api assumes a redirect uri of '' redirect_uri = '' if signed_data: # parsed data can fail because of signing issues if 'oauth_token' in signed_data: logger.info('Got access_token from parsed data') # we already have an active access token in the data access_token = signed_data['oauth_token'] else: logger.info('Got code from parsed data') # no access token, need to use this code to get one code = signed_data.get('code', None) if not access_token: if code: cache_key = hash_key('convert_code_%s' % code) access_token = cache.get(cache_key) if not access_token: # exchange the code for an access token # based on the php api # https://github.com/facebook/php-sdk/blob/master/src/base_facebook.php # create a default for the redirect_uri # when using the javascript sdk the default # should be '' an empty string # for other pages it should be the url if not redirect_uri: redirect_uri = '' # we need to drop signed_data, code and state redirect_uri = cleanup_oauth_url(redirect_uri) try: logger.info( 'trying to convert the code with redirect uri: %s', redirect_uri) # This is realy slow, that's why it's cached token_response = FacebookAuthorization.convert_code( code, redirect_uri=redirect_uri) expires = token_response.get('expires') access_token = token_response['access_token'] # would use cookies instead, but django's cookie setting # is a bit of a mess cache.set(cache_key, access_token, 60 * 60 * 2) except (open_facebook_exceptions.OAuthException, open_facebook_exceptions.ParameterException), e: # this sometimes fails, but it shouldnt raise because # it happens when users remove your # permissions and then try to reauthenticate logger.warn('Error when trying to convert code %s', unicode(e)) if raise_: raise else: return None elif request.user.is_authenticated(): # support for offline access tokens stored in the users profile profile = try_get_profile(request.user) access_token = get_user_attribute( request.user, profile, 'access_token') if not access_token: if raise_: message = 'Couldnt find an access token in the request or the users profile' raise open_facebook_exceptions.OAuthException(message) else: return None else: if raise_: message = 'Couldnt find an access token in the request or cookies' raise open_facebook_exceptions.OAuthException(message) else: return None
def send(self, graph=None, shared_explicitly=False): result = None # update the last attempt self.last_attempt = datetime.now() self.save() # see if the graph is enabled profile = try_get_profile(self.user) user_or_profile = get_instance_for_attribute(self.user, profile, 'access_token') graph = graph or user_or_profile.get_offline_graph() user_enabled = shared_explicitly or \ (user_or_profile.facebook_open_graph and self.facebook_user_id) # start sharing if graph and user_enabled: graph_location = '%s/%s' % (self.facebook_user_id, self.action_domain) share_dict = self.get_share_dict() from open_facebook.exceptions import OpenFacebookException try: result = graph.set(graph_location, **share_dict) share_id = result.get('id') if not share_id: error_message = 'No id in Facebook response, found %s for url %s with data %s' % ( result, graph_location, share_dict) logger.error(error_message) raise OpenFacebookException(error_message) self.share_id = share_id self.error_message = None self.completed_at = datetime.now() self.save() except OpenFacebookException as e: logger.warn('Open graph share failed, writing message %s' % str(e)) self.error_message = repr(e) self.save() # maybe we need a new access token new_token_required = self.exception_requires_new_token( e, graph) # verify that the token didnt change in the mean time user_or_profile = user_or_profile.__class__.objects.get( id=user_or_profile.id) token_changed = graph.access_token != user_or_profile.access_token logger.info('new token required is %s and token_changed is %s', new_token_required, token_changed) if new_token_required and not token_changed: logger.info( 'a new token is required, setting the flag on the user or profile' ) # time to ask the user for a new token update_user_attributes(self.user, profile, dict(new_token_required=True), save=True) elif not graph: self.error_message = 'no graph available' self.save() elif not user_enabled: self.error_message = 'user not enabled' self.save() return result
def _update_user(user, facebook, overwrite=True): ''' Updates the user and his/her profile with the data from facebook ''' # if you want to add fields to ur user model instead of the # profile thats fine # partial support (everything except raw_data and facebook_id is included) facebook_data = facebook.facebook_registration_data(username=False) facebook_fields = [ 'facebook_name', 'facebook_profile_url', 'gender', 'date_of_birth', 'about_me', 'website_url', 'first_name', 'last_name' ] profile = try_get_profile(user) # which attributes to update attributes_dict = {} # send the signal that we're updating signals.facebook_pre_update.send(sender=get_user_model(), user=user, profile=profile, facebook_data=facebook_data) # set the facebook id and make sure we are the only user with this id current_facebook_id = get_user_attribute(user, profile, 'facebook_id') facebook_id_changed = facebook_data['facebook_id'] != current_facebook_id overwrite_allowed = overwrite or not current_facebook_id # update the facebook id and access token facebook_id_overwritten = False if facebook_id_changed and overwrite_allowed: # when not overwriting we only update if there is no # profile.facebook_id logger.info('profile facebook id changed from %s to %s', repr(facebook_data['facebook_id']), repr(current_facebook_id)) attributes_dict['facebook_id'] = facebook_data['facebook_id'] facebook_id_overwritten = True if facebook_id_overwritten: _remove_old_connections(facebook_data['facebook_id'], user.id) # update all fields on both user and profile for f in facebook_fields: facebook_value = facebook_data.get(f, False) current_value = get_user_attribute(user, profile, f, None) if facebook_value and not current_value: attributes_dict[f] = facebook_value # write the raw data in case we missed something serialized_fb_data = json.dumps(facebook.facebook_profile_data()) current_raw_data = get_user_attribute(user, profile, 'raw_data') if current_raw_data != serialized_fb_data: attributes_dict['raw_data'] = serialized_fb_data image_url = facebook_data['image'] # update the image if we are allowed and have to if facebook_settings.FACEBOOK_STORE_LOCAL_IMAGE: image_field = get_user_attribute(user, profile, 'image', True) if not image_field: image_name, image_file = _update_image( facebook_data['facebook_id'], image_url) image_field.save(image_name, image_file) # save both models if they changed update_user_attributes(user, profile, attributes_dict) if getattr(user, '_fb_is_dirty', False): user.save() if getattr(profile, '_fb_is_dirty', False): profile.save() signals.facebook_post_update.send(sender=get_user_model(), user=user, profile=profile, facebook_data=facebook_data) return user
def connect_user(request, access_token=None, facebook_graph=None, connect_facebook=False): ''' Given a request either - (if authenticated) connect the user - login - register ''' logger.info('CU01: Start data access_token %s' % access_token) logger.info('CU02: Start data facebook_graph %s' % facebook_graph) logger.info('CU03: Connect facebook %s' % connect_facebook) user = None graph = facebook_graph or get_facebook_graph(request, access_token) converter = get_instance_for('user_conversion', graph) assert converter.is_authenticated() facebook_data = converter.facebook_profile_data() force_registration = request.REQUEST.get('force_registration') or\ request.REQUEST.get('force_registration_hard') logger.info('CU04: Force registration %s' % force_registration) if connect_facebook and request.user.is_authenticated() and not force_registration: logger.info('CU05: User is authenticated %s' % request.user.is_authenticated()) # we should only allow connect if users indicate they really want to connect # only when the request.CONNECT_FACEBOOK = 1 # if this isn't present we just do a login action = CONNECT_ACTIONS.CONNECT logger.info('CU06: Action %s' % action) # default behaviour is not to overwrite old data user = _connect_user(request, converter, overwrite=True) else: logger.info('CU07: Bad day') email = facebook_data.get('email', False) logger.info('CU08: Email %s' % email) email_verified = facebook_data.get('verified', False) kwargs = {} if email and email_verified: logger.info('CU09: Facebook email added to kwargs') kwargs = {'facebook_email': email} # social-auth support # trying to find social user with given email logger.info('CU10: Social auth support') social_user = get_user_model().objects.filter(email=email) logger.info('CU11: Social users found %s' % social_user) social_user = social_user[0] if social_user else None logger.info('CU12: Social user %s' % social_user) if social_user and UserSocialAuth.objects.filter(user__id=social_user.id).exists(): logger.info('CU13: Social user exists') try: current_user_profile = try_get_profile(social_user) logger.info('CU14: Got profile for social user %s' % current_user_profile) except: logger.info('CU15: No profile') profile_model = get_profile_model() logger.info('CU16: Profile model %s' % profile_model) profile_model.objects.create(user=social_user) logger.info('CU17: Profile object created') auth_user = authenticate(facebook_id=facebook_data['id'], **kwargs) logger.info('CU19: Aunthenticated user %s ' % auth_user) if auth_user and not force_registration: referer_url = request.META.get('HTTP_REFERER', None) if referer_url: urlparsed = urlparse(referer_url) is_facebook = urlparsed.netloc.endswith('facebook.com') else: is_facebook = False logger.info('CU32 is_facebook: %s, referer_url: %s'%(is_facebook, referer_url)) if auth_user.is_client() or auth_user.is_subclient() and not is_facebook: action = CONNECT_ACTIONS.CLIENT_REDIRECT logger.info('CU31: Client or subclient detected. Action: CLIENT_REDIRECT') return action, auth_user action = CONNECT_ACTIONS.LOGIN logger.info('CU20: Action %s' % action) # Has the user registered without Facebook, using the verified FB # email address? # It is after all quite common to use email addresses for usernames update = getattr(auth_user, 'fb_update_required', False) logger.info('CU21: Update %s' % update) profile = try_get_profile(auth_user) logger.info('CU22: Got profile %s' % profile) current_facebook_id = get_user_attribute( auth_user, profile, 'facebook_id') logger.info('CU23: Current facebook_id %s' % current_facebook_id) if not current_facebook_id: update = True # login the user user = _login_user(request, converter, auth_user, update=update) else: action = CONNECT_ACTIONS.REGISTER logger.info('CU24: Action %s' % action) # when force registration is active we should remove the old # profile try: user = _register_user(request, converter, remove_old_connections=force_registration) logger.info('CU25: User registered %s' % user) except facebook_exceptions.AlreadyRegistered, e: # in Multithreaded environments it's possible someone beats us to # the punch, in that case just login logger.info( 'CU26: parallel register encountered, slower thread is doing a login') auth_user = authenticate( facebook_id=facebook_data['id'], **kwargs) logger.info('CU27: Auth user %s' % auth_user) if not auth_user: logger.info('CU28: No auth user') # We don't have a valid user so raise raise e logger.info('CU29: Login user') action = CONNECT_ACTIONS.LOGIN user = _login_user(request, converter, auth_user, update=False)
def save(self, *args, **kwargs): if self.user and not self.facebook_user_id: profile = try_get_profile(self.user) self.facebook_user_id = get_user_attribute( self.user, profile, 'facebook_id') return BaseModel.save(self, *args, **kwargs)
def get_facebook_graph(request=None, access_token=None, redirect_uri=None, raise_=False): ''' given a request from one of these - js authentication flow (signed cookie) - facebook app authentication flow (signed cookie) - facebook oauth redirect (code param in url) - mobile authentication flow (direct access_token) - offline access token stored in user profile returns a graph object redirect path is the path from which you requested the token for some reason facebook needs exactly this uri when converting the code to a token falls back to the current page without code in the request params specify redirect_uri if you are not posting and recieving the code on the same page ''' # this is not a production flow, but very handy for testing if not access_token and request.REQUEST.get('access_token'): access_token = request.REQUEST['access_token'] # should drop query params be included in the open facebook api, # maybe, weird this... from open_facebook import OpenFacebook, FacebookAuthorization from django.core.cache import cache expires = None if hasattr(request, 'facebook') and request.facebook: graph = request.facebook _add_current_user_id(graph, request.user) return graph # parse the signed request if we have it signed_data = None if request: signed_request_string = request.REQUEST.get('signed_data') if signed_request_string: logger.info('Got signed data from facebook') signed_data = parse_signed_request(signed_request_string) if signed_data: logger.info('We were able to parse the signed data') # the easy case, we have an access token in the signed data if signed_data and 'oauth_token' in signed_data: access_token = signed_data['oauth_token'] if not access_token: # easy case, code is in the get code = request.REQUEST.get('code') if code: logger.info('Got code from the request data') if not code: # signed request or cookie leading, base 64 decoding needed cookie_name = 'fbsr_%s' % facebook_settings.FACEBOOK_APP_ID cookie_data = request.COOKIES.get(cookie_name) if cookie_data: signed_request_string = cookie_data if signed_request_string: logger.info('Got signed data from cookie') signed_data = parse_signed_request(signed_request_string) if signed_data: logger.info('Parsed the cookie data') # the javascript api assumes a redirect uri of '' redirect_uri = '' if signed_data: # parsed data can fail because of signing issues if 'oauth_token' in signed_data: logger.info('Got access_token from parsed data') # we already have an active access token in the data access_token = signed_data['oauth_token'] else: logger.info('Got code from parsed data') # no access token, need to use this code to get one code = signed_data.get('code', None) if not access_token: if code: cache_key = hash_key('convert_code_%s' % code) access_token = cache.get(cache_key) if not access_token: # exchange the code for an access token # based on the php api # https://github.com/facebook/php-sdk/blob/master/src/base_facebook.php # create a default for the redirect_uri # when using the javascript sdk the default # should be '' an empty string # for other pages it should be the url if not redirect_uri: redirect_uri = '' # we need to drop signed_data, code and state redirect_uri = cleanup_oauth_url(redirect_uri) try: logger.info( 'trying to convert the code with redirect uri: %s', redirect_uri) # This is realy slow, that's why it's cached token_response = FacebookAuthorization.convert_code( code, redirect_uri=redirect_uri) expires = token_response.get('expires') access_token = token_response['access_token'] # would use cookies instead, but django's cookie setting # is a bit of a mess cache.set(cache_key, access_token, 60 * 60 * 2) except (open_facebook_exceptions.OAuthException, open_facebook_exceptions.ParameterException) as e: # this sometimes fails, but it shouldnt raise because # it happens when users remove your # permissions and then try to reauthenticate logger.warn('Error when trying to convert code %s', unicode(e)) if raise_: raise else: return None elif request.user.is_authenticated(): # support for offline access tokens stored in the users profile profile = try_get_profile(request.user) access_token = get_user_attribute( request.user, profile, 'access_token') if not access_token: if raise_: message = 'Couldnt find an access token in the request or the users profile' raise open_facebook_exceptions.OAuthException(message) else: return None else: if raise_: message = 'Couldnt find an access token in the request or cookies' raise open_facebook_exceptions.OAuthException(message) else: return None graph = OpenFacebook(access_token, signed_data, expires=expires) # add user specific identifiers if request: _add_current_user_id(graph, request.user) return graph
def connect_user(request, access_token=None, facebook_graph=None, connect_facebook=False): ''' Given a request either - (if authenticated) connect the user - login - register ''' user = None graph = facebook_graph or get_facebook_graph(request, access_token) converter = get_instance_for('user_conversion', graph) assert converter.is_authenticated() facebook_data = converter.facebook_profile_data() logger.debug('facebook_data %s', facebook_data) force_registration = request.POST.get('force_registration') or \ request.GET.get('force_registration') or \ request.POST.get('force_registration_hard') or \ request.GET.get('force_registration_hard') logger.debug('force registration is set to %s', force_registration) if connect_facebook and request.user.is_authenticated() and not force_registration: # we should only allow connect if users indicate they really want to connect # only when the request.CONNECT_FACEBOOK = 1 # if this isn't present we just do a login action = CONNECT_ACTIONS.CONNECT # default behaviour is not to overwrite old data user = _connect_user(request, converter, overwrite=True) else: name = facebook_data.get('name', None) email = facebook_data.get('email', False) #email = False email_verified = facebook_data.get('verified') if not email_verified: logger.info('overriding email_verified to true. we dont care about this flag.') email_verified = True kwargs = {} logger.debug('email, email_verified: %s %s', email, email_verified) if email and email_verified: kwargs = {'facebook_email': email} logger.debug('authenticating %s', facebook_data['id']) auth_user = authenticate(facebook_id=facebook_data['id'], **kwargs) logger.debug('auth_user, force_registration: %s %s', auth_user, force_registration) if auth_user and not force_registration: action = CONNECT_ACTIONS.LOGIN # Has the user registered without Facebook, using the verified FB # email address? # It is after all quite common to use email addresses for usernames update = getattr(auth_user, 'fb_update_required', False) profile = try_get_profile(auth_user) current_facebook_id = get_user_attribute( auth_user, profile, 'facebook_id') if not current_facebook_id: update = True # login the user user = _login_user(request, converter, auth_user, update=update) else: logger.info('going for _register_user') if not email: logger.info('raising AccountNotVerifiedException') raise facebook_exceptions.AccountNotVerifiedException(name) action = CONNECT_ACTIONS.REGISTER # when force registration is active we should remove the old # profile try: user = _register_user(request, converter, remove_old_connections=force_registration) except facebook_exceptions.AlreadyRegistered as e: # in Multithreaded environments it's possible someone beats us to # the punch, in that case just login logger.info( 'parallel register encountered, slower thread is doing a login') auth_user = authenticate( facebook_id=facebook_data['id'], **kwargs) if not auth_user: # We don't have a valid user so raise raise e action = CONNECT_ACTIONS.LOGIN user = _login_user(request, converter, auth_user, update=False) _update_likes_and_friends(request, user, converter) _update_access_token(user, graph) logger.info('connect finished with action %s', action) return action, user