diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-11-20 19:23:55 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-11-20 19:23:55 +0000 |
commit | 7c5f759473960ddccf81cc426587e3ff43810f5b (patch) | |
tree | 6e939c7cddf823173ba18cf5f07e15c6a6458a20 | |
parent | b3a3e27b39f13dbfc4331a2f3b2a610b98276429 (diff) | |
parent | 4ceb57d02b8bbed30578a8052a31b982a1339f41 (diff) | |
download | django_openstack_auth-7c5f759473960ddccf81cc426587e3ff43810f5b.tar.gz |
Merge "Make region and project sticky"
-rw-r--r-- | openstack_auth/backend.py | 91 | ||||
-rw-r--r-- | openstack_auth/tests/tests.py | 35 | ||||
-rw-r--r-- | openstack_auth/user.py | 21 | ||||
-rw-r--r-- | openstack_auth/utils.py | 43 | ||||
-rw-r--r-- | openstack_auth/views.py | 11 |
5 files changed, 132 insertions, 69 deletions
diff --git a/openstack_auth/backend.py b/openstack_auth/backend.py index cc50f56..9ad57df 100644 --- a/openstack_auth/backend.py +++ b/openstack_auth/backend.py @@ -115,46 +115,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) - - # 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) + keystone_exceptions.AuthorizationFailure): + auth_ref = None + + 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) diff --git a/openstack_auth/tests/tests.py b/openstack_auth/tests/tests.py index b0edf17..46dd8e9 100644 --- a/openstack_auth/tests/tests.py +++ b/openstack_auth/tests/tests.py @@ -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') @@ -545,9 +554,9 @@ class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, test.TestCase): 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() diff --git a/openstack_auth/user.py b/openstack_auth/user.py index 6eda8f5..42b1071 100644 --- a/openstack_auth/user.py +++ b/openstack_auth/user.py @@ -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 @@ -291,19 +293,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 diff --git a/openstack_auth/utils.py b/openstack_auth/utils.py index 6a0d73b..f826335 100644 --- a/openstack_auth/utils.py +++ b/openstack_auth/utils.py @@ -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) diff --git a/openstack_auth/views.py b/openstack_auth/views.py index 938d720..53b9ce9 100644 --- a/openstack_auth/views.py +++ b/openstack_auth/views.py @@ -10,7 +10,6 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. - import logging import django @@ -197,7 +196,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 @@ -217,4 +219,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 |