diff --git a/.gitignore b/.gitignore index 158c4d4..b7b7c08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ *.sqlite *.pyc -venv/* +.ropeproject/ diff --git a/README.md b/README.md index 345cfed..9bae937 100644 --- a/README.md +++ b/README.md @@ -1,283 +1,44 @@ -# How to create an OAuth 2.0 Provider +# Example for OAuth 2 Server -This is an example of OAuth 2.0 server in [Authlib](https://authlib.org/). -If you are looking for old Flask-OAuthlib implementation, check the -`flask-oauthlib` branch. +This is an example of OAuth 2 Server. -- Documentation: -- Authlib Repo: +Find more details on -## Sponsors +# Installation - - - - - -
If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at auth0.com/overview.
+ $ pip install -r requirements.txt -## Take a quick look +# Usage -This is a ready to run example, let's take a quick experience at first. To -run the example, we need to install all the dependencies: +1. Start your provider server with: -```bash -$ pip install -r requirements.txt -``` + $ python app.py -Set Flask and Authlib environment variables: +2. Visit [http://127.0.0.1:5000/](http://127.0.0.1:5000/) and fill a username. -```bash -# disable check https (DO NOT SET THIS IN PRODUCTION) -$ export AUTHLIB_INSECURE_TRANSPORT=1 -``` + [![00_oauth_server_form.png](img/00_oauth_server_form.png)]() -Create Database and run the development server: +3. And then visit [http://127.0.0.1:5000/client](http://127.0.0.1:5000/client) -```bash -$ flask run -``` + [![01_oauth_server.png](img/01_oauth_server.png)]() -Now, you can open your browser with `http://127.0.0.1:5000/`, login with any -name you want. +4. Take the client key and client secret, and modify our [client.py](client.py) +script with the key and secret. +Specifically update `CLIENT_ID` and `CLIENT_SECRET` variables on lines 5-6. -Before testing, we need to create a client: +5. Start the client server with: -![create a client](https://user-images.githubusercontent.com/290496/38811988-081814d4-41c6-11e8-88e1-cb6c25a6f82e.png) + $ python client.py -### Password flow example -Get your `client_id` and `client_secret` for testing. In this example, we -have enabled `password` grant types, let's try: +6. Visit [http://localhost:8000](http://localhost:8000), everything should work +correctly. We will be redirected to a confirm page, if we choose yes, client +will obtain a pair of access token and secret. -``` -$ curl -u ${client_id}:${client_secret} -XPOST http://127.0.0.1:5000/oauth/token -F grant_type=password -F username=${username} -F password=valid -F scope=profile -``` + [![02_oauth_client.png](img/02_oauth_client.png)]() + [![03_oauth_client_authorized.png](img/03_oauth_client_authorized.png)]() -Because this is an example, every user's password is `valid`. Now you can access `/api/me`: +7. Finally, if you visit [http://localhost:8000](http://localhost:8000) +you can access the authorized data: -```bash -$ curl -H "Authorization: Bearer ${access_token}" http://127.0.0.1:5000/api/me -``` - -### Authorization code flow example - -To test the authorization code flow, you can just open this URL in your browser. -```bash -$ open http://127.0.0.1:5000/oauth/authorize?response_type=code&client_id=${client_id}&scope=profile -``` - -After granting the authorization, you should be redirected to `${redirect_uri}/?code=${code}` - -Then your app can send the code to the authorization server to get an access token: - -```bash -$ curl -u ${client_id}:${client_secret} -XPOST http://127.0.0.1:5000/oauth/token -F grant_type=authorization_code -F scope=profile -F code=${code} -``` - -Now you can access `/api/me`: - -```bash -$ curl -H "Authorization: Bearer ${access_token}" http://127.0.0.1:5000/api/me -``` - -For now, you can read the source in example or follow the long boring tutorial below. - -**IMPORTANT**: To test implicit grant, you need to `token_endpoint_auth_method` to `none`. - -## Preparation - -Assume this example doesn't exist at all. Let's write an OAuth 2.0 server -from scratch step by step. - -### Create folder structure - -Here is our Flask website structure: - -``` -app.py --- FLASK_APP -website/ - app.py --- Flask App Factory - __init__.py --- module initialization (empty) - models.py --- SQLAlchemy Models - oauth2.py --- OAuth 2.0 Provider Configuration - routes.py --- Routes views - templates/ -``` - -### Installation - -Create a virtualenv and install all the requirements. You can also put the -dependencies into `requirements.txt`: - -``` -Flask -Flask-SQLAlchemy -Authlib -``` - -### Hello World! - -Create a home route view to say "Hello World!". It is used to test if things -working well. - - -```python -# website/routes.py -from flask import Blueprint -bp = Blueprint(__name__, 'home') - -@bp.route('/') -def home(): - return 'Hello World!' -``` - -```python -# website/app.py -from flask import Flask -from .routes import bp - -def create_app(config=None): - app = Flask(__name__) - # load app sepcified configuration - if config is not None: - if isinstance(config, dict): - app.config.update(config) - elif config.endswith('.py'): - app.config.from_pyfile(config) - setup_app(app) - return app - -def setup_app(app): - app.register_blueprint(bp, url_prefix='') -``` - -```python -# app.py -from website.app import create_app - -app = create_app({ - 'SECRET_KEY': 'secret', -}) -``` - -Create an empty ```__init__.py``` file in the ```website``` folder. - -The "Hello World!" example should run properly: - - $ FLASK_APP=app.py flask run - -## Define Models - -We will use SQLAlchemy and SQLite for our models. You can also use other -databases and other ORM engines. Authlib has some built-in SQLAlchemy mixins -which will make it easier for creating models. - -Let's create the models in `website/models.py`. We need four models, which are - -- User: you need a user to test and create your application -- OAuth2Client: the oauth client model -- OAuth2AuthorizationCode: for `grant_type=code` flow -- OAuth2Token: save the `access_token` in this model. - -Check how to define these models in `website/models.py`. - -Once you've created your own `website/models.py` (or copied our version), you'll need to import the database object `db`. Add the line `from .models import db` just after `from flask import Flask` in your scratch-built version of `website/app.py`. - -To initialize the database upon startup, if no tables exist, you'll add a few lines to the `setup_app()` function in `website/app.py` so that it now looks like: - -```python -def setup_app(app): - # Create tables if they do not exist already - @app.before_first_request - def create_tables(): - db.create_all() - - db.init_app(app) - app.register_blueprint(bp, url_prefix='') -``` - -You can try running the app again as above to make sure it works. - -## Implement Grants - -The source code is in `website/oauth2.py`. There are four standard grant types: - -- Authorization Code Grant -- Implicit Grant -- Client Credentials Grant -- Resource Owner Password Credentials Grant - -And Refresh Token is implemented as a Grant in Authlib. You don't have to do -anything on Implicit and Client Credentials grants, but there are missing -methods to be implemented in other grants. Check out the source code in -`website/oauth2.py`. - -Once you've created your own `website/oauth2.py`, import the oauth2 config object from the oauth2 module. Add the line `from .oauth2 import config_oauth` just after the import you added above in your scratch-built version of `website/app.py`. - -To initialize the oauth object, add `config_oauth(app)` to the `setup_app()` function, just before the line that starts with `app.register_blueprint` so it looks like: - -```python -def setup_app(app): - # Create tables if they do not exist already - @app.before_first_request - def create_tables(): - db.create_all() - - db.init_app(app) - config_oauth(app) - app.register_blueprint(bp, url_prefix='') -``` -You can try running the app again as above to make sure it still works. - -## `@require_oauth` - -Authlib has provided a `ResourceProtector` for you to create the decorator -`@require_oauth`, which can be easily implemented: - -```py -from authlib.flask.oauth2 import ResourceProtector - -require_oauth = ResourceProtector() -``` - -For now, only Bearer Token is supported. Let's add bearer token validator to -this ResourceProtector: - -```py -from authlib.flask.oauth2.sqla import create_bearer_token_validator - -# helper function: create_bearer_token_validator -bearer_cls = create_bearer_token_validator(db.session, OAuth2Token) -require_oauth.register_token_validator(bearer_cls()) -``` - -Check the full implementation in `website/oauth2.py`. - - -## OAuth Routes - -For OAuth server itself, we only need to implement routes for authentication, -and issuing tokens. Since we have added token revocation feature, we need a -route for revoking too. - -Checkout these routes in `website/routes.py`. Their path begin with `/oauth/`. - - -## Other Routes - -But that is not enough. In this demo, you will need to have some web pages to -create and manage your OAuth clients. Check that `/create_client` route. - -And we have an API route for testing. Check the code of `/api/me`. - - -## Finish - -Here you go. You've got an OAuth 2.0 server. - -Read more information on . - -## License - -Same license with [Authlib](https://authlib.org/plans). + [![04_oauth_client_retrieves_data.png](img/04_oauth_client_retrieves_data.png)]() diff --git a/app.py b/app.py index b9978ff..4b51b16 100644 --- a/app.py +++ b/app.py @@ -1,9 +1,260 @@ -from website.app import create_app +# coding: utf-8 +from datetime import datetime, timedelta +from flask import Flask +from flask import session, request +from flask import render_template, redirect, jsonify +from flask_sqlalchemy import SQLAlchemy +from werkzeug.security import gen_salt +from flask_oauthlib.provider import OAuth2Provider -app = create_app({ - 'SECRET_KEY': 'secret', - 'OAUTH2_REFRESH_TOKEN_GENERATOR': True, - 'SQLALCHEMY_TRACK_MODIFICATIONS': False, + +app = Flask(__name__, template_folder='templates') +app.debug = True +app.secret_key = 'secret' +app.config.update({ 'SQLALCHEMY_DATABASE_URI': 'sqlite:///db.sqlite', }) +db = SQLAlchemy(app) +oauth = OAuth2Provider(app) + + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(40), unique=True) + + +class Client(db.Model): + client_id = db.Column(db.String(40), primary_key=True) + client_secret = db.Column(db.String(55), nullable=False) + + user_id = db.Column(db.ForeignKey('user.id')) + user = db.relationship('User') + + _redirect_uris = db.Column(db.Text) + _default_scopes = db.Column(db.Text) + + @property + def client_type(self): + return 'public' + + @property + def redirect_uris(self): + if self._redirect_uris: + return self._redirect_uris.split() + return [] + + @property + def default_redirect_uri(self): + return self.redirect_uris[0] + + @property + def default_scopes(self): + if self._default_scopes: + return self._default_scopes.split() + return [] + + +class Grant(db.Model): + id = db.Column(db.Integer, primary_key=True) + + user_id = db.Column( + db.Integer, db.ForeignKey('user.id', ondelete='CASCADE') + ) + user = db.relationship('User') + + client_id = db.Column( + db.String(40), db.ForeignKey('client.client_id'), + nullable=False, + ) + client = db.relationship('Client') + + code = db.Column(db.String(255), index=True, nullable=False) + + redirect_uri = db.Column(db.String(255)) + expires = db.Column(db.DateTime) + + _scopes = db.Column(db.Text) + + def delete(self): + db.session.delete(self) + db.session.commit() + return self + + @property + def scopes(self): + if self._scopes: + return self._scopes.split() + return [] + + +class Token(db.Model): + id = db.Column(db.Integer, primary_key=True) + client_id = db.Column( + db.String(40), db.ForeignKey('client.client_id'), + nullable=False, + ) + client = db.relationship('Client') + + user_id = db.Column( + db.Integer, db.ForeignKey('user.id') + ) + user = db.relationship('User') + + # currently only bearer is supported + token_type = db.Column(db.String(40)) + + access_token = db.Column(db.String(255), unique=True) + refresh_token = db.Column(db.String(255), unique=True) + expires = db.Column(db.DateTime) + _scopes = db.Column(db.Text) + + @property + def scopes(self): + if self._scopes: + return self._scopes.split() + return [] + + +def current_user(): + if 'id' in session: + uid = session['id'] + return User.query.get(uid) + return None + + +@app.route('/', methods=('GET', 'POST')) +def home(): + if request.method == 'POST': + username = request.form.get('username') + user = User.query.filter_by(username=username).first() + if not user: + user = User(username=username) + db.session.add(user) + db.session.commit() + session['id'] = user.id + return redirect('/') + user = current_user() + return render_template('home.html', user=user) + + +@app.route('/client') +def client(): + user = current_user() + if not user: + return redirect('/') + item = Client( + client_id=gen_salt(40), + client_secret=gen_salt(50), + _redirect_uris=' '.join([ + 'http://localhost:8000/authorized', + 'http://127.0.0.1:8000/authorized', + 'http://127.0.1:8000/authorized', + 'http://127.1:8000/authorized', + ]), + _default_scopes='email', + user_id=user.id, + ) + db.session.add(item) + db.session.commit() + return jsonify( + client_id=item.client_id, + client_secret=item.client_secret, + ) + + +@oauth.clientgetter +def load_client(client_id): + return Client.query.filter_by(client_id=client_id).first() + + +@oauth.grantgetter +def load_grant(client_id, code): + return Grant.query.filter_by(client_id=client_id, code=code).first() + + +@oauth.grantsetter +def save_grant(client_id, code, request, *args, **kwargs): + # decide the expires time yourself + expires = datetime.utcnow() + timedelta(seconds=100) + grant = Grant( + client_id=client_id, + code=code['code'], + redirect_uri=request.redirect_uri, + _scopes=' '.join(request.scopes), + user=current_user(), + expires=expires + ) + db.session.add(grant) + db.session.commit() + return grant + + +@oauth.tokengetter +def load_token(access_token=None, refresh_token=None): + if access_token: + return Token.query.filter_by(access_token=access_token).first() + elif refresh_token: + return Token.query.filter_by(refresh_token=refresh_token).first() + + +@oauth.tokensetter +def save_token(token, request, *args, **kwargs): + toks = Token.query.filter_by( + client_id=request.client.client_id, + user_id=request.user.id + ) + # make sure that every client has only one token connected to a user + for t in toks: + db.session.delete(t) + + expires_in = token.pop('expires_in') + expires = datetime.utcnow() + timedelta(seconds=expires_in) + + tok = Token( + access_token=token['access_token'], + refresh_token=token['refresh_token'], + token_type=token['token_type'], + _scopes=token['scope'], + expires=expires, + client_id=request.client.client_id, + user_id=request.user.id, + ) + db.session.add(tok) + db.session.commit() + return tok + + +@app.route('/oauth/token', methods=['GET', 'POST']) +@oauth.token_handler +def access_token(): + return None + + +@app.route('/oauth/authorize', methods=['GET', 'POST']) +@oauth.authorize_handler +def authorize(*args, **kwargs): + user = current_user() + if not user: + return redirect('/') + if request.method == 'GET': + client_id = kwargs.get('client_id') + client = Client.query.filter_by(client_id=client_id).first() + kwargs['client'] = client + kwargs['user'] = user + return render_template('authorize.html', **kwargs) + + confirm = request.form.get('confirm', 'no') + return confirm == 'yes' + + +@app.route('/api/me') +@oauth.require_oauth() +def me(): + user = request.oauth.user + return jsonify(username=user.username) + + +if __name__ == '__main__': + db.create_all() + app.run() diff --git a/client.py b/client.py new file mode 100644 index 0000000..263b71b --- /dev/null +++ b/client.py @@ -0,0 +1,59 @@ +from flask import Flask, url_for, session, request, jsonify +from flask_oauthlib.client import OAuth + + +CLIENT_ID = 'GbRmKgbSMmlE2NlugMeFfQIba8hoVyBFsWS8Igsq' +CLIENT_SECRET = 'BfP7jsN8dSsXjGLfTTPiEvarMJOpkZQ2Y7IVVee8X929LfolMV' + + +app = Flask(__name__) +app.debug = True +app.secret_key = 'secret' +oauth = OAuth(app) + +remote = oauth.remote_app( + 'remote', + consumer_key=CLIENT_ID, + consumer_secret=CLIENT_SECRET, + request_token_params={'scope': 'email'}, + base_url='http://127.0.0.1:5000/api/', + request_token_url=None, + access_token_url='http://127.0.0.1:5000/oauth/token', + authorize_url='http://127.0.0.1:5000/oauth/authorize' +) + + +@app.route('/') +def index(): + if 'remote_oauth' in session: + resp = remote.get('me') + return jsonify(resp.data) + next_url = request.args.get('next') or request.referrer or None + return remote.authorize( + callback=url_for('authorized', next=next_url, _external=True) + ) + + +@app.route('/authorized') +def authorized(): + resp = remote.authorized_response() + if resp is None: + return 'Access denied: reason=%s error=%s' % ( + request.args['error_reason'], + request.args['error_description'] + ) + print(resp) + session['remote_oauth'] = (resp['access_token'], '') + return jsonify(oauth_token=resp['access_token']) + + +@remote.tokengetter +def get_oauth_token(): + return session.get('remote_oauth') + + +if __name__ == '__main__': + import os + os.environ['DEBUG'] = 'true' + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true' + app.run(host='localhost', port=8000) diff --git a/img/00_oauth_server_form.png b/img/00_oauth_server_form.png new file mode 100644 index 0000000..c0c0c38 Binary files /dev/null and b/img/00_oauth_server_form.png differ diff --git a/img/01_oauth_server.png b/img/01_oauth_server.png new file mode 100644 index 0000000..2c01f50 Binary files /dev/null and b/img/01_oauth_server.png differ diff --git a/img/02_oauth_client.png b/img/02_oauth_client.png new file mode 100644 index 0000000..0eaa439 Binary files /dev/null and b/img/02_oauth_client.png differ diff --git a/img/03_oauth_client_authorized.png b/img/03_oauth_client_authorized.png new file mode 100644 index 0000000..19c9611 Binary files /dev/null and b/img/03_oauth_client_authorized.png differ diff --git a/img/04_oauth_client_retrieves_data.png b/img/04_oauth_client_retrieves_data.png new file mode 100644 index 0000000..e0a98d1 Binary files /dev/null and b/img/04_oauth_client_retrieves_data.png differ diff --git a/requirements.txt b/requirements.txt index a9a0f2d..6dca7dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ -Flask -Flask-SQLAlchemy -Authlib +Flask==0.10.1 +Flask-SQLAlchemy==1.0 +werkzeug==0.9.4 +Flask-OAuthlib>=0.5.0 diff --git a/templates/authorize.html b/templates/authorize.html new file mode 100644 index 0000000..b9d8f0a --- /dev/null +++ b/templates/authorize.html @@ -0,0 +1,23 @@ + + + + + Authorization + + +

Client: {{ client.client_id }}

+

User: {{ user.username }}

+
+

Allow access?

+ + + + + {% if state %} + + {% endif %} + + +
+ + diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..d2cc058 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,20 @@ + + + + + + + + {% if user %} +

You are {{ user.username }}

+ {% else %} +

You are not authenticated

+ {% endif %} + +

Type any username:

+
+ + +
+ + diff --git a/website/__init__.py b/website/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/website/app.py b/website/app.py deleted file mode 100644 index af99dba..0000000 --- a/website/app.py +++ /dev/null @@ -1,36 +0,0 @@ -import os -from flask import Flask -from .models import db -from .oauth2 import config_oauth -from .routes import bp - - -def create_app(config=None): - app = Flask(__name__) - - # load default configuration - app.config.from_object('website.settings') - - # load environment configuration - if 'WEBSITE_CONF' in os.environ: - app.config.from_envvar('WEBSITE_CONF') - - # load app specified configuration - if config is not None: - if isinstance(config, dict): - app.config.update(config) - elif config.endswith('.py'): - app.config.from_pyfile(config) - - setup_app(app) - return app - - -def setup_app(app): - - db.init_app(app) - # Create tables if they do not exist already - with app.app_context(): - db.create_all() - config_oauth(app) - app.register_blueprint(bp, url_prefix='') diff --git a/website/models.py b/website/models.py deleted file mode 100644 index b79068a..0000000 --- a/website/models.py +++ /dev/null @@ -1,56 +0,0 @@ -import time -from flask_sqlalchemy import SQLAlchemy -from authlib.integrations.sqla_oauth2 import ( - OAuth2ClientMixin, - OAuth2AuthorizationCodeMixin, - OAuth2TokenMixin, -) - -db = SQLAlchemy() - - -class User(db.Model): - id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(40), unique=True) - - def __str__(self): - return self.username - - def get_user_id(self): - return self.id - - def check_password(self, password): - return password == 'valid' - - -class OAuth2Client(db.Model, OAuth2ClientMixin): - __tablename__ = 'oauth2_client' - - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column( - db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')) - user = db.relationship('User') - - -class OAuth2AuthorizationCode(db.Model, OAuth2AuthorizationCodeMixin): - __tablename__ = 'oauth2_code' - - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column( - db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')) - user = db.relationship('User') - - -class OAuth2Token(db.Model, OAuth2TokenMixin): - __tablename__ = 'oauth2_token' - - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column( - db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')) - user = db.relationship('User') - - def is_refresh_token_active(self): - if self.revoked: - return False - expires_at = self.issued_at + self.expires_in * 2 - return expires_at >= time.time() diff --git a/website/oauth2.py b/website/oauth2.py deleted file mode 100644 index 2f7d71f..0000000 --- a/website/oauth2.py +++ /dev/null @@ -1,101 +0,0 @@ -from authlib.integrations.flask_oauth2 import ( - AuthorizationServer, - ResourceProtector, -) -from authlib.integrations.sqla_oauth2 import ( - create_query_client_func, - create_save_token_func, - create_revocation_endpoint, - create_bearer_token_validator, -) -from authlib.oauth2.rfc6749 import grants -from authlib.oauth2.rfc7636 import CodeChallenge -from .models import db, User -from .models import OAuth2Client, OAuth2AuthorizationCode, OAuth2Token - - -class AuthorizationCodeGrant(grants.AuthorizationCodeGrant): - TOKEN_ENDPOINT_AUTH_METHODS = [ - 'client_secret_basic', - 'client_secret_post', - 'none', - ] - - def save_authorization_code(self, code, request): - code_challenge = request.data.get('code_challenge') - code_challenge_method = request.data.get('code_challenge_method') - auth_code = OAuth2AuthorizationCode( - code=code, - client_id=request.client.client_id, - redirect_uri=request.redirect_uri, - scope=request.scope, - user_id=request.user.id, - code_challenge=code_challenge, - code_challenge_method=code_challenge_method, - ) - db.session.add(auth_code) - db.session.commit() - return auth_code - - def query_authorization_code(self, code, client): - auth_code = OAuth2AuthorizationCode.query.filter_by( - code=code, client_id=client.client_id).first() - if auth_code and not auth_code.is_expired(): - return auth_code - - def delete_authorization_code(self, authorization_code): - db.session.delete(authorization_code) - db.session.commit() - - def authenticate_user(self, authorization_code): - return User.query.get(authorization_code.user_id) - - -class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant): - def authenticate_user(self, username, password): - user = User.query.filter_by(username=username).first() - if user is not None and user.check_password(password): - return user - - -class RefreshTokenGrant(grants.RefreshTokenGrant): - def authenticate_refresh_token(self, refresh_token): - token = OAuth2Token.query.filter_by(refresh_token=refresh_token).first() - if token and token.is_refresh_token_active(): - return token - - def authenticate_user(self, credential): - return User.query.get(credential.user_id) - - def revoke_old_credential(self, credential): - credential.revoked = True - db.session.add(credential) - db.session.commit() - - -query_client = create_query_client_func(db.session, OAuth2Client) -save_token = create_save_token_func(db.session, OAuth2Token) -authorization = AuthorizationServer( - query_client=query_client, - save_token=save_token, -) -require_oauth = ResourceProtector() - - -def config_oauth(app): - authorization.init_app(app) - - # support all grants - authorization.register_grant(grants.ImplicitGrant) - authorization.register_grant(grants.ClientCredentialsGrant) - authorization.register_grant(AuthorizationCodeGrant, [CodeChallenge(required=True)]) - authorization.register_grant(PasswordGrant) - authorization.register_grant(RefreshTokenGrant) - - # support revocation - revocation_cls = create_revocation_endpoint(db.session, OAuth2Token) - authorization.register_endpoint(revocation_cls) - - # protect resource - bearer_cls = create_bearer_token_validator(db.session, OAuth2Token) - require_oauth.register_token_validator(bearer_cls()) diff --git a/website/routes.py b/website/routes.py deleted file mode 100644 index da00c59..0000000 --- a/website/routes.py +++ /dev/null @@ -1,129 +0,0 @@ -import time -from flask import Blueprint, request, session, url_for -from flask import render_template, redirect, jsonify -from werkzeug.security import gen_salt -from authlib.integrations.flask_oauth2 import current_token -from authlib.oauth2 import OAuth2Error -from .models import db, User, OAuth2Client -from .oauth2 import authorization, require_oauth - - -bp = Blueprint('home', __name__) - - -def current_user(): - if 'id' in session: - uid = session['id'] - return User.query.get(uid) - return None - - -def split_by_crlf(s): - return [v for v in s.splitlines() if v] - - -@bp.route('/', methods=('GET', 'POST')) -def home(): - if request.method == 'POST': - username = request.form.get('username') - user = User.query.filter_by(username=username).first() - if not user: - user = User(username=username) - db.session.add(user) - db.session.commit() - session['id'] = user.id - # if user is not just to log in, but need to head back to the auth page, then go for it - next_page = request.args.get('next') - if next_page: - return redirect(next_page) - return redirect('/') - user = current_user() - if user: - clients = OAuth2Client.query.filter_by(user_id=user.id).all() - else: - clients = [] - - return render_template('home.html', user=user, clients=clients) - - -@bp.route('/logout') -def logout(): - del session['id'] - return redirect('/') - - -@bp.route('/create_client', methods=('GET', 'POST')) -def create_client(): - user = current_user() - if not user: - return redirect('/') - if request.method == 'GET': - return render_template('create_client.html') - - client_id = gen_salt(24) - client_id_issued_at = int(time.time()) - client = OAuth2Client( - client_id=client_id, - client_id_issued_at=client_id_issued_at, - user_id=user.id, - ) - - form = request.form - client_metadata = { - "client_name": form["client_name"], - "client_uri": form["client_uri"], - "grant_types": split_by_crlf(form["grant_type"]), - "redirect_uris": split_by_crlf(form["redirect_uri"]), - "response_types": split_by_crlf(form["response_type"]), - "scope": form["scope"], - "token_endpoint_auth_method": form["token_endpoint_auth_method"] - } - client.set_client_metadata(client_metadata) - - if form['token_endpoint_auth_method'] == 'none': - client.client_secret = '' - else: - client.client_secret = gen_salt(48) - - db.session.add(client) - db.session.commit() - return redirect('/') - - -@bp.route('/oauth/authorize', methods=['GET', 'POST']) -def authorize(): - user = current_user() - # if user log status is not true (Auth server), then to log it in - if not user: - return redirect(url_for('home.home', next=request.url)) - if request.method == 'GET': - try: - grant = authorization.get_consent_grant(end_user=user) - except OAuth2Error as error: - return error.error - return render_template('authorize.html', user=user, grant=grant) - if not user and 'username' in request.form: - username = request.form.get('username') - user = User.query.filter_by(username=username).first() - if request.form['confirm']: - grant_user = user - else: - grant_user = None - return authorization.create_authorization_response(grant_user=grant_user) - - -@bp.route('/oauth/token', methods=['POST']) -def issue_token(): - return authorization.create_token_response() - - -@bp.route('/oauth/revoke', methods=['POST']) -def revoke_token(): - return authorization.create_endpoint_response('revocation') - - -@bp.route('/api/me') -@require_oauth('profile') -def api_me(): - user = current_token.user - return jsonify(id=user.id, username=user.username) diff --git a/website/settings.py b/website/settings.py deleted file mode 100644 index e69de29..0000000 diff --git a/website/templates/authorize.html b/website/templates/authorize.html deleted file mode 100644 index 10868ca..0000000 --- a/website/templates/authorize.html +++ /dev/null @@ -1,22 +0,0 @@ -

The application {{grant.client.client_name}} is requesting: -{{ grant.request.scope }} -

- -

- from You - a.k.a. {{ user.username }} -

- -
- - {% if not user %} -

You haven't logged in. Log in with:

-
- -
- {% endif %} -
- -
diff --git a/website/templates/create_client.html b/website/templates/create_client.html deleted file mode 100644 index 20b3ed3..0000000 --- a/website/templates/create_client.html +++ /dev/null @@ -1,42 +0,0 @@ - - -Home - -
- - - - - - - - -
diff --git a/website/templates/home.html b/website/templates/home.html deleted file mode 100644 index 012a801..0000000 --- a/website/templates/home.html +++ /dev/null @@ -1,26 +0,0 @@ -{% if user %} - -
Logged in as {{user}} (Log Out)
- -{% for client in clients %} -
-Client Info
-  {%- for key in client.client_info %}
-  {{ key }}: {{ client.client_info[key] }}
-  {%- endfor %}
-Client Metadata
-  {%- for key in client.client_metadata %}
-  {{ key }}: {{ client.client_metadata[key] }}
-  {%- endfor %}
-
-
-{% endfor %} - -
Create Client - -{% else %} -
- - -
-{% endif %}