# HG changeset patch # User durandn # Date 1456744927 -3600 # Node ID 4407b131a70ebdb26ad6933d171fc1435a65fd0f # Parent 8bc8b208441da07ed0a9ff8d7e7e857d3b36b3e2 adjustments on Oauth server and mock ged client + Readme diff -r 8bc8b208441d -r 4407b131a70e .hgignore --- a/.hgignore Wed Feb 17 16:14:04 2016 +0100 +++ b/.hgignore Mon Feb 29 12:22:07 2016 +0100 @@ -7,6 +7,7 @@ server/src/MANIFEST.in oauth/settings/client_settings.py oauth/settings/oauth_settings.py +oauth/db.sqlite syntax: regexp ^authserver/homestead/\.vagrant$ diff -r 8bc8b208441d -r 4407b131a70e oauth/README.md --- a/oauth/README.md Wed Feb 17 16:14:04 2016 +0100 +++ b/oauth/README.md Mon Feb 29 12:22:07 2016 +0100 @@ -1,24 +1,52 @@ -# Example OAuth 2 Server & GED mock client +# OAuth 2 Server & GED mock client for test and development -# INSTALLATION +## INSTALLATION use a different virtualenv than the one for the django metaeducation app $ mkvirtualenv mtdc_oauth_env $ pip install -r requirements.txt -# CONFIGURATION +## CONFIGURATION Edit client_settings.py.tmpl and oauth_settings.py.tmpl to match your renkan django app config. -# RUNNING +## RUNNING To run the oauth server: -$ workon mtdc_oauth_env -$ python oauth.py + $ workon mtdc_oauth_env + $ python oauth.py To run the GED mock client: -$ workon mtdc_oauth_env -$ python client.py \ No newline at end of file + $ workon mtdc_oauth_env + $ python client.py + +Oauth server runs on 127.0.0.1:5000 +Mock ged client runs on 127.0.0.1:8000 + +NOTE: OAuth server will expect you to run Renkan django project on 127.0.0.1:8001 + +## GETTING MOCK GED CLIENT ID AND SECRET + +You need to run oauth.py, then go to 127.0.0.1:500/get-client-credentials . +The Oauth server will print a JSON with client_id and client_secret that will allow you to complete the client_settings.py + +## USAGE + +First of all you must log into the oauth server + + 127.0.0.1:5000 + +Once you are logged, you can visit the mock GED client + + 127.0.0.1:8000 + +You will start an authorization flow that will validate that you are logged into the OAuth server +(note: while Itop's server will auto-validate clients, we keep the validation steps for testing and dev as it makes debugging easier) + +From there you end up on the client's main page, with two links + +* One of them is a link to a new Renkan that will open in your browser when everything is properly configured (see server/src/metaeducation/README.md to configure the django app), using Authorization Code flow +* The other is a link to create a new Renkan from the API endpoint, using client credentials OAuth flow \ No newline at end of file diff -r 8bc8b208441d -r 4407b131a70e oauth/client.py --- a/oauth/client.py Wed Feb 17 16:14:04 2016 +0100 +++ b/oauth/client.py Mon Feb 29 12:22:07 2016 +0100 @@ -1,8 +1,9 @@ from flask import Flask, url_for, session, request, jsonify, render_template, redirect from flask_oauthlib.client import OAuth from settings.client_settings import ClientSettings - - +import base64 +import requests +import json app = Flask(__name__) app.debug = True @@ -24,15 +25,47 @@ @app.route('/') def index(): - if 'remote_oauth' in session: - resp = remote.get('me') - username = resp.data.get("username", "") - return render_template('client/index.html', username=username) - next_url = request.args.get('next') or request.referrer or None - return remote.authorize( - callback=url_for('authorized', next=next_url, _external=True) - ) + if 'remote_oauth_authorizationcode' not in session: + next_url = request.args.get('next') or request.referrer or None + return remote.authorize( + callback=url_for('authorized', next=next_url, _external=True) + ) + if 'me' not in session: + resp = remote.get('user/InfoComplete') + print("authcode resp data: "+str(resp.data)) + me = resp.data.get("username", "") + session["me"] = me + if 'remote_oauth_clientcredentials' not in session: + auth_string = bytes(app.config["CLIENT_ID"]+':'+app.config['CLIENT_SECRET'], "utf-8") + auth_code = base64.b64encode(auth_string).decode("utf-8") + resp = requests.post(app.config["ACCESS_TOKEN_URL"]+"?grant_type=client_credentials&scope=basic", data={}, headers={ + 'Authorization': 'Basic %s' % auth_code, + }) + if resp is None: + return 'Access denied: reason=%s error=%s' % ( + request.args['error_reason'], + request.args['error_description'] + ) + session['remote_oauth_clientcredentials'] = (json.loads(resp.text)['access_token'], '') + resp = remote.get('user/InfoComplete') + print("clientcredentials resp data: "+str(resp.data)) + server = resp.data.get("username", "") + session["server"] = server + return render_template('client/index.html', current_username=session["me"], oauth_username=session["server"]) +@app.route('/renkan-request') +def renkan_request(): + if 'remote_oauth_clientcredentials' in session: + resp = requests.post( + app.config["CREATE_RENKAN_ENDPOINT"]+"?act_as="+session.get("me", "anonymous"), + {"title": "RENKAN_FROM_GED"}, + headers={ + 'Authorization': 'Bearer %s' % session['remote_oauth_clientcredentials'][0], + 'renkan-act-as': session.get("me", "anonymous") + } + ) + print(resp.text) + return redirect('/') @app.route('/authorized') def authorized(): @@ -42,14 +75,19 @@ request.args['error_reason'], request.args['error_description'] ) - print resp - session['remote_oauth'] = (resp['access_token'], '') + session['remote_oauth_authorizationcode'] = (resp['access_token'], '') + return redirect('/') @remote.tokengetter def get_oauth_token(): - return session.get('remote_oauth') + print("referrer : "+request.referrer) + if 'remote_oauth_clientcredentials' in session and 'server' not in session: + return session['remote_oauth_clientcredentials'] + else: + return session.get('remote_oauth_authorizationcode', '') + if __name__ == '__main__': diff -r 8bc8b208441d -r 4407b131a70e oauth/oauth.py --- a/oauth/oauth.py Wed Feb 17 16:14:04 2016 +0100 +++ b/oauth/oauth.py Mon Feb 29 12:22:07 2016 +0100 @@ -28,15 +28,15 @@ class Client(db.Model): client_id = db.Column(db.String(40), primary_key=True) client_secret = db.Column(db.String(55), nullable=False) + client_type = db.Column(db.String(12), nullable=False, default='public') + 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() @@ -103,7 +103,7 @@ 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) + refresh_token = db.Column(db.String(255), unique=True, nullable=True) expires = db.Column(db.DateTime) _scopes = db.Column(db.Text) @@ -118,6 +118,7 @@ if 'id' in session: uid = session['id'] return User.query.get(uid) + print(session) return None @@ -135,28 +136,6 @@ user = current_user() return render_template('oauth/home.html', user=user) -def generate_credentials(redirect_uris): - item = Client( - client_id=gen_salt(40), - client_secret=gen_salt(50), - _redirect_uris=' '.join(redirect_uris), - _default_scopes='basic', - ) - db.session.add(item) - db.session.commit() - return jsonify( - client_id=item.client_id, - client_secret=item.client_secret, - ) - -@app.route('/get-client-credentials') -def make_client_credentials(): - return generate_credentials(app.config.get("CLIENT_REDIRECT_URIS", [])) - -@app.route('/get-renkan-credentials') -def make_renkan_credentials(): - return generate_credentials(app.config.get("RENKAN_REDIRECT_URIS", [])) - @oauth.clientgetter def load_client(client_id): return Client.query.filter_by(client_id=client_id).first() @@ -207,7 +186,6 @@ tok = Token( access_token=token['access_token'], - refresh_token=token['refresh_token'], token_type=token['token_type'], _scopes=token['scope'], expires=expires, @@ -219,15 +197,16 @@ return tok -@app.route('/oauth/token', methods=['GET', 'POST']) +@app.route('/oauth/oauth2/token', methods=['GET', 'POST']) @oauth.token_handler def access_token(): return None -@app.route('/oauth/authorize', methods=['GET', 'POST']) +@app.route('/oauth/oauth2/authorize', methods=['GET', 'POST']) @oauth.authorize_handler def authorize(*args, **kwargs): + print(request.headers) user = current_user() if not user: return redirect('/') @@ -242,13 +221,62 @@ return confirm == 'yes' -@app.route('/api/me') +@app.route('/rest/user/InfoComplete') @oauth.require_oauth() -def me(): +def user_info(): user = request.oauth.user return jsonify(id=user.id, username=user.username) +@app.route('/rest/oauth/validate/') +def validate_token(token): + print(request.headers) + database_token = Token.query.filter_by(access_token=token).first() + related_client = database_token.client + return jsonify( + access_token=token, + redirect_uri= related_client.redirect_uris, + error=0, + description= "", + scope=database_token.scopes + ) + +def init_client(client_id, client_secret, redirect_uris, client_owner, confidential=False): + client = Client.query.filter_by(client_id=client_id, client_secret=client_secret).first() + if not client: + print("Creating client for "+client_owner) + user = User.query.filter_by(username=client_owner).first() + if not user: + user = User(username=username) + db.session.add(user) + db.session.commit() + if confidential: + type="confidential" + else: + type="public" + client = Client( + client_id=client_id, + client_secret=client_secret, + _redirect_uris=' '.join(redirect_uris), + _default_scopes='basic', + user_id=user.id, + client_type=type + ) + db.session.add(client) + db.session.commit() if __name__ == '__main__': db.create_all() + init_client( + client_id=app.config["RENKAN_CLIENT_ID"], + client_secret=app.config["RENKAN_CLIENT_SECRET"], + redirect_uris=app.config["RENKAN_REDIRECT_URIS"], + client_owner=app.config["RENKAN_SERVER_USER"] + ) + init_client( + client_id=app.config["MOCK_GED_CLIENT_ID"], + client_secret=app.config["MOCK_GED_CLIENT_SECRET"], + redirect_uris=app.config["MOCK_GED_REDIRECT_URIS"], + client_owner=app.config["MOCK_GED_SERVER_USER"], + confidential=True + ) app.run() diff -r 8bc8b208441d -r 4407b131a70e oauth/requirement.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/oauth/requirement.txt Mon Feb 29 12:22:07 2016 +0100 @@ -0,0 +1,4 @@ +Flask==0.10.1 +Flask-SQLAlchemy==1.0 +werkzeug==0.9.4 +Flask-OAuthlib>=0.5.0 diff -r 8bc8b208441d -r 4407b131a70e oauth/requirements.txt --- a/oauth/requirements.txt Wed Feb 17 16:14:04 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -Flask==0.10.1 -Flask-SQLAlchemy==1.0 -werkzeug==0.9.4 -Flask-OAuthlib>=0.5.0 diff -r 8bc8b208441d -r 4407b131a70e oauth/settings/client_settings.py.tmpl --- a/oauth/settings/client_settings.py.tmpl Wed Feb 17 16:14:04 2016 +0100 +++ b/oauth/settings/client_settings.py.tmpl Mon Feb 29 12:22:07 2016 +0100 @@ -1,15 +1,13 @@ class ClientSettings(object): - CLIENT_ID = '' - CLIENT_SECRET = '' - - # Oauth server config - BASE_URL = 'http://127.0.0.1:5000/api/' + # OAUTH SERVER CONFIG + BASE_URL = 'http://127.0.0.1:5000/rest/' 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' - REQUEST_TOKEN_PARAMS = {'scope': ''} + ACCESS_TOKEN_URL = 'http://127.0.0.1:5000/oauth/oauth2/token' + AUTHORIZE_URL = 'http://127.0.0.1:5000/oauth/oauth2/authorize' + REQUEST_TOKEN_PARAMS = {'scope': 'basic'} - # Renkan config - CREATE_RENKAN_URL = '' \ No newline at end of file + # RENKAN CONFIG + CREATE_RENKAN_URL = 'http://127.0.0.1:8001/front/new/?context=http://127.0.0.1:5000' + CREATE_RENKAN_ENDPOINT = 'http://127.0.0.1:8001/api/v1_0/renkans/' \ No newline at end of file diff -r 8bc8b208441d -r 4407b131a70e oauth/settings/oauth_settings.py.tmpl --- a/oauth/settings/oauth_settings.py.tmpl Wed Feb 17 16:14:04 2016 +0100 +++ b/oauth/settings/oauth_settings.py.tmpl Mon Feb 29 12:22:07 2016 +0100 @@ -1,14 +1,24 @@ class OAuthSettings(object): - # Redirect URIs for GED mock client - CLIENT_REDIRECT_URIS = [ + # MOCK GED CONFIG + MOCK_GED_REDIRECT_URIS = [ 'http://localhost:8000/authorized', 'http://127.0.0.1:8000/authorized', 'http://127.0.1:8000/authorized', 'http://127.1:8000/authorized', ] - # Redirect URIs for Renkan django app + MOCK_GED_CLIENT_ID = '' + MOCK_GED_CLIENT_SECRET = '' + MOCK_GED_SERVER_USER = 'mock_ged_server' + + # RENKAN CONFIG RENKAN_REDIRECT_URIS = [ - '/accounts/mtdc/login/callback/', - ] \ No newline at end of file + 'http://127.0.0.1:8001/accounts/mtdc/login/callback/', + 'http://127.0.0.1:8001/accounts/mtdc/login/callback/', + 'http://127.0.1:8001/accounts/mtdc/login/callback/', + 'http://127.1:8001/accounts/mtdc/login/callback/', + ] + RENKAN_CLIENT_ID = '' + RENKAN_CLIENT_SECRET = '' + RENKAN_SERVER_USER = 'renkan_server' \ No newline at end of file diff -r 8bc8b208441d -r 4407b131a70e oauth/templates/client/index.html --- a/oauth/templates/client/index.html Wed Feb 17 16:14:04 2016 +0100 +++ b/oauth/templates/client/index.html Mon Feb 29 12:22:07 2016 +0100 @@ -6,7 +6,9 @@

Mock GED client

-

You are: {{ username }}

-

Create a new Renkan

+

Client Credentials OAuth token owner is: {{ oauth_username }}

+

Logged user is: {{ current_username }}

+

Create a new Renkan and edit (authorization flow)

+

Create a new Renkan from server (clients credentials)

\ No newline at end of file diff -r 8bc8b208441d -r 4407b131a70e oauth/templates/oauth/authorize.html --- a/oauth/templates/oauth/authorize.html Wed Feb 17 16:14:04 2016 +0100 +++ b/oauth/templates/oauth/authorize.html Mon Feb 29 12:22:07 2016 +0100 @@ -7,7 +7,7 @@

Client: {{ client.client_id }}

User: {{ user.username }}

-
+

Allow access?