summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-11-20 19:23:55 +0000
committerGerrit Code Review <review@openstack.org>2014-11-20 19:23:55 +0000
commit7c5f759473960ddccf81cc426587e3ff43810f5b (patch)
tree6e939c7cddf823173ba18cf5f07e15c6a6458a20
parentb3a3e27b39f13dbfc4331a2f3b2a610b98276429 (diff)
parent4ceb57d02b8bbed30578a8052a31b982a1339f41 (diff)
downloaddjango_openstack_auth-7c5f759473960ddccf81cc426587e3ff43810f5b.tar.gz
Merge "Make region and project sticky"
-rw-r--r--openstack_auth/backend.py91
-rw-r--r--openstack_auth/tests/tests.py35
-rw-r--r--openstack_auth/user.py21
-rw-r--r--openstack_auth/utils.py43
-rw-r--r--openstack_auth/views.py11
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