Make region and project sticky
This change will make the region and project "sticky" in that whatever is selected will remain selected. When users select other projects or login/logout the region will stay what the user last selected, and users will try to be returned to the last used project Change-Id: I8b38ab2cb8b616ad6976aa8167b8209926054df4 Closes-Bug: 1357047 Closes-Bug: 1389401
This commit is contained in:
parent
aeab556f8b
commit
4ceb57d02b
|
@ -112,46 +112,63 @@ class KeystoneBackend(object):
|
|||
self.check_auth_expiry(unscoped_auth_ref)
|
||||
|
||||
# Check if token is automatically scoped to default_project
|
||||
# grab the project from this token, to use as a default
|
||||
# if no recent_project is found in the cookie
|
||||
token_proj_id = None
|
||||
if unscoped_auth_ref.project_scoped:
|
||||
auth_ref = unscoped_auth_ref
|
||||
else:
|
||||
# For now we list all the user's projects and iterate through.
|
||||
token_proj_id = unscoped_auth_ref.get('project',
|
||||
{}).get('id')
|
||||
|
||||
# We list all the user's projects
|
||||
try:
|
||||
if utils.get_keystone_version() < 3:
|
||||
projects = client.tenants.list()
|
||||
else:
|
||||
client.management_url = auth_url
|
||||
projects = client.projects.list(
|
||||
user=unscoped_auth_ref.user_id)
|
||||
except (keystone_exceptions.ClientException,
|
||||
keystone_exceptions.AuthorizationFailure) as exc:
|
||||
msg = _('Unable to retrieve authorized projects.')
|
||||
raise exceptions.KeystoneAuthException(msg)
|
||||
|
||||
# Abort if there are no projects for this user
|
||||
if not projects:
|
||||
msg = _('You are not authorized for any projects.')
|
||||
raise exceptions.KeystoneAuthException(msg)
|
||||
|
||||
# the recent project id a user might have set in a cookie
|
||||
recent_project = None
|
||||
if request:
|
||||
recent_project = request.COOKIES.get('recent_project',
|
||||
token_proj_id)
|
||||
|
||||
# if a most recent project was found, try using it first
|
||||
for pos, project in enumerate(projects):
|
||||
if project.id == recent_project:
|
||||
# move recent project to the beginning
|
||||
projects.pop(pos)
|
||||
projects.insert(0, project)
|
||||
break
|
||||
|
||||
for project in projects:
|
||||
try:
|
||||
if utils.get_keystone_version() < 3:
|
||||
projects = client.tenants.list()
|
||||
else:
|
||||
client.management_url = auth_url
|
||||
projects = client.projects.list(
|
||||
user=unscoped_auth_ref.user_id)
|
||||
client = keystone_client.Client(
|
||||
tenant_id=project.id,
|
||||
token=unscoped_auth_ref.auth_token,
|
||||
auth_url=auth_url,
|
||||
insecure=insecure,
|
||||
cacert=ca_cert,
|
||||
debug=settings.DEBUG)
|
||||
auth_ref = client.auth_ref
|
||||
break
|
||||
except (keystone_exceptions.ClientException,
|
||||
keystone_exceptions.AuthorizationFailure) as exc:
|
||||
msg = _('Unable to retrieve authorized projects.')
|
||||
raise exceptions.KeystoneAuthException(msg)
|
||||
keystone_exceptions.AuthorizationFailure):
|
||||
auth_ref = None
|
||||
|
||||
# Abort if there are no projects for this user
|
||||
if not projects:
|
||||
msg = _('You are not authorized for any projects.')
|
||||
raise exceptions.KeystoneAuthException(msg)
|
||||
|
||||
while projects:
|
||||
project = projects.pop()
|
||||
try:
|
||||
client = keystone_client.Client(
|
||||
tenant_id=project.id,
|
||||
token=unscoped_auth_ref.auth_token,
|
||||
auth_url=auth_url,
|
||||
insecure=insecure,
|
||||
cacert=ca_cert,
|
||||
debug=settings.DEBUG)
|
||||
auth_ref = client.auth_ref
|
||||
break
|
||||
except (keystone_exceptions.ClientException,
|
||||
keystone_exceptions.AuthorizationFailure):
|
||||
auth_ref = None
|
||||
|
||||
if auth_ref is None:
|
||||
msg = _("Unable to authenticate to any available projects.")
|
||||
raise exceptions.KeystoneAuthException(msg)
|
||||
if auth_ref is None:
|
||||
msg = _("Unable to authenticate to any available projects.")
|
||||
raise exceptions.KeystoneAuthException(msg)
|
||||
|
||||
# Check expiry for our new scoped token.
|
||||
self.check_auth_expiry(auth_ref)
|
||||
|
|
|
@ -131,7 +131,7 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
|
|||
|
||||
form_data = self.get_form_data(user)
|
||||
self._mock_unscoped_client_list_tenants(user, tenants)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_two.id)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_one.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -157,8 +157,8 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
|
|||
|
||||
form_data = self.get_form_data(user)
|
||||
self._mock_unscoped_client_list_tenants(user, tenants)
|
||||
self._mock_client_token_auth_failure(unscoped, self.data.tenant_two.id)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_one.id)
|
||||
self._mock_client_token_auth_failure(unscoped, self.data.tenant_one.id)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_two.id)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('login')
|
||||
|
@ -171,6 +171,14 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
|
|||
response = self.client.post(url, form_data)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
def test_login_w_bad_region_cookie(self):
|
||||
self.client.cookies['services_region'] = "bad_region"
|
||||
self._login()
|
||||
self.assertNotEqual("bad_region",
|
||||
self.client.session['services_region'])
|
||||
self.assertEqual("RegionOne",
|
||||
self.client.session['services_region'])
|
||||
|
||||
def test_no_enabled_tenants(self):
|
||||
tenants = [self.data.tenant_one, self.data.tenant_two]
|
||||
user = self.data.user
|
||||
|
@ -178,8 +186,8 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
|
|||
|
||||
form_data = self.get_form_data(user)
|
||||
self._mock_unscoped_client_list_tenants(user, tenants)
|
||||
self._mock_client_token_auth_failure(unscoped, self.data.tenant_two.id)
|
||||
self._mock_client_token_auth_failure(unscoped, self.data.tenant_one.id)
|
||||
self._mock_client_token_auth_failure(unscoped, self.data.tenant_two.id)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('login')
|
||||
|
@ -289,7 +297,7 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
|
|||
form_data = self.get_form_data(user)
|
||||
|
||||
self._mock_unscoped_client_list_tenants(user, tenants)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_two.id)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_one.id)
|
||||
self._mock_scoped_client_for_tenant(scoped, tenant.id,
|
||||
url=sc.url_for(endpoint_type=et))
|
||||
self.mox.ReplayAll()
|
||||
|
@ -332,7 +340,7 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
|
|||
form_data = self.get_form_data(user)
|
||||
|
||||
self._mock_unscoped_client_list_tenants(user, tenants)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_two.id)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_one.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -364,6 +372,7 @@ class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase):
|
|||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
self.assertEqual(self.client.session['services_region'], region)
|
||||
self.assertEqual(self.client.cookies['services_region'].value, region)
|
||||
|
||||
def test_switch_region_with_next(self, next=None):
|
||||
self.test_switch_region(next='/next_url')
|
||||
|
@ -500,7 +509,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
|
|||
|
||||
form_data = self.get_form_data(user)
|
||||
self._mock_unscoped_client_list_projects(user, projects)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_two.id)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -522,8 +531,8 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
|
|||
form_data = self.get_form_data(user)
|
||||
self._mock_unscoped_client_list_projects(user, projects)
|
||||
self._mock_client_token_auth_failure(unscoped,
|
||||
self.data.project_two.id)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
self.data.project_one.id)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_two.id)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('login')
|
||||
|
@ -544,10 +553,10 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
|
|||
form_data = self.get_form_data(user)
|
||||
|
||||
self._mock_unscoped_client_list_projects(user, projects)
|
||||
self._mock_client_token_auth_failure(unscoped,
|
||||
self.data.project_two.id)
|
||||
self._mock_client_token_auth_failure(unscoped,
|
||||
self.data.project_one.id)
|
||||
self._mock_client_token_auth_failure(unscoped,
|
||||
self.data.project_two.id)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('login')
|
||||
|
@ -638,7 +647,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
|
|||
form_data = self.get_form_data(user)
|
||||
|
||||
self._mock_unscoped_client_list_projects(user, projects)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_two.id)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
self._mock_scoped_client_for_tenant(
|
||||
unscoped,
|
||||
project.id,
|
||||
|
@ -683,7 +692,7 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase):
|
|||
|
||||
form_data = self.get_form_data(user)
|
||||
self._mock_unscoped_client_list_projects(user, projects)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_two.id)
|
||||
self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
|
|
@ -35,6 +35,9 @@ def set_session_from_user(request, user):
|
|||
|
||||
|
||||
def create_user_from_token(request, token, endpoint, services_region=None):
|
||||
# if the region is provided, use that, otherwise use the preferred region
|
||||
svc_region = services_region or \
|
||||
utils.default_services_region(token.serviceCatalog, request)
|
||||
return User(id=token.user['id'],
|
||||
token=token,
|
||||
user=token.user['name'],
|
||||
|
@ -50,7 +53,7 @@ def create_user_from_token(request, token, endpoint, services_region=None):
|
|||
service_catalog=token.serviceCatalog,
|
||||
roles=token.roles,
|
||||
endpoint=endpoint,
|
||||
services_region=services_region)
|
||||
services_region=svc_region)
|
||||
|
||||
|
||||
class Token(object):
|
||||
|
@ -180,8 +183,7 @@ class User(models.AnonymousUser):
|
|||
self.project_id = project_id or tenant_id
|
||||
self.project_name = project_name or tenant_name
|
||||
self.service_catalog = service_catalog
|
||||
self._services_region = (services_region or
|
||||
self.default_services_region())
|
||||
self._services_region = services_region
|
||||
self.roles = roles or []
|
||||
self.endpoint = endpoint
|
||||
self.enabled = enabled
|
||||
|
@ -286,19 +288,6 @@ class User(models.AnonymousUser):
|
|||
def authorized_tenants(self, tenant_list):
|
||||
self._authorized_tenants = tenant_list
|
||||
|
||||
def default_services_region(self):
|
||||
"""Returns the first endpoint region for first non-identity service.
|
||||
|
||||
Extracted from the service catalog.
|
||||
"""
|
||||
if self.service_catalog:
|
||||
for service in self.service_catalog:
|
||||
if service['type'] == 'identity':
|
||||
continue
|
||||
for endpoint in service['endpoints']:
|
||||
return endpoint['region']
|
||||
return None
|
||||
|
||||
@property
|
||||
def services_region(self):
|
||||
return self._services_region
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
import datetime
|
||||
import functools
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import auth
|
||||
|
@ -25,6 +26,8 @@ from keystoneclient.v3 import client as client_v3
|
|||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_PROJECT_CACHE = {}
|
||||
|
||||
_TOKEN_TIMEOUT_MARGIN = getattr(settings, 'TOKEN_TIMEOUT_MARGIN', 0)
|
||||
|
@ -195,3 +198,43 @@ def get_project_list(*args, **kwargs):
|
|||
|
||||
projects.sort(key=lambda project: project.name.lower())
|
||||
return projects
|
||||
|
||||
|
||||
def default_services_region(service_catalog, request=None):
|
||||
"""Returns the first endpoint region for first non-identity service.
|
||||
|
||||
Extracted from the service catalog.
|
||||
"""
|
||||
if service_catalog:
|
||||
available_regions = [endpoint['region'] for service
|
||||
in service_catalog for endpoint
|
||||
in service['endpoints']
|
||||
if service['type'] != 'identity']
|
||||
if not available_regions:
|
||||
# this is very likely an incomplete keystone setup
|
||||
LOG.warning('No regions could be found excluding identity.')
|
||||
available_regions = [endpoint['region'] for service
|
||||
in service_catalog for endpoint
|
||||
in service['endpoints']]
|
||||
if not available_regions:
|
||||
# this is a critical problem and it's not clear how this occurs
|
||||
LOG.error('No regions can be found in the service catalog.')
|
||||
return None
|
||||
selected_region = None
|
||||
if request:
|
||||
selected_region = request.COOKIES.get('services_region',
|
||||
available_regions[0])
|
||||
if selected_region not in available_regions:
|
||||
selected_region = available_regions[0]
|
||||
return selected_region
|
||||
return None
|
||||
|
||||
|
||||
def set_response_cookie(response, cookie_name, cookie_value):
|
||||
"""a common policy of setting cookies for last used project
|
||||
and region, can be reused in other locations.
|
||||
this method will set the cookie to expire in 365 days.
|
||||
"""
|
||||
now = timezone.now()
|
||||
expire_date = now + datetime.timedelta(days=365)
|
||||
response.set_cookie(cookie_name, cookie_value, expires=expire_date)
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
|
||||
import django
|
||||
|
@ -196,7 +195,10 @@ def switch(request, tenant_id, redirect_field_name=auth.REDIRECT_FIELD_NAME):
|
|||
user = auth_user.create_user_from_token(
|
||||
request, auth_user.Token(auth_ref), endpoint)
|
||||
auth_user.set_session_from_user(request, user)
|
||||
return shortcuts.redirect(redirect_to)
|
||||
response = shortcuts.redirect(redirect_to)
|
||||
utils.set_response_cookie(response, 'recent_project',
|
||||
request.user.project_id)
|
||||
return response
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -216,4 +218,7 @@ def switch_region(request, region_name,
|
|||
if not is_safe_url(url=redirect_to, host=request.get_host()):
|
||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
||||
|
||||
return shortcuts.redirect(redirect_to)
|
||||
response = shortcuts.redirect(redirect_to)
|
||||
utils.set_response_cookie(response, 'services_region',
|
||||
request.session['services_region'])
|
||||
return response
|
||||
|
|
Loading…
Reference in New Issue