def get_client_details(): """Extracts the client id and secret from the HTTP request.""" if not has_request_context(): return None, '' basic = request.authorization if basic: return basic.username, basic.password else: client_id = flask_extras.get_parameter('client_id') client_secret = flask_extras.get_parameter('client_secret') or '' if not client_id: agent = request.headers.get('User-Agent', '') if 'RogerAndroid/' in agent: client_id = 'android' elif 'Roger/' in agent: client_id = 'ios' elif 'Fika/' in agent: client_id = 'fika' elif 'FikaDesktop/' in agent: # TODO: Unique client id? client_id = 'fika' elif 'FikaIO/' in agent: client_id = 'fikaio' elif 'ReactionCam/' in agent: client_id = 'reactioncam' elif 'roger-web-client' in agent or 'Mozilla' in agent or 'Opera' in agent: client_id = 'web' return client_id, client_secret
def grant_type_authorization_code(client): code = flask_extras.get_parameter('code') if not code: raise errors.MissingArgument('A code is required') redirect_uri = flask_extras.get_parameter('redirect_uri') if not redirect_uri: redirect_uri = None if redirect_uri and redirect_uri not in client.redirect_uris: raise errors.InvalidArgument('Invalid redirect_uri value') session = auth.Session.from_auth_code(code, client.key.id(), redirect_uri) return accounts.get_handler(session.account)
def request_token(): """ Attempts to log the user in with the specified grant type. Request: POST /oauth2/token?grant_type=password&client_id=ios username=bob&password=1234 Response: { "access_token": "RLDvsbckw7tJJCiCPzU9bF", "refresh_token": "pArhTbEs8ex1f79vAqxR2", "token_type": "bearer", "expires_in": 3600, "account": { "id": 12345678, "username": "******", "status": "active" } } """ client_id, client_secret = auth.get_client_details() if not client_id: raise errors.MissingArgument('A client id is required') client = services.get_client(client_id) if client_secret != client.client_secret: # TODO: Enforce a valid client_secret. logging.warning('Client secret appears invalid (client_id=%s)', client_id) grant_type = flask_extras.get_parameter('grant_type') if grant_type == 'authorization_code': user = grant_type_authorization_code(client) elif grant_type == 'password': user = grant_type_password(client) elif grant_type == 'refresh_token': user = grant_type_refresh_token(client) else: raise errors.NotSupported( 'Unsupported grant type "{}"'.format(grant_type)) # Report the successful token exchange. event = events.TokenExchangeV1(user.account.key.id(), client_id=client_id, grant_type=grant_type) event.report() # Track all fika.io logins. if grant_type != 'refresh_token': taskqueue.add(url='/_ah/jobs/track_login', countdown=20, params={ 'account_id': user.account_id, 'client_id': client_id }, queue_name=config.INTERNAL_QUEUE) # Return the session and some additional data about the user. data = utils.get_streams_data(user.account) session = user.create_session(extra_data=data) g.public_options['include_extras'] = True g.public_options['view_account'] = session.account return session
def report_to_bigquery(): """Flush all pending events of a certain type to BigQuery.""" # Schedule multiple flush jobs per minute for some events. if request.method == 'GET': tasks = [] for delay in xrange(0, 60, 5): tasks.append(taskqueue.Task(method='POST', url=request.path, countdown=delay, params={'event_name': 'content_vote_v1'})) tasks.append(taskqueue.Task(method='POST', url=request.path)) taskqueue.Queue(config.BIGQUERY_CRON_QUEUE_NAME).add(tasks) return '' # Retrieve pending events from pull queue. try: q = taskqueue.Queue(config.BIGQUERY_QUEUE_NAME) tasks = q.lease_tasks_by_tag(config.BIGQUERY_LEASE_TIME.total_seconds(), config.BIGQUERY_LEASE_AMOUNT, tag=flask_extras.get_parameter('event_name')) logging.debug('Leased %d event(s) from %s', len(tasks), config.BIGQUERY_QUEUE_NAME) except taskqueue.TransientError: logging.warning('Could not lease events due to transient error') return '', 503 if not tasks: return '' # Insert the events into BigQuery. table_id = tasks[0].tag rows = [json.loads(t.payload) for t in tasks] bigquery_client.insert_rows(table_id, rows) # Delete the tasks now that we're done with them. q.delete_tasks(tasks) return ''
def post_transcode_complete(): try: content_id = int(flask_extras.get_parameter('content_id')) except: raise errors.InvalidArgument('Invalid content_id parameter') stream_url = flask_extras.get_parameter('stream_url') if not stream_url: raise errors.InvalidArgument('Invalid stream_url parameter') content = models.Content.get_by_id(content_id) if not content: raise errors.ResourceNotFound('Content not found') if not content.metadata: content.metadata = {} content.metadata['raw_video_url'] = content.video_url content.video_url = stream_url content.put() return {'success': True}
def get_or_create_content_from_request_async(prefix='content', get_if_needed=False, **kwargs): if prefix and not prefix.endswith('_'): prefix = prefix + '_' elif not prefix: prefix = '' content_id = flask_extras.get_parameter(prefix + 'id') if content_id: try: kwargs.setdefault('content_id', int(content_id)) except: raise errors.InvalidArgument('Invalid %sid value' % (prefix, )) kwargs.setdefault( 'creator', flask_extras.get_parameter(prefix + 'creator_identifier')) kwargs.setdefault('url', flask_extras.get_parameter(prefix + 'url')) duration = flask_extras.get_parameter(prefix + 'duration') if duration: try: kwargs.setdefault('duration', int(duration)) except: raise errors.InvalidArgument('Invalid %sduration value' % (prefix, )) kwargs.setdefault('thumb_url', flask_extras.get_parameter(prefix + 'thumb_url')) kwargs.setdefault('title', flask_extras.get_parameter(prefix + 'title')) kwargs.setdefault('video_url', flask_extras.get_parameter(prefix + 'video_url')) key, content = yield get_or_create_content_async(**kwargs) if key and get_if_needed and not content: content = yield key.get_async() raise ndb.Return((key, content))
def grant_type_refresh_token(client): refresh_token = flask_extras.get_parameter('refresh_token') if not refresh_token: raise errors.MissingArgument('A refresh token is required') try: session = auth.Session.from_refresh_token(refresh_token) except ValueError: logging.exception('Failed to restore refresh token') raise errors.InvalidCredentials() return accounts.get_handler(session.account)
def grant_type_password(client): username = flask_extras.get_parameter('username') password = flask_extras.get_parameter('password') if not username: raise errors.MissingArgument('A username is required') if not password: raise errors.MissingArgument('A password is required') try: user = accounts.get_handler(username) except errors.ResourceNotFound: # Return a 401 instead of a 404 when the account does not exist. raise errors.InvalidCredentials() if not user.validate_password(password): raise errors.InvalidCredentials() report.user_logged_in(user.account_id, auth_identifier=username, challenge='password') return user
def post_chunk_played(): # Only internal websites can use this endpoint. try: payload = security.decrypt(config.WEB_ENCRYPTION_KEY, flask_extras.get_parameter('payload'), block_segments=True) data = json.loads(payload) fingerprint = data['fingerprint'] stream_key = ndb.Key('Stream', data['stream_id']) chunk_key = ndb.Key('Chunk', data['chunk_id'], parent=stream_key) except: raise errors.InvalidArgument('Invalid payload') cache_key = 'external_plays:%d:%d:%s' % (stream_key.id(), chunk_key.id(), fingerprint) if memcache.get(cache_key): logging.debug( 'Repeat chunk play for fingerprint %s (stream %d chunk %d)', fingerprint, stream_key.id(), chunk_key.id()) return {'success': True} memcache.set(cache_key, True, 172800) stream, chunk = ndb.get_multi([stream_key, chunk_key]) if not stream or not chunk: raise errors.ResourceNotFound('That chunk does not exist') chunk.external_plays += 1 chunk.put() for local_chunk in stream.chunks: if local_chunk.chunk_id == chunk.key.id(): local_chunk.external_plays = chunk.external_plays stream.put() break logging.debug('New chunk play for fingerprint %s (stream %d chunk %d)', fingerprint, stream_key.id(), chunk_key.id()) logging.debug('Total external chunk plays is now %d', chunk.external_plays) handler = streams.MutableStream(chunk.sender, stream) if chunk.external_plays == 1: handler.notify_first_play(chunk) handler.notify(notifs.ON_STREAM_CHUNK_EXTERNAL_PLAY, add_stream=True, chunk=chunk) return {'success': True}
def upload_and_send(stream): extras = flask_extras.get_flag_dict('allow_duplicate', 'export', 'mute_notification', 'persist') # Export everything by default. extras.setdefault('export', True) persist = extras.get('persist', False) show_in_recents = flask_extras.get_flag('show_in_recents') if show_in_recents is not None: extras['show_for_sender'] = show_in_recents # Timed transcriptions. text_segments = flask_extras.get_parameter('text_segments') if text_segments: extras['text_segments'] = [ models.TextSegment(**s) for s in json.loads(text_segments) ] # Payload can either be a file + duration or a URL to download. if g.api_version >= 29: payload = request.files.get('payload') url = flask_extras.get_parameter('url') else: payload = request.files.get('audio') url = flask_extras.get_parameter('audio_url') if payload: try: duration = int(flask_extras.get_parameter('duration')) except (TypeError, ValueError): raise errors.InvalidArgument( 'Duration should be milliseconds as an int') path = files.upload(payload.filename, payload.stream, persist=persist) elif url: if url.startswith(config.STORAGE_URL_HOST): path = '/' + url[len(config.STORAGE_URL_HOST):] try: duration = int(flask_extras.get_parameter('duration')) except (TypeError, ValueError): raise errors.InvalidArgument( 'Duration should be milliseconds as an int') else: raise errors.InvalidArgument('Cannot use that URL') else: return False # Upload file attachments. extras['attachments'] = [] attachments = request.files.getlist('attachment') for attachment in attachments: p = files.upload(attachment.filename, attachment.stream, persist=persist) extras['attachments'].append( models.ChunkAttachment(title=attachment.filename, url=files.storage_url(p))) # Add URL attachments. attachment_titles = flask_extras.get_parameter_list('attachment_title') attachment_urls = flask_extras.get_parameter_list('attachment_url') try: # Validate URLs. parsed_urls = map(urlparse.urlparse, attachment_urls) except: raise errors.InvalidArgument('Invalid attachment URL provided') if not attachment_titles: attachment_titles = [u''] * len(attachment_urls) for i, title in enumerate(attachment_titles): if title: continue # Default empty titles to URL hostname. attachment_titles[i] = parsed_urls[i].hostname if len(attachment_titles) != len(attachment_urls): raise errors.InvalidArgument('Attachment title/URL count mismatch') for title, url in zip(attachment_titles, attachment_urls): extras['attachments'].append( models.ChunkAttachment(title=title, url=url)) # Support creating public links to chunk. if extras.get('export', False): custom_content_id = flask_extras.get_parameter('external_content_id') if custom_content_id: # TODO: WARNING, this can be abused to take over existing shared links! if not re.match(r'[a-zA-Z0-9]{21}$', custom_content_id): raise errors.InvalidArgument( 'Custom external_content_id must be 21 base62 digits') extras['external_content_id'] = custom_content_id client_id, _ = auth.get_client_details() extras.setdefault('client_id', client_id) stream.send(path, duration, token=flask_extras.get_parameter('chunk_token'), **extras) # Ping IFTTT to fetch the new data. # TODO: Do this in some nicer way. Maybe on_new_chunk for services? if stream.service_id == 'ifttt' and stream.account.username != 'ifttt': ping_ifttt(stream.service_owner or stream.account) return True