From b095bc191fbf6846de8e2b1a9352d22d0156936d Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Fri, 26 Aug 2016 01:40:07 -0500 Subject: [PATCH 01/41] [WIP] Connect with OpenCTF --- forms.py | 35 ++++++++++++++++++++++++++------- models.py | 11 +++++++++-- requirements.txt | 1 + templates/events/manage.html | 25 ----------------------- templates/users/oauthorize.html | 28 ++++++++++++++++++++++++++ views/events.py | 8 ++++---- views/oauth.py | 2 +- 7 files changed, 71 insertions(+), 39 deletions(-) create mode 100644 templates/users/oauthorize.html diff --git a/forms.py b/forms.py index f919a44..a9fcebb 100644 --- a/forms.py +++ b/forms.py @@ -4,6 +4,7 @@ from wtforms.fields import * from wtforms.validators import * from wtforms.widgets import TextArea +from wtforms_components import read_only import util from models import User @@ -48,17 +49,37 @@ def validate_username(self, field): raise ValidationError('Username taken!') -class EventForm(Form): +class EventCreateForm(Form): title = StringField('Title', validators=[InputRequired(), Length(max=256)]) - start_time = IntegerField('Start Time', - validators=[InputRequired(), NumberRange(min=0, max=2147483647, - message='Start time must be between 0 and 2147483647!')]) - duration = FloatField('Duration (hours)', - validators=[InputRequired(), NumberRange(min=0, max=2147483647, - message='Duration must be between 0 and 2147483647!')]) + start_time = IntegerField('Start Time', validators=[InputRequired(), NumberRange(min=0, max=2147483647, + message='Start time must be between 0 and 2147483647!')]) + duration = FloatField('Duration (hours)', validators=[InputRequired(), NumberRange(min=0, max=2147483647, + message='Duration must be between 0 and 2147483647!')]) description = StringField('Description', widget=TextArea(), validators=[InputRequired(), Length(max=1024)]) link = StringField('Link', validators=[InputRequired(), Length(max=256)]) def validate_link(self, field): if not any(field.data.startswith(prefix) for prefix in [u'http://', u'https://']): raise ValidationError('Invalid link') + + +class EventManageForm(Form): + title = StringField('Title', validators=[InputRequired(), Length(max=256)]) + start_time = IntegerField('Start Time', validators=[InputRequired(), NumberRange(min=0, max=2147483647, + message='Start time must be between 0 and 2147483647!')]) + duration = FloatField('Duration (hours)', validators=[InputRequired(), NumberRange(min=0, max=2147483647, + message='Duration must be between 0 and 2147483647!')]) + description = StringField('Description', widget=TextArea(), validators=[InputRequired(), Length(max=1024)]) + link = StringField('Link', validators=[InputRequired(), Length(max=256)]) + client_id = StringField('Client ID') + client_secret = StringField('Client Secret') + redirect_uris = StringField('Redirect URIs', widget=TextArea(), validators=[]) + + def __init__(self, *args, **kwargs): + super(EventManageForm, self).__init__(*args, **kwargs) + read_only(self.client_id) + read_only(self.client_secret) + + def validate_link(self, field): + if not any(field.data.startswith(prefix) for prefix in [u'http://', u'https://']): + raise ValidationError('Invalid link') diff --git a/models.py b/models.py index e34403a..ea94b83 100644 --- a/models.py +++ b/models.py @@ -101,7 +101,7 @@ class Event(db.Model): client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False, default=util.generate_string(32)) is_confidential = db.Column(db.Boolean, default=True) _redirect_uris = db.Column(db.Text) - _default_scopes = db.Column(db.Text) + _default_scopes = db.Column(db.Text, default='profile') @property def client_type(self): @@ -111,10 +111,17 @@ def client_type(self): @property def redirect_uris(self): + 'getting' if self._redirect_uris: - return self._redirect_uris.split() + return self._redirect_uris#.split() return [] + @redirect_uris.setter + def redirect_uris(self, value): + 'setting' + self._redirect_uris = value + db.session.commit() + @property def default_redirect_uri(self): return self.redirect_uris[0] diff --git a/requirements.txt b/requirements.txt index 318c1a9..7a10840 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,4 @@ pymysql python-dotenv sqlalchemy wtforms +wtforms_components \ No newline at end of file diff --git a/templates/events/manage.html b/templates/events/manage.html index e318d68..947badf 100644 --- a/templates/events/manage.html +++ b/templates/events/manage.html @@ -56,31 +56,6 @@

Manage Event

-
-
-

OAuth Information

-
-
-
-
- -
- -
-
-
- -
- -
-
-
-
-
-
-
diff --git a/templates/users/oauthorize.html b/templates/users/oauthorize.html new file mode 100644 index 0000000..d3505cd --- /dev/null +++ b/templates/users/oauthorize.html @@ -0,0 +1,28 @@ +{% extends "layout.html" %} + +{% block title %} + Authorize {{ client.title }}? +{% endblock %} + +{% block content %} +
+
+
+
+

Authorize Application?

+
+
+
+
+

{{ client.title }}

+
+

to access your profile?

+
+ +
+
+
+
+
+
+{% endblock %} diff --git a/views/events.py b/views/events.py index ce5e26b..bc3c7d4 100644 --- a/views/events.py +++ b/views/events.py @@ -3,7 +3,7 @@ from flask import abort, Blueprint, redirect, render_template, url_for, flash from flask_login import current_user, login_required -from forms import EventForm +from forms import EventCreateForm, EventManageForm from models import db, Event from util import admin_required, isoformat @@ -13,7 +13,7 @@ @blueprint.route('/create', methods=['GET', 'POST']) @login_required def events_create(): - event_create_form = EventForm() + event_create_form = EventCreateForm() if event_create_form.validate_on_submit(): new_event = Event(owner=current_user) event_create_form.populate_obj(new_event) @@ -112,10 +112,10 @@ def events_manage(event_id): event = Event.query.get_or_404(event_id) if current_user != event.owner: abort(403) - event_form = EventForm(obj=event) + event_form = EventManageForm(obj=event) if event_form.validate_on_submit(): event_form.populate_obj(event) - return redirect(url_for('.events_detail', event_id=event_id)) + return redirect(url_for('.events_manage', event_id=event_id)) return render_template('events/manage.html', event=event, event_form=event_form) diff --git a/views/oauth.py b/views/oauth.py index 46ed448..3871414 100644 --- a/views/oauth.py +++ b/views/oauth.py @@ -14,7 +14,7 @@ def authorize(*args, **kwargs): client_id = kwargs.get('client_id') client = Event.query.filter_by(client_id=client_id).first() kwargs['client'] = client - return render_template('oauthorize.html', **kwargs) + return render_template('users/oauthorize.html', **kwargs) confirm = request.form.get('confirm', 'no') return confirm == 'yes' From 0ad271bf589d5dee78e242473970c6704367931f Mon Sep 17 00:00:00 2001 From: David Hou Date: Tue, 30 Aug 2016 15:56:20 -0700 Subject: [PATCH 02/41] Use functools.partial for functions as SQLAlchemy Column defaults. These are evaluated at interpret time so it would only be run once if you just put the plain value (function call) on. --- models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/models.py b/models.py index ea94b83..4a912bb 100644 --- a/models.py +++ b/models.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from functools import partial from flask_login import current_user, LoginManager from flask_oauthlib.provider import OAuth2Provider @@ -97,8 +98,8 @@ class Event(db.Model): removed = db.Column(db.Boolean, default=False) # OAuth2 stuff - client_id = db.Column(db.String(40), unique=True, default=util.generate_string(16)) - client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False, default=util.generate_string(32)) + client_id = db.Column(db.String(40), unique=True, default=partial(util.generate_string, 16)) + client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False, default=partial(util.generate_string, 32)) is_confidential = db.Column(db.Boolean, default=True) _redirect_uris = db.Column(db.Text) _default_scopes = db.Column(db.Text, default='profile') From aba5bb8d30d6b953e7d5cc3a75288c57763396a5 Mon Sep 17 00:00:00 2001 From: David Hou Date: Tue, 30 Aug 2016 14:58:11 -0700 Subject: [PATCH 03/41] Use url_for instead of hardcoding URLs in layout template. --- templates/layout.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/layout.html b/templates/layout.html index 3c7d222..a5e1b52 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -36,18 +36,18 @@ From 855bee600cf71575d3c07a7b3b802795e8b64ba7 Mon Sep 17 00:00:00 2001 From: David Hou Date: Tue, 30 Aug 2016 15:28:02 -0700 Subject: [PATCH 04/41] Paginate lists. --- .cache/v/cache/lastfailed | 1 + .coverage | 1 + config.py | 3 + views/events.py | 122 ++++++++++++++++++++++++++++---------- views/users.py | 18 ++++-- 5 files changed, 109 insertions(+), 36 deletions(-) create mode 100644 .cache/v/cache/lastfailed create mode 100644 .coverage diff --git a/.cache/v/cache/lastfailed b/.cache/v/cache/lastfailed new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.cache/v/cache/lastfailed @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.coverage b/.coverage new file mode 100644 index 0000000..99a10b1 --- /dev/null +++ b/.coverage @@ -0,0 +1 @@ +!coverage.py: This is a private format, don't read it directly!{"lines": {"/vagrant/tests/__init__.py": [1], "/vagrant/filters.py": [], "/vagrant/views/events.py": [], "/vagrant/views/oauth.py": [], "/vagrant/forms.py": [], "/vagrant/manage.py": [], "/vagrant/models.py": [], "/vagrant/util.py": [], "/vagrant/views/users.py": [], "/vagrant/tests/test_general.py": [1, 2, 3], "/vagrant/config.py": [], "/vagrant/cal.py": [], "/vagrant/constants.py": [], "/vagrant/views/__init__.py": [], "/vagrant/views/base.py": [], "/vagrant/conftest.py": [1]}} \ No newline at end of file diff --git a/config.py b/config.py index 0a309f6..e6590a5 100644 --- a/config.py +++ b/config.py @@ -5,6 +5,9 @@ load_dotenv(find_dotenv()) +EVENT_LIST_PAGE_SIZE = 25 +USER_LIST_PAGE_SIZE = 50 + class CalendarConfig: def __init__(self, app_root=None, testing=False): diff --git a/views/events.py b/views/events.py index ce5e26b..29a1e01 100644 --- a/views/events.py +++ b/views/events.py @@ -3,6 +3,7 @@ from flask import abort, Blueprint, redirect, render_template, url_for, flash from flask_login import current_user, login_required +import config from forms import EventForm from models import db, Event from util import admin_required, isoformat @@ -24,67 +25,124 @@ def events_create(): @blueprint.route('/list/json') -def events_list_json(): - events = Event.query.filter_by().order_by(Event.start_time.desc()).all() - event_list = [] - for event in events: - start_time = event.start_time - obj = { - 'id': event.id, - 'approved': event.approved, - 'name': event.title, - 'startTime': start_time * 1000, - 'startTimeFormat': isoformat(start_time), - 'endTime': (start_time + event.duration * 60 * 60) * 1000, - 'duration': event.duration - } - event_list.append(obj) +@blueprint.route('/list/json/') +def events_list_json(page_number=1): + if page_number <= 0: + abort(404) + + page_size = config.EVENT_LIST_PAGE_SIZE + page_offset = (page_number - 1) * page_size + events = Event.query.order_by(Event.start_time.desc()).offset(page_offset).limit(page_size) + if page_number != 1 and not events: + abort(404) + + event_list = [{ + 'id': event.id, + 'approved': event.approved, + 'name': event.title, + 'startTime': event.start_time * 1000, + 'startTimeFormat': isoformat(event.start_time), + 'endTime': (event.start_time + event.duration * 60 * 60) * 1000, + 'duration': event.duration + } for event in events] return json.dumps(event_list) @blueprint.route('/') @blueprint.route('/all') -def events_all(): - events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()).all() +@blueprint.route('/all/') +def events_all(page_number=1): + if page_number <= 0: + abort(404) + + page_size = config.EVENT_LIST_PAGE_SIZE + page_offset = (page_number - 1) * page_size + events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()) \ + .offset(page_offset).limit(page_size).all() + if page_number != 1 and not events: + abort(404) + for event in events: event.start_time_format = isoformat(event.start_time) - return render_template('events/list.html', tab='all', events=events) + return render_template('events/list.html', tab='all', page_number=page_number, events=events) # todo @blueprint.route('/upcoming') -def events_upcoming(): - events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()).all() - for event in events: +@blueprint.route('/upcoming/') +def events_upcoming(page_number=1): + if page_number <= 0: + abort(404) + + page_size = config.EVENT_LIST_PAGE_SIZE + page_offset = (page_number - 1) * page_size + upcoming_events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()) \ + .offset(page_offset).limit(page_size).all() + if page_number != 1 and not upcoming_events: + abort(404) + + for event in upcoming_events: event.start_time_format = isoformat(event.start_time) - return render_template('events/list.html', tab='upcoming', events=events) + return render_template('events/list.html', tab='upcoming', page_number=page_number, events=upcoming_events) # todo @blueprint.route('/past') -def events_past(): - events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()).all() - for event in events: +@blueprint.route('/past/') +def events_past(page_number=1): + if page_number <= 0: + abort(404) + + page_size = config.EVENT_LIST_PAGE_SIZE + page_offset = (page_number - 1) * page_size + past_events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()) \ + .offset(page_offset).limit(page_size).all() + if page_number != 1 and not past_events: + abort(404) + + for event in past_events: event.start_time_format = isoformat(event.start_time) - return render_template('events/list.html', tab='past', events=events) + return render_template('events/list.html', tab='past', page_number=page_number, events=past_events) @blueprint.route('/unapproved') +@blueprint.route('/unapproved/') @admin_required -def events_unapproved(): - unapproved_events = Event.query.filter_by(approved=False, removed=False).order_by(Event.start_time.desc()).all() +def events_unapproved(page_number=1): + if page_number <= 0: + abort(404) + + page_size = config.EVENT_LIST_PAGE_SIZE + page_offset = (page_number - 1) * page_size + unapproved_events = Event.query.filter_by(approved=False, removed=False).order_by(Event.start_time.desc()) \ + .offset(page_offset).limit(page_size).all() + if page_number != 1 and not unapproved_events: + abort(404) + for event in unapproved_events: event.start_time_format = isoformat(event.start_time) - return render_template('events/list.html', tab='unapproved', events=unapproved_events, enabled_actions=['approve']) + return render_template('events/list.html', tab='unapproved', page_number=page_number, events=unapproved_events, + enabled_actions=['approve']) @blueprint.route('/owned') +@blueprint.route('/owned/') @login_required -def events_owned(): - owned_events = current_user.events.filter_by(removed=False) +def events_owned(page_number=1): + if page_number <= 0: + abort(404) + + page_size = config.EVENT_LIST_PAGE_SIZE + page_offset = (page_number - 1) * page_size + owned_events = current_user.events.filter_by(removed=False).order_by(Event.start_time.desc()) \ + .offset(page_offset).limit(page_size).all() + if page_number != 1 and not owned_events: + abort(404) + for event in owned_events: event.start_time_format = isoformat(event.start_time) - return render_template('events/list.html', tab='owned', events=owned_events, enabled_actions=['manage', 'remove']) + return render_template('events/list.html', tab='owned', page_number=page_number, events=owned_events, + enabled_actions=['manage', 'remove']) @blueprint.route('/') diff --git a/views/users.py b/views/users.py index ab39286..b2156b2 100644 --- a/views/users.py +++ b/views/users.py @@ -1,6 +1,7 @@ -from flask import Blueprint, render_template, redirect, url_for, send_file +from flask import abort, Blueprint, render_template, redirect, url_for, send_file from flask_login import current_user, login_required, login_user, logout_user +import config from forms import LoginForm, RegisterForm from models import db from models import login_manager, User @@ -52,9 +53,18 @@ def profile(): @blueprint.route('/users') -def users_list(): - users = User.query.order_by(User.id).all() - return render_template('users/list.html', users=users) +@blueprint.route('/users/') +def users_list(page_number=1): + if page_number <= 0: + abort(404) + + page_size = config.USER_LIST_PAGE_SIZE + page_offset = (page_number - 1) * page_size + users = User.query.order_by(User.id).order_by(User.id.desc()).offset(page_offset).limit(page_size) + if page_number != 1 and not users: + abort(404) + + return render_template('users/list.html', page_number=page_number, users=users) @blueprint.route('/users/') From a32515a357a517049156c4d799f3a1170cc7a57b Mon Sep 17 00:00:00 2001 From: David Hou Date: Tue, 30 Aug 2016 15:31:43 -0700 Subject: [PATCH 05/41] Move Event start time isoformatting to a @property --- models.py | 4 ++++ templates/events/list.html | 2 +- views/events.py | 23 ++++------------------- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/models.py b/models.py index a9c2eb4..a0aada9 100644 --- a/models.py +++ b/models.py @@ -104,6 +104,10 @@ class Event(db.Model): _redirect_uris = db.Column(db.Text) _default_scopes = db.Column(db.Text) + @property + def formatted_start_time(self): + return util.isoformat(self.start_time) + @property def client_type(self): if self.is_confidential: diff --git a/templates/events/list.html b/templates/events/list.html index 84b2eaf..2c418cf 100644 --- a/templates/events/list.html +++ b/templates/events/list.html @@ -46,7 +46,7 @@ {{ event.description }} + datetime="{{ event.formatted_start_time }}">{{ event.formatted_start_time }} {{ event.duration|duration('hour') }} Website diff --git a/views/events.py b/views/events.py index 29a1e01..f43842c 100644 --- a/views/events.py +++ b/views/events.py @@ -58,12 +58,9 @@ def events_all(page_number=1): page_size = config.EVENT_LIST_PAGE_SIZE page_offset = (page_number - 1) * page_size events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()) \ - .offset(page_offset).limit(page_size).all() + .offset(page_offset).limit(page_size) if page_number != 1 and not events: abort(404) - - for event in events: - event.start_time_format = isoformat(event.start_time) return render_template('events/list.html', tab='all', page_number=page_number, events=events) @@ -77,12 +74,9 @@ def events_upcoming(page_number=1): page_size = config.EVENT_LIST_PAGE_SIZE page_offset = (page_number - 1) * page_size upcoming_events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()) \ - .offset(page_offset).limit(page_size).all() + .offset(page_offset).limit(page_size) if page_number != 1 and not upcoming_events: abort(404) - - for event in upcoming_events: - event.start_time_format = isoformat(event.start_time) return render_template('events/list.html', tab='upcoming', page_number=page_number, events=upcoming_events) @@ -96,12 +90,9 @@ def events_past(page_number=1): page_size = config.EVENT_LIST_PAGE_SIZE page_offset = (page_number - 1) * page_size past_events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()) \ - .offset(page_offset).limit(page_size).all() + .offset(page_offset).limit(page_size) if page_number != 1 and not past_events: abort(404) - - for event in past_events: - event.start_time_format = isoformat(event.start_time) return render_template('events/list.html', tab='past', page_number=page_number, events=past_events) @@ -115,12 +106,9 @@ def events_unapproved(page_number=1): page_size = config.EVENT_LIST_PAGE_SIZE page_offset = (page_number - 1) * page_size unapproved_events = Event.query.filter_by(approved=False, removed=False).order_by(Event.start_time.desc()) \ - .offset(page_offset).limit(page_size).all() + .offset(page_offset).limit(page_size) if page_number != 1 and not unapproved_events: abort(404) - - for event in unapproved_events: - event.start_time_format = isoformat(event.start_time) return render_template('events/list.html', tab='unapproved', page_number=page_number, events=unapproved_events, enabled_actions=['approve']) @@ -138,9 +126,6 @@ def events_owned(page_number=1): .offset(page_offset).limit(page_size).all() if page_number != 1 and not owned_events: abort(404) - - for event in owned_events: - event.start_time_format = isoformat(event.start_time) return render_template('events/list.html', tab='owned', page_number=page_number, events=owned_events, enabled_actions=['manage', 'remove']) From 7657e0531f07e180d95a909b578c41b2023db9ad Mon Sep 17 00:00:00 2001 From: David Hou Date: Tue, 30 Aug 2016 15:45:42 -0700 Subject: [PATCH 06/41] Implement upcoming and past events --- models.py | 10 +++++++++- views/events.py | 9 +++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/models.py b/models.py index a0aada9..d59150a 100644 --- a/models.py +++ b/models.py @@ -92,7 +92,7 @@ class Event(db.Model): approved = db.Column(db.Boolean, default=False) title = db.Column(db.Unicode(length=256)) start_time = db.Column(db.Integer, index=True) - duration = db.Column(db.Float) + duration = db.Column(db.Float) # in hours description = db.Column(db.UnicodeText) link = db.Column(db.Unicode(length=256)) removed = db.Column(db.Boolean, default=False) @@ -108,6 +108,14 @@ class Event(db.Model): def formatted_start_time(self): return util.isoformat(self.start_time) + @hybrid_property + def end_time(self): + return self.start_time + self.duration * 60 + + @property + def formatted_end_time(self): + return util.isoformat(self.end_time) + @property def client_type(self): if self.is_confidential: diff --git a/views/events.py b/views/events.py index f43842c..c471ddc 100644 --- a/views/events.py +++ b/views/events.py @@ -1,4 +1,5 @@ import json +import time from flask import abort, Blueprint, redirect, render_template, url_for, flash from flask_login import current_user, login_required @@ -73,8 +74,8 @@ def events_upcoming(page_number=1): page_size = config.EVENT_LIST_PAGE_SIZE page_offset = (page_number - 1) * page_size - upcoming_events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()) \ - .offset(page_offset).limit(page_size) + upcoming_events = Event.query.filter_by(approved=True, removed=False).filter(Event.start_time > time.time()) \ + .order_by(Event.start_time.desc()).offset(page_offset).limit(page_size) if page_number != 1 and not upcoming_events: abort(404) return render_template('events/list.html', tab='upcoming', page_number=page_number, events=upcoming_events) @@ -89,8 +90,8 @@ def events_past(page_number=1): page_size = config.EVENT_LIST_PAGE_SIZE page_offset = (page_number - 1) * page_size - past_events = Event.query.filter_by(approved=True, removed=False).order_by(Event.start_time.desc()) \ - .offset(page_offset).limit(page_size) + past_events = Event.query.filter_by(approved=True, removed=False).filter(Event.end_time <= time.time()) \ + .order_by(Event.start_time.desc()).offset(page_offset).limit(page_size) if page_number != 1 and not past_events: abort(404) return render_template('events/list.html', tab='past', page_number=page_number, events=past_events) From 6eadae3406b16b01e03a442d701fcb404e923065 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Tue, 30 Aug 2016 23:16:02 -0500 Subject: [PATCH 07/41] Add page numbers + use .all() --- templates/events/list.html | 6 ++++++ templates/users/list.html | 8 +++++++- views/events.py | 10 +++++----- views/users.py | 4 ++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/templates/events/list.html b/templates/events/list.html index 2c418cf..09e49f4 100644 --- a/templates/events/list.html +++ b/templates/events/list.html @@ -80,6 +80,12 @@
+

+ {% if page_number == 1 %}Prev{% else %}Prev{% endif %} + / Page {{ page_number }} / + Next +

+ - {% endblock %} diff --git a/templates/events/list.html b/templates/events/list.html index 7263ee0..9709a48 100644 --- a/templates/events/list.html +++ b/templates/events/list.html @@ -80,13 +80,13 @@
-

+

{% if page_number == 1 %}Prev{% else %} Prev{% endif %} / Page {{ page_number }} / {% if last_page %}Next{% else %} Next{% endif %} -

+
- - + + + + - +
+ {% with messages = get_flashed_messages() %} + {% if messages %} +
+
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+
+ {% endif %} + {% endwith %} + {% block content %}{% endblock %}
- -
- {% with messages = get_flashed_messages() %} - {% if messages %} -
-
    - {% for message in messages %} -
  • {{ message }}
  • - {% endfor %} -
-
- {% endif %} - {% endwith %} - {% block content %}{% endblock %} -
\ No newline at end of file diff --git a/templates/users/list.html b/templates/users/list.html index 11cfe83..75acbf0 100644 --- a/templates/users/list.html +++ b/templates/users/list.html @@ -16,11 +16,11 @@ {% endfor %} -

+

{% if page_number == 1 %}Prev{% else %} Prev{% endif %} / Page {{ page_number }} / {% if last_page %}Next{% else %} Next{% endif %} -

+
{% endblock %} diff --git a/templates/users/profile.html b/templates/users/profile.html index 3a004f0..a96dc12 100644 --- a/templates/users/profile.html +++ b/templates/users/profile.html @@ -27,7 +27,7 @@

{{ user.name }}

Joined - +
{% endif %} @@ -53,24 +53,24 @@

{{ user.username }}'s Events

- - - - - + + + + + - {% for event in user.events %} - - - - - - - - {% endfor %} + {% for event in user.events %} + + + + + + + + {% endfor %}
NameDescriptionDateDurationWebsiteNameDescriptionDateDurationWebsite
- {{ event.title }} - {{ event.description }}{{ event.duration|duration('hour') }}Website
+ {{ event.title }} + {{ event.description }}{{ event.duration|duration('hour') }}Website
diff --git a/tests/test_util.py b/tests/test_util.py index e9bfce4..d4da904 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -33,8 +33,8 @@ def foo(): login_user(User.get_by_identifier(USER["username"])) try: - bar = foo() - except Exception, e: + foo() + except Exception as e: assert type(e) is Forbidden def test_iso_format(self): diff --git a/util.py b/util.py index 691f73c..8f34e5d 100644 --- a/util.py +++ b/util.py @@ -18,6 +18,10 @@ def generate_string(length=32, alpha='0123456789abcdef'): return "".join([random.choice(alpha) for x in range(length)]) +def generate_string_of(length): + return lambda: generate_string(length=length) + + def hash_password(password, rounds=10): return bcrypt.encrypt(password, rounds=rounds) From 85ae46c7539e235b760f08d518827981f8e969a6 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Tue, 22 Nov 2016 00:17:57 -0600 Subject: [PATCH 38/41] ayy --- .gitignore | 4 ++++ Procfile | 2 +- Vagrantfile | 2 +- cal.py | 1 + models.py | 8 ++++---- requirements.txt | 1 + .../oauthorize.html => oauth/authorize.html} | 0 templates/oauth/errors.html | 14 ++++++++++++++ views/__init__.py | 1 + views/api.py | 12 ++++++++++++ views/oauth.py | 8 +++++++- 11 files changed, 46 insertions(+), 7 deletions(-) rename templates/{users/oauthorize.html => oauth/authorize.html} (100%) create mode 100644 templates/oauth/errors.html create mode 100644 views/api.py diff --git a/.gitignore b/.gitignore index 50b27e9..78d87a0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ .env .secret_key npm-debug.log + +.coverage +.cache +coverage.xml \ No newline at end of file diff --git a/Procfile b/Procfile index ca5c020..ec4c7f8 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -web: python manage.py db upgrade && gunicorn cal:app --log-file - +web: python manage.py db upgrade && gunicorn -w 4 cal:app --log-file - dev: python manage.py db upgrade && python manage.py runserver \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile index 3b13747..df8a32f 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -24,7 +24,7 @@ Vagrant.configure(2) do |config| # accessing "localhost:8080" will access port 80 on the guest machine. # config.vm.network "forwarded_port", guest: 80, host: 8080 config.vm.network "forwarded_port", guest: 5000, host: 5000 - config.vm.network "forwarded_port", guest: 5432, host: 5432 + #config.vm.network "forwarded_port", guest: 5432, host: 5432 # Create a private network, which allows host-only access to the machine # using a specific IP. diff --git a/cal.py b/cal.py index d5c1186..180fc16 100644 --- a/cal.py +++ b/cal.py @@ -15,6 +15,7 @@ login_manager.init_app(app) oauth.init_app(app) +app.register_blueprint(views.api.blueprint, url_prefix='/api') app.register_blueprint(views.base.blueprint) app.register_blueprint(views.events.blueprint, url_prefix='/events') app.register_blueprint(views.oauth.blueprint, url_prefix='/oauth') diff --git a/models.py b/models.py index 4a912bb..85c5dd4 100644 --- a/models.py +++ b/models.py @@ -1,5 +1,4 @@ from datetime import datetime, timedelta -from functools import partial from flask_login import current_user, LoginManager from flask_oauthlib.provider import OAuth2Provider @@ -98,8 +97,8 @@ class Event(db.Model): removed = db.Column(db.Boolean, default=False) # OAuth2 stuff - client_id = db.Column(db.String(40), unique=True, default=partial(util.generate_string, 16)) - client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False, default=partial(util.generate_string, 32)) + client_id = db.Column(db.String(40), unique=True, default=lambda: util.generate_string(16)) + client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False, default=lambda: util.generate_string(32)) is_confidential = db.Column(db.Boolean, default=True) _redirect_uris = db.Column(db.Text) _default_scopes = db.Column(db.Text, default='profile') @@ -114,7 +113,7 @@ def client_type(self): def redirect_uris(self): 'getting' if self._redirect_uris: - return self._redirect_uris#.split() + return self._redirect_uris # .split() return [] @redirect_uris.setter @@ -129,6 +128,7 @@ def default_redirect_uri(self): @property def default_scopes(self): + return ["user"] if self._default_scopes: return self._default_scopes.split() return [] diff --git a/requirements.txt b/requirements.txt index 7a10840..a76b7b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ Flask-SQLAlchemy Flask-WTF gunicorn jinja2 +git+git://github.com/failedxyz/oauthlib.git@master#egg=oauthlib passlib pathlib psycopg2 diff --git a/templates/users/oauthorize.html b/templates/oauth/authorize.html similarity index 100% rename from templates/users/oauthorize.html rename to templates/oauth/authorize.html diff --git a/templates/oauth/errors.html b/templates/oauth/errors.html new file mode 100644 index 0000000..a7a7365 --- /dev/null +++ b/templates/oauth/errors.html @@ -0,0 +1,14 @@ +{% extends "layout.html" %} + +{% block title %} + Error +{% endblock %} + +{% block content %} + +

Here's the details: +

{{ error }}
+

+{% endblock %} diff --git a/views/__init__.py b/views/__init__.py index 7e6278f..e7f6c1d 100644 --- a/views/__init__.py +++ b/views/__init__.py @@ -1,3 +1,4 @@ +import api import base import events import oauth diff --git a/views/api.py b/views/api.py new file mode 100644 index 0000000..6266aff --- /dev/null +++ b/views/api.py @@ -0,0 +1,12 @@ +from flask import Blueprint, request, jsonify + +from models import oauth + +blueprint = Blueprint('api', __name__) + + +@blueprint.route('/me') +@oauth.require_oauth('user') +def me(): + user = request.oauth.user + return jsonify(email=user.email, username=user.username, id=user.id) diff --git a/views/oauth.py b/views/oauth.py index 3871414..470a024 100644 --- a/views/oauth.py +++ b/views/oauth.py @@ -14,12 +14,18 @@ def authorize(*args, **kwargs): client_id = kwargs.get('client_id') client = Event.query.filter_by(client_id=client_id).first() kwargs['client'] = client - return render_template('users/oauthorize.html', **kwargs) + return render_template('oauth/authorize.html', **kwargs) confirm = request.form.get('confirm', 'no') return confirm == 'yes' +@blueprint.route('/errors') +def errors(): + error = request.args.get('error', '') + return render_template('oauth/errors.html', error=error) + + @blueprint.route('/token') @oauth.token_handler def access_token(): From b434ff1f4979c054bf4b961c0e2cf32310ae0deb Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Tue, 22 Nov 2016 00:32:45 -0600 Subject: [PATCH 39/41] hi --- views/oauth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/oauth.py b/views/oauth.py index 470a024..41678b4 100644 --- a/views/oauth.py +++ b/views/oauth.py @@ -26,7 +26,7 @@ def errors(): return render_template('oauth/errors.html', error=error) -@blueprint.route('/token') +@blueprint.route('/token', methods=['GET', 'POST']) @oauth.token_handler def access_token(): pass From 1c59dd5231a13bcf8251984cb6b94a555fede099 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Tue, 22 Nov 2016 07:26:34 -0600 Subject: [PATCH 40/41] Some stuff. --- templates/layout.html | 1 + views/users.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/templates/layout.html b/templates/layout.html index 1ab87c4..d918587 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -2,6 +2,7 @@ {% block title %}{% endblock %} - CTF Calendar + diff --git a/views/users.py b/views/users.py index d95b1fb..522a308 100644 --- a/views/users.py +++ b/views/users.py @@ -20,8 +20,8 @@ def login(): login_form = LoginForm() if login_form.validate_on_submit(): login_user(login_form.get_user()) - return redirect( - url_for('.profile')) # TODO: implement safe redirection based on url value for login and register + to = request.args.get('next') or url_for('.profile') + return redirect(to) return render_template('users/login.html', login_form=login_form) @@ -34,7 +34,8 @@ def register(): db.session.add(new_user) db.session.commit() login_user(new_user) - return redirect(url_for('.profile')) + to = request.args.get('next') or url_for('.profile') + return redirect(to) return render_template('users/register.html', register_form=register_form) From 3c09f6687437d5c056166d15f5a83280ef588ac0 Mon Sep 17 00:00:00 2001 From: David Hou Date: Wed, 1 Feb 2017 09:46:43 -0800 Subject: [PATCH 41/41] Add TODO marking the invalidated redirect. --- Vagrantfile | 2 +- views/users.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index df8a32f..3b13747 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -24,7 +24,7 @@ Vagrant.configure(2) do |config| # accessing "localhost:8080" will access port 80 on the guest machine. # config.vm.network "forwarded_port", guest: 80, host: 8080 config.vm.network "forwarded_port", guest: 5000, host: 5000 - #config.vm.network "forwarded_port", guest: 5432, host: 5432 + config.vm.network "forwarded_port", guest: 5432, host: 5432 # Create a private network, which allows host-only access to the machine # using a specific IP. diff --git a/views/users.py b/views/users.py index 522a308..e963f82 100644 --- a/views/users.py +++ b/views/users.py @@ -20,7 +20,7 @@ def login(): login_form = LoginForm() if login_form.validate_on_submit(): login_user(login_form.get_user()) - to = request.args.get('next') or url_for('.profile') + to = request.args.get('next') or url_for('.profile') # TODO: Validate/filter/map redirect parameter return redirect(to) return render_template('users/login.html', login_form=login_form) @@ -34,7 +34,7 @@ def register(): db.session.add(new_user) db.session.commit() login_user(new_user) - to = request.args.get('next') or url_for('.profile') + to = request.args.get('next') or url_for('.profile') # TODO: Validate/filter/map redirect parameter return redirect(to) return render_template('users/register.html', register_form=register_form)