1
# -*- coding: utf-8 -*-
2
"""
3
    swtr
4
    ~~~~~~
5
6
    http://swtr.us
7
8
    :license: BSD, see LICENSE for more details.
9
"""
10
from __future__ import with_statement
11
from pymongo import Connection
12
from bson.objectid import ObjectId
13
from bson.errors import InvalidId
14
from flask import Flask, request, session, g, redirect, url_for, abort, \
15
     render_template, flash, _app_ctx_stack, make_response, jsonify
16
from urllib import unquote_plus
17
import json
18
import conf
19
import requests
20
21
# TODO:
22
#    restify
23
#    APIs as follows:
24
#        GET /sweets/q -> query sweets
25
#                         args: who, where, what, how
26
#        GET /sweets/<id> -> get specific sweet
27
#        POST /sweets -> post sweets (one or a batch of)
28
#        OPTIONS /sweets - > CORS policy .. understand it better
29
#   classes!
30
#   sqlAlchemy
31
#   Postgres
32
#   Persona, Auth in API endpoints
33
34
# TODO: move this in a config file
35
# configuration
36
37
DATABASE = 'sweets_production'
38
COLLECTION_NAME = 'posts'
39
DEBUG = True
40
SECRET_KEY = conf.SECRET_KEY
41
USERNAME = 'admin'
42
PASSWORD = 'default'
43
DB_PORT = 27017
44
DB_HOST = 'localhost'
45
URL = 'http://localhost:5001'
46
MOZ_PERSONA_VERIFIER = 'https://verifier.login.persona.org/verify'
47
MOZ_PERSONA_AUDIENCE = 'http://localhost:5000'
48
49
appURL_map = {'img-anno': 'http://localhost:5000/?where=',
50
              're-narration': 'http://y.a11y.in/web?foruri=',
51
              'idh-mowl': 'http://app.swtr.us/?where=',
52
              'testFromAPI': 'http://app.swtr.us/?where='}
53
54
# create our little application :)
55
# ^ ... It's going to be big now :P
56
app = Flask(__name__)
57
app.config.from_object(__name__)
58
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
59
60
# Jinja filters
61
app.jinja_env.filters['len'] = len
62
63
64
def validateSweet(payload):
65
    for i in payload:
66
        try:
67
            if len(i['who']) and len(i['what']) and len(i['where']) and\
68
               len(i['how']) and len(i['created']):
69
                pass
70
            else:
71
                return False
72
        except KeyError:
73
            return False
74
    return True
75
76
77
def getUsers():
78
    db = g.connection[app.config['DATABASE']]
79
    coll = db['sweet_users']
80
    users = []
81
    for i in coll.find():
82
        users.append(i['user'])
83
    return users
84
85
def gatherStats(coll):
86
    stats = {}
87
    stats['total_sweets'] = coll.count()
88
    return stats
89
90
@app.before_request
91
def init_db():
92
    g.connection = Connection(app.config['DB_HOST'], app.config['DB_PORT'])
93
    db = g.connection[app.config['DATABASE']]
94
    g.collection = db[app.config["COLLECTION_NAME"]]
95
    g.stats = gatherStats(g.collection)
96
97
98
@app.teardown_request
99
def close_db(exception):
100
    g.connection.disconnect()
101
102
103
@app.errorhandler(404)
104
def page_not_found(e):
105
    return render_template('404.html'), 404
106
107
108
@app.errorhandler(500)
109
def internal_error(e):
110
    return render_template('500.html'), 500
111
112
@app.route('/')
113
def show_entries():
114
    res = g.collection.find().sort('_id',direction=-1).limit(100)
115
    entries = make_list(res)
116
    return render_template('show_entries.html', entries=entries,
117
                           appURL_map=appURL_map, stats=g.stats)
118
119
120
# TODO: understand if we really need the OPTIONS
121
@app.route('/sweets', methods=['POST', 'OPTIONS'])
122
@app.route('/add', methods=['POST', 'OPTIONS'])
123
def addSweets():
124
    print request.method
125
126
    if request.method == 'OPTIONS':
127
        response = make_response()
128
        response.status_code = 200
129
        response.headers['Access-Control-Allow-Origin'] =\
130
        'http://localhost:5000'
131
        response.headers['Access-Control-Max-Age'] = '20days'
132
        response.headers['Access-Control-Allow-Headers'] = 'Origin,\
133
                         X-Requested-With, Content-Type, Accept'
134
        return response
135
136
    response = make_response()
137
    response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000'
138
    response.headers['Access-Control-Allow-Headers'] = 'Origin,\
139
                     X-Requested-With, Content-Type, Accept'
140
    data = {}
141
    data_list = []
142
143
    if 'email' in session:
144
        print 'identifed user'
145
        print session['email']
146
    else:
147
        print 'unidentified user'
148
149
    # TODO: find a better way of handling reqeust sweets
150
    try:
151
        payload = json.loads(request.form['data'])
152
    except:
153
        try:
154
            payload = [{'who': request.form['who'], 'what': request.form['what'],
155
                    'where': request.form['where'], 'how': request.form['how']}]
156
        except:
157
            try:
158
                payload = request.json
159
            except:
160
                payload = json.loads(request.data)
161
162
163
    valid = validateSweet(payload)
164
    if not valid:
165
        response.status_code = 400
166
        response.data = "Bad or Malformed Request. Please check the validity\
167
        of your request"
168
        return response
169
    print 'swt payload rcvd..'
170
    print payload
171
    for i in payload:
172
        data = i
173
        id = g.collection.insert(i)
174
        data['permalink'] = app.config['URL'] + '/posts/' + str(ObjectId(id))
175
        data['id'] = str(ObjectId(id))
176
        del(data['_id'])
177
        print 'data', data
178
        data_list.append(data)
179
    response.data = json.dumps(data_list)
180
    print 'swt stored..'
181
    return response
182
183
184
@app.route('/login', methods=['GET', 'POST'])
185
def login():
186
    error = None
187
    if request.method == 'POST':
188
        if request.form['username'] != app.config['USERNAME']:
189
            error = 'Invalid username'
190
        elif request.form['password'] != app.config['PASSWORD']:
191
            error = 'Invalid password'
192
        else:
193
            session['logged_in'] = True
194
            flash('You were logged in')
195
            return redirect(url_for('show_entries'))
196
    return render_template('login.html', error=error)
197
198
199
@app.route('/sweets/q', methods=['GET'])
200
def searchSweets():
201
    response = make_response()
202
    response.status_code = 200
203
    response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000'
204
    response.headers['Access-Control-Max-Age'] = '20days'
205
    response.headers['Access-Control-Allow-Headers'] = 'Origin,\
206
                      X-Requested-With, Content-Type, Accept'
207
208
    args = request.args
209
210
    if args is None:
211
        reponse.status_code = 400
212
        return response
213
214
    #if args['where'] is None:
215
    #    reponse.status_code = 400
216
    #    return response
217
218
    params = {}
219
220
    if args.get('where'):
221
        params['where'] = args.get('where')
222
    if args.get('who'):
223
        params['who'] = args.get('who')
224
    if args.get('what'):
225
        params['what'] = args.get('what')
226
    if args.get('how'):
227
        params['how'] = args.get('how')
228
229
230
    print params
231
    res = g.collection.find(params)
232
233
    if res.count() < 1:
234
        response.status_code = 404
235
        return response
236
237
    swt_list = []
238
    for swt in res:
239
        _id = swt['_id']
240
        del(swt['_id'])
241
        swt['id'] = str(_id)
242
        swt_list.append(swt)
243
244
    response.data = json.dumps(swt_list)
245
    return response
246
247
248
@app.route('/sweets/<post_id>', methods=['GET'])
249
@app.route('/query/<post_id>',methods=['GET'])
250
def return_database_entry(post_id):
251
    try:
252
        res = g.collection.find_one({'_id':ObjectId(post_id)})
253
        if(res):
254
            res['blog'] = url_for('show_specific_entry', post_id = str(res['_id']))
255
            del(res['_id'])
256
            return jsonify(res)
257
            # entries = make_list(res)
258
            # return render_template('show_posts.html', entries=res, str=str)
259
        else:
260
            abort(404)
261
    except InvalidId:
262
        abort(404)
263
264
265
266
@app.route('/posts/<post_id>',methods=['GET'])
267
def show_specific_entry(post_id):
268
    try:
269
        res = g.collection.find({'_id':ObjectId(post_id)})
270
        if(res.count() > 0):
271
            #entries = make_list(res)
272
            entries = []
273
            for i in res:
274
                _id = i['_id']
275
                del(i['_id'])
276
                i['id'] = _id
277
                entries.append(i)
278
            return render_template('show_posts.html', entries=entries, str=str)
279
        else:
280
            abort(404)
281
    except InvalidId:
282
        abort(404)
283
284
285
@app.route('/posts/delete/', methods=['POST'])
286
def delete_post():
287
    try:
288
        g.collection.remove({'_id':ObjectId(request.form['post_id'])})
289
        return jsonify(status='ok')
290
    except:
291
        abort(500)
292
293
@app.route('/logout')
294
def logout():
295
    session.pop('logged_in', None)
296
    flash('You were logged out')
297
    return redirect(url_for('show_entries'))
298
299
@app.route('/serveUser')
300
def serveUser():
301
    if "logged_in" in session:
302
        #print session["logged_in"]
303
        session['key'] = conf.SECRET_KEY
304
        return render_template('user.html')
305
    else:
306
        return render_template('login.html', error=None)
307
308
@app.route('/user/', methods=['POST', 'GET'])
309
@app.route('/user/<user_id>', methods=['GET'])
310
def user(user_id='all'):
311
    if request.method == 'POST':
312
        response = make_response()
313
        db = g.connection[app.config['DATABASE']]
314
        collection = db['sweet_users']
315
316
        # check if user already exists
317
        if request.form['user'] in getUsers():
318
            #print 'user already exists!'
319
            flash('User already exists!')
320
            return redirect(url_for('serveUser'))
321
322
        # else insert new user
323
        collection.insert({'user': request.form['user'],
324
                           'key': request.form['key']})
325
        response.status_code = 200
326
        response.data = 'User added.'
327
        return response
328
329
    elif request.method == 'GET':
330
        db = g.connection[app.config['DATABASE']]
331
        collection = db['sweet_users']
332
        users = []
333
        if user_id == 'all':
334
            users = getUsers()
335
        else:
336
            user = collection.find_one({'user': user_id})
337
            if user:
338
                users.append(user['user'])
339
            else:
340
                abort(404)
341
        return render_template("users.html", users=users)
342
343
344
@app.route('/authenticate', methods=['POST','GET'])
345
def authenticate():
346
    if request.method == "POST":
347
        response = make_response()
348
        db = g.connection[app.config['DATABASE']]
349
        collection = db['sweet_users']
350
        for i in collection.find():
351
            if i['user'] == request.form['user'] and i['key'] == request.form['hash']:
352
                response.status_code = 200
353
                response.headers['Access-Control-Allow-Origin'] = '*'
354
                return response
355
            else:
356
                pass
357
        response.status_code = 403
358
        response.headers['Access-Control-Allow-Origin'] = '*'
359
        return response
360
    elif request.method == "GET":
361
        return app.send_static_file("sweet-authenticate.js")
362
363
@app.route('/auth/login', methods=['POST'])
364
def authLogin():
365
    response = make_response()
366
    response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000'
367
    response.headers['Access-Control-Allow-Credentials'] = 'true'
368
    response.headers['Access-Control-Max-Age'] = '20days'
369
    response.headers['Access-Control-Allow-Headers'] = 'Origin,\
370
                      X-Requested-With, Content-Type, Accept'
371
372
    if 'assertion' not in request.form:
373
        response.status_code = 400
374
        return response
375
376
    data = {'assertion': request.form['assertion'], 'audience':
377
            MOZ_PERSONA_AUDIENCE}
378
    resp = requests.post(MOZ_PERSONA_VERIFIER, data=data, verify=True)
379
    print resp.status_code
380
    print resp.json()
381
382
    if resp.ok:
383
        verified_data = json.loads(resp.content)
384
        if verified_data['status'] == 'okay':
385
            #session.update({'email': verified_data['email']})
386
            session['email'] = verified_data['email']
387
            response.status_code = 200
388
            response.data = {'email': verified_data['email']}
389
            return response
390
391
    response.status_code = 500
392
    return response
393
394
@app.route('/auth/logout', methods=['POST'])
395
def authLogout():
396
    response = make_response()
397
    response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5000'
398
    response.headers['Access-Control-Allow-Credentials'] = 'true'
399
    response.headers['Access-Control-Max-Age'] = '20days'
400
    response.headers['Access-Control-Allow-Headers'] = 'Origin,\
401
                      X-Requested-With, Content-Type, Accept'
402
403
    if 'email' in session:
404
        print 'logging out '
405
        print session['email']
406
        session.pop('email')
407
408
    response.status_code = 200
409
    return response
410
411
def make_list(res):
412
    entries = []
413
    for row in res:
414
        d = row
415
        d['id'] = str(row['_id'])
416
        try:
417
            if d['who'] in getUsers() or d['author'] in getUsers():
418
                d['registered'] = True
419
        except KeyError:
420
            pass
421
        entries.append(d)
422
    return entries
423
424
if __name__ == '__main__':
425
    app.run(debug=True, port=5001)