Switch to side-by-side view

--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -2,6 +2,7 @@
 from urllib import urlencode
 from pprint import pformat
 
+import bson
 from tg import expose, session, flash, redirect, validate, config
 from tg.decorators import with_trailing_slash, without_trailing_slash
 from pylons import c, g, request, response
@@ -11,7 +12,7 @@
 from allura.lib.security import require_authenticated, has_artifact_access
 from allura.lib import helpers as h
 from allura.lib import plugin
-from allura.lib.widgets import SubscriptionForm
+from allura.lib.widgets import SubscriptionForm, OAuthApplicationForm, OAuthRevocationForm
 from allura.lib import exceptions as exc
 from allura.controllers import BaseController
 
@@ -33,11 +34,14 @@
 
 class F(object):
     subscription_form=SubscriptionForm()
+    oauth_application_form = OAuthApplicationForm(action='register')
+    oauth_revocation_form = OAuthRevocationForm(action='revoke_oauth')
 
 class AuthController(BaseController):
 
     def __init__(self):
         self.prefs = PreferencesController()
+        self.oauth = OAuth()
 
     @expose('jinja:login.html')
     @with_trailing_slash
@@ -253,6 +257,7 @@
     def index(self, **kw):
         require_authenticated()
         c.form = F.subscription_form
+        c.revoke_access = F.oauth_revocation_form
         subscriptions = []
         for mb in M.Mailbox.query.find(dict(user_id=c.user._id)):
             try:
@@ -273,7 +278,10 @@
             except exc.NoSuchProjectError:
                 mb.delete() # project went away
         api_token = M.ApiToken.query.get(user_id=c.user._id)
-        return dict(subscriptions=subscriptions, api_token=api_token)
+        return dict(
+            subscriptions=subscriptions,
+            api_token=api_token,
+            authorized_applications=M.OAuthAccessToken.for_user(c.user))
 
     @h.vardec
     @expose()
@@ -338,3 +346,43 @@
         if tok is None: return
         tok.delete()
         redirect(request.referer)
+
+    @expose()
+    def revoke_oauth(self, _id=None):
+        tok = M.OAuthAccessToken.query.get(_id=bson.ObjectId(_id))
+        if tok is None:
+            flash('Invalid app ID', 'error')
+            redirect('.')
+        if tok.user_id != c.user._id:
+            flash('Invalid app ID', 'error')
+            redirect('.')
+        tok.delete()
+        flash('Application access revoked')
+        redirect('.')
+
+class OAuth(BaseController):
+
+    @expose('jinja:oauth_applications.html')
+    def index(self, **kw):
+        c.form = F.oauth_application_form
+        return dict(apps=M.OAuthConsumerToken.for_user(c.user))
+
+    @expose()
+    @validate(F.oauth_application_form, error_handler=index)
+    def register(self, application_name=None, application_description=None, **kw):
+        M.OAuthConsumerToken(name=application_name, description=application_description)
+        flash('Application registered')
+        redirect('.')
+
+    @expose()
+    def delete(self, id=None):
+        app = M.OAuthConsumerToken.query.get(_id=bson.ObjectId(id))
+        if app is None:
+            flash('Invalid app ID', 'error')
+            redirect('.')
+        if app.user_id != c.user._id:
+            flash('Invalid app ID', 'error')
+            redirect('.')
+        app.delete()
+        flash('Application deleted')
+        redirect('.')