Esempio n. 1
0
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
Esempio n. 2
0
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)
Esempio n. 3
0
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
Esempio n. 4
0
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 ''
Esempio n. 5
0
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}
Esempio n. 6
0
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))
Esempio n. 7
0
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)
Esempio n. 8
0
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
Esempio n. 9
0
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}
Esempio n. 10
0
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