--- 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$
--- 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
--- 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__':
--- 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/<token>')
+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()
--- /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
--- 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
--- 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
--- 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 = [
- '<renkan-base-url>/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
--- 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 @@
</head>
<body>
<h4>Mock GED client</h4>
- <p>You are: {{ username }}</p>
- <p>Create a <a href="{{ config['CREATE_RENKAN_URL'] }}">new Renkan</a></p>
+ <p>Client Credentials OAuth token owner is: {{ oauth_username }}</p>
+ <p>Logged user is: {{ current_username }}</p>
+ <p>Create a <a href="{{ config['CREATE_RENKAN_URL'] }}">new Renkan</a> and edit (authorization flow)</p>
+ <p>Create a <a href="{{ url_for('renkan_request') }}">new Renkan</a> from server (clients credentials)</p>
</body>
</html>
\ No newline at end of file
--- 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 @@
<body>
<p>Client: {{ client.client_id }}</p>
<p>User: {{ user.username }}</p>
- <form action="/oauth/authorize" method="post">
+ <form action="/oauth/oauth2/authorize" method="post">
<p>Allow access?</p>
<input type="hidden" name="client_id" value="{{ client.client_id }}">
<input type="hidden" name="scope" value="{{ scopes|join(' ') }}">