Skip to content

Commit 18ccdcc

Browse files
author
Michael Zhang
committed
Hope i didn't break anything!
1 parent 1cf2d63 commit 18ccdcc

File tree

6 files changed

+261
-3
lines changed

6 files changed

+261
-3
lines changed

cal.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import os
22

33
from flask import Flask
4+
from flask_oauthlib.provider import OAuth2Provider
45

56
import config
67
import views
78
from filters import filters
8-
from models import db, login_manager
9+
from models import db, login_manager, oauth
910

1011
app = Flask(__name__, static_url_path='')
1112
self_path = os.path.dirname(os.path.abspath(__file__))
1213
app.config.from_object(config.CalendarConfig(app_root=self_path))
1314
db.init_app(app)
1415

1516
login_manager.init_app(app)
17+
oauth.init_app(app)
1618

1719
app.register_blueprint(views.base.blueprint)
1820
app.register_blueprint(views.events.blueprint, url_prefix='/events')
21+
app.register_blueprint(views.oauth.blueprint, url_prefix='/oauth')
1922
app.register_blueprint(views.users.blueprint)
2023

2124
app.jinja_env.trim_blocks = True

migrations/versions/8a9998406bc7_.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""Add OAuth2 stuff
2+
3+
Revision ID: 8a9998406bc7
4+
Revises: daf5b5301c75
5+
Create Date: 2016-08-25 02:37:06.559438
6+
7+
"""
8+
9+
# revision identifiers, used by Alembic.
10+
revision = '8a9998406bc7'
11+
down_revision = 'daf5b5301c75'
12+
13+
from alembic import op
14+
import sqlalchemy as sa
15+
16+
17+
def upgrade():
18+
### commands auto generated by Alembic - please adjust! ###
19+
op.add_column(u'events', sa.Column('_default_scopes', sa.Text(), nullable=True))
20+
op.add_column(u'events', sa.Column('_redirect_uris', sa.Text(), nullable=True))
21+
op.add_column(u'events', sa.Column('client_id', sa.String(length=40), nullable=True))
22+
op.add_column(u'events', sa.Column('client_secret', sa.String(length=55), nullable=False))
23+
op.add_column(u'events', sa.Column('is_confidential', sa.Boolean(), nullable=True))
24+
op.create_index(op.f('ix_events_client_secret'), 'events', ['client_secret'], unique=True)
25+
op.create_unique_constraint(None, 'events', ['client_id'])
26+
op.create_table('token',
27+
sa.Column('id', sa.Integer(), nullable=False),
28+
sa.Column('client_id', sa.String(length=40), nullable=False),
29+
sa.Column('user_id', sa.Integer(), nullable=True),
30+
sa.Column('token_type', sa.String(length=40), nullable=True),
31+
sa.Column('access_token', sa.String(length=255), nullable=True),
32+
sa.Column('refresh_token', sa.String(length=255), nullable=True),
33+
sa.Column('expires', sa.DateTime(), nullable=True),
34+
sa.Column('_scopes', sa.Text(), nullable=True),
35+
sa.ForeignKeyConstraint(['client_id'], ['events.client_id'], ),
36+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
37+
sa.PrimaryKeyConstraint('id'),
38+
sa.UniqueConstraint('access_token'),
39+
sa.UniqueConstraint('refresh_token')
40+
)
41+
op.create_table('grant',
42+
sa.Column('id', sa.Integer(), nullable=False),
43+
sa.Column('user_id', sa.Integer(), nullable=True),
44+
sa.Column('client_id', sa.String(length=40), nullable=False),
45+
sa.Column('code', sa.String(length=255), nullable=False),
46+
sa.Column('redirect_uri', sa.String(length=255), nullable=True),
47+
sa.Column('expires', sa.DateTime(), nullable=True),
48+
sa.Column('_scopes', sa.Text(), nullable=True),
49+
sa.ForeignKeyConstraint(['client_id'], ['events.client_id'], ),
50+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
51+
sa.PrimaryKeyConstraint('id')
52+
)
53+
op.create_index(op.f('ix_grant_code'), 'grant', ['code'], unique=False)
54+
### end Alembic commands ###
55+
56+
57+
def downgrade():
58+
### commands auto generated by Alembic - please adjust! ###
59+
op.drop_constraint(None, 'events', type_='unique')
60+
op.drop_index(op.f('ix_events_client_secret'), table_name='events')
61+
op.drop_column(u'events', 'is_confidential')
62+
op.drop_column(u'events', 'client_secret')
63+
op.drop_column(u'events', 'client_id')
64+
op.drop_column(u'events', '_redirect_uris')
65+
op.drop_column(u'events', '_default_scopes')
66+
op.drop_table('token')
67+
op.drop_index(op.f('ix_grant_code'), table_name='grant')
68+
op.drop_table('grant')
69+
### end Alembic commands ###

models.py

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
from flask_login import LoginManager
1+
from flask_login import current_user, LoginManager
2+
from flask_oauthlib.provider import OAuth2Provider
23
from flask_sqlalchemy import SQLAlchemy
4+
35
from sqlalchemy import UniqueConstraint
46
from sqlalchemy.orm import backref
57
from sqlalchemy.ext.hybrid import hybrid_property
68

9+
from datetime import datetime, timedelta
710
import util
811

912
db = SQLAlchemy()
1013
login_manager = LoginManager()
14+
oauth = OAuth2Provider()
1115

1216

1317
class User(db.Model):
@@ -92,6 +96,35 @@ class Event(db.Model):
9296
link = db.Column(db.Unicode(length=256))
9397
removed = db.Column(db.Boolean, default=False)
9498

99+
# OAuth2 stuff
100+
client_id = db.Column(db.String(40), unique=True)
101+
client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False)
102+
is_confidential = db.Column(db.Boolean)
103+
_redirect_uris = db.Column(db.Text)
104+
_default_scopes = db.Column(db.Text)
105+
106+
@property
107+
def client_type(self):
108+
if self.is_confidential:
109+
return 'confidential'
110+
return 'public'
111+
112+
@property
113+
def redirect_uris(self):
114+
if self._redirect_uris:
115+
return self._redirect_uris.split()
116+
return []
117+
118+
@property
119+
def default_redirect_uri(self):
120+
return self.redirect_uris[0]
121+
122+
@property
123+
def default_scopes(self):
124+
if self._default_scopes:
125+
return self._default_scopes.split()
126+
return []
127+
95128

96129
class EventVote(db.Model):
97130
__tablename__ = 'eventvotes'
@@ -102,3 +135,125 @@ class EventVote(db.Model):
102135
event = db.relationship('Event', backref='votes')
103136
direction = db.Column(db.Boolean)
104137
__table_args__ = (UniqueConstraint('user_id', 'event_id', name='eventvote_user_event_uc'),)
138+
139+
140+
class Grant(db.Model):
141+
id = db.Column(db.Integer, primary_key=True)
142+
user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'))
143+
user = db.relationship('User')
144+
145+
client_id = db.Column(db.String(40), db.ForeignKey('events.client_id'), nullable=False)
146+
client = db.relationship('Event')
147+
148+
code = db.Column(db.String(255), index=True, nullable=False)
149+
150+
redirect_uri = db.Column(db.String(255))
151+
expires = db.Column(db.DateTime)
152+
153+
_scopes = db.Column(db.Text)
154+
155+
def delete(self):
156+
db.session.delete(self)
157+
db.session.commit()
158+
return self
159+
160+
@property
161+
def scopes(self):
162+
if self._scopes:
163+
return self._scopes.split()
164+
return []
165+
166+
167+
class Token(db.Model):
168+
id = db.Column(db.Integer, primary_key=True)
169+
client_id = db.Column(
170+
db.String(40), db.ForeignKey('events.client_id'),
171+
nullable=False,
172+
)
173+
client = db.relationship('Event')
174+
175+
user_id = db.Column(
176+
db.Integer, db.ForeignKey('users.id')
177+
)
178+
user = db.relationship('User')
179+
180+
token_type = db.Column(db.String(40))
181+
182+
access_token = db.Column(db.String(255), unique=True)
183+
refresh_token = db.Column(db.String(255), unique=True)
184+
expires = db.Column(db.DateTime)
185+
_scopes = db.Column(db.Text)
186+
187+
def delete(self):
188+
db.session.delete(self)
189+
db.session.commit()
190+
return self
191+
192+
@property
193+
def scopes(self):
194+
if self._scopes:
195+
return self._scopes.split()
196+
return []
197+
198+
def get_current_user():
199+
if current_user:
200+
return current_user
201+
return None
202+
203+
204+
@oauth.clientgetter
205+
def load_client(client_id):
206+
return Event.query.filter_by(client_id=client_id).first()
207+
208+
209+
@oauth.grantgetter
210+
def load_grant(client_id, code):
211+
return Grant.query.filter_by(client_id=client_id, code=code).first()
212+
213+
@oauth.grantsetter
214+
def save_grant(client_id, code, request, *args, **kwargs):
215+
expires = datetime.utcnow() + timedelta(seconds=100)
216+
grant = Grant(
217+
client_id=client_id,
218+
code=code['code'],
219+
redirect_uri=request.redirect_uri,
220+
_scopes=' '.join(request.scopes),
221+
user=get_current_user(),
222+
expires=expires
223+
)
224+
db.session.add(grant)
225+
db.session.commit()
226+
return grant
227+
228+
229+
@oauth.tokengetter
230+
def load_token(access_token=None, refresh_token=None):
231+
if access_token:
232+
return Token.query.filter_by(access_token=access_token).first()
233+
elif refresh_token:
234+
return Token.query.filter_by(refresh_token=refresh_token).first()
235+
236+
237+
@oauth.tokensetter
238+
def save_token(token, request, *args, **kwargs):
239+
toks = Token.query.filter_by(client_id=request.client.client_id,
240+
user_id=request.user.id)
241+
# make sure that every client has only one token connected to a user
242+
for t in toks:
243+
db.session.delete(t)
244+
245+
expires_in = token.get('expires_in')
246+
expires = datetime.utcnow() + timedelta(seconds=expires_in)
247+
248+
tok = Token(
249+
access_token=token['access_token'],
250+
refresh_token=token['refresh_token'],
251+
token_type=token['token_type'],
252+
_scopes=token['scope'],
253+
expires=expires,
254+
client_id=request.client.client_id,
255+
user_id=request.user.id,
256+
)
257+
db.session.add(tok)
258+
db.session.commit()
259+
return tok

views/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import base
22
import events
3+
import oauth
34
import users

views/events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,4 @@ def events_remove(event_id):
128128
abort(403)
129129
event.removed = True
130130
db.session.commit()
131-
return redirect(url_for('.events_owned'))
131+
return redirect(url_for('.events_owned'))

views/oauth.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from flask import Blueprint, request
2+
from flask_login import login_required
3+
4+
from models import db, oauth, Event
5+
6+
blueprint = Blueprint('oauth', __name__, template_folder='templates')
7+
8+
@blueprint.route('/authorize', methods=['GET', 'POST'])
9+
@login_required
10+
@oauth.authorize_handler
11+
def authorize(*args, **kwargs):
12+
if request.method == 'GET':
13+
client_id = kwargs.get('client_id')
14+
client = Event.query.filter_by(client_id=client_id).first()
15+
kwargs['client'] = client
16+
return render_template('oauthorize.html', **kwargs)
17+
18+
confirm = request.form.get('confirm', 'no')
19+
return confirm == 'yes'
20+
21+
22+
@blueprint.route('/token')
23+
@oauth.token_handler
24+
def access_token():
25+
return None
26+
27+
28+
@blueprint.route('/revoke', methods=['POST'])
29+
@oauth.revoke_handler
30+
def revoke_token(): pass

0 commit comments

Comments
 (0)