Skip to content

Commit 784cad6

Browse files
Josh BrandoffJosh Brandoff
Josh Brandoff
authored and
Josh Brandoff
committed
Cleaned up comments and documentation.
1 parent 24b1fda commit 784cad6

File tree

5 files changed

+115
-56
lines changed

5 files changed

+115
-56
lines changed

app.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,45 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
14
from flask import Flask
25
from validator import MyRequestValidator
36
from core import db, oauth
47
from views import yoloapi
58

69

710
def create_app(settings_override=None):
11+
"""
12+
Method for creating and initializing application.
13+
14+
:param settings_override: Dictionary of settings to override.
15+
"""
816
app = Flask(__name__)
917

10-
# Update configuration
18+
# Update configuration.
1119
app.config.from_object('settings')
1220
app.config.from_pyfile('settings.cfg', silent=True)
1321
app.config.from_object(settings_override)
1422

15-
# Initialize extensions on the application
23+
# Initialize extensions on the application.
1624
db.init_app(app)
1725
oauth.init_app(app)
1826
oauth._validator = MyRequestValidator()
1927

20-
# Register views on the application
28+
# Register views on the application.
2129
app.register_blueprint(yoloapi)
2230

2331
return app
2432

2533

2634
if __name__ == '__main__':
2735

28-
# We like flask_oauthlib logging for this application
36+
# Enable Flask-OAuthlib logging for this application.
2937
import logging
3038
logger = logging.getLogger('flask_oauthlib')
3139
logger.addHandler(logging.StreamHandler())
3240
logger.setLevel(logging.DEBUG)
3341

34-
# Create app, create SQL schemes in db and run the application
42+
# Create app and SQL schemas in database, then run the application.
3543
app = create_app()
3644
db.create_all(app=app)
3745
app.run()

core.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
14
"""
2-
The core module holds generic functions and the flask extensions
5+
The core module holds generic functions and the Flask extensions.
36
"""
47

58
from flask_sqlalchemy import SQLAlchemy

models.py

+77-35
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
14
from datetime import datetime, timedelta
25
from werkzeug.security import gen_salt
36
from core import db
47

58

69
class User(db.Model):
7-
"""User which owns resources behind our own api"""
10+
""" User which will be querying resources from the API.
811
9-
id = db.Column(db.Integer, primary_key=True)
12+
:param db.Model: Base class for database models.
13+
"""
14+
id = db.Column(db.Integer, primary_key=True)
1015
username = db.Column(db.String(40), unique=True)
1116
password = db.Column(db.String(40))
1217

1318
@staticmethod
1419
def find_with_password(username, password, *args, **kwargs):
20+
""" Query the User collection for a record with matching username and password.
21+
If only a username is supplied, find the first matching document with that username.
22+
23+
:param username: Username of the user.
24+
:param password: Password of the user.
25+
:param *args: Variable length argument list.
26+
:param **kwargs: Arbitrary keyword arguments.
27+
"""
1528
if password:
1629
return User.query.filter_by(
1730
username=username, password=password).first()
@@ -20,25 +33,28 @@ def find_with_password(username, password, *args, **kwargs):
2033

2134
@staticmethod
2235
def save(username, password):
36+
""" Create a new User record with the supplied username and password.
37+
38+
:param username: Username of the user.
39+
:param password: Password of the user.
40+
"""
2341
user = User(username=username, password=password)
2442
db.session.add(user)
2543
db.session.commit()
2644

2745
@staticmethod
2846
def all():
47+
""" Return all User records found in the database. """
2948
return User.query.all()
3049

3150

3251
class Client(db.Model):
33-
"""http://tools.ietf.org/html/rfc6749#section-2
34-
35-
RFC 6749 Section 2 describes clients.
52+
""" Client application through which user is authenticating.
3653
37-
One thing to note is that redirection URIs are mandatory for clients. We
38-
skip this requirement as this example will only allow the resource owner
39-
password credentials grant(described in section 4.3).
54+
RFC 6749 Section 2 (http://tools.ietf.org/html/rfc6749#section-2)
55+
describes clients:
4056
41-
+----------+
57+
+----------+
4258
| Resource |
4359
| Owner |
4460
| |
@@ -56,84 +72,110 @@ class Client(db.Model):
5672
| | (w/ Optional Refresh Token) | |
5773
+---------+ +---------------+
5874
59-
In this flow the Authorization Server will not redirect the user as
60-
described in subsection 3.1.2 (Redirection Endpoint).
75+
Redirection URIs are mandatory for clients. We skip this requirement
76+
as this example only allows the resource owner password credentials
77+
grant (described in Section 4.3). In this flow, the Authorization
78+
Server will not redirect the user as described in subsection 3.1.2
79+
(Redirection Endpoint).
6180
81+
:param db.Model: Base class for database models.
6282
"""
63-
64-
client_id = db.Column(db.String(40), primary_key=True)
83+
client_id d= db.Column(db.String(40), primary_key=True)
6584
client_type = db.Column(db.String(40))
6685

6786
@property
6887
def allowed_grant_types(self):
88+
""" Returns allowed grant types.
89+
90+
Presently, only the password grant type is allowed.
91+
"""
6992
return ['password']
7093

7194
@property
7295
def default_scopes(self):
96+
""" Returns default scopes associated with the Client. """
7397
return []
7498

7599
@staticmethod
76100
def find(id):
101+
""" Queries the Client table and returns first client with
102+
matching id.
103+
104+
:param id: Client id
105+
"""
77106
return Client.query.filter_by(client_id=id).first()
78107

79108
@staticmethod
80109
def generate():
110+
""" Generate a new public client with the ObjectID helper."""
81111
client = Client(client_id=gen_salt(40), client_type='public')
82112
db.session.add(client)
83113
db.session.commit()
84114

85115
@staticmethod
86116
def all():
117+
""" Return all Client documents found in the database. """
87118
return Client.query.all()
88119

89120
def default_redirect_uri():
121+
""" Return a blank default redirect URI since we are not implementing
122+
redirects.
123+
"""
90124
return ''
91125

92126

93127
class Token(db.Model):
94-
"""Access or refresh token
128+
""" Access or refresh token
95129
96-
Note that an access token is aware about to which user and client it was
97-
issued, thus we can identify which user is making a request based on this
98-
token.
99-
"""
100-
101-
id = db.Column(db.Integer, primary_key=True)
102-
103-
client_id = db.Column(db.String(40), db.ForeignKey('client.client_id'),
104-
nullable=False)
105-
client = db.relationship('Client')
106-
107-
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
108-
user = db.relationship('User')
130+
Because of our current grant flow, we are able to associate tokens
131+
with the users who are requesting them. This can be used to track usage
132+
and potential abuse. Only bearer tokens currently supported.
109133
110-
# currently only bearer is supported
111-
token_type = db.Column(db.String(40))
112-
113-
access_token = db.Column(db.String(255), unique=True)
134+
:param db.Model: Base class for database models.
135+
"""
136+
id = db.Column(db.Integer, primary_key=True)
137+
client_id = db.Column(db.String(40), db.ForeignKey('client.client_id'), nullable=False)
138+
client = db.relationship('Client')
139+
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
140+
user = db.relationship('User')
141+
token_type = db.Column(db.String(40))
142+
access_token = db.Column(db.String(255), unique=True)
114143
refresh_token = db.Column(db.String(255), unique=True)
115-
expires = db.Column(db.DateTime)
116-
scopes = ['']
144+
expires = db.Column(db.DateTime)
145+
scopes = ['']
117146

118147
@staticmethod
119148
def find(access_token=None, refresh_token=None):
149+
""" Retrieve a token record using submitted access token or
150+
refresh token.
151+
152+
:param access_token: User access token.
153+
:param refresh_token: User refresh token.
154+
"""
120155
if access_token:
121156
return Token.query.filter_by(access_token=access_token).first()
122157
elif refresh_token:
123158
return Token.query.filter_by(refresh_token=refresh_token).first()
124159

125160
@staticmethod
126161
def save(token, request, *args, **kwargs):
162+
""" Save a new token to the database.
163+
164+
:param token: Token dictionary containing access and refresh tokens, plus token type.
165+
:param request: Request dictionary containing information about the client and user.
166+
:param *args: Variable length argument list.
167+
:param **kwargs: Arbitrary keyword arguments.
168+
"""
127169
toks = Token.query.filter_by(
128170
client_id=request.client.client_id,
129171
user_id=request.user.id)
130172

131-
# Make sure that there is only one Grant Token for every
173+
# Make sure that there is only one grant token for every
132174
# (client, user) combination.
133175
[db.session.delete(t) for t in toks]
134176

135177
expires_in = token.pop('expires_in')
136-
expires = datetime.utcnow() + timedelta(seconds=expires_in)
178+
expires = datetime.utcnow() + timedelta(seconds=expires_in)
137179

138180
tok = Token(
139181
access_token=token['access_token'],

validator.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
14
from flask_oauthlib.provider import OAuth2RequestValidator
25
from models import User, Client, Token
36

47

58
class MyRequestValidator(OAuth2RequestValidator):
6-
"""
7-
To understand the reason purpose of this class read
8-
http://flask-oauthlib.readthedocs.org/en/latest/api.html#flask_oauthlib.provider.OAuth2RequestValidator
9-
"""
9+
""" Defines a custom OAuth2 Request Validator based on the Client, User
10+
and Token models.
1011
12+
:param OAuth2RequestValidator: Overrides the OAuth2RequestValidator.
13+
"""
1114
def __init__(self):
1215
self._clientgetter = Client.find
1316
self._usergetter = User.find_with_password

views.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
14
from flask import Blueprint, render_template, request
25
from models import Client, User
36
from core import oauth
47

58
yoloapi = Blueprint('yoloApi', __name__)
69

7-
8-
# set endpoint for token handler
910
@yoloapi.route('/oauth/token', methods=['POST'])
1011
@oauth.token_handler
1112
def access_token(*args, **kwargs):
12-
# Returns a dictionary or None as the extra credentials
13-
# for creating the token response.
14-
return None
13+
""" This endpoint is for exchanging/refreshing an access token.
1514
15+
Returns a dictionary or None as the extra credentials for creating the token response.
16+
17+
:param *args: Variable length argument list.
18+
:param **kwargs: Arbitrary keyword arguments.
19+
"""
20+
return None
1621

17-
# This is our little mangement interface for creating test users and clients
1822
@yoloapi.route('/', methods=['GET', 'POST'])
1923
def management():
24+
""" This endpoint is for vieweing and adding users and clients. """
2025
if request.method == 'POST' and request.form['submit'] == 'adduser':
2126
User.save(request.form['username'], request.form['password'])
2227
if request.method == 'POST' and request.form['submit'] == 'addclient':
2328
Client.generate()
24-
return render_template('management.html', users=User.all(),
25-
clients=Client.all())
26-
29+
return render_template('management.html', users=User.all(), clients=Client.all())
2730

28-
# The resource we are trying to protect
2931
@yoloapi.route('/yolo')
3032
@oauth.require_oauth()
3133
def yolo():
32-
return "YOLO!!! You made it through and accessed the protected resource"
34+
""" This is an example endpoint we are trying to protect. """
35+
return "YOLO! Congraulations, you made it through and accessed the protected resource!"

0 commit comments

Comments
 (0)