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)
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)))
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})