From: xray7224 Date: Fri, 28 Jun 2013 16:59:32 +0000 (+0100) Subject: Working client registration X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=4990b47ce401dc86353a261825771a6811be4a8c;p=mediagoblin.git Working client registration --- diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 826d47ba..4c39c025 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -105,6 +105,29 @@ class User(Base, UserMixin): _log.info('Deleted user "{0}" account'.format(self.username)) +class Client(Base): + """ + Model representing a client - Used for API Auth + """ + __tablename__ = "core__clients" + + id = Column(Unicode, nullable=True, primary_key=True) + secret = Column(Unicode, nullable=False) + expirey = Column(DateTime, nullable=True) + application_type = Column(Unicode, nullable=False) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + updated = Column(DateTime, nullable=False, default=datetime.datetime.now) + + # optional stuff + redirect_uri = Column(Unicode, nullable=True) + logo_uri = Column(Unicode, nullable=True) + application_name = Column(Unicode, nullable=True) + + def __repr__(self): + return "".format(self.id) + + + class MediaEntry(Base, MediaEntryMixin): """ TODO: Consider fetching the media_files using join @@ -580,7 +603,7 @@ with_polymorphic( [ProcessingNotification, CommentNotification]) MODELS = [ - User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, + User, Client, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, Notification, CommentNotification, ProcessingNotification, CommentSubscription] diff --git a/mediagoblin/federation/views.py b/mediagoblin/federation/views.py index 097dc625..bfd58d27 100644 --- a/mediagoblin/federation/views.py +++ b/mediagoblin/federation/views.py @@ -14,21 +14,47 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from mediagoblin.tools.json import json_response +import json + +from mediagoblin.meddleware.csrf import csrf_exempt +from mediagoblin.tools.response import json_response +from mediagoblin.tools.crypto import random_string +from mediagoblin.db.models import Client # possible client types client_types = ["web", "native"] # currently what pump supports +@csrf_exempt def client_register(request): """ Endpoint for client registration """ - if request.method == "POST": - # new client registration - - return json_response({"dir":dir(request)}) + data = request.get_data() + if request.content_type == "application/json": + try: + data = json.loads(data) + except ValueError: + return json_response({"error":"Could not decode JSON"}) + else: + return json_response({"error":"Unknown Content-Type"}, status=400) - # check they haven't given us client_id or client_type, they're only used for updating - pass + if "type" not in data: + return json_response({"error":"No registration type provided"}, status=400) + + # generate the client_id and client_secret + client_id = random_string(22) # seems to be what pump uses + client_secret = random_string(43) # again, seems to be what pump uses + expirey = 0 # for now, lets not have it expire + expirey_db = None if expirey == 0 else expirey + client = Client( + id=client_id, + secret=client_secret, + expirey=expirey_db, + application_type=data["type"] + ) + client.save() - elif request.method == "PUT": - # updating client - pass + return json_response( + { + "client_id":client_id, + "client_secret":client_secret, + "expires_at":expirey, + }) diff --git a/mediagoblin/routing.py b/mediagoblin/routing.py index 986eb2ed..3a54aaa0 100644 --- a/mediagoblin/routing.py +++ b/mediagoblin/routing.py @@ -36,6 +36,7 @@ def get_url_map(): import mediagoblin.webfinger.routing import mediagoblin.listings.routing import mediagoblin.notifications.routing + import mediagoblin.federation.routing for route in PluginManager().get_routes(): add_route(*route) diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py index 1379d21b..917e674c 100644 --- a/mediagoblin/tools/crypto.py +++ b/mediagoblin/tools/crypto.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import base64 +import string import errno import itsdangerous import logging @@ -24,6 +26,9 @@ from mediagoblin import mg_globals _log = logging.getLogger(__name__) +# produces base64 alphabet +alphabet = string.ascii_letters + "-_" +base = len(alphabet) # Use the system (hardware-based) random number generator if it exists. # -- this optimization is lifted from Django @@ -111,3 +116,13 @@ def get_timed_signer_url(namespace): assert __itsda_secret is not None return itsdangerous.URLSafeTimedSerializer(__itsda_secret, salt=namespace) + +def random_string(length): + """ Returns a URL safe base64 encoded crypographically strong string """ + rstring = "" + for i in range(length): + n = getrandbits(6) # 6 bytes = 2^6 = 64 + n = divmod(n, base)[1] + rstring += alphabet[n] + + return rstring diff --git a/mediagoblin/tools/json.py b/mediagoblin/tools/json.py deleted file mode 100644 index a8437b82..00000000 --- a/mediagoblin/tools/json.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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 . - -import json - -from werkzeug.wrappers import Response - -def json_response(serializable, _disable_cors=False, *args, **kw): - ''' - Serializes a json objects and returns a werkzeug Response object with the - serialized value as the response body and Content-Type: application/json. - - :param serializable: A json-serializable object - - Any extra arguments and keyword arguments are passed to the - Response.__init__ method. - ''' - response = Response(json.dumps(serializable), *args, content_type='application/json', **kw) - - if not _disable_cors: - cors_headers = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With'} - for key, value in cors_headers.iteritems(): - response.headers.set(key, value) - - return response diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index 0be1f835..1fd242fb 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import json + import werkzeug.utils from werkzeug.wrappers import Response as wz_Response from mediagoblin.tools.template import render_template @@ -31,7 +33,6 @@ def render_to_response(request, template, context, status=200): render_template(request, template, context), status=status) - def render_error(request, status=500, title=_('Oops!'), err_msg=_('An error occured')): """Render any error page with a given error code, title and text body @@ -44,7 +45,6 @@ def render_error(request, status=500, title=_('Oops!'), {'err_code': status, 'title': title, 'err_msg': err_msg}), status=status) - def render_403(request): """Render a standard 403 page""" _ = pass_to_ugettext @@ -106,3 +106,26 @@ def redirect_obj(request, obj): Requires obj to have a .url_for_self method.""" return redirect(request, location=obj.url_for_self(request.urlgen)) + +def json_response(serializable, _disable_cors=False, *args, **kw): + ''' + Serializes a json objects and returns a werkzeug Response object with the + serialized value as the response body and Content-Type: application/json. + + :param serializable: A json-serializable object + + Any extra arguments and keyword arguments are passed to the + Response.__init__ method. + ''' + + response = wz_Response(json.dumps(serializable), *args, content_type='application/json', **kw) + + if not _disable_cors: + cors_headers = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With'} + for key, value in cors_headers.iteritems(): + response.headers.set(key, value) + + return response