From 850cba472673d34807051af44564da233fc5a7d3 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Feb 17 2017 13:45:54 +0000 Subject: [PATCH 1/2] Allow provider to act on revoking consent Signed-off-by: Patrick Uiterwijk --- 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 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 --- 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: