From 405aa45adc14d3c67a120618ecc0ae792f5881de Mon Sep 17 00:00:00 2001
From: xray7224
Date: Wed, 10 Jul 2013 15:49:59 +0100
Subject: [PATCH] Adds more support for oauth - access_token & decorators still
to do
---
mediagoblin/db/models.py | 2 +-
mediagoblin/federation/forms.py | 8 +
mediagoblin/federation/views.py | 163 ++++++++++++++++--
mediagoblin/static/css/base.css | 7 +
.../templates/mediagoblin/api/authorize.html | 56 ++++++
.../templates/mediagoblin/api/oob.html | 33 ++++
mediagoblin/tools/request.py | 2 +-
7 files changed, 259 insertions(+), 12 deletions(-)
create mode 100644 mediagoblin/federation/forms.py
create mode 100644 mediagoblin/templates/mediagoblin/api/authorize.html
create mode 100644 mediagoblin/templates/mediagoblin/api/oob.html
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index 8a71aa09..b6ae533e 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -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
index 00000000..39d6fc27
--- /dev/null
+++ b/mediagoblin/federation/forms.py
@@ -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")
diff --git a/mediagoblin/federation/views.py b/mediagoblin/federation/views.py
index 6c000855..9559df10 100644
--- a/mediagoblin/federation/views.py
+++ b/mediagoblin/federation/views.py
@@ -14,15 +14,22 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-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
diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css
index 8b57584d..0d813bf5 100644
--- a/mediagoblin/static/css/base.css
+++ b/mediagoblin/static/css/base.css
@@ -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
index 00000000..d0ec2616
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/api/authorize.html
@@ -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 .
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block title -%}
+ {% trans %}Authorization{% endtrans %} — {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+{% trans %}Authorize{% endtrans %}
+
+
+ {% trans %}You are logged in as{% endtrans %}
+ {{user.username}}
+
+
+ {% trans %}Do you want to authorize {% endtrans %}
+ {% if client.application_name -%}
+ {{ client.application_name }}
+ {%- else -%}
+ {% trans %}an unknown application{% endtrans %}
+ {%- endif %}
+ {% trans %} to access your account? {% endtrans %}
+
+ {% trans %}Applications with access to your account can: {% endtrans %}
+
+ - {% trans %}Post new media as you{% endtrans %}
+ - {% trans %}See your information (e.g profile, meida, etc...){% endtrans %}
+ - {% trans %}Change your information{% endtrans %}
+
+
+
+
+
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/api/oob.html b/mediagoblin/templates/mediagoblin/api/oob.html
new file mode 100644
index 00000000..d290472a
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/api/oob.html
@@ -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 .
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block title -%}
+ {% trans %}Authorization Finished{% endtrans %} — {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+{% trans %}Authorization Complete{% endtrans %}
+
+{% trans %}Copy and paste this into your client:{% endtrans %}
+
+
+ {{ oauth_request.verifier }}
+
+{% endblock %}
diff --git a/mediagoblin/tools/request.py b/mediagoblin/tools/request.py
index ed903ce0..2c9e609d 100644
--- a/mediagoblin/tools/request.py
+++ b/mediagoblin/tools/request.py
@@ -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 = ""
--
2.25.1