OAuth Support

This commit is contained in:
2011-06-29 11:38:05 +01:00
parent c96d059f04
commit b2dbea0f5f
9 changed files with 216 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from api.models import AuthAPIKey, AuthAPILog
from piston.models import Consumer, Token
class AuthAPIKeyAdmin(admin.ModelAdmin):
list_display = ('key', 'name', 'url', 'active')
@@ -18,3 +18,5 @@ class AuthAPILogAdmin(admin.ModelAdmin):
admin.site.register(AuthAPIKey, AuthAPIKeyAdmin)
admin.site.register(AuthAPILog, AuthAPILogAdmin)
admin.site.register(Consumer, admin.ModelAdmin)
admin.site.register(Token, admin.ModelAdmin)

View File

@@ -1,2 +1,3 @@
from v1 import *
from v2 import *
from oauth import *

120
app/api/handlers/oauth.py Normal file
View File

@@ -0,0 +1,120 @@
import re
from datetime import datetime
from xml.dom import minidom
from operator import itemgetter
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.models import User
from django.http import HttpResponse
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404
from django.conf import settings
from piston.handler import BaseHandler
from piston.utils import rc, throttle
from api.models import AuthAPIKey, AuthAPILog
from eve_proxy.models import CachedDocument
from eve_proxy.exceptions import *
from eve_api.app_defines import *
from eve_api.models import EVEAccount, EVEPlayerCharacter
class OAuthEveAPIHandler(BaseHandler):
allowed_methods = ('GET')
exclude = ('api_key')
def read(self, request):
if request.user:
s = EVEAccount.objects.filter(user=request.user)
return {'keys': s.values('api_user_id', 'user_id', 'api_status', 'api_last_updated')}
class OAuthOpTimerHandler(BaseHandler):
allowed_methods = ('GET')
def read(self, request, id=None):
objs = EVEAccount.objects.filter(user=request.user, api_keytype=API_KEYTYPE_FULL)
if not objs.count():
objs = [get_object_or_404(EVEAccount, pk=settings.FULL_API_USER_ID)]
events = []
for obj in objs:
params = {'userID': obj.pk, 'apiKey': obj.api_key, 'characterID': settings.FULL_API_CHARACTER_ID}
error_doc = {'ops': [{'startsIn': -1, 'eventID': 0, 'ownerName': '', 'eventDate': '', 'eventTitle': '<div style="text-align:center">The EVE API calendar is unavailable</div>', 'duration': 0, 'isImportant': 0, 'eventText': 'Fuck CCP tbqh imho srsly', 'endsIn':-1, 'forumLink': ''}]}
try:
cached_doc = CachedDocument.objects.api_query('/char/UpcomingCalendarEvents.xml.aspx', params, timeout=10, service="Optimer")
except DocumentRetrievalError:
return error_doc
dom = minidom.parseString(cached_doc.body.encode('utf-8'))
if dom.getElementsByTagName('error'):
error_doc['raw_xml'] = cached_doc.body
return error_doc
for node in dom.getElementsByTagName('rowset')[0].childNodes:
if node.nodeType == 1:
ownerID = node.getAttribute('ownerID')
if ownerID != '1':
date = node.getAttribute('eventDate')
dt = datetime.strptime(date, '%Y-%m-%d %H:%M:%S')
now = datetime.utcnow()
startsIn = int(dt.strftime('%s')) - int(now.strftime('%s'))
duration = int(node.getAttribute('duration'))
fid = re.search('topic=[\d]+', node.getAttribute('eventText'))
if fid:
forumlink = 'http://forum.pleaseignore.com/index.php?%s' % fid.group(0)
else:
forumlink = ''
#In case people forget to set a duration, we'll give a default of 1 hour
if duration == 0:
duration = 60
endsIn = startsIn + (duration * 60)
if startsIn < 0:
startsIn = 0
if endsIn > 0:
event = {
'startsIn': startsIn,
'eventID': node.getAttribute('eventID'),
'ownerName': node.getAttribute('ownerName'),
'eventDate': date,
'eventTitle': node.getAttribute('eventTitle'),
'duration': duration,
'isImportant': node.getAttribute('importance'),
'eventText': node.getAttribute('eventText'),
'endsIn':endsIn,
'forumLink': forumlink}
events.append(event)
if len(events) == 0:
return {'ops':[{
'startsIn': -1,
'eventID': 0,
'ownerName': '',
'eventDate': '',
'eventTitle': '<div style="text-align:center">No ops are currently scheduled</div>',
'duration': 0,
'isImportant': 0,
'eventText': 'Add ops using EVE-Gate or the in-game calendar',
'endsIn':-1,
'forumLink': ''}]}
else:
events.sort(key=itemgetter('startsIn'))
return {'ops': events, 'doc_time': cached_doc.time_retrieved, 'cache_until': cached_doc.cached_until, 'current_time': datetime.utcnow() }
class CharacterHandler(BaseHandler):
allowed_methods = ('GET')
fields = ('id', 'name', ('corporation', ('id', 'name', ('alliance', ('id', 'name')))), 'corporation_date', 'balance', 'total_sp', 'security_status', 'director', 'skillset')
@classmethod
def skillset(cls, instance):
return instance.eveplayercharacterskill_set.all().values('skill__id', 'skill__name', 'level', 'skillpoints')
def read(self, request):
return EVEPlayerCharacter.objects.filter(eveaccount__user=request.user)

View File

@@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block title %}Application Access{% endblock %}
{% block content %}
<h1>Application Access</h1>
<p>This is the list of external applications that can access your data stored on Auth, this includes any EVE API data and also any linked information (Reddit accounts, service accounts).</p>
{% if tokens %}
<table>
<tr><th>Application</th><th>Granted Date/Time</th><th>Action</th></tr>
{% for token in tokens %}
<tr>
<tr><td>{{ token.consumer.name }}</td><td></td><td><a href="{% url oauth-revoke-token token.key %}">Revoke</a></td>
</tr>
{% endfor %}
{% else %}
<p><b>You have not granted any applications access to your data</b></p>
{% endif %}
{% endblock %}

View File

@@ -1,5 +1,5 @@
from django.conf.urls.defaults import *
from piston.authentication import NoAuthentication
from piston.authentication import NoAuthentication, OAuthAuthentication
from api.resource import SentryResource as Resource
from api.auth import APIKeyAuthentication
@@ -7,6 +7,7 @@ from api.handlers import *
noauth = {'authentication': NoAuthentication() }
apikeyauth = {'authentication': APIKeyAuthentication() }
oauth = {'authentication': OAuthAuthentication() }
# v1 APIs
user_resource = Resource(handler=UserHandler, **apikeyauth)
@@ -19,17 +20,6 @@ characters_resource = Resource(handler=CharacterHandler, **apikeyauth)
announce_resource = Resource(handler=AnnounceHandler, **apikeyauth)
urlpatterns = patterns('',
url(r'^user/$', user_resource),
url(r'^login/$', login_resource),
url(r'^eveapi/$', eveapi_resource),
url(r'^eveapi/', eveapiproxy_resource, name='api-eveapiproxy'),
url(r'^character/$', characters_resource),
url(r'^optimer/$', optimer_resource),
url(r'^blacklist/$', blacklist_resource),
url(r'^announce/$', announce_resource),
)
urlpatterns += patterns('',
url(r'^1.0/user/$', user_resource),
url(r'^1.0/login/$', login_resource),
url(r'^1.0/eveapi/$', eveapi_resource),
@@ -50,3 +40,23 @@ urlpatterns += patterns('',
url(r'^2.0/proxy/', v2_eveapiproxy_resource, name='v2-api-eveapiproxy'),
url(r'^2.0/user/(?P<userid>\d+)/$', v2_user_resource, name='v2-api-user'),
)
# OAuth
urlpatterns += patterns('piston.authentication',
url(r'^oauth/request_token/$','oauth_request_token'),
url(r'^oauth/authorize/$','oauth_user_auth'),
url(r'^oauth/access_token/$','oauth_access_token'),
)
urlpatterns += patterns('api.views',
url(r'^oauth/tokens/$', 'oauth_list_tokens', name='oauth-list-tokens'),
url(r'^oauth/tokens/(?P<key>.*)$', 'oauth_revoke_token', name='oauth-revoke-token'),
)
oauth_optimer_resource = Resource(handler=OAuthOpTimerHandler, **oauth)
# API
urlpatterns += patterns('',
url(r'^oauth/optimer/$', oauth_optimer_resource),
)

View File

@@ -1,5 +1,39 @@
from django.http import HttpResponse
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from piston import forms
from piston.models import Token
def oauth_callback_view(request, other):
return HttpResponse("The application you allowed access to didn't request a callback, thats odd and should be fixed")
def oauth_callback(request, other):
return HttpResponse('Fake callback view.')
@login_required
def oauth_auth_view(request, token, callback, params):
form = forms.OAuthAuthenticationForm(initial={
'oauth_token': token.key,
'oauth_callback': token.get_callback_url() or callback,
})
return render_to_response('piston/authorize_token.html',
{ 'form': form, 'consumer': token.consumer, 'user': token.user, 'request': request }, context_instance=RequestContext(request))
@login_required
def oauth_list_tokens(request):
# List all Access tokens
tokens = Token.objects.filter(token_type=Token.ACCESS)
return render_to_response('api/view_tokens.html', { 'tokens': tokens, 'request': request }, context_instance=RequestContext(request))
@login_required
def oauth_revoke_token(request, key):
token = get_object_or_404(Token, key=key)
if token.user == request.user:
token.delete()
messages.success(request, "Access for %s has been revoked" % token.consumer.name, fail_silently=True)
return redirect(oauth_list_tokens)

View File

@@ -87,6 +87,11 @@ AUTH_PROFILE_MODULE = 'sso.SSOUser'
LOGIN_REDIRECT_URL = "/profile"
LOGIN_URL = "/login"
### OAuth
OAUTH_AUTH_VIEW = 'api.views.oauth_auth_view'
OAUTH_CALLBACK_VIEW = 'api.views.oauth_callback_view'
### Celery Schedule
from celeryschedule import *

View File

@@ -19,6 +19,9 @@
{% if "groups"|installed %}
<li><a href="{% url groups.views.group_list %}">Groups</a></li>
{% endif %}
{% if "api"|installed %}
<li><a href="{% url oauth-list-tokens %}">Application Access</a></li>
{% endif %}
{% if "hr"|installed %}
<li><a href="{% url hr.views.index %}">HR</a></li>
{% endif %}

View File

@@ -8,6 +8,9 @@
<p>You have come here because you are in the process of allowing a external application to access your private Auth data. If you are not, then please close this window. Otherwise, please confirm below if you wish to give access to your private Auth data. This can be revoked at any time from the main Auth panel.</p>
<p><b>{{ consumer.name }}</b></p>
<p>{{ consumer.description }}</p>
<form action="{% url piston.authentication.oauth_user_auth %}" method="POST">
<table>
{{ form.as_table }}