diff options
Diffstat (limited to 'mediagoblin/plugins')
-rw-r--r-- | mediagoblin/plugins/oauth/__init__.py | 90 | ||||
-rw-r--r-- | mediagoblin/plugins/oauth/models.py | 58 | ||||
-rw-r--r-- | mediagoblin/plugins/oauth/views.py | 105 |
3 files changed, 253 insertions, 0 deletions
diff --git a/mediagoblin/plugins/oauth/__init__.py b/mediagoblin/plugins/oauth/__init__.py new file mode 100644 index 00000000..af39ae93 --- /dev/null +++ b/mediagoblin/plugins/oauth/__init__.py @@ -0,0 +1,90 @@ +# 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/>. + +import os +import logging + +from routes.route import Route +from webob import exc + +from mediagoblin.tools import pluginapi +from mediagoblin.tools.response import render_to_response +from mediagoblin.plugins.oauth.models import OAuthToken + +_log = logging.getLogger(__name__) + +PLUGIN_DIR = os.path.dirname(__file__) + + +def setup_plugin(): + config = pluginapi.get_config('mediagoblin.plugins.oauth') + + _log.info('Setting up OAuth...') + _log.debug('OAuth config: {0}'.format(config)) + + routes = [ + Route('mediagoblin.plugins.oauth.authorize', '/oauth/authorize', + controller='mediagoblin.plugins.oauth.views:authorize'), + Route('mediagoblin.plugins.oauth.test', '/api/test', + controller='mediagoblin.plugins.oauth.views:api_test'), + Route('mediagoblin.plugins.oauth.access_token', '/oauth/access_token', + controller='mediagoblin.plugins.oauth.views:access_token')] + + pluginapi.register_routes(routes) + pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates')) + + +class OAuthAuth(object): + ''' + An object with two significant methods, 'trigger' and 'run'. + + Using a similar object to this, plugins can register specific + authentication logic, for example the GET param 'access_token' for OAuth. + + - trigger: Analyze the 'request' argument, return True if you think you + can handle the request, otherwise return False + - run: The authentication logic, set the request.user object to the user + you intend to authenticate and return True, otherwise return False. + + If run() returns False, an HTTP 403 Forbidden error will be shown. + + You may also display custom errors, just raise them within the run() + method. + ''' + def __init__(self): + pass + + def trigger(self, request): + return True + + def __call__(self, request, *args, **kw): + access_token = request.GET.get('access_token') + if access_token: + token = OAuthToken.query.filter(OAuthToken.token == access_token)\ + .first() + + if not token: + return False + + request.user = token.user + + return True + + +hooks = { + 'setup': setup_plugin, + 'auth': OAuthAuth() + } diff --git a/mediagoblin/plugins/oauth/models.py b/mediagoblin/plugins/oauth/models.py new file mode 100644 index 00000000..295987c8 --- /dev/null +++ b/mediagoblin/plugins/oauth/models.py @@ -0,0 +1,58 @@ +# 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/>. + +from datetime import datetime, timedelta + +from mediagoblin.db.sql.base import Base +from mediagoblin.db.sql.models import User + +from sqlalchemy import ( + Column, Unicode, Integer, DateTime, ForeignKey) +from sqlalchemy.orm import relationship + + +class OAuthToken(Base): + __tablename__ = 'oauth__tokens' + + id = Column(Integer, primary_key=True) + created = Column(DateTime, nullable=False, + default=datetime.now) + expires = Column(DateTime, nullable=False, + default=lambda: datetime.now() + timedelta(days=30)) + token = Column(Unicode, index=True) + refresh_token = Column(Unicode, index=True) + + user_id = Column(Integer, ForeignKey(User.id), nullable=False, + index=True) + user = relationship(User) + + +class OAuthCode(Base): + __tablename__ = 'oauth__codes' + + id = Column(Integer, primary_key=True) + created = Column(DateTime, nullable=False, + default=datetime.now) + expires = Column(DateTime, nullable=False, + default=lambda: datetime.now() + timedelta(minutes=5)) + code = Column(Unicode, index=True) + + user_id = Column(Integer, ForeignKey(User.id), nullable=False, + index=True) + user = relationship(User) + + +MODELS = [OAuthToken, OAuthCode] diff --git a/mediagoblin/plugins/oauth/views.py b/mediagoblin/plugins/oauth/views.py new file mode 100644 index 00000000..7627b97a --- /dev/null +++ b/mediagoblin/plugins/oauth/views.py @@ -0,0 +1,105 @@ +# 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/>. + +import logging +import json + +from webob import exc, Response +from urllib import urlencode +from uuid import uuid4 +from datetime import datetime +from functools import wraps + +from mediagoblin.tools import pluginapi +from mediagoblin.tools.response import render_to_response +from mediagoblin.decorators import require_active_login +from mediagoblin.messages import add_message, SUCCESS, ERROR +from mediagoblin.tools.translate import pass_to_ugettext as _ +from mediagoblin.plugins.oauth.models import OAuthCode, OAuthToken + +_log = logging.getLogger(__name__) + + +@require_active_login +def authorize(request): + # TODO: Check if allowed + + # Client is allowed by the user + if True or already_authorized: + # Generate a code + # Save the code, the client will later use it to obtain an access token + # Redirect the user agent to the redirect_uri with the code + + if not 'redirect_uri' in request.GET: + add_message(request, ERROR, _('No redirect_uri found')) + + code = OAuthCode() + code.code = unicode(uuid4()) + code.user = request.user + code.save() + + redirect_uri = ''.join([ + request.GET.get('redirect_uri'), + '?', + urlencode({'code': code.code})]) + + _log.debug('Redirecting to {0}'.format(redirect_uri)) + + return exc.HTTPFound(location=redirect_uri) + else: + # Show prompt to allow client to access data + # - on accept: send the user agent back to the redirect_uri with the + # code parameter + # - on deny: send the user agent back to the redirect uri with error + # information + pass + return render_to_response(request, 'oauth/base.html', {}) + + +def access_token(request): + if request.GET.get('code'): + code = OAuthCode.query.filter(OAuthCode.code == request.GET.get('code'))\ + .first() + + if code: + token = OAuthToken() + token.token = unicode(uuid4()) + token.user = code.user + token.save() + + access_token_data = { + 'access_token': token.token, + 'token_type': 'what_do_i_use_this_for', # TODO + 'expires_in': + (token.expires - datetime.now()).total_seconds(), + 'refresh_token': 'This should probably be safe'} + return Response(json.dumps(access_token_data)) + + error_data = { + 'error': 'Incorrect code'} + return Response(json.dumps(error_data)) + + +@pluginapi.api_auth +def api_test(request): + if not request.user: + return exc.HTTPForbidden() + + user_data = { + 'username': request.user.username, + 'email': request.user.email} + + return Response(json.dumps(user_data)) |