def user_from_facebook_auth_response(auth_response, initial_user=None, is_test=False): if 'accessToken' in auth_response: facebook_access_token = auth_response['accessToken'] else: facebook_access_token = auth_response fb_user = fb_user_from_facebook_access_token(facebook_access_token) facebook_id = fb_user['id'] try: linked_facebook = LinkedFacebookAccount.objects.filter(facebook_id=facebook_id).select_related( 'user__profile').first() if not linked_facebook: raise LinkedFacebookAccount.DoesNotExist() save_linked_facebook(linked_facebook.user, facebook_access_token, fb_user, linked_facebook=linked_facebook) user = linked_facebook.user location = initial_user and initial_user.get('location') if location: location_controller.update_profile_location(user.ap, location) except LinkedFacebookAccount.DoesNotExist: debug_logger.debug('LinkedFacebookAccount.DoesNotExist for facebook_id %s' % facebook_id) if 'email' not in fb_user: dev_msg = 'Facebook user has no email: %s' % json.dumps(fb_user) debug_logger.error(dev_msg) raise ShoutitBadRequest(message=FB_LINK_ERROR_EMAIL, developer_message=dev_msg) user = user_controller.auth_with_facebook(fb_user, initial_user, is_test) try: save_linked_facebook(user, facebook_access_token, fb_user) except (ValidationError, IntegrityError) as e: raise ShoutitBadRequest(message=FB_LINK_ERROR_TRY_AGAIN, developer_message=str(e)) return user
def can_promote(self, shout, user): if user.credit < self.credits: raise ShoutitBadRequest( _("You don't have enough Shoutit Credit for this action")) if shout.promotions.exists(): raise ShoutitBadRequest(_('This Shout is already promoted'))
def facebook_graph_object(graph_path, params): graph_url = FB_GRAPH_URL + graph_path try: response = requests.get(graph_url, params=params, timeout=20) response_data = response.json() error = response_data.get('error') if response.status_code != 200 or error: dev_msg = error.get('message') if isinstance(error, dict) else str(response_data) dev_msg = "Facebook Graph error: %s" % dev_msg raise ShoutitBadRequest(message=FB_ERROR_TRY_AGAIN, developer_message=dev_msg) return response_data except requests.RequestException as e: dev_msg = "Facebook Graph error: %s" % str(e) debug_logger.error(dev_msg) raise ShoutitBadRequest(message=FB_ERROR_TRY_AGAIN, developer_message=dev_msg)
def auth_with_gplus(gplus_user, credentials, initial_user=None, is_test=False): email = gplus_user.get('emails')[0].get('value').lower() name = gplus_user.get('name', {}) first_name = name.get('givenName') last_name = name.get('familyName') username = initial_user.get('username') gplus_id = gplus_user.get('id') gender = gplus_user.get('gender') profile_fields = {} location = {} if initial_user: if initial_user.get('location'): location = initial_user.get('location') location_controller.add_predefined_city(location) elif initial_user.get('ip'): location = location_controller.from_ip(initial_user.get('ip')) profile_fields.update(location) profile_fields.update({'gender': gender}) try: # Todo: Check! # in rare cases when clients send multiple requests and while the first one is still in progress # (post save isn't yet completed i.e. no profile yet) the second one passes this check and breaks # in the coming lines of updating profile location as there is no profile. The result of such call can be even # worse as two users will be created user = User.objects.get(email=email) debug_logger.debug( 'Found user: {} with same email of gplus_user: {}'.format( user, gplus_id)) if location: location_controller.update_profile_location(user.profile, location, add_pc=False) except User.DoesNotExist: user = create_user(email=email, first_name=first_name, last_name=last_name, username=username, is_activated=True, profile_fields=profile_fields, is_test=is_test) if not user.is_activated: user.activate() if not user.profile.gender and gender: user.profile.update(gender=gender) credentials_json = credentials.to_json() try: LinkedGoogleAccount.objects.create(user=user, credentials_json=credentials_json, gplus_id=gplus_id) except IntegrityError as e: raise ShoutitBadRequest( message=_("Could not access your Google account, try again later"), developer_message=str(e)) image_url = gplus_user['image']['url'].split('?')[0] media_controller.set_profile_media(user.profile, 'image', image_url) return user
def video_identity(self, request): """ Retrieve video client identity to make a call <pre><code> GET: /twilio/video_identity?profile=username </code></pre> ###REQUIRES AUTH ###Response <pre><code> { "identity": "7c6ca4737db3447f936037374473e61f" } </code></pre> Returns 403 if the call is not allowed. --- omit_serializer: true parameters: - name: profile description: Profile username paramType: query """ # Todo: Check whether calling user is allowed to do this call or not # Todo: Move the logic to Serializer other_username = request.query_params.get('profile') if not other_username: raise RequiredParameter('profile') if other_username == request.user.username: raise InvalidParameter('profile', "You can't call your self") try: other_user = User.objects.get(username=other_username) except User.DoesNotExist: msg = _("Profile with username '%(username)s' does not exist") % { 'username': other_username } raise InvalidParameter('profile', message=msg) if hasattr(other_user, 'video_client'): video_client = other_user.video_client else: # Create video client for the other user try: video_client = create_video_client(other_user) except (ValidationError, IntegrityError) as e: msg = _("Error calling %(name)s") % { 'name': other_username.name } raise ShoutitBadRequest(message=msg, developer_message=unicode(e)) # Notify the other user notifications_controller.notify_user_of_incoming_video_call( user=other_user, caller=request.user) res = {'identity': video_client.identity} return Response(res)
def call(self, request, *args, **kwargs): """ Get the mobile of this Shout ##Response <pre><code> { "mobile": "01701700555" } </code></pre> This endpoint will be throttled to prevent multiple requests from same client in short time. --- omit_serializer: true omit_parameters: - form """ user = request.user profile = user.ap shout = self.get_object() mobile = shout.mobile if shout.is_mobile_set else None if not mobile: raise ShoutitBadRequest(_("No mobile to be called")) track_properties = { 'api_client': getattr(request, 'api_client', None), 'api_version': request.version, 'mp_country_code': profile.country, '$region': profile.state, '$city': profile.city, 'shout_id': shout.pk, 'shout_country': shout.get_country_display(), 'shout_region': shout.state, 'shout_city': shout.city, } mixpanel_controller.track(user.pk, 'show_mobile', track_properties) return Response({'mobile': mobile})
def unlink_facebook_user(user, strict=True): """ Deleted the user's LinkedFacebookAccount """ linked_account = LinkedFacebookAccount.objects.filter(user=user) if linked_account.exists(): linked_account.delete() elif strict: debug_logger.warning("User: %s, tried to unlink non-existing facebook account" % user) raise ShoutitBadRequest(FB_LINK_ERROR_NO_LINK)
def fb_page_from_facebook_page_id(facebook_page_id, facebook_access_token): params = { 'fields': 'access_token,category,name,id,perms', 'access_token': facebook_access_token } accounts = facebook_graph_object('/me/accounts', params)['data'] for account in accounts: if account['id'] == facebook_page_id: return account raise ShoutitBadRequest(_("Couldn't find the specified Facebook Page"))
def get_object(self): value = self.kwargs.get(self.lookup_field) try: uuid.UUID(value) except: raise ShoutitBadRequest( message=_("Resource not found"), developer_message="'%s' is not a valid id" % value, reason=ERROR_REASON.INVALID_IDENTIFIER) return super(UUIDViewSetMixin, self).get_object()
def link_facebook_account(user, facebook_access_token): """ Add LinkedFacebookAccount to user """ fb_user = fb_user_from_facebook_access_token(facebook_access_token) facebook_id = fb_user['id'] linked_page_ids = [] # Check whether the Facebook account is already linked try: la = LinkedFacebookAccount.objects.get(facebook_id=facebook_id) debug_logger.warning('User %s tried to link already linked facebook account id: %s.' % (user, facebook_id)) if la.user != user: raise ShoutitBadRequest(_("Facebook account is already linked to somebody else's profile")) linked_page_ids = list(la.pages.values_list('facebook_id', flat=True)) # copy the list except LinkedFacebookAccount.DoesNotExist: pass # Unlink previous Facebook account unlink_facebook_user(user, strict=False) # Link Facebook account try: linked_facebook = save_linked_facebook(user, facebook_access_token, fb_user) except (ValidationError, IntegrityError) as e: debug_logger.error("LinkedFacebookAccount creation error: %s" % str(e)) raise ShoutitBadRequest(message=FB_LINK_ERROR_TRY_AGAIN, developer_message=str(e)) # Link Facebook Pages if any existed in the previous LinkedFacebookAccount for facebook_page_id in linked_page_ids: link_facebook_page(linked_facebook, facebook_page_id) # Activate the user if not yet activated if not user.is_activated: user.notify = False user.activate()
def listen(self, request, *args, **kwargs): """ Start/Stop listening to a Profile ###REQUIRES AUTH ###Start listening <pre><code> POST: /users/{username}/listen </code></pre> ###Stop listening <pre><code> DELETE: /users/{username}/listen </code></pre> --- omit_serializer: true omit_parameters: - form """ user = self.get_object() ap = user.ap api_client = getattr(request, 'api_client', None) if request.user == user: raise ShoutitBadRequest(_("You can't listen to your self")) if request.method == 'POST': listen_controller.listen_to_object(request.user, ap, api_client=api_client, api_version=request.version) msg = _("You started listening to shouts from %(name)s") % { 'name': user.name } else: listen_controller.stop_listening_to_object(request.user, ap) msg = _("You stopped listening to shouts from %(name)s") % { 'name': user.name } data = {'success': msg, 'new_listeners_count': user.listeners_count} return Response(data=data, status=status.HTTP_202_ACCEPTED)
def extend_token(short_lived_token): exchange_url = FB_GRAPH_ACCESS_TOKEN_URL params = { 'client_id': settings.FACEBOOK_APP_ID, 'client_secret': settings.FACEBOOK_APP_SECRET, 'grant_type': "fb_exchange_token", 'fb_exchange_token': short_lived_token } try: response = requests.get(exchange_url, params=params, timeout=20) if response.status_code != 200: raise ValueError("Invalid access token: %s" % response.content) response_params = dict(urlparse.parse_qsl(response.content)) access_token = response_params.get('access_token') if not access_token: raise ValueError('`access_token` not in response: %s' % response.content) expires = response_params.get('expires') # Sometimes Facebook doesn't return expiry, assume 60 days response_params['expires'] = expires or SIXTY_DAYS except (requests.RequestException, ValueError) as e: debug_logger.error("Facebook token extend error: %s" % str(e)) raise ShoutitBadRequest(message=FB_ERROR_TRY_AGAIN, developer_message=str(e)) return response_params
def video_auth(self, request): """ Create a video chat endpoint. ###REQUIRES AUTH ###Response <pre><code> { "token": "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCIsICJjdHkiOiAidHdpbGlvLWZwYTt2PTEifQ.eyJpc3MiOiAiU0s3MDFlYzE", "identity": "7c6ca4737db3447f936037374473e61f" } </code></pre> --- """ # Todo: Move the logic to Serializer user = request.user try: video_client = user.video_client # Check whether client has an expired token if timezone.now() > video_client.expires_at: video_client.delete() raise ValueError() except (AttributeError, ValueError): try: video_client = create_video_client(user) except (ValidationError, IntegrityError) as e: msg = _("Couldn't authorize you to make video calls") raise ShoutitBadRequest(message=msg, developer_message=unicode(e)) # Return token info res = OrderedDict([('identity', video_client.identity), ('token', video_client.token), ('expires_at', video_client.expires_at_unix)]) debug_logger.debug("Authorized %s to use Twilio with identity: %s" % (user, video_client.identity)) return Response(res)
def has_permission(self, request, view): if settings.ENFORCE_SECURE and not request.is_secure(): raise ShoutitBadRequest(message=_("Secure connection is required"), reason=ERROR_REASON.INSECURE_CONNECTION) return True
def reply(self, request, *args, **kwargs): """ Send message to the owner about this Shout ###REQUIRES AUTH ###Request <pre><code> { "text": "text goes here", "attachments": [ { "shout": { "id": "" } }, { "profile": { "id": "" } }, { "location": { "latitude": 12.345, "longitude": 12.345 } }, { "images": [], // list of image urls "videos": [] // list of {Video Object}s } ] } </code></pre> Either `text`, `attachments` or both has to be provided. Images and videos are to be compressed and uploaded before submitting. CDN urls should be sent. --- serializer: MessageSerializer omit_parameters: - form parameters: - name: body paramType: body """ shout = self.get_object() if request.user == shout.owner: raise ShoutitBadRequest( _("You can not start a conversation about your own shout")) context = { 'request': request, 'conversation': None, 'to_users': [shout.owner], 'about': shout } serializer = MessageSerializer(data=request.data, partial=True, context=context) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_message_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def chat(self, request, *args, **kwargs): """ Start or continue chatting (conversation whose its `type` is `chat`) with the Profile ###REQUIRES AUTH > Profile can only message its Listeners, or someone whom it already has an existing conversation with. ###Request <pre><code> { "text": "text goes here", "attachments": [ { "shout": { "id": "" } }, { "profile": { "id": "" } }, { "location": { "latitude": 12.345, "longitude": 12.345 } }, { "images": [], // list of image urls "videos": [] // list of {Video Object}s } ] } </code></pre> Either `text`, `attachments` or both has to be provided. Images and videos are to be compressed and uploaded before submitting. CDN urls should be sent. --- response_serializer: MessageSerializer omit_parameters: - form parameters: - name: body paramType: body """ # Todo: move validation to the serializer user = self.get_object() logged_user = request.user if logged_user == user: raise ShoutitBadRequest( _("You can not start a conversation with your self")) if not (message_controller.conversation_exist( users=[user, logged_user]) or user.is_listening(logged_user)): raise ShoutitBadRequest( _("You can only start a conversation with your listeners")) context = { 'request': request, 'conversation': None, 'to_users': [user] } serializer = MessageSerializer(data=request.data, partial=False, context=context) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_message_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def to_internal_value(self, data): user = self.context['request'].user if self.instance.is_owner(user): raise ShoutitBadRequest(_("You can not like your own shouts")) return {'user': user}