From: xray7224 Date: Thu, 11 Jul 2013 16:58:58 +0000 (+0100) Subject: Finishes most of oauth, just decorator to complete X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=2b60a56cbec44f789ee2efe71294979d7784515c;p=mediagoblin.git Finishes most of oauth, just decorator to complete --- diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index ece222f5..ce26e46c 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -22,7 +22,8 @@ from werkzeug.exceptions import Forbidden, NotFound from mediagoblin import mg_globals as mgg from mediagoblin import messages from mediagoblin.db.models import MediaEntry, User -from mediagoblin.tools.response import redirect, render_404 +from mediagoblin.tools.request import decode_authorization_header +from mediagoblin.tools.response import json_response, redirect, render_404 from mediagoblin.tools.translate import pass_to_ugettext as _ @@ -268,3 +269,16 @@ def auth_enabled(controller): return controller(request, *args, **kwargs) return wrapper + +def oauth_requeired(controller): + """ Used to wrap API endpoints where oauth is required """ + @wraps(controller) + def wrapper(request, *args, **kwargs): + data = request.headers + authorization = decode_authorization_header(data) + + if authorization == dict(): + error = "Missing required parameter." + return json_response({"error": error}, status=400) + + diff --git a/mediagoblin/federation/views.py b/mediagoblin/federation/views.py index 9559df10..a6dcc79b 100644 --- a/mediagoblin/federation/views.py +++ b/mediagoblin/federation/views.py @@ -18,14 +18,15 @@ import datetime import oauthlib.common from oauthlib.oauth1 import (AuthorizationEndpoint, RequestValidator, - RequestTokenEndpoint) + RequestTokenEndpoint, AccessTokenEndpoint) 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.request import decode_request, decode_authorization_header from mediagoblin.tools.response import (render_to_response, redirect, - json_response, render_400) + json_response, render_400, + form_response) from mediagoblin.tools.crypto import random_string from mediagoblin.tools.validator import validate_email, validate_url from mediagoblin.db.models import User, Client, RequestToken, AccessToken @@ -184,7 +185,7 @@ class GMGRequestValidator(RequestValidator): def save_request_token(self, token, request): """ Saves request token in db """ - client_id = self.POST[u"Authorization"][u"oauth_consumer_key"] + client_id = self.POST[u"oauth_consumer_key"] request_token = RequestToken( token=token["oauth_token"], @@ -200,17 +201,21 @@ class GMGRequestValidator(RequestValidator): 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"], + secret=token["oauth_token_secret"], ) - access_token.request_token = request.body["oauth_token"] - access_token.user = token["user"].id + access_token.request_token = request.oauth_token + request_token = RequestToken.query.filter_by(token=request.oauth_token).first() + access_token.user = request_token.user access_token.save() + def get_realms(*args, **kwargs): + """ Currently a stub - called when making AccessTokens """ + return list() + @csrf_exempt def request_token(request): """ Returns request token """ @@ -224,45 +229,32 @@ def request_token(request): 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) + if not data and request.headers: + data = request.headers + + data = dict(data) # mutableifying + authorization = decode_authorization_header(data) - # Convert 'Authorization' to a dictionary - authorization = {} - for item in data["Authorization"].split(","): - key, value = item.split("=", 1) - authorization[key] = value - data[u"Authorization"] = authorization - if "oauth_consumer_key" not in data[u"Authorization"]: + if authorization == dict() or u"oauth_consumer_key" not in authorization: error = "Missing required parameter." - return json_respinse({"error": error}, status=400) + return json_response({"error": error}, status=400) # check the client_id - client_id = data[u"Authorization"][u"oauth_consumer_key"] + client_id = authorization[u"oauth_consumer_key"] client = Client.query.filter_by(id=client_id).first() if client is None: # client_id is invalid error = "Invalid client_id" return json_response({"error": error}, status=400) - request_validator = GMGRequestValidator(data) + # make request token and return to client + request_validator = GMGRequestValidator(authorization) rv = RequestTokenEndpoint(request_validator) 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) + return form_response(tokens) class WTFormData(dict): """ @@ -375,14 +367,18 @@ def authorize_finish(request): @csrf_exempt def access_token(request): """ Provides an access token based on a valid verifier and request token """ - try: - data = decode_request(request) - except ValueError: - error = "Could not decode data." - return json_response({"error": error}, status=400) + data = request.headers - if data == "": - error = "Unknown Content-Type" + parsed_tokens = decode_authorization_header(data) + + if parsed_tokens == dict() or "oauth_token" not in parsed_tokens: + error = "Missing required parameter." return json_response({"error": error}, status=400) - print "debug: %s" % data + + request.oauth_token = parsed_tokens["oauth_token"] + request_validator = GMGRequestValidator(data) + av = AccessTokenEndpoint(request_validator) + tokens = av.create_access_token(request, {}) + return form_response(tokens) + diff --git a/mediagoblin/tools/request.py b/mediagoblin/tools/request.py index 2c9e609d..0c0fc557 100644 --- a/mediagoblin/tools/request.py +++ b/mediagoblin/tools/request.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import re import json import logging from mediagoblin.db.models import User @@ -25,6 +26,9 @@ _log = logging.getLogger(__name__) form_encoded = "application/x-www-form-urlencoded" json_encoded = "application/json" +# Regex for Authorization header +auth_header_re = re.compile('(\w+)[:=] ?"?(\w+)"?') + def setup_user_in_request(request): """ Examine a request and tack on a request.user parameter if that's @@ -53,3 +57,9 @@ def decode_request(request): else: data = "" return data + +def decode_authorization_header(header): + """ Decodes a HTTP Authorization Header to python dictionary """ + authorization = header.get("Authorization", "") + tokens = dict(auth_header_re.findall(authorization)) + return tokens diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index db8fc388..b0401e08 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -138,3 +138,22 @@ def json_response(serializable, _disable_cors=False, *args, **kw): response.headers.set(key, value) return response + +def form_response(data, *args, **kwargs): + """ + Responds using application/x-www-form-urlencoded and returns a werkzeug + Response object with the data argument as the body + and 'application/x-www-form-urlencoded' as the Content-Type. + + Any extra arguments and keyword arguments are passed to the + Response.__init__ method. + """ + + response = wz_Response( + data, + content_type="application/x-www-form-urlencoded", + *args, + **kwargs + ) + + return response