Skip to content

Commit

Permalink
Invalidate user tokens when password is changed
Browse files Browse the repository at this point in the history
Fixes bug 996595

This commit will cause all valid tokens to be deleted for a user
who's password is changed (implemented for the sql and kvs backends)

Change-Id: I6ad7da8957b7041983a3fc91d9ba9368667d06ac
  • Loading branch information
derekhiggins authored and apevec committed Jun 14, 2012
1 parent aa7e7b9 commit ea03d05
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 1 deletion.
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -25,6 +25,7 @@ Darren Birkett <darren.birkett@gmail.com>
dcramer <david.cramer@rackspace.com>
Dean Troyer <dtroyer@gmail.com>
Deepak Garg <deepakgarg.iitg@gmail.com>
Derek Higgins <derekh@redhat.com>
Devin Carlen <devin.carlen@gmail.com>
Dolph Mathews <dolph.mathews@gmail.com>
Dolph Mathews <dolph.mathews@rackspace.com>
Expand Down
14 changes: 13 additions & 1 deletion keystone/identity/core.py
Expand Up @@ -24,12 +24,15 @@
from keystone import exception
from keystone import policy
from keystone import token
from keystone.common import logging
from keystone.common import manager
from keystone.common import wsgi


CONF = config.CONF

LOG = logging.getLogger(__name__)


class Manager(manager.Manager):
"""Default pivot point for the Identity backend.
Expand Down Expand Up @@ -418,7 +421,16 @@ def set_user_enabled(self, context, user_id, user):
return self.update_user(context, user_id, user)

def set_user_password(self, context, user_id, user):
return self.update_user(context, user_id, user)
user_ref = self.update_user(context, user_id, user)
try:
for token_id in self.token_api.list_tokens(context, user_id):
self.token_api.delete_token(context, token_id)
except exception.NotImplemented:
# The password has been changed but tokens remain valid for
# backends that can't list tokens for users
LOG.warning('Password changed for %s, but existing tokens remain '
'valid' % user_id)
return user_ref

def update_user_tenant(self, context, user_id, user):
"""Update the default tenant."""
Expand Down
15 changes: 15 additions & 0 deletions keystone/token/backends/kvs.py
Expand Up @@ -44,3 +44,18 @@ def delete_token(self, token_id):
return self.db.delete('token-%s' % token_id)
except KeyError:
raise exception.TokenNotFound(token_id=token_id)

def list_tokens(self, user_id):
tokens = []
now = datetime.datetime.utcnow()
for token, user_ref in self.db.items():
if not token.startswith('token-'):
continue
if 'user' not in user_ref:
continue
if user_ref['user'].get('id') != user_id:
continue
if user_ref.get('expires') and user_ref.get('expires') < now:
continue
tokens.append(token.split('-', 1)[1])
return tokens
14 changes: 14 additions & 0 deletions keystone/token/backends/sql.py
Expand Up @@ -81,3 +81,17 @@ def delete_token(self, token_id):
with session.begin():
session.delete(token_ref)
session.flush()

def list_tokens(self, user_id):
session = self.get_session()
tokens = []
now = datetime.datetime.utcnow()
for token_ref in session.query(TokenModel)\
.filter(TokenModel.expires > now):
token_ref_dict = token_ref.to_dict()
if 'user' not in token_ref_dict:
continue
if token_ref_dict['user'].get('id') != user_id:
continue
tokens.append(token_ref['id'])
return tokens
10 changes: 10 additions & 0 deletions keystone/token/core.py
Expand Up @@ -87,6 +87,16 @@ def delete_token(self, token_id):
"""
raise exception.NotImplemented()

def list_tokens(self, user_id):
"""Returns a list of current token_id's for a user
:param user_id: identity of the user
:type user_id: string
:returns: list of token_id's
"""
raise exception.NotImplemented()

def _get_default_expire_time(self):
"""Determine when a token should expire based on the config.
Expand Down
23 changes: 23 additions & 0 deletions tests/test_keystoneclient.py
Expand Up @@ -286,6 +286,29 @@ def test_invalid_user_password(self):
username='blah',
password='blah')

def test_change_password_invalidates_token(self):
from keystoneclient import exceptions as client_exceptions

client = self.get_client(admin=True)

username = uuid.uuid4().hex
passwd = uuid.uuid4().hex
user = client.users.create(name=username, password=passwd,
email=uuid.uuid4().hex)

token_id = client.tokens.authenticate(username=username,
password=passwd).id

# authenticate with a token should work before a password change
client.tokens.authenticate(token=token_id)

client.users.update_password(user=user.id, password=uuid.uuid4().hex)

# authenticate with a token should not work after a password change
self.assertRaises(client_exceptions.Unauthorized,
client.tokens.authenticate,
token=token_id)

def test_user_create_update_delete(self):
from keystoneclient import exceptions as client_exceptions

Expand Down

0 comments on commit ea03d05

Please sign in to comment.