def send(self, graph=None): result = None #update the last attempt self.last_attempt = datetime.datetime.now() self.save() #see if the graph is enabled profile = get_fb_profile(self.user) graph = graph or profile.get_offline_graph() user_enabled = 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.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 retry_facebook_invite(modeladmin, request, queryset): ''' Retries sending the invite to the users wall ''' invites = list(queryset) user_invites = defaultdict(list) for invite in invites: user_invites[invite.user].append(invite) for user, invites in user_invites.items(): profile = get_fb_profile(self.user) graph = profile.get_offline_graph() if not graph: error_message = 'couldnt connect to the graph, user access token is %s' % profile.access_token messages.error(request, error_message) continue logger.info('got graph %s for user %s, retrying %s invites', graph, user, len(invites)) for invite in invites: invite_result = invite.resend(graph) message = 'User %s sent attempt to sent with id %s s6 is %s' % ( user, invite_result.wallpost_id, not invite_result.error) if invite_result.error: message += ' got error %s' % invite_result.error_message messages.info(request, message) profile.update_invite_denormalizations() profile.save()
def 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. ''' if facebook_id or facebook_email: profile_class = get_profile_class() 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: user = models.User.objects.get(email=facebook_email) except models.User.DoesNotExist: user = None profile = get_fb_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 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) facebook = FacebookUserConverter(graph) assert facebook.is_authenticated() facebook_data = facebook.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')) 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, facebook) 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) if not get_fb_profile(auth_user).facebook_id: update = True #login the user user = _login_user(request, facebook, 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, facebook, 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, facebook, auth_user, update=False)
def resend(self, graph=None): from django_facebook.invite import post_on_profile if not graph: graph = get_fb_profile(self.user).get_offline_graph() if not graph: return facebook_id = self.user_invited invite_result = post_on_profile( self.user, graph, facebook_id, self.message, force_send=True) return invite_result
def facebook_profile(open_graph_share): ''' Nicely displayed version of the facebook user with user id and image and link to facebook :) ''' user = open_graph_share.user profile = get_fb_profile(user) facebook_id = profile.facebook_id facebook_url = 'http://www.facebook.com/%s/' % facebook_id link = '<p><a href="%s"><img src="http://graph.facebook.com/%s/picture/?type=large" width="100px" style="float:left"/>%s</a><br/></p>' % (facebook_url, facebook_id, facebook_id) return link
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() and graph: profile = get_fb_profile(user) facebook_id = getattr(profile, 'facebook_id', None) if facebook_id: graph.current_user_id = facebook_id
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.")) profile = get_fb_profile(request.user) profile.disconnect_facebook() profile.save() response = next_redirect(request) return response
def _update_access_token(user, graph): ''' Conditionally updates the access token in the database ''' profile = get_fb_profile(user) #store the access token for later usage if the profile model supports it if hasattr(profile, 'access_token'): # update if not equal to the current token new_token = graph.access_token != 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') profile.access_token = graph.access_token profile.save() #see if we can extend the access token #this runs in a task, after extending the token we fire an event profile.extend_access_token()
def save(self, *args, **kwargs): if self.user and not self.facebook_user_id: self.facebook_user_id = get_fb_profile(self.user).facebook_id return models.Model.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 parsed_data = None expires = None if hasattr(request, 'facebook'): graph = request.facebook _add_current_user_id(graph, request.user) return graph 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 signed_data = request.REQUEST.get('signed_request') cookie_name = 'fbsr_%s' % facebook_settings.FACEBOOK_APP_ID cookie_data = request.COOKIES.get(cookie_name) if cookie_data: signed_data = cookie_data #the javascript api assumes a redirect uri of '' redirect_uri = '' if signed_data: logger.info('Got signed data from facebook') parsed_data = FacebookAuthorization.parse_signed_data( signed_data) if parsed_data: logger.info('Got parsed data from facebook') #parsed data can fail because of signing issues if 'oauth_token' in parsed_data: logger.info('Got access_token from parsed data') # we already have an active access token in the data access_token = parsed_data['oauth_token'] else: logger.info('Got code from parsed data') # no access token, need to use this code to get one code = parsed_data.get('code', None) if not access_token: if code: cache_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_request, 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, 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 = get_fb_profile(request.user) access_token = getattr(profile, 'access_token', None) 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 _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'] user_dirty = profile_dirty = False profile = get_fb_profile(user) signals.facebook_pre_update.send(sender=get_profile_class(), profile=profile, facebook_data=facebook_data) profile_field_names = [f.name for f in profile._meta.fields] user_field_names = [f.name for f in user._meta.fields] #set the facebook id and make sure we are the only user with this id facebook_id_changed = facebook_data['facebook_id'] != profile.facebook_id overwrite_allowed = overwrite or not profile.facebook_id #update the facebook id and access token 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(profile.facebook_id)) profile.facebook_id = facebook_data['facebook_id'] profile_dirty = True _remove_old_connections(profile.facebook_id, user.id) #update all fields on both user and profile for f in facebook_fields: facebook_value = facebook_data.get(f, False) if facebook_value: if (f in profile_field_names and hasattr(profile, f)): logger.debug('profile field %s changed from %s to %s', f, getattr(profile, f), facebook_value) setattr(profile, f, facebook_value) profile_dirty = True elif (f in user_field_names and hasattr(user, f)): logger.debug('user field %s changed from %s to %s', f, getattr(user, f), facebook_value) setattr(user, f, facebook_value) user_dirty = True #write the raw data in case we missed something if hasattr(profile, 'raw_data'): serialized_fb_data = json.dumps(facebook.facebook_profile_data()) if profile.raw_data != serialized_fb_data: logger.debug('profile raw data changed from %s to %s', profile.raw_data, serialized_fb_data) profile.raw_data = serialized_fb_data profile_dirty = True image_url = facebook_data['image'] #update the image if we are allowed and have to if facebook_settings.FACEBOOK_STORE_LOCAL_IMAGE: if hasattr(profile, 'image') and not profile.image: profile_dirty = _update_image(profile, image_url) #save both models if they changed if user_dirty: user.save() if profile_dirty: profile.save() signals.facebook_post_update.send(sender=get_profile_class(), profile=profile, facebook_data=facebook_data) return user
def post_registration_redirect(self, request, user): ''' Handled by the Django Facebook app ''' response = get_fb_profile(user).post_facebook_registration(request) return response