Merge "Add expiring user group memberships on mapped authentication"
This commit is contained in:
commit
b3d3735138
@ -26,6 +26,14 @@ For information about Identity API protection, see
|
||||
<https://docs.openstack.org/keystone/latest/admin/service-api-protection.html>`_
|
||||
in the OpenStack Cloud Administrator Guide.
|
||||
|
||||
===================================
|
||||
What's New in Version 3.14 (Ussuri)
|
||||
===================================
|
||||
|
||||
- New attribute ``authorization_ttl`` for identity providers
|
||||
- New attribute ``membership_expires_at`` when listing groups for a user
|
||||
- Ability to persist group memberships carried through mapping for a federated user
|
||||
|
||||
==================================
|
||||
What's New in Version 3.13 (Train)
|
||||
==================================
|
||||
|
@ -143,6 +143,17 @@ associating them with a local keystone group and inheriting its role
|
||||
assignments, or dynamically provisioning projects within keystone based on these
|
||||
rules.
|
||||
|
||||
.. note::
|
||||
|
||||
By default, group memberships that a user gets from a mapping are only valid
|
||||
for the duration of the token. It is possible to persist these groups
|
||||
memberships for a limited period of time. To enable this, either
|
||||
set the ``authorization_ttl` attribute of the identity provider, or the
|
||||
``[federation] default_authorization_ttl`` in the keystone.conf file. This
|
||||
value is in minutes, and will result in a lag from when a user is removed
|
||||
from a group in the identity provider, and when that will happen in keystone.
|
||||
Please consider your security requirements carefully.
|
||||
|
||||
An Identity Provider has exactly one mapping specified per protocol.
|
||||
Mapping objects can be used multiple times by different combinations of Identity
|
||||
Provider and Protocol.
|
||||
|
@ -28,9 +28,9 @@ _DISCOVERY_BLUEPRINT = flask.Blueprint('Discovery', __name__)
|
||||
def _get_versions_list(identity_url):
|
||||
versions = {}
|
||||
versions['v3'] = {
|
||||
'id': 'v3.13',
|
||||
'id': 'v3.14',
|
||||
'status': 'stable',
|
||||
'updated': '2019-07-19T00:00:00Z',
|
||||
'updated': '2020-04-07T00:00:00Z',
|
||||
'links': [{
|
||||
'rel': 'self',
|
||||
'href': identity_url,
|
||||
|
@ -235,10 +235,12 @@ def handle_unscoped_token(auth_payload, resource_api, federation_api,
|
||||
get_user_unique_id_and_display_name(mapped_properties)
|
||||
)
|
||||
email = mapped_properties['user'].get('email')
|
||||
user = identity_api.shadow_federated_user(identity_provider,
|
||||
protocol, unique_id,
|
||||
display_name,
|
||||
email)
|
||||
user = identity_api.shadow_federated_user(
|
||||
identity_provider,
|
||||
protocol, unique_id,
|
||||
display_name,
|
||||
email,
|
||||
group_ids=mapped_properties['group_ids'])
|
||||
|
||||
if 'projects' in mapped_properties:
|
||||
idp_domain_id = federation_api.get_idp(
|
||||
|
@ -182,7 +182,7 @@ class Manager(manager.Manager):
|
||||
self.driver.delete_protocol(idp_id, protocol_id)
|
||||
|
||||
for shadow_user in shadow_users:
|
||||
PROVIDERS.identity_api.shadow_federated_user.invalidate(
|
||||
PROVIDERS.identity_api._shadow_federated_user.invalidate(
|
||||
PROVIDERS.identity_api, shadow_user['idp_id'],
|
||||
shadow_user['protocol_id'], shadow_user['unique_id'],
|
||||
shadow_user['display_name'],
|
||||
|
@ -1135,7 +1135,7 @@ class Manager(manager.Manager):
|
||||
self.get_user_by_name.invalidate(self, user_old['name'],
|
||||
user_old['domain_id'])
|
||||
for fed_user in fed_users:
|
||||
self.shadow_federated_user.invalidate(
|
||||
self._shadow_federated_user.invalidate(
|
||||
self, fed_user['idp_id'], fed_user['protocol_id'],
|
||||
fed_user['unique_id'], fed_user['display_name'],
|
||||
user_old.get('extra', {}).get('email'))
|
||||
@ -1402,18 +1402,8 @@ class Manager(manager.Manager):
|
||||
return PROVIDERS.shadow_users_api.create_nonlocal_user(user)
|
||||
|
||||
@MEMOIZE
|
||||
def shadow_federated_user(self, idp_id, protocol_id, unique_id,
|
||||
display_name, email=None):
|
||||
"""Map a federated user to a user.
|
||||
|
||||
:param idp_id: identity provider id
|
||||
:param protocol_id: protocol id
|
||||
:param unique_id: unique id for the user within the IdP
|
||||
:param display_name: user's display name
|
||||
:param email: user's email
|
||||
|
||||
:returns: dictionary of the mapped User entity
|
||||
"""
|
||||
def _shadow_federated_user(self, idp_id, protocol_id, unique_id,
|
||||
display_name, email=None):
|
||||
user_dict = {}
|
||||
try:
|
||||
PROVIDERS.shadow_users_api.update_federated_user_display_name(
|
||||
@ -1440,6 +1430,29 @@ class Manager(manager.Manager):
|
||||
PROVIDERS.shadow_users_api.set_last_active_at(user_dict['id'])
|
||||
return user_dict
|
||||
|
||||
def shadow_federated_user(self, idp_id, protocol_id, unique_id,
|
||||
display_name, email=None, group_ids=None):
|
||||
"""Map a federated user to a user.
|
||||
|
||||
:param idp_id: identity provider id
|
||||
:param protocol_id: protocol id
|
||||
:param unique_id: unique id for the user within the IdP
|
||||
:param display_name: user's display name
|
||||
:param email: user's email
|
||||
:param group_ids: list of group ids to add the user to
|
||||
|
||||
:returns: dictionary of the mapped User entity
|
||||
"""
|
||||
user_dict = self._shadow_federated_user(
|
||||
idp_id, protocol_id, unique_id, display_name, email)
|
||||
# Note(knikolla): The shadowing operation can be cached,
|
||||
# however we need to update the expiring group memberships.
|
||||
if group_ids:
|
||||
for group_id in group_ids:
|
||||
PROVIDERS.shadow_users_api.add_user_to_group_expires(
|
||||
user_dict['id'], group_id)
|
||||
return user_dict
|
||||
|
||||
|
||||
class MappingManager(manager.Manager):
|
||||
"""Default pivot point for the ID Mapping backend."""
|
||||
|
@ -672,6 +672,41 @@ class SqlIdentity(SqlTests,
|
||||
negative_user['id'])
|
||||
self.assertEqual(0, len(group_refs))
|
||||
|
||||
def test_add_user_to_group_expiring_mapped(self):
|
||||
self._build_fed_resource()
|
||||
domain = self._get_domain_fixture()
|
||||
self.config_fixture.config(group='federation',
|
||||
default_authorization_ttl=5)
|
||||
time = datetime.datetime.utcnow()
|
||||
tick = datetime.timedelta(minutes=5)
|
||||
|
||||
new_group = unit.new_group_ref(domain_id=domain['id'])
|
||||
new_group = PROVIDERS.identity_api.create_group(new_group)
|
||||
|
||||
fed_dict = unit.new_federated_user_ref()
|
||||
fed_dict['idp_id'] = 'myidp'
|
||||
fed_dict['protocol_id'] = 'mapped'
|
||||
|
||||
with freezegun.freeze_time(time - tick) as frozen_time:
|
||||
user = PROVIDERS.identity_api.shadow_federated_user(
|
||||
**fed_dict, group_ids=[new_group['id']])
|
||||
|
||||
PROVIDERS.identity_api.check_user_in_group(user['id'],
|
||||
new_group['id'])
|
||||
|
||||
# Expiration
|
||||
frozen_time.tick(tick)
|
||||
self.assertRaises(exception.NotFound,
|
||||
PROVIDERS.identity_api.check_user_in_group,
|
||||
user['id'],
|
||||
new_group['id'])
|
||||
|
||||
# Renewal
|
||||
PROVIDERS.identity_api.shadow_federated_user(
|
||||
**fed_dict, group_ids=[new_group['id']])
|
||||
PROVIDERS.identity_api.check_user_in_group(user['id'],
|
||||
new_group['id'])
|
||||
|
||||
def test_add_user_to_group_expiring(self):
|
||||
self._build_fed_resource()
|
||||
domain = self._get_domain_fixture()
|
||||
|
@ -36,9 +36,9 @@ v3_MEDIA_TYPES = [
|
||||
]
|
||||
|
||||
v3_EXPECTED_RESPONSE = {
|
||||
"id": "v3.13",
|
||||
"id": "v3.14",
|
||||
"status": "stable",
|
||||
"updated": "2019-07-19T00:00:00Z",
|
||||
"updated": "2020-04-07T00:00:00Z",
|
||||
"links": [
|
||||
{
|
||||
"rel": "self",
|
||||
|
@ -12,4 +12,4 @@
|
||||
|
||||
|
||||
def release_string():
|
||||
return 'v3.13'
|
||||
return 'v3.14'
|
||||
|
19
releasenotes/notes/bug-1809116-b65502f3b606b060.yaml
Normal file
19
releasenotes/notes/bug-1809116-b65502f3b606b060.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
[`bug 1809116 <https://bugs.launchpad.net/keystone/+bug/1809116>`_]
|
||||
It is now possible to have group memberships carried over through mapping
|
||||
persist for a limited time after a user authenticates using federation.
|
||||
The "time to live" of these memberships is specified via the configuration
|
||||
option `[federation] default_authorization_ttl` or for each identity
|
||||
provider by setting `authorization_ttl` on the identity provider. Every
|
||||
time a user authenticates carrying over that membership, it will be
|
||||
renewed.
|
||||
security:
|
||||
- |
|
||||
If expiring user group memberships are enabled via the `[federation]
|
||||
default_authorization_ttl` configuration option, or on an idp by idp
|
||||
basis by setting `authorization_ttl`, there will be a lag between when
|
||||
a user is removed from a group in an identity provider, and when that
|
||||
will be reflected in keystone. That amount of time will be equal to
|
||||
the last time the user logged in + idp ttl.
|
Loading…
Reference in New Issue
Block a user