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 %}
  • +
+
+ +
+ {{ csrf_token }} + {{ authorize_form.oauth_token }} + {{ authorize_form.oauth_verifier }} + +
+

+{% 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