From 850cba472673d34807051af44564da233fc5a7d3 Mon Sep 17 00:00:00 2001
From: Patrick Uiterwijk <puiterwijk@redhat.com>
Date: Feb 17 2017 13:45:54 +0000
Subject: [PATCH 1/2] Allow provider to act on revoking consent


Signed-off-by: Patrick Uiterwijk <puiterwijk@redhat.com>

---

diff --git a/ipsilon/providers/common.py b/ipsilon/providers/common.py
index 5ec369b..7fd357e 100644
--- a/ipsilon/providers/common.py
+++ b/ipsilon/providers/common.py
@@ -85,6 +85,9 @@ class ProviderBase(ConfigHelper, PluginObject):
     def consent_to_display(self, consentdata):
         raise NotImplementedError
 
+    def revoke_consent(self, user, clientid):
+        return True
+
 
 class ProviderPageBase(Page):
 
diff --git a/ipsilon/user/common.py b/ipsilon/user/common.py
index 82cc4b7..2d89028 100644
--- a/ipsilon/user/common.py
+++ b/ipsilon/user/common.py
@@ -22,6 +22,13 @@ class UserPortalConsent(UserPortalPage):
     def revoke(self, provider, clientid):
         us = UserSession()
         user = us.get_user()
+
+        provname = provider
+        provmod = self._site['provider_config'].available.get(provname,
+                                                              None)
+        if provmod is not None:
+            if not provmod.revoke_consent(user.name, clientid):
+                raise Exception('Provider refused to revoke')
         user.revoke_consent(provider, clientid)
         raise cherrypy.HTTPRedirect(self._master.url)
     revoke.public_function = True

From 433cb51bba6b93c00c5b991bde6564f395dbee49 Mon Sep 17 00:00:00 2001
From: Patrick Uiterwijk <puiterwijk@redhat.com>
Date: Feb 17 2017 13:46:24 +0000
Subject: [PATCH 2/2] Make OpenID Connect revoke active tokens when consent is revoked


Signed-off-by: Patrick Uiterwijk <puiterwijk@redhat.com>

---

diff --git a/ipsilon/providers/openidc/store.py b/ipsilon/providers/openidc/store.py
index 21d5354..75cec4b 100644
--- a/ipsilon/providers/openidc/store.py
+++ b/ipsilon/providers/openidc/store.py
@@ -289,6 +289,18 @@ class OpenIDCStore(Store):
 
         self.del_unique_data('token', token_id)
 
+    def revokeConsent(self, username, client_id):
+        data = self.get_unique_data('token', name='username', value=username)
+
+        removed_token = False
+        for uuid in data.keys():
+            token = self.get_unique_data('token', uuid)[uuid]
+            if token['client_id'] == client_id:
+                self.invalidateToken(uuid)
+                removed_token = True
+
+        return removed_token
+
     def storeUserInfo(self, userinfo):
         to_store = {}
         for key in userinfo:
diff --git a/ipsilon/providers/openidcp.py b/ipsilon/providers/openidcp.py
index 58ba86e..3bb1c81 100644
--- a/ipsilon/providers/openidcp.py
+++ b/ipsilon/providers/openidcp.py
@@ -223,6 +223,9 @@ Provides OpenID Connect authentication infrastructure. """
 
         return d
 
+    def revoke_consent(self, user, clientid):
+        return self.datastore.revokeConsent(user, clientid)
+
     def on_reconfigure(self):
         super(IdpProvider, self).on_reconfigure()
         self.init_idp()
diff --git a/tests/openidc.py b/tests/openidc.py
index 2cdcb69..b28c6f3 100755
--- a/tests/openidc.py
+++ b/tests/openidc.py
@@ -257,7 +257,7 @@ if __name__ == '__main__':
             'amr': json.dumps([]),
             'acr': '0'
         }
-        token = check_info_results(page.text, expect)
+        old_token = check_info_results(page.text, expect)
     except ValueError, e:
         print >> sys.stderr, " ERROR: %s" % repr(e)
         sys.exit(1)
@@ -279,7 +279,7 @@ if __name__ == '__main__':
     try:
         page = sess.revoke_all_consent(idpname)
     except ValueError, e:
-        print >> sys.stderr, "" % repr(e)
+        print >> sys.stderr, " ERROR: %s" % repr(e)
         sys.exit(1)
     print " SUCCESS"
 
@@ -289,6 +289,17 @@ if __name__ == '__main__':
                                'https://127.0.0.11:45081/sp/redirect_uri?log'
                                'out=https%3A%2F%2F127.0.0.11%3A45081%2Fsp%2F',
                                require_consent=True)
+        h = hashlib.sha256()
+        h.update('127.0.0.11')
+        h.update(user)
+        h.update('testcase')
+        expect = {
+            'sub': h.hexdigest(),
+            'iss': 'https://127.0.0.10:45080/idp1/openidc/',
+            'amr': json.dumps([]),
+            'acr': '0'
+        }
+        new_token = check_info_results(page.text, expect)
     except ValueError, e:
         print >> sys.stderr, " ERROR: %s" % repr(e)
         sys.exit(1)
@@ -309,13 +320,13 @@ if __name__ == '__main__':
     try:
         # Testing token without client auth
         r = requests.post('https://127.0.0.10:45080/idp1/openidc/TokenInfo',
-                          data={'token': token['access_token']})
+                          data={'token': new_token['access_token']})
         if r.status_code != 401:
             raise Exception('No 401 provided')
 
         # Testing token where we removed part of token ID
         r = requests.post('https://127.0.0.10:45080/idp1/openidc/TokenInfo',
-                          data={'token': token['access_token'][1:],
+                          data={'token': new_token['access_token'][1:],
                                 'client_id': reg_resp['client_id'],
                                 'client_secret': reg_resp['client_secret']})
         r.raise_for_status()
@@ -325,7 +336,7 @@ if __name__ == '__main__':
 
         # Testing token where we rempoved part of check string
         r = requests.post('https://127.0.0.10:45080/idp1/openidc/TokenInfo',
-                          data={'token': token['access_token'][:-1],
+                          data={'token': new_token['access_token'][:-1],
                                 'client_id': reg_resp['client_id'],
                                 'client_secret': reg_resp['client_secret']})
         r.raise_for_status()
@@ -335,7 +346,7 @@ if __name__ == '__main__':
 
         # Testing valid token
         r = requests.post('https://127.0.0.10:45080/idp1/openidc/TokenInfo',
-                          data={'token': token['access_token'],
+                          data={'token': new_token['access_token'],
                                 'client_id': reg_resp['client_id'],
                                 'client_secret': reg_resp['client_secret']})
         r.raise_for_status()
@@ -359,10 +370,25 @@ if __name__ == '__main__':
         if len(info['scope']) != 0:
             raise Exception('Unexpected scopes found: %s' % info['scope'])
 
+        # Testing previously revoked token
+        r = requests.post('https://127.0.0.10:45080/idp1/openidc/TokenInfo',
+                          data={'token': old_token['access_token'],
+                                'client_id': reg_resp['client_id'],
+                                'client_secret': reg_resp['client_secret']})
+        r.raise_for_status()
+        info = r.json()
+        if 'error' in info:
+            raise Exception('Token introspection returned error: %s'
+                            % info['error'])
+        if info['active']:
+            raise Exception('Revoked token active')
+        if len(info) != 1:
+            raise Exception('Token contained more info then inactive')
+
         # Delete test client and then try to use it
         sess.delete_oidc_client(idpname, reg_resp['client_id'])
         r = requests.post('https://127.0.0.10:45080/idp1/openidc/TokenInfo',
-                          data={'token': token['access_token'],
+                          data={'token': new_token['access_token'],
                                 'client_id': reg_resp['client_id'],
                                 'client_secret': reg_resp['client_secret']})
         if r.status_code != 400:
@@ -381,7 +407,7 @@ if __name__ == '__main__':
 
         # Testing valid token
         r = requests.post('https://127.0.0.10:45080/idp1/openidc/UserInfo',
-                          data={'access_token': token['access_token']})
+                          data={'access_token': new_token['access_token']})
         r.raise_for_status()
         info = r.json()
         if 'sub' not in info:

