Add OAuth models, plugin DB migrations, api_auth
This commit is contained in:
90
mediagoblin/plugins/oauth/__init__.py
Normal file
90
mediagoblin/plugins/oauth/__init__.py
Normal file
@@ -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()
|
||||
}
|
||||
58
mediagoblin/plugins/oauth/models.py
Normal file
58
mediagoblin/plugins/oauth/models.py
Normal file
@@ -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]
|
||||
105
mediagoblin/plugins/oauth/views.py
Normal file
105
mediagoblin/plugins/oauth/views.py
Normal file
@@ -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))
|
||||
Reference in New Issue
Block a user