Beispiel #1
0
def login_callback():
    current_app.logger.debug('callback fields: %s', request.args)

    state = request.args.get('state')
    next_url = state or url_for('views.index')
    auth_url, token_url, micropub_url = session['endpoints']

    if not auth_url:
        flash('Login failed: No authorization URL in session')
        return redirect(next_url)

    code = request.args.get('code')
    client_id = get_settings().site_url
    redirect_uri = url_for('.login_callback', _external=True)

    current_app.logger.debug('callback with auth endpoint %s', auth_url)
    response = requests.post(auth_url, data={
        'code': code,
        'client_id': client_id,
        'redirect_uri': redirect_uri,
        'state': state,
    })

    rdata = urllib.parse.parse_qs(response.text)
    if response.status_code != 200:
        current_app.logger.debug('call to auth endpoint failed %s', response)
        flash('Login failed {}: {}'.format(rdata.get('error'),
                                           rdata.get('error_description')))
        return redirect(next_url)

    current_app.logger.debug('verify response %s', response.text)
    if 'me' not in rdata:
        current_app.logger.debug('Verify response missing required "me" field')
        flash('Verify response missing required "me" field {}'.format(
            response.text))
        return redirect(next_url)

    me = rdata.get('me')[0]
    scopes = rdata.get('scope')
    user = auth.load_user(urllib.parse.urlparse(me).netloc)
    if not user:
        flash('No user for domain {}'.format(me))
        return redirect(next_url)

    try_micropub_config(token_url, micropub_url, scopes, code, me,
                        redirect_uri, client_id, state)

    current_app.logger.debug('Logging in user %s', user)
    flask_login.login_user(user, remember=True)
    flash('Logged in with domain {}'.format(me))
    current_app.logger.debug('Logged in with domain %s', me)

    return redirect(next_url)
Beispiel #2
0
def login():
    me = request.args.get('me')
    if not me:
        return render_template('admin/login.jinja2',
                               next=request.args.get('next'))

    if current_app.config.get('BYPASS_INDIEAUTH'):
        user = auth.load_user(urllib.parse.urlparse(me).netloc)
        current_app.logger.debug('Logging in user %s', user)
        flask_login.login_user(user, remember=True)
        flash('logged in as {}'.format(me))
        current_app.logger.debug('Logged in with domain %s', me)
        return redirect(request.args.get('next') or url_for('views.index'))

    if not me:
        return make_response('Missing "me" parameter', 400)
    if not me.startswith('http://') and not me.startswith('https://'):
        me = 'http://' + me
    auth_url, token_url, micropub_url = discover_endpoints(me)
    if not auth_url:
        auth_url = 'https://indieauth.com/auth'

    current_app.logger.debug('Found endpoints %s, %s, %s', auth_url, token_url,
                             micropub_url)
    state = request.args.get('next')
    session['endpoints'] = (auth_url, token_url, micropub_url)

    auth_params = {
        'me': me,
        'client_id': get_settings().site_url,
        'redirect_uri': url_for('.login_callback', _external=True),
        'state': state,
    }

    # if they support micropub try to get read indie-config permission
    if token_url and micropub_url:
        auth_params['scope'] = 'config'

    return redirect('{}?{}'.format(
        auth_url, urllib.parse.urlencode(auth_params)))
Beispiel #3
0
def micropub_endpoint():
    current_app.logger.info(
        "received micropub request %s, args=%s, form=%s, headers=%s",
        request, request.args, request.form, request.headers)

    if request.method == 'GET':
        current_app.logger.debug('micropub GET request %s -> %s', request,
                                 request.args)
        q = request.args.get('q')
        if q == 'syndicate-to':
            current_app.logger.debug('returning syndication targets')
            response = make_response(urllib.parse.urlencode([
                ('syndicate-to[]', target) for target in SYNDICATION_TARGETS]))
            response.headers['Content-Type'] = 'application/x-www-form-urlencoded'
            return response

        elif q in ('actions', 'json_actions'):
            current_app.logger.debug('returning action handlers')
            reply_url = url_for('admin.new_post', type='reply', _external=True)
            repost_url = url_for('admin.new_post', type='share', _external=True)
            like_url = url_for('admin.new_post', type='like', _external=True)
            payload = {
                'reply': reply_url + '?url={url}',
                'repost': repost_url + '?url={url}',
                'favorite': like_url + '?url={url}',
                'like': like_url + '?url={url}',
            }
            accept_header = request.headers.get('accept', '')
            if q == 'json_actions' or 'application/json' in accept_header:
                return jsonify(payload)
            else:
                response = make_response(urllib.parse.urlencode(payload))
                response.headers['Content-Type'] = 'application/x-www-form-urlencoded'
                return response

        else:
            abort(404)

    bearer_prefix = 'Bearer '
    header_token = request.headers.get('authorization')
    if header_token and header_token.startswith(bearer_prefix):
        token = header_token[len(bearer_prefix):]
    else:
        token = request.form.get('access_token')

    if not token:
        current_app.logger.warn('hit micropub endpoint with no access token')
        abort(401)

    try:
        decoded = util.jwt_decode(token)
    except jwt.DecodeError as e:
        current_app.logger.warn('could not decode access token: %s', e)
        abort(401)

    me = decoded.get('me')
    client_id = decoded.get('client_id')
    parsed = urllib.parse.urlparse(me)
    user = auth.load_user(parsed.netloc)
    if not user or not user.is_authenticated():
        current_app.logger.warn(
            'received valid access token for invalid user: %s', me)
        abort(401)

    h = request.form.get('h')
    in_reply_to = request.form.get('in-reply-to')
    like_of = request.form.get('like-of')
    photo_file = request.files.get('photo')
    bookmark = request.form.get('bookmark') or request.form.get('bookmark-of')
    repost_of = request.form.get('repost-of')

    post_type = ('event' if h == 'event' 
                 else 'article' if 'name' in request.form
                 else 'photo' if photo_file else 'reply' if in_reply_to
                 else 'like' if like_of else 'bookmark' if bookmark
                 else 'share' if repost_of else 'note')

    latitude = None
    longitude = None
    location_name = None
    venue_id = None
    
    loc_str = request.form.get('location')
    geo_prefix = 'geo:'
    if loc_str:
        if loc_str.startswith(geo_prefix):
            loc_str = loc_str[len(geo_prefix):]
            loc_params = loc_str.split(';')
            if loc_params:
                latitude, longitude = loc_params[0].split(',', 1)
                location_name = request.form.get('place_name')
        else:
            venue_prefix = urllib.parse.urljoin(get_settings().site_url, 'venues/')
            if loc_str.startswith(venue_prefix):
                slug = loc_str[len(venue_prefix):]
                venue = Venue.query.filter_by(slug=slug).first()
                if venue:
                    venue_id = venue.id

    # url of the venue, e.g. https://kylewm.com/venues/cafe-trieste-berkeley-california
    venue = request.form.get('venue')
            
    syndicate_to = request.form.getlist('syndicate-to[]')
    syndication = request.form.get('syndication')

    # TODO check client_id
    if client_id == 'https://kylewm-responses.appspot.com/' and syndication:
        current_app.logger.debug(
            'checking for existing post with syndication %s', syndication)
        existing = Post.query.filter(
            Post.syndication.like(db.literal('%"' + syndication + '"%'))
        ).first()
        if existing:
            current_app.logger.debug(
                'found post for %s: %s', syndication, existing)
            return redirect(existing.permalink)
        else:
            current_app.logger.debug(
                'no post found with syndication %s', syndication)

    # translate from micropub's verbage.TODO unify
    translated = util.filter_empty_keys({
        'post_type': post_type,
        'published': request.form.get('published'),
        'start': request.form.get('start'),
        'end': request.form.get('end'),
        'title': request.form.get('name'),
        'content': request.form.get('content'),
        'venue': venue_id,
        'latitude': latitude,
        'longitude': longitude,
        'location_name': location_name,
        'syndication': syndication,
        'in_reply_to': in_reply_to,
        'like_of': like_of,
        'repost_of': repost_of,
        'bookmark_of': bookmark,
        'photo': photo_file,
        'syndicate-to': [SYNDICATION_TARGETS.get(to) for to in syndicate_to],
        'hidden': 'true' if like_of or bookmark else 'false',
    })
    with current_app.test_request_context(
            base_url=get_settings().site_url, path='/save_new',
            method='POST', data=translated
    ):
        current_app.logger.debug('received fake request %s: %s',
                                 request, request.args)
        login_user(user)
        current_app.logger.debug('successfully authenticated as user %s => %s',
                                 me, user)
        from . import admin
        resp = admin.save_new()
        return make_response('Created', 201, {'Location': resp.location})