diff --git a/app/api/admin.py b/app/api/admin.py
index 14478d5..cde57ba 100644
--- a/app/api/admin.py
+++ b/app/api/admin.py
@@ -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)
diff --git a/app/api/handlers/__init__.py b/app/api/handlers/__init__.py
index 08641d5..8a31312 100644
--- a/app/api/handlers/__init__.py
+++ b/app/api/handlers/__init__.py
@@ -1,2 +1,3 @@
from v1 import *
from v2 import *
+from oauth import *
diff --git a/app/api/handlers/oauth.py b/app/api/handlers/oauth.py
new file mode 100644
index 0000000..9a56d1f
--- /dev/null
+++ b/app/api/handlers/oauth.py
@@ -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': '
The EVE API calendar is unavailable
', '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': 'No ops are currently scheduled
',
+ '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)
diff --git a/app/api/templates/api/view_tokens.html b/app/api/templates/api/view_tokens.html
new file mode 100644
index 0000000..f4d99cb
--- /dev/null
+++ b/app/api/templates/api/view_tokens.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block title %}Application Access{% endblock %}
+
+{% block content %}
+
+Application Access
+
+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).
+
+{% if tokens %}
+
+| Application | Granted Date/Time | Action |
+{% for token in tokens %}
+
+
| {{ token.consumer.name }} | | Revoke |
+
+{% endfor %}
+{% else %}
+You have not granted any applications access to your data
+{% endif %}
+
+{% endblock %}
diff --git a/app/api/urls.py b/app/api/urls.py
index 2db1166..89c938e 100755
--- a/app/api/urls.py
+++ b/app/api/urls.py
@@ -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\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.*)$', 'oauth_revoke_token', name='oauth-revoke-token'),
+)
+
+oauth_optimer_resource = Resource(handler=OAuthOpTimerHandler, **oauth)
+
+# API
+urlpatterns += patterns('',
+ url(r'^oauth/optimer/$', oauth_optimer_resource),
+)
+
diff --git a/app/api/views.py b/app/api/views.py
index 9665ce6..a160298 100644
--- a/app/api/views.py
+++ b/app/api/views.py
@@ -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)
diff --git a/app/conf/common.py b/app/conf/common.py
index c2051ce..413a37a 100644
--- a/app/conf/common.py
+++ b/app/conf/common.py
@@ -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 *
diff --git a/app/templates/base.html b/app/templates/base.html
index 2bfd197..21e1241 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -19,6 +19,9 @@
{% if "groups"|installed %}
Groups
{% endif %}
+ {% if "api"|installed %}
+ Application Access
+ {% endif %}
{% if "hr"|installed %}
HR
{% endif %}
diff --git a/app/templates/piston/authorize_token.html b/app/templates/piston/authorize_token.html
index fb4b9dc..cf68c64 100644
--- a/app/templates/piston/authorize_token.html
+++ b/app/templates/piston/authorize_token.html
@@ -8,6 +8,9 @@
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.
+ {{ consumer.name }}
+ {{ consumer.description }}
+