adjustments on Oauth server and mock ged client + Readme
authordurandn
Mon, 29 Feb 2016 12:22:07 +0100
changeset 5 4407b131a70e
parent 4 8bc8b208441d
child 6 39cecdd5260e
adjustments on Oauth server and mock ged client + Readme
.hgignore
oauth/README.md
oauth/client.py
oauth/oauth.py
oauth/requirement.txt
oauth/requirements.txt
oauth/settings/client_settings.py.tmpl
oauth/settings/oauth_settings.py.tmpl
oauth/templates/client/index.html
oauth/templates/oauth/authorize.html
--- 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(' ') }}">