Adds more support for oauth - access_token & decorators still to do
authorxray7224 <xray7224@googlemail.com>
Wed, 10 Jul 2013 14:49:59 +0000 (15:49 +0100)
committerxray7224 <jessica@megworld.co.uk>
Thu, 11 Jul 2013 17:21:43 +0000 (18:21 +0100)
mediagoblin/db/models.py
mediagoblin/federation/forms.py [new file with mode: 0644]
mediagoblin/federation/views.py
mediagoblin/static/css/base.css
mediagoblin/templates/mediagoblin/api/authorize.html [new file with mode: 0644]
mediagoblin/templates/mediagoblin/api/oob.html [new file with mode: 0644]
mediagoblin/tools/request.py

index 8a71aa09975e9ebea72a60a0d61a074e1c154987..b6ae533eba8babdc5c5c193c8f22a34ba86fead8 100644 (file)
@@ -143,7 +143,7 @@ class RequestToken(Base):
     used = Column(Boolean, default=False)
     authenticated = Column(Boolean, default=False)
     verifier = Column(Unicode, nullable=True)
-    callback = Column(Unicode, nullable=True)
+    callback = Column(Unicode, nullable=False, default=u"oob")
     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
     
diff --git a/mediagoblin/federation/forms.py b/mediagoblin/federation/forms.py
new file mode 100644 (file)
index 0000000..39d6fc2
--- /dev/null
@@ -0,0 +1,8 @@
+import wtforms
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+
+class AuthorizeForm(wtforms.Form):
+    """ Form used to authorize the request token """
+
+    oauth_token = wtforms.HiddenField("oauth_token")
+    oauth_verifier = wtforms.HiddenField("oauth_verifier")
index 6c0008555ddace81a9dfe80886f3c4f32a863dce..9559df10c6215f8c3b378ec05408b04ab7835cc8 100644 (file)
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from oauthlib.oauth1 import RequestValidator, RequestTokenEndpoint
+import datetime
 
+import oauthlib.common
+from oauthlib.oauth1 import (AuthorizationEndpoint, RequestValidator, 
+                             RequestTokenEndpoint)
+
+from mediagoblin.decorators import require_active_login
 from mediagoblin.tools.translate import pass_to_ugettext
 from mediagoblin.meddleware.csrf import csrf_exempt
 from mediagoblin.tools.request import decode_request
-from mediagoblin.tools.response import json_response, render_400
+from mediagoblin.tools.response import (render_to_response, redirect, 
+                                        json_response, render_400)
 from mediagoblin.tools.crypto import random_string
 from mediagoblin.tools.validator import validate_email, validate_url
-from mediagoblin.db.models import Client, RequestToken, AccessToken
+from mediagoblin.db.models import User, Client, RequestToken, AccessToken
+from mediagoblin.federation.forms import AuthorizeForm
 
 # possible client types
 client_types = ["web", "native"] # currently what pump supports
@@ -120,7 +127,8 @@ def client_register(request):
                 )
     else:
         client.logo_url = logo_url
-    application_name=data.get("application_name", None)
+    
+    client.application_name = data.get("application_name", None)
 
     contacts = data.get("contact", None)
     if contacts is not None:
@@ -171,7 +179,7 @@ class ValidationException(Exception):
 
 class GMGRequestValidator(RequestValidator):
 
-    def __init__(self, data):
+    def __init__(self, data=None):
         self.POST = data
 
     def save_request_token(self, token, request):
@@ -183,8 +191,25 @@ class GMGRequestValidator(RequestValidator):
                 secret=token["oauth_token_secret"],
                 )
         request_token.client = client_id
+        request_token.callback = token.get("oauth_callback", None)
         request_token.save()
 
+    def save_verifier(self, token, verifier, request):
+        """ Saves the oauth request verifier """
+        request_token = RequestToken.query.filter_by(token=token).first()
+        request_token.verifier = verifier["oauth_verifier"]
+        request_token.save()
+
+
+    def save_access_token(self, token, request):
+        """ Saves access token in db """
+        access_token = AccessToken(
+                token=token["oauth_token"],
+                secret=token["oauth_secret"],
+        )
+        access_token.request_token = request.body["oauth_token"]
+        access_token.user = token["user"].id
+        access_token.save()
 
 @csrf_exempt
 def request_token(request):
@@ -195,10 +220,16 @@ def request_token(request):
         error = "Could not decode data."
         return json_response({"error": error}, status=400)
 
-    if data is "":
+    if data == "":
         error = "Unknown Content-Type"
         return json_response({"error": error}, status=400)
 
+    print data
+
+    if "Authorization" not in data:
+        error = "Missing required parameter."
+        return json_response({"error": error}, status=400)
+
 
     # Convert 'Authorization' to a dictionary
     authorization = {}
@@ -207,6 +238,10 @@ def request_token(request):
         authorization[key] = value
     data[u"Authorization"] = authorization
 
+    if "oauth_consumer_key" not in data[u"Authorization"]:
+        error = "Missing required parameter."
+        return json_respinse({"error": error}, status=400)
+
     # check the client_id
     client_id = data[u"Authorization"][u"oauth_consumer_key"]
     client = Client.query.filter_by(id=client_id).first()
@@ -217,29 +252,137 @@ def request_token(request):
 
     request_validator = GMGRequestValidator(data)
     rv = RequestTokenEndpoint(request_validator)
-    tokens = rv.create_request_token(request, {})
+    tokens = rv.create_request_token(request, authorization)
 
     tokenized = {}
     for t in tokens.split("&"):
         key, value = t.split("=")
         tokenized[key] = value
 
+    print "[DEBUG] %s" % tokenized
+
     # check what encoding to return them in
     return json_response(tokenized)
-    
+
+class WTFormData(dict):
+    """
+        Provides a WTForm usable dictionary
+    """
+    def getlist(self, key):
+        v = self[key]
+        if not isinstance(v, (list, tuple)):
+            v = [v]
+        return v
+
+@require_active_login    
 def authorize(request):
     """ Displays a page for user to authorize """
+    if request.method == "POST":
+        return authorize_finish(request)
+    
     _ = pass_to_ugettext
     token = request.args.get("oauth_token", None)
     if token is None:
         # no token supplied, display a html 400 this time
-        err_msg = _("Must provide an oauth_token")
+        err_msg = _("Must provide an oauth_token.")
         return render_400(request, err_msg=err_msg)
 
+    oauth_request = RequestToken.query.filter_by(token=token).first()
+    if oauth_request is None:
+        err_msg = _("No request token found.")
+        return render_400(request, err_msg)
+    
+    if oauth_request.used:
+        return authorize_finish(request)
+    
+    if oauth_request.verifier is None:
+        orequest = oauthlib.common.Request(
+                uri=request.url,
+                http_method=request.method,
+                body=request.get_data(),
+                headers=request.headers
+                )
+        request_validator = GMGRequestValidator()
+        auth_endpoint = AuthorizationEndpoint(request_validator)
+        verifier = auth_endpoint.create_verifier(orequest, {})
+        oauth_request.verifier = verifier["oauth_verifier"]
+
+    oauth_request.user = request.user.id
+    oauth_request.save()
+
+    # find client & build context
+    client = Client.query.filter_by(id=oauth_request.client).first()
+
+    authorize_form = AuthorizeForm(WTFormData({
+            "oauth_token": oauth_request.token,
+            "oauth_verifier": oauth_request.verifier
+            }))
+
+    context = {
+            "user": request.user,
+            "oauth_request": oauth_request,
+            "client": client,
+            "authorize_form": authorize_form,
+            }
+
+
     # AuthorizationEndpoint
+    return render_to_response(
+            request,
+            "mediagoblin/api/authorize.html",
+            context
+            )
+            
+
+def authorize_finish(request):
+    """ Finishes the authorize """
+    _ = pass_to_ugettext
+    token = request.form["oauth_token"]
+    verifier = request.form["oauth_verifier"]
+    oauth_request = RequestToken.query.filter_by(token=token, verifier=verifier)
+    oauth_request = oauth_request.first()
     
+    if oauth_request is None:
+        # invalid token or verifier
+        err_msg = _("No request token found.")
+        return render_400(request, err_msg)
+
+    oauth_request.used = True
+    oauth_request.updated = datetime.datetime.now()
+    oauth_request.save()
+
+    if oauth_request.callback == "oob":
+        # out of bounds
+        context = {"oauth_request": oauth_request}
+        return render_to_response(
+                request,
+                "mediagoblin/api/oob.html",
+                context
+                )
+
+    # okay we need to redirect them then!
+    querystring = "?oauth_token={0}&oauth_verifier={1}".format(
+            oauth_request.token,
+            oauth_request.verifier
+            )
+
+    return redirect(
+            request,
+            querystring=querystring,
+            location=oauth_request.callback
+            )
 
 @csrf_exempt
 def access_token(request):
     """ Provides an access token based on a valid verifier and request token """ 
-    pass
+    try:
+        data = decode_request(request)
+    except ValueError:
+        error = "Could not decode data."
+        return json_response({"error": error}, status=400)
+
+    if data == "":
+        error = "Unknown Content-Type"
+        return json_response({"error": error}, status=400)
+
+    print "debug: %s" % data
index 8b57584d0ecba6f49a3bbbe82e87cd4ffedf68f9..0d813bf5cf31f6973a72401541eea05c7fbfac9f 100644 (file)
@@ -753,3 +753,10 @@ pre {
 #exif_additional_info table tr {
    margin-bottom: 10px;
 }
+
+p.verifier {
+    text-align:center;
+    font-size:50px;
+    none repeat scroll 0% 0% rgb(221, 221, 221);
+    padding: 1em 0px;
+}
diff --git a/mediagoblin/templates/mediagoblin/api/authorize.html b/mediagoblin/templates/mediagoblin/api/authorize.html
new file mode 100644 (file)
index 0000000..d0ec261
--- /dev/null
@@ -0,0 +1,56 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors.  See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block title -%}
+  {% trans %}Authorization{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+<h1>{% trans %}Authorize{% endtrans %}</h1>
+
+<p>
+    {% trans %}You are logged in as{% endtrans %}
+     <strong>{{user.username}}</strong>
+    <br /><br />
+    
+    {% trans %}Do you want to authorize {% endtrans %}
+    {% if client.application_name -%}
+        <em>{{ client.application_name }}</em>
+    {%- else -%}
+        <em>{% trans %}an unknown application{% endtrans %}</em>
+    {%- endif %}
+    {% trans %} to access your account? {% endtrans %}
+    <br /><br />
+    {% trans %}Applications with access to your account can: {% endtrans %}
+    <ul>
+        <li>{% trans %}Post new media as you{% endtrans %}</li>
+        <li>{% trans %}See your information (e.g profile, meida, etc...){% endtrans %}</li>
+        <li>{% trans %}Change your information{% endtrans %}</li>
+    </ul>
+    <br />
+
+    <form method="POST">
+        {{ csrf_token }}
+        {{ authorize_form.oauth_token }}
+        {{ authorize_form.oauth_verifier }}
+        <input type="submit" value="{% trans %}Authorize{% endtrans %}">
+    </form>
+</p>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/api/oob.html b/mediagoblin/templates/mediagoblin/api/oob.html
new file mode 100644 (file)
index 0000000..d290472
--- /dev/null
@@ -0,0 +1,33 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors.  See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block title -%}
+  {% trans %}Authorization Finished{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+<h1>{% trans %}Authorization Complete{% endtrans %}</h1>
+
+<h4>{% trans %}Copy and paste this into your client:{% endtrans %}</h4>
+
+<p class="verifier">
+   {{ oauth_request.verifier }}
+</p>
+{% endblock %}
index ed903ce00ae7962643bf582e3238423b0f7defb8..2c9e609d68700a4398db5c8e1f580c981927c442 100644 (file)
@@ -48,7 +48,7 @@ def decode_request(request):
     
     if request.content_type == json_encoded:
         data = json.loads(data)
-    elif request.content_type == form_encoded:
+    elif request.content_type == form_encoded or request.content_type == "":
         data = request.form
     else:
         data = ""