-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathogeapi.py
285 lines (213 loc) · 7.66 KB
/
ogeapi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import json # psycopg2
import sys
# from os import path
from flask import Flask, Response
from flask import request, session, redirect, url_for, render_template, flash
from flask.ext.sqlalchemy import SQLAlchemy
# from flask.ext.wtf import Form
# from wtforms import TextField, PasswordField, validators
sys.path.append("../..")
import urllib
import urllib2
import cookielib
import uuid
import datetime
from datetime import timedelta
from bs4 import BeautifulSoup
from functools import wraps
from forms import LoginForm
from writeSQL import (questions_sql, get_api_token_expiration, get_api_token,
insert_api_token, dump_sql, state_sql, question_id_sql,
question_name_sql, state_question_id_sql,
state_question_sql)
import config
uuid._uuid_generate_time = None
uuid._uuid_generate_random = None
app = Flask(__name__)
app.secret_key = config.s_key
app.config['SQLALCHEMY_DATABASE_URI'] = config.SQLALCHEMY_DATABASE_URI
db = SQLAlchemy(app)
# TODO: ADD LOGIN REQ AGAIN, SET SETTINGS
if __name__ == "__main__" and __package__ is None:
__package__ = "ogea.api"
class Request(db.Model):
id = db.Column(db.Integer, primary_key=True)
token = db.Column(db.String(1000))
params = db.Column(db.String(1000))
url = db.Column(db.String(1000))
ip = db.Column(db.String(15))
timestamp = db.Column(db.DateTime)
def record_action(request):
token = request.args.get('token')
params = request.data
url = request.url
timestamp = datetime.datetime.now()
ip = request.remote_addr
r = Request(
token=token,
params=params,
url=url,
timestamp=timestamp,
ip=ip
)
db.session.add(r)
db.session.commit()
print "Request: ", url, params, token, timestamp, ip
def api_login_required(view):
@wraps(view)
def decorated_view(*args, **kwargs):
if request.headers.get('accept', None) == 'application/json':
if not validate_token(request.args.get('token')):
return json.dumps(
{'error': '''
Invalid token, please login to retrieve a valid token.
'''})
else:
# if not (g.user and g.user.is_authenticated()):
if not session.get('logged_in', False):
flash('You must be logged in to access this resource.')
return redirect(url_for('login', next=request.url))
return view(*args, **kwargs)
return decorated_view
def validate_token(token):
"""
Checks if the token is in the database, and that the
"""
expiration = get_api_token_expiration(token)
if expiration:
expiration = expiration[0]['expiration']
else:
return False
if not expiration or expiration >= datetime.date.today():
return True
return False
def login_to_noisite(username, password):
"""
In order to login to the API, the user must have an existing
account with neworganizing.com. This function passes along the
provided username and password to the NOI login form, taking
CSRF into account, and if the resulting redirect is not back
to the login form, then the authentication was successful.
TODO: Enable the neworganizing.com site to react appropriately
to application/json requests, and return a proper authentication
confirmation.
"""
if config.DEBUG:
return True
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)
login_url = 'http://{0}/accounts/login/'.format(config.NOISITE_DOMAIN)
request = urllib2.Request(login_url)
request.add_header('User-Agent', 'Browser')
response = urllib2.urlopen(request)
html = response.read()
doc = BeautifulSoup(html)
csrf_input = doc.find(attrs=dict(name='csrfmiddlewaretoken'))
csrf_token = csrf_input['value']
params = urllib.urlencode(
dict(
username=username,
password=password,
csrfmiddlewaretoken=csrf_token
)
)
request.data = params
response = urllib2.urlopen(request)
if response.geturl() == login_url:
return False
else:
return True
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm(csrf_enabled=False)
if session.get('logged_in', False):
return redirect(url_for('main'))
if request.form:
username = request.form.get('username', None)
password = request.form.get('password', None)
logged_in = login_to_noisite(username, password)
if logged_in:
accept_header = request.headers.get('accept', None)
if not accept_header:
accept_header = request.headers.get('Accept', None)
if accept_header and accept_header == 'application/json':
# Check if token already exists (i.e. permanent tokens)
token = get_api_token(username)
if not token:
token = uuid.uuid4()
expiration = datetime.date.today()+timedelta(days=1)
insert_api_token(username, token, expiration)
return json.dumps({'logged_in': 'true', 'token': str(token)})
else:
session['logged_in'] = True
return redirect(request.args.get('next') or url_for('main'))
else:
if request.headers['accept'] == 'application/json':
return json.dumps({'logged_in': 'false'})
else:
flash('Failed to login.')
return render_template('login.html', form=form, title='Login')
@app.route('/logout')
def logout():
flash('You have been logged out')
session['logged_in'] = False
return redirect(url_for('main'))
@app.route('/')
def main():
if not session.get('logged_in', False):
flash('You are not logged in')
return render_template('main.html')
@app.route('/questions/', methods=['GET'])
@api_login_required
def get_questions():
record_action(request)
return json.dumps(questions_sql())
@app.route('/question/<int:q_id>', methods=['GET'])
@api_login_required
def get_question_id(q_id):
record_action(request)
return json.dumps(question_id_sql(q_id))
@app.route('/question/<path:ques>', methods=['GET'])
@api_login_required
def get_question(ques):
record_action(request)
ques = urllib.unquote(ques)
return json.dumps(question_name_sql(ques))
@app.route('/<state>/<int:q_id>', methods=['GET'])
@api_login_required
def get_state_question_id(state, q_id):
record_action(request)
return json.dumps(state_question_id_sql(state, q_id))
@app.route('/<state>/<path:ques>', methods=['GET'])
@api_login_required
def get_state_question(state, ques):
record_action(request)
ques = urllib.unquote(ques)
return json.dumps(state_question_sql(state, ques))
@app.route('/<state>', methods=['GET'])
@api_login_required
def get_state(state):
record_action(request)
return json.dumps(state_sql(str(state)))
@app.route('/dump', methods=['GET'])
@api_login_required
def dump():
record_action(request)
return json.dumps(dump_sql())
@app.errorhandler(401)
def handler_401(error):
return Response(
'Error: Unauthorized API Access Attempt',
401,
{'WWWAuthenticate': 'Basic realm="Login Required"'}
)
# topic/subtopic at some later date
if not app.debug:
import logging
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler(config.API_LOG_PATH+'ogeapi.log')
file_handler.setLevel(logging.WARNING)
app.logger.addHandler(file_handler)
if __name__ == '__main__':
app.run(debug=config.DEBUG)