Add capability for Keystone V3 Authentication.
For multi-domain model, set OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT to True and the login form will prompt the user for Domain name. For single-domain model, set OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT to False. The application will use the name of the default domain in OPENSTACK_KEYSTONE_DEFAULT_DOMAIN to login. Cleanup the unused Tenant field in the login form. Implements blueprint login-domain-support
This commit is contained in:
		| @@ -1,18 +1,18 @@ | |||||||
| """ Module defining the Django auth backend class for the Keystone API. """ | """ Module defining the Django auth backend class for the Keystone API. """ | ||||||
|  |  | ||||||
| import hashlib |  | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.utils.translation import ugettext_lazy as _ | from django.utils.translation import ugettext_lazy as _ | ||||||
|  |  | ||||||
| from keystoneclient.v2_0 import client as keystone_client |  | ||||||
| from keystoneclient import exceptions as keystone_exceptions | from keystoneclient import exceptions as keystone_exceptions | ||||||
| from keystoneclient.v2_0.tokens import Token, TokenManager |  | ||||||
|  |  | ||||||
| from .exceptions import KeystoneAuthException | from .exceptions import KeystoneAuthException | ||||||
| from .user import create_user_from_token | from .user import create_user_from_token | ||||||
| from .utils import check_token_expiration, is_ans1_token | from .user import Token | ||||||
|  | from .utils import check_token_expiration | ||||||
|  | from .utils import get_keystone_client | ||||||
|  | from .utils import get_keystone_version | ||||||
|  |  | ||||||
|  |  | ||||||
| LOG = logging.getLogger(__name__) | LOG = logging.getLogger(__name__) | ||||||
| @@ -22,11 +22,12 @@ KEYSTONE_CLIENT_ATTR = "_keystoneclient" | |||||||
|  |  | ||||||
|  |  | ||||||
| class KeystoneBackend(object): | class KeystoneBackend(object): | ||||||
|  |     """Django authentication backend class for use with | ||||||
|  |       ``django.contrib.auth``. | ||||||
|     """ |     """ | ||||||
|     Django authentication backend class for use with ``django.contrib.auth``. |  | ||||||
|     """ |     def check_auth_expiry(self, auth_ref): | ||||||
|     def check_auth_expiry(self, token): |         if not check_token_expiration(auth_ref): | ||||||
|         if not check_token_expiration(token): |  | ||||||
|             msg = _("The authentication token issued by the Identity service " |             msg = _("The authentication token issued by the Identity service " | ||||||
|                     "has expired.") |                     "has expired.") | ||||||
|             LOG.warning("The authentication token issued by the Identity " |             LOG.warning("The authentication token issued by the Identity " | ||||||
| @@ -37,41 +38,41 @@ class KeystoneBackend(object): | |||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     def get_user(self, user_id): |     def get_user(self, user_id): | ||||||
|         """ |         """Returns the current user (if authenticated) based on the user ID | ||||||
|         Returns the current user (if authenticated) based on the user ID |  | ||||||
|         and session data. |         and session data. | ||||||
|  |  | ||||||
|         Note: this required monkey-patching the ``contrib.auth`` middleware |         Note: this required monkey-patching the ``contrib.auth`` middleware | ||||||
|         to make the ``request`` object available to the auth backend class. |         to make the ``request`` object available to the auth backend class. | ||||||
|         """ |         """ | ||||||
|         if user_id == self.request.session["user_id"]: |         if user_id == self.request.session["user_id"]: | ||||||
|             token = Token(TokenManager(None), |             token = self.request.session['token'] | ||||||
|                           self.request.session['token'], |  | ||||||
|                           loaded=True) |  | ||||||
|             endpoint = self.request.session['region_endpoint'] |             endpoint = self.request.session['region_endpoint'] | ||||||
|             services_region = self.request.session['services_region'] |             services_region = self.request.session['services_region'] | ||||||
|             return create_user_from_token(self.request, token, endpoint, |             user = create_user_from_token(self.request, token, endpoint, | ||||||
|                                           services_region) |                                           services_region) | ||||||
|  |             return user | ||||||
|         else: |         else: | ||||||
|             return None |             return None | ||||||
|  |  | ||||||
|     def authenticate(self, request=None, username=None, password=None, |     def authenticate(self, request=None, username=None, password=None, | ||||||
|                      tenant=None, auth_url=None): |                      user_domain_name=None, auth_url=None): | ||||||
|         """ Authenticates a user via the Keystone Identity API. """ |         """Authenticates a user via the Keystone Identity API. """ | ||||||
|         LOG.debug('Beginning user authentication for user "%s".' % username) |         LOG.debug('Beginning user authentication for user "%s".' % username) | ||||||
|  |  | ||||||
|         insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) |         insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) | ||||||
|  |  | ||||||
|  |         keystone_client = get_keystone_client() | ||||||
|         try: |         try: | ||||||
|             client = keystone_client.Client(username=username, |             client = keystone_client.Client( | ||||||
|                                             password=password, |                 user_domain_name=user_domain_name, | ||||||
|                                             tenant_id=tenant, |                 username=username, | ||||||
|                                             auth_url=auth_url, |                 password=password, | ||||||
|                                             insecure=insecure) |                 auth_url=auth_url, | ||||||
|             unscoped_token_data = {"token": client.service_catalog.get_token()} |                 insecure=insecure, | ||||||
|             unscoped_token = Token(TokenManager(None), |                 debug=settings.DEBUG) | ||||||
|                                    unscoped_token_data, |  | ||||||
|                                    loaded=True) |             unscoped_auth_ref = client.auth_ref | ||||||
|  |             unscoped_token = Token(auth_ref=unscoped_auth_ref) | ||||||
|         except (keystone_exceptions.Unauthorized, |         except (keystone_exceptions.Unauthorized, | ||||||
|                 keystone_exceptions.Forbidden, |                 keystone_exceptions.Forbidden, | ||||||
|                 keystone_exceptions.NotFound) as exc: |                 keystone_exceptions.NotFound) as exc: | ||||||
| @@ -85,54 +86,60 @@ class KeystoneBackend(object): | |||||||
|             LOG.debug(exc.message) |             LOG.debug(exc.message) | ||||||
|             raise KeystoneAuthException(msg) |             raise KeystoneAuthException(msg) | ||||||
|  |  | ||||||
|         # Check expiry for our unscoped token. |         # Check expiry for our unscoped auth ref. | ||||||
|         self.check_auth_expiry(unscoped_token) |         self.check_auth_expiry(unscoped_auth_ref) | ||||||
|  |  | ||||||
|         # FIXME: Log in to default tenant when the Keystone API returns it... |         # Check if token is automatically scoped to default_project | ||||||
|         # For now we list all the user's tenants and iterate through. |         if unscoped_auth_ref.project_scoped: | ||||||
|         try: |             auth_ref = unscoped_auth_ref | ||||||
|             tenants = client.tenants.list() |         else: | ||||||
|         except (keystone_exceptions.ClientException, |             # For now we list all the user's projects and iterate through. | ||||||
|                 keystone_exceptions.AuthorizationFailure): |  | ||||||
|             msg = _('Unable to retrieve authorized projects.') |  | ||||||
|             raise KeystoneAuthException(msg) |  | ||||||
|  |  | ||||||
|         # Abort if there are no tenants for this user |  | ||||||
|         if not tenants: |  | ||||||
|             msg = _('You are not authorized for any projects.') |  | ||||||
|             raise KeystoneAuthException(msg) |  | ||||||
|  |  | ||||||
|         while tenants: |  | ||||||
|             tenant = tenants.pop() |  | ||||||
|             try: |             try: | ||||||
|                 client = keystone_client.Client(tenant_id=tenant.id, |                 if get_keystone_version() < 3: | ||||||
|                                                 token=unscoped_token.id, |                     projects = client.tenants.list() | ||||||
|                                                 auth_url=auth_url, |                 else: | ||||||
|                                                 insecure=insecure) |                     client.management_url = auth_url | ||||||
|                 token = client.tokens.authenticate(username=username, |                     projects = client.projects.list( | ||||||
|                                                    token=unscoped_token.id, |                         user=unscoped_auth_ref.user_id) | ||||||
|                                                    tenant_id=tenant.id) |  | ||||||
|                 break |  | ||||||
|             except (keystone_exceptions.ClientException, |             except (keystone_exceptions.ClientException, | ||||||
|                     keystone_exceptions.AuthorizationFailure): |                     keystone_exceptions.AuthorizationFailure) as exc: | ||||||
|                 token = None |                 msg = _('Unable to retrieve authorized projects.') | ||||||
|  |                 raise KeystoneAuthException(msg) | ||||||
|  |  | ||||||
|         if token is None: |             # Abort if there are no projects for this user | ||||||
|             msg = _("Unable to authenticate to any available projects.") |             if not projects: | ||||||
|             raise KeystoneAuthException(msg) |                 msg = _('You are not authorized for any projects.') | ||||||
|  |                 raise 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, | ||||||
|  |                         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 KeystoneAuthException(msg) | ||||||
|  |  | ||||||
|         # Check expiry for our new scoped token. |         # Check expiry for our new scoped token. | ||||||
|         self.check_auth_expiry(token) |         self.check_auth_expiry(auth_ref) | ||||||
|  |  | ||||||
|         # If we made it here we succeeded. Create our User! |         # If we made it here we succeeded. Create our User! | ||||||
|         user = create_user_from_token(request, |         user = create_user_from_token(request, | ||||||
|                                       token, |                                       Token(auth_ref), | ||||||
|                                       client.service_catalog.url_for()) |                                       client.service_catalog.url_for()) | ||||||
|  |  | ||||||
|         if request is not None: |         if request is not None: | ||||||
|             if is_ans1_token(unscoped_token.id): |  | ||||||
|                 hashed_token = hashlib.md5(unscoped_token.id).hexdigest() |  | ||||||
|                 unscoped_token._info['token']['id'] = hashed_token |  | ||||||
|             request.session['unscoped_token'] = unscoped_token.id |             request.session['unscoped_token'] = unscoped_token.id | ||||||
|             request.user = user |             request.user = user | ||||||
|  |  | ||||||
| @@ -143,15 +150,17 @@ class KeystoneBackend(object): | |||||||
|         return user |         return user | ||||||
|  |  | ||||||
|     def get_group_permissions(self, user, obj=None): |     def get_group_permissions(self, user, obj=None): | ||||||
|         """ Returns an empty set since Keystone doesn't support "groups". """ |         """Returns an empty set since Keystone doesn't support "groups". """ | ||||||
|  |         # Keystone V3 added "groups". The Auth token response includes the | ||||||
|  |         # roles from the user's Group assignment. It should be fine just | ||||||
|  |         # returning an empty set here. | ||||||
|         return set() |         return set() | ||||||
|  |  | ||||||
|     def get_all_permissions(self, user, obj=None): |     def get_all_permissions(self, user, obj=None): | ||||||
|         """ |         """Returns a set of permission strings that this user has through | ||||||
|         Returns a set of permission strings that this user has through his/her |            his/her Keystone "roles". | ||||||
|         Keystone "roles". |  | ||||||
|  |  | ||||||
|         The permissions are returned as ``"openstack.{{ role.name }}"``. |           The permissions are returned as ``"openstack.{{ role.name }}"``. | ||||||
|         """ |         """ | ||||||
|         if user.is_anonymous() or obj is not None: |         if user.is_anonymous() or obj is not None: | ||||||
|             return set() |             return set() | ||||||
| @@ -163,16 +172,15 @@ class KeystoneBackend(object): | |||||||
|         return role_perms | service_perms |         return role_perms | service_perms | ||||||
|  |  | ||||||
|     def has_perm(self, user, perm, obj=None): |     def has_perm(self, user, perm, obj=None): | ||||||
|         """ Returns True if the given user has the specified permission. """ |         """Returns True if the given user has the specified permission. """ | ||||||
|         if not user.is_active: |         if not user.is_active: | ||||||
|             return False |             return False | ||||||
|         return perm in self.get_all_permissions(user, obj) |         return perm in self.get_all_permissions(user, obj) | ||||||
|  |  | ||||||
|     def has_module_perms(self, user, app_label): |     def has_module_perms(self, user, app_label): | ||||||
|         """ |         """Returns True if user has any permissions in the given app_label. | ||||||
|         Returns True if user has any permissions in the given app_label. |  | ||||||
|  |  | ||||||
|         Currently this matches for the app_label ``"openstack"``. |            Currently this matches for the app_label ``"openstack"``. | ||||||
|         """ |         """ | ||||||
|         if not user.is_active: |         if not user.is_active: | ||||||
|             return False |             return False | ||||||
|   | |||||||
| @@ -16,8 +16,18 @@ LOG = logging.getLogger(__name__) | |||||||
| class Login(AuthenticationForm): | class Login(AuthenticationForm): | ||||||
|     """ Form used for logging in a user. |     """ Form used for logging in a user. | ||||||
|  |  | ||||||
|     Handles authentication with Keystone, choosing a tenant, and fetching |     Handles authentication with Keystone by providing the domain name, username | ||||||
|     a scoped token token for that tenant. |     and password. A scoped token is fetched after successful authentication. | ||||||
|  |  | ||||||
|  |     A domain name is required if authenticating with Keystone V3 running | ||||||
|  |     multi-domain configuration. | ||||||
|  |  | ||||||
|  |     If the user authenticated has a default project set, the token will be | ||||||
|  |     automatically scoped to their default project. | ||||||
|  |  | ||||||
|  |     If the user authenticated has no default project set, the authentication | ||||||
|  |     backend will try to scope to the projects returned from the user's assigned | ||||||
|  |     projects. The first successful project scoped will be returned. | ||||||
|  |  | ||||||
|     Inherits from the base ``django.contrib.auth.forms.AuthenticationForm`` |     Inherits from the base ``django.contrib.auth.forms.AuthenticationForm`` | ||||||
|     class for added security features. |     class for added security features. | ||||||
| @@ -26,10 +36,16 @@ class Login(AuthenticationForm): | |||||||
|     username = forms.CharField(label=_("User Name")) |     username = forms.CharField(label=_("User Name")) | ||||||
|     password = forms.CharField(label=_("Password"), |     password = forms.CharField(label=_("Password"), | ||||||
|                                widget=forms.PasswordInput(render_value=False)) |                                widget=forms.PasswordInput(render_value=False)) | ||||||
|     tenant = forms.CharField(required=False, widget=forms.HiddenInput()) |  | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         super(Login, self).__init__(*args, **kwargs) |         super(Login, self).__init__(*args, **kwargs) | ||||||
|  |         self.fields.keyOrder = ['username', 'password', 'region'] | ||||||
|  |         if getattr(settings, | ||||||
|  |                    'OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT', | ||||||
|  |                     False): | ||||||
|  |             self.fields['domain'] = forms.CharField(label=_("Domain"), | ||||||
|  |                                                     required=True) | ||||||
|  |             self.fields.keyOrder = ['domain', 'username', 'password', 'region'] | ||||||
|         self.fields['region'].choices = self.get_region_choices() |         self.fields['region'].choices = self.get_region_choices() | ||||||
|         if len(self.fields['region'].choices) == 1: |         if len(self.fields['region'].choices) == 1: | ||||||
|             self.fields['region'].initial = self.fields['region'].choices[0][0] |             self.fields['region'].initial = self.fields['region'].choices[0][0] | ||||||
| @@ -42,13 +58,13 @@ class Login(AuthenticationForm): | |||||||
|  |  | ||||||
|     @sensitive_variables() |     @sensitive_variables() | ||||||
|     def clean(self): |     def clean(self): | ||||||
|  |         default_domain = getattr(settings, | ||||||
|  |                                  'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN', | ||||||
|  |                                  'Default') | ||||||
|         username = self.cleaned_data.get('username') |         username = self.cleaned_data.get('username') | ||||||
|         password = self.cleaned_data.get('password') |         password = self.cleaned_data.get('password') | ||||||
|         region = self.cleaned_data.get('region') |         region = self.cleaned_data.get('region') | ||||||
|         tenant = self.cleaned_data.get('tenant') |         domain = self.cleaned_data.get('domain', default_domain) | ||||||
|  |  | ||||||
|         if not tenant: |  | ||||||
|             tenant = None |  | ||||||
|  |  | ||||||
|         if not (username and password): |         if not (username and password): | ||||||
|             # Don't authenticate, just let the other validators handle it. |             # Don't authenticate, just let the other validators handle it. | ||||||
| @@ -58,7 +74,7 @@ class Login(AuthenticationForm): | |||||||
|             self.user_cache = authenticate(request=self.request, |             self.user_cache = authenticate(request=self.request, | ||||||
|                                            username=username, |                                            username=username, | ||||||
|                                            password=password, |                                            password=password, | ||||||
|                                            tenant=tenant, |                                            user_domain_name=domain, | ||||||
|                                            auth_url=region) |                                            auth_url=region) | ||||||
|             msg = 'Login successful for user "%(username)s".' % \ |             msg = 'Login successful for user "%(username)s".' % \ | ||||||
|                 {'username': username} |                 {'username': username} | ||||||
|   | |||||||
| @@ -4,12 +4,11 @@ from datetime import timedelta | |||||||
| 
 | 
 | ||||||
| from django.utils import datetime_safe | from django.utils import datetime_safe | ||||||
| 
 | 
 | ||||||
|  | from keystoneclient.access import AccessInfo | ||||||
|  | from keystoneclient.service_catalog import ServiceCatalog | ||||||
| from keystoneclient.v2_0.roles import Role, RoleManager | from keystoneclient.v2_0.roles import Role, RoleManager | ||||||
| from keystoneclient.v2_0.tenants import Tenant, TenantManager | from keystoneclient.v2_0.tenants import Tenant, TenantManager | ||||||
| from keystoneclient.v2_0.tokens import Token, TokenManager |  | ||||||
| from keystoneclient.v2_0.users import User, UserManager | from keystoneclient.v2_0.users import User, UserManager | ||||||
| from keystoneclient.service_catalog import ServiceCatalog |  | ||||||
| from keystoneclient import access |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestDataContainer(object): | class TestDataContainer(object): | ||||||
| @@ -18,7 +17,7 @@ class TestDataContainer(object): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def generate_test_data(): | def generate_test_data(): | ||||||
|     ''' Builds a set of test_data data as returned by Keystone. ''' |     ''' Builds a set of test_data data as returned by Keystone V2. ''' | ||||||
|     test_data = TestDataContainer() |     test_data = TestDataContainer() | ||||||
| 
 | 
 | ||||||
|     keystone_service = { |     keystone_service = { | ||||||
| @@ -96,49 +95,49 @@ def generate_test_data(): | |||||||
|     expiration = datetime_safe.datetime.isoformat(tomorrow) |     expiration = datetime_safe.datetime.isoformat(tomorrow) | ||||||
| 
 | 
 | ||||||
|     scoped_token_dict = { |     scoped_token_dict = { | ||||||
|         'token': { |         'access': { | ||||||
|             'id': uuid.uuid4().hex, |             'token': { | ||||||
|             'expires': expiration, |                 'id': uuid.uuid4().hex, | ||||||
|             'tenant': tenant_dict_1, |                 'expires': expiration, | ||||||
|             'tenants': [tenant_dict_1, tenant_dict_2]}, |                 'tenant': tenant_dict_1, | ||||||
|         'user': { |                 'tenants': [tenant_dict_1, tenant_dict_2]}, | ||||||
|             'id': user_dict['id'], |             'user': { | ||||||
|             'name': user_dict['name'], |                 'id': user_dict['id'], | ||||||
|             'roles': [role_dict]}, |                 'name': user_dict['name'], | ||||||
|         'serviceCatalog': [keystone_service, nova_service] |                 'roles': [role_dict]}, | ||||||
|  |             'serviceCatalog': [keystone_service, nova_service] | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     test_data.scoped_token = Token(TokenManager(None), | 
 | ||||||
|                                    scoped_token_dict, |     test_data.scoped_access_info = AccessInfo.factory( | ||||||
|                                    loaded=True) |         resp=None, | ||||||
|  |         body=scoped_token_dict) | ||||||
| 
 | 
 | ||||||
|     unscoped_token_dict = { |     unscoped_token_dict = { | ||||||
|         'token': { |         'access': { | ||||||
|             'id': uuid.uuid4().hex, |             'token': { | ||||||
|             'expires': expiration}, |                 'id': uuid.uuid4().hex, | ||||||
|         'user': { |                 'expires': expiration}, | ||||||
|             'id': user_dict['id'], |             'user': { | ||||||
|             'name': user_dict['name'], |                      'id': user_dict['id'], | ||||||
|             'roles': [role_dict]}, |                      'name': user_dict['name'], | ||||||
|         'serviceCatalog': [keystone_service] |                      'roles': [role_dict]}, | ||||||
|  |             'serviceCatalog': [keystone_service] | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     test_data.unscoped_token = Token(TokenManager(None), |     test_data.unscoped_access_info = AccessInfo.factory( | ||||||
|                                      unscoped_token_dict, |         resp=None, | ||||||
|                                      loaded=True) |         body=unscoped_token_dict) | ||||||
| 
 | 
 | ||||||
|     # Service Catalog |     # Service Catalog | ||||||
|     test_data.service_catalog = ServiceCatalog.factory({ |     test_data.service_catalog = ServiceCatalog.factory({ | ||||||
|         'serviceCatalog': [keystone_service, nova_service], |         'serviceCatalog': [keystone_service, nova_service], | ||||||
|         'token': { |         'token': { | ||||||
|             'id': scoped_token_dict['token']['id'], |             'id': scoped_token_dict['access']['token']['id'], | ||||||
|             'expires': scoped_token_dict['token']['expires'], |             'expires': scoped_token_dict['access']['token']['expires'], | ||||||
|             'user_id': user_dict['id'], |             'user_id': user_dict['id'], | ||||||
|             'tenant_id': tenant_dict_1['id'] |             'tenant_id': tenant_dict_1['id'] | ||||||
|         } |         } | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     versioned_scoped_toked_dict = scoped_token_dict |  | ||||||
|     versioned_scoped_toked_dict['version'] = 'v2.0' |  | ||||||
| 
 |  | ||||||
|     test_data.access_info = access.AccessInfo(versioned_scoped_toked_dict) |  | ||||||
| 
 |  | ||||||
|     return test_data |     return test_data | ||||||
							
								
								
									
										233
									
								
								openstack_auth/tests/data_v3.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								openstack_auth/tests/data_v3.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | |||||||
|  | import requests | ||||||
|  | import uuid | ||||||
|  |  | ||||||
|  | from datetime import timedelta | ||||||
|  |  | ||||||
|  | from django.utils import datetime_safe | ||||||
|  |  | ||||||
|  | from keystoneclient.access import AccessInfo | ||||||
|  | from keystoneclient.service_catalog import ServiceCatalog | ||||||
|  | from keystoneclient.v3.domains import Domain, DomainManager | ||||||
|  | from keystoneclient.v3.roles import Role, RoleManager | ||||||
|  | from keystoneclient.v3.projects import Project, ProjectManager | ||||||
|  | from keystoneclient.v3.users import User, UserManager | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestDataContainer(object): | ||||||
|  |     """ Arbitrary holder for test data in an object-oriented fashion. """ | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestResponse(requests.Response): | ||||||
|  |     """ Class used to wrap requests.Response and provide some | ||||||
|  |         convenience to initialize with a dict """ | ||||||
|  |  | ||||||
|  |     def __init__(self, data): | ||||||
|  |         self._text = None | ||||||
|  |         super(TestResponse, self) | ||||||
|  |         if isinstance(data, dict): | ||||||
|  |             self.status_code = data.get('status_code', None) | ||||||
|  |             self.headers = data.get('headers', None) | ||||||
|  |             # Fake the text attribute to streamline Response creation | ||||||
|  |             self._text = data.get('text', None) | ||||||
|  |         else: | ||||||
|  |             self.status_code = data | ||||||
|  |  | ||||||
|  |     def __eq__(self, other): | ||||||
|  |         return self.__dict__ == other.__dict__ | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def text(self): | ||||||
|  |         return self._text | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def generate_test_data(): | ||||||
|  |     ''' Builds a set of test_data data as returned by Keystone V2. ''' | ||||||
|  |     test_data = TestDataContainer() | ||||||
|  |  | ||||||
|  |     keystone_service = { | ||||||
|  |         'type': 'identity', | ||||||
|  |         'id': uuid.uuid4().hex, | ||||||
|  |         'endpoints': [ | ||||||
|  |             { | ||||||
|  |                 'url': 'http://admin.localhost:35357/v3', | ||||||
|  |                 'region': 'RegionOne', | ||||||
|  |                 'interface': 'admin', | ||||||
|  |                 'id': uuid.uuid4().hex, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'url': 'http://internal.localhost:5000/v3', | ||||||
|  |                 'region': 'RegionOne', | ||||||
|  |                 'interface': 'internal', | ||||||
|  |                 'id': uuid.uuid4().hex | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'url':'http://public.localhost:5000/v3', | ||||||
|  |                 'region':'RegionOne', | ||||||
|  |                 'interface': 'public', | ||||||
|  |                  'id': uuid.uuid4().hex | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     # Domains | ||||||
|  |     domain_dict = {'id': uuid.uuid4().hex, | ||||||
|  |                    'name': 'domain', | ||||||
|  |                    'description': '', | ||||||
|  |                    'enabled': True} | ||||||
|  |     test_data.domain = Domain(DomainManager(None), domain_dict, loaded=True) | ||||||
|  |  | ||||||
|  |     # Users | ||||||
|  |     user_dict = {'id': uuid.uuid4().hex, | ||||||
|  |                  'name': 'gabriel', | ||||||
|  |                  'email': 'gabriel@example.com', | ||||||
|  |                  'password': 'swordfish', | ||||||
|  |                  'domain_id': domain_dict['id'], | ||||||
|  |                  'token': '', | ||||||
|  |                  'enabled': True} | ||||||
|  |     test_data.user = User(UserManager(None), user_dict, loaded=True) | ||||||
|  |  | ||||||
|  |     # Projects | ||||||
|  |     project_dict_1 = {'id': uuid.uuid4().hex, | ||||||
|  |                      'name': 'tenant_one', | ||||||
|  |                      'description': '', | ||||||
|  |                      'domain_id': domain_dict['id'], | ||||||
|  |                      'enabled': True} | ||||||
|  |     project_dict_2 = {'id': uuid.uuid4().hex, | ||||||
|  |                      'name': '', | ||||||
|  |                      'description': '', | ||||||
|  |                      'domain_id': domain_dict['id'], | ||||||
|  |                      'enabled': False} | ||||||
|  |     test_data.project_one = Project(ProjectManager(None), | ||||||
|  |                                   project_dict_1, | ||||||
|  |                                   loaded=True) | ||||||
|  |     test_data.project_two = Project(ProjectManager(None), | ||||||
|  |                                   project_dict_2, | ||||||
|  |                                   loaded=True) | ||||||
|  |  | ||||||
|  |     # Roles | ||||||
|  |     role_dict = {'id': uuid.uuid4().hex, | ||||||
|  |                  'name': 'Member'} | ||||||
|  |     test_data.role = Role(RoleManager, role_dict) | ||||||
|  |  | ||||||
|  |     nova_service = { | ||||||
|  |         'type': 'compute', | ||||||
|  |         'id': uuid.uuid4().hex, | ||||||
|  |         'endpoints': [ | ||||||
|  |             { | ||||||
|  |                 'url': 'http://nova-admin.localhost:8774/v2.0/%s' \ | ||||||
|  |                        % (project_dict_1['id']), | ||||||
|  |                 'region': 'RegionOne', | ||||||
|  |                 'interface': 'admin', | ||||||
|  |                 'id': uuid.uuid4().hex, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'url': 'http://nova-internal.localhost:8774/v2.0/%s' \ | ||||||
|  |                        % (project_dict_1['id']), | ||||||
|  |                 'region': 'RegionOne', | ||||||
|  |                 'interface': 'internal', | ||||||
|  |                 'id': uuid.uuid4().hex | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'url':'http://nova-public.localhost:8774/v2.0/%s' \ | ||||||
|  |                        % (project_dict_1['id']), | ||||||
|  |                 'region':'RegionOne', | ||||||
|  |                 'interface': 'public', | ||||||
|  |                  'id': uuid.uuid4().hex | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'url': 'http://nova2-admin.localhost:8774/v2.0/%s' \ | ||||||
|  |                        % (project_dict_1['id']), | ||||||
|  |                 'region': 'RegionTwo', | ||||||
|  |                 'interface': 'admin', | ||||||
|  |                 'id': uuid.uuid4().hex, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'url': 'http://nova2-internal.localhost:8774/v2.0/%s' \ | ||||||
|  |                        % (project_dict_1['id']), | ||||||
|  |                 'region': 'RegionTwo', | ||||||
|  |                 'interface': 'internal', | ||||||
|  |                 'id': uuid.uuid4().hex | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'url':'http://nova2-public.localhost:8774/v2.0/%s' \ | ||||||
|  |                        % (project_dict_1['id']), | ||||||
|  |                 'region':'RegionTwo', | ||||||
|  |                 'interface': 'public', | ||||||
|  |                  'id': uuid.uuid4().hex | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     # Tokens | ||||||
|  |     tomorrow = datetime_safe.datetime.now() + timedelta(days=1) | ||||||
|  |     expiration = datetime_safe.datetime.isoformat(tomorrow) | ||||||
|  |     auth_token = uuid.uuid4().hex | ||||||
|  |     auth_response_headers = { | ||||||
|  |         'X-Subject-Token': auth_token | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     auth_response = TestResponse({ | ||||||
|  |         "headers": auth_response_headers | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     scoped_token_dict = { | ||||||
|  |         'token': { | ||||||
|  |             'methods': ['password'], | ||||||
|  |             'expires_at': expiration, | ||||||
|  |             'project': { | ||||||
|  |                 'id': project_dict_1['id'], | ||||||
|  |                 'name': project_dict_1['name'], | ||||||
|  |                 'domain': { | ||||||
|  |                     'id': domain_dict['id'], | ||||||
|  |                     'name': domain_dict['name'] | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             'user': { | ||||||
|  |                 'id': user_dict['id'], | ||||||
|  |                 'name': user_dict['name'], | ||||||
|  |                 'domain': { | ||||||
|  |                     'id': domain_dict['id'], | ||||||
|  |                     'name': domain_dict['name'] | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             'roles': [role_dict], | ||||||
|  |             'catalog': [keystone_service, nova_service] | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     test_data.scoped_access_info = AccessInfo.factory( | ||||||
|  |         resp=auth_response, | ||||||
|  |         body=scoped_token_dict | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     unscoped_token_dict = { | ||||||
|  |         'token': { | ||||||
|  |             'methods': ['password'], | ||||||
|  |             'expires_at': expiration, | ||||||
|  |             'user': { | ||||||
|  |                 'id': user_dict['id'], | ||||||
|  |                 'name': user_dict['name'], | ||||||
|  |                 'domain': { | ||||||
|  |                     'id': domain_dict['id'], | ||||||
|  |                     'name': domain_dict['name'] | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             'roles': [role_dict], | ||||||
|  |             'catalog': [keystone_service] | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     test_data.unscoped_access_info = AccessInfo.factory( | ||||||
|  |         resp=auth_response, | ||||||
|  |         body=unscoped_token_dict | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # Service Catalog | ||||||
|  |     test_data.service_catalog = ServiceCatalog.factory({ | ||||||
|  |         'methods': ['password'], | ||||||
|  |         'user': {}, | ||||||
|  |         'catalog': [keystone_service, nova_service], | ||||||
|  |     }, token=auth_token) | ||||||
|  |  | ||||||
|  |     return test_data | ||||||
| @@ -25,3 +25,13 @@ ROOT_URLCONF = 'openstack_auth.tests.urls' | |||||||
| LOGIN_REDIRECT_URL = '/' | LOGIN_REDIRECT_URL = '/' | ||||||
|  |  | ||||||
| SECRET_KEY = 'badcafe' | SECRET_KEY = 'badcafe' | ||||||
|  |  | ||||||
|  | OPENSTACK_API_VERSIONS = { | ||||||
|  |     "identity": 2.0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | USE_TZ = True | ||||||
|  |  | ||||||
|  | OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = False | ||||||
|  |  | ||||||
|  | OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'domain' | ||||||
|   | |||||||
| @@ -1,26 +1,34 @@ | |||||||
|  | import mox | ||||||
|  |  | ||||||
| from django import test | from django import test | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.contrib.auth import REDIRECT_FIELD_NAME | from django.contrib.auth import REDIRECT_FIELD_NAME | ||||||
| from django.core.urlresolvers import reverse | from django.core.urlresolvers import reverse | ||||||
|  |  | ||||||
| from keystoneclient import exceptions as keystone_exceptions | from keystoneclient import exceptions as keystone_exceptions | ||||||
| from keystoneclient.v2_0 import client | from keystoneclient.v2_0 import client as client_v2 | ||||||
|  | from keystoneclient.v3 import client as client_v3 | ||||||
|  |  | ||||||
| import mox | from .data_v2 import generate_test_data as data_v2 | ||||||
|  | from .data_v3 import generate_test_data as data_v3 | ||||||
| from .data import generate_test_data |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OpenStackAuthTests(test.TestCase): | DEFAULT_DOMAIN = settings.OPENSTACK_KEYSTONE_DEFAULT_DOMAIN | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OpenStackAuthTestsV2(test.TestCase): | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         super(OpenStackAuthTests, self).setUp() |         super(OpenStackAuthTestsV2, self).setUp() | ||||||
|         self.mox = mox.Mox() |         self.mox = mox.Mox() | ||||||
|         self.data = generate_test_data() |         self.data = data_v2() | ||||||
|  |         self.ks_client_module = client_v2 | ||||||
|         endpoint = settings.OPENSTACK_KEYSTONE_URL |         endpoint = settings.OPENSTACK_KEYSTONE_URL | ||||||
|         self.keystone_client = client.Client(endpoint=endpoint, |         self.keystone_client_unscoped = self.ks_client_module.Client( | ||||||
|                                              auth_ref=self.data.access_info) |             endpoint=endpoint, | ||||||
|         if not hasattr(self.keystone_client, 'service_catalog'): |             auth_ref=self.data.unscoped_access_info) | ||||||
|             self.keystone_client.service_catalog = self.data.service_catalog |         self.keystone_client_scoped = self.ks_client_module.Client( | ||||||
|  |             endpoint=endpoint, | ||||||
|  |             auth_ref=self.data.scoped_access_info) | ||||||
|  |  | ||||||
|     def tearDown(self): |     def tearDown(self): | ||||||
|         self.mox.UnsetStubs() |         self.mox.UnsetStubs() | ||||||
| @@ -29,31 +37,30 @@ class OpenStackAuthTests(test.TestCase): | |||||||
|     def test_login(self): |     def test_login(self): | ||||||
|         tenants = [self.data.tenant_one, self.data.tenant_two] |         tenants = [self.data.tenant_one, self.data.tenant_two] | ||||||
|         user = self.data.user |         user = self.data.user | ||||||
|         sc = self.data.service_catalog |         unscoped = self.data.unscoped_access_info | ||||||
|  |  | ||||||
|         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|                      'password': user.password, |                      'password': user.password, | ||||||
|                      'username': user.name} |                      'username': user.name} | ||||||
|  |  | ||||||
|         self.mox.StubOutWithMock(client, "Client") |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|         self.mox.StubOutWithMock(self.keystone_client.tenants, "list") |         self.mox.StubOutWithMock(self.keystone_client_unscoped.tenants, "list") | ||||||
|         self.mox.StubOutWithMock(self.keystone_client.tokens, "authenticate") |  | ||||||
|  |  | ||||||
|         client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|                       password=user.password, |                                      password=user.password, | ||||||
|                       username=user.name, |                                      username=user.name, | ||||||
|                       insecure=False, |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|                       tenant_id=None).AndReturn(self.keystone_client) |                                      insecure=False, | ||||||
|         self.keystone_client.tenants.list().AndReturn(tenants) |                                      debug=False)\ | ||||||
|         client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, |                 .AndReturn(self.keystone_client_unscoped) | ||||||
|                       tenant_id=self.data.tenant_two.id, |         self.keystone_client_unscoped.tenants.list().AndReturn(tenants) | ||||||
|                       insecure=False, |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|                       token=sc.get_token()['id']) \ |                                      tenant_id=self.data.tenant_two.id, | ||||||
|                 .AndReturn(self.keystone_client) |                                      insecure=False, | ||||||
|         self.keystone_client.tokens.authenticate(tenant_id=tenants[1].id, |                                      token=unscoped.auth_token, | ||||||
|                                                  token=sc.get_token()['id'], |                                      debug=False) \ | ||||||
|                                                  username=user.name) \ |                 .AndReturn(self.keystone_client_scoped) | ||||||
|                             .AndReturn(self.data.scoped_token) |  | ||||||
|  |  | ||||||
|         self.mox.ReplayAll() |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
| @@ -71,18 +78,21 @@ class OpenStackAuthTests(test.TestCase): | |||||||
|         user = self.data.user |         user = self.data.user | ||||||
|  |  | ||||||
|         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|                      'password': user.password, |                      'password': user.password, | ||||||
|                      'username': user.name} |                      'username': user.name} | ||||||
|  |  | ||||||
|         self.mox.StubOutWithMock(client, "Client") |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|         self.mox.StubOutWithMock(self.keystone_client.tenants, "list") |         self.mox.StubOutWithMock(self.keystone_client_unscoped.tenants, "list") | ||||||
|  |  | ||||||
|         client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|                       password=user.password, |                                      password=user.password, | ||||||
|                       username=user.name, |                                      username=user.name, | ||||||
|                       insecure=False, |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|                       tenant_id=None).AndReturn(self.keystone_client) |                                      insecure=False, | ||||||
|         self.keystone_client.tenants.list().AndReturn([]) |                                      debug=False)\ | ||||||
|  |                 .AndReturn(self.keystone_client_unscoped) | ||||||
|  |         self.keystone_client_unscoped.tenants.list().AndReturn([]) | ||||||
|  |  | ||||||
|         self.mox.ReplayAll() |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
| @@ -102,17 +112,19 @@ class OpenStackAuthTests(test.TestCase): | |||||||
|         user = self.data.user |         user = self.data.user | ||||||
|  |  | ||||||
|         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|                      'password': "invalid", |                      'password': "invalid", | ||||||
|                      'username': user.name} |                      'username': user.name} | ||||||
|  |  | ||||||
|         self.mox.StubOutWithMock(client, "Client") |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|  |  | ||||||
|         exc = keystone_exceptions.Unauthorized(401) |         exc = keystone_exceptions.Unauthorized(401) | ||||||
|         client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|                       password="invalid", |                                      password="invalid", | ||||||
|                       username=user.name, |                                      username=user.name, | ||||||
|                       insecure=False, |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|                       tenant_id=None).AndRaise(exc) |                                      insecure=False, | ||||||
|  |                                      debug=False).AndRaise(exc) | ||||||
|  |  | ||||||
|         self.mox.ReplayAll() |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
| @@ -131,17 +143,19 @@ class OpenStackAuthTests(test.TestCase): | |||||||
|         user = self.data.user |         user = self.data.user | ||||||
|  |  | ||||||
|         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|                      'password': user.password, |                      'password': user.password, | ||||||
|                      'username': user.name} |                      'username': user.name} | ||||||
|  |  | ||||||
|         self.mox.StubOutWithMock(client, "Client") |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|  |  | ||||||
|         exc = keystone_exceptions.ClientException(500) |         exc = keystone_exceptions.ClientException(500) | ||||||
|         client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|                       password=user.password, |                                      password=user.password, | ||||||
|                       username=user.name, |                                      username=user.name, | ||||||
|                       insecure=False, |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|                       tenant_id=None).AndRaise(exc) |                                      insecure=False, | ||||||
|  |                                      debug=False).AndRaise(exc) | ||||||
|  |  | ||||||
|         self.mox.ReplayAll() |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
| @@ -163,41 +177,39 @@ class OpenStackAuthTests(test.TestCase): | |||||||
|         tenant = self.data.tenant_two |         tenant = self.data.tenant_two | ||||||
|         tenants = [self.data.tenant_one, self.data.tenant_two] |         tenants = [self.data.tenant_one, self.data.tenant_two] | ||||||
|         user = self.data.user |         user = self.data.user | ||||||
|         scoped = self.data.scoped_token |         unscoped = self.data.unscoped_access_info | ||||||
|  |         scoped = self.data.scoped_access_info | ||||||
|         sc = self.data.service_catalog |         sc = self.data.service_catalog | ||||||
|  |  | ||||||
|         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|                      'username': user.name, |                      'username': user.name, | ||||||
|                      'password': user.password} |                      'password': user.password} | ||||||
|  |  | ||||||
|         self.mox.StubOutWithMock(client, "Client") |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|         self.mox.StubOutWithMock(self.keystone_client.tenants, "list") |         self.mox.StubOutWithMock(self.keystone_client_unscoped.tenants, "list") | ||||||
|         self.mox.StubOutWithMock(self.keystone_client.tokens, "authenticate") |  | ||||||
|  |  | ||||||
|         client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|                       password=user.password, |                                      password=user.password, | ||||||
|                       username=user.name, |                                      username=user.name, | ||||||
|                       insecure=False, |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|                       tenant_id=None).AndReturn(self.keystone_client) |                                      insecure=False, | ||||||
|         self.keystone_client.tenants.list().AndReturn(tenants) |                                      debug=False) \ | ||||||
|         self.keystone_client.tokens.authenticate(tenant_id=tenants[1].id, |                 .AndReturn(self.keystone_client_unscoped) | ||||||
|                                                  token=sc.get_token()['id'], |         self.keystone_client_unscoped.tenants.list().AndReturn(tenants) | ||||||
|                                                  username=user.name) \ |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|                             .AndReturn(scoped) |                                      tenant_id=self.data.tenant_two.id, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      token=unscoped.auth_token, | ||||||
|  |                                      debug=False) \ | ||||||
|  |                 .AndReturn(self.keystone_client_scoped) | ||||||
|  |  | ||||||
|         client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, |         self.ks_client_module.Client(auth_url=sc.url_for(), | ||||||
|                       tenant_id=self.data.tenant_two.id, |                                      tenant_id=tenant.id, | ||||||
|                       insecure=False, |                                      token=scoped.auth_token, | ||||||
|                       token=sc.get_token()['id']) \ |                                      insecure=False, | ||||||
|                 .AndReturn(self.keystone_client) |                                      debug=False) \ | ||||||
|  |                 .AndReturn(self.keystone_client_scoped) | ||||||
|         client.Client(endpoint=sc.url_for(), |  | ||||||
|                       insecure=False) \ |  | ||||||
|                 .AndReturn(self.keystone_client) |  | ||||||
|  |  | ||||||
|         self.keystone_client.tokens.authenticate(tenant_id=tenant.id, |  | ||||||
|                                                  token=sc.get_token()['id']) \ |  | ||||||
|                             .AndReturn(scoped) |  | ||||||
|  |  | ||||||
|         self.mox.ReplayAll() |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
| @@ -211,10 +223,7 @@ class OpenStackAuthTests(test.TestCase): | |||||||
|  |  | ||||||
|         url = reverse('switch_tenants', args=[tenant.id]) |         url = reverse('switch_tenants', args=[tenant.id]) | ||||||
|  |  | ||||||
|         scoped.tenant['id'] = self.data.tenant_two._info |         scoped['token']['tenant']['id'] = self.data.tenant_two.id | ||||||
|         sc.catalog['token']['id'] = self.data.tenant_two.id |  | ||||||
|  |  | ||||||
|         form_data['tenant_id'] = tenant.id |  | ||||||
|  |  | ||||||
|         if next: |         if next: | ||||||
|             form_data.update({REDIRECT_FIELD_NAME: next}) |             form_data.update({REDIRECT_FIELD_NAME: next}) | ||||||
| @@ -226,8 +235,9 @@ class OpenStackAuthTests(test.TestCase): | |||||||
|             self.assertEqual(response['location'], expected_url) |             self.assertEqual(response['location'], expected_url) | ||||||
|         else: |         else: | ||||||
|             self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) |             self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) | ||||||
|         self.assertEqual(self.client.session['token']['token']['tenant']['id'], |  | ||||||
|                          scoped.tenant['id']) |         self.assertEqual(self.client.session['token'].tenant['id'], | ||||||
|  |                          scoped.tenant_id) | ||||||
|  |  | ||||||
|     def test_switch_with_next(self): |     def test_switch_with_next(self): | ||||||
|         self.test_switch(next='/next_url') |         self.test_switch(next='/next_url') | ||||||
| @@ -236,33 +246,331 @@ class OpenStackAuthTests(test.TestCase): | |||||||
|         tenant = self.data.tenant_one |         tenant = self.data.tenant_one | ||||||
|         tenants = [self.data.tenant_one, self.data.tenant_two] |         tenants = [self.data.tenant_one, self.data.tenant_two] | ||||||
|         user = self.data.user |         user = self.data.user | ||||||
|         scoped = self.data.scoped_token |         unscoped = self.data.unscoped_access_info | ||||||
|         sc = self.data.service_catalog |         sc = self.data.service_catalog | ||||||
|  |  | ||||||
|         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|                      'username': user.name, |                      'username': user.name, | ||||||
|                      'password': user.password} |                      'password': user.password} | ||||||
|  |  | ||||||
|         self.mox.StubOutWithMock(client, "Client") |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|         self.mox.StubOutWithMock(self.keystone_client.tenants, "list") |         self.mox.StubOutWithMock(self.keystone_client_unscoped.tenants, "list") | ||||||
|         self.mox.StubOutWithMock(self.keystone_client.tokens, "authenticate") |  | ||||||
|  |  | ||||||
|         client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|                       password=user.password, |                                      password=user.password, | ||||||
|                       username=user.name, |                                      username=user.name, | ||||||
|                       insecure=False, |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|                       tenant_id=None).AndReturn(self.keystone_client) |                                      insecure=False, | ||||||
|         self.keystone_client.tenants.list().AndReturn(tenants) |                                      debug=False) \ | ||||||
|         self.keystone_client.tokens.authenticate(tenant_id=tenants[1].id, |                 .AndReturn(self.keystone_client_unscoped) | ||||||
|                                                  token=sc.get_token()['id'], |         self.keystone_client_unscoped.tenants.list().AndReturn(tenants) | ||||||
|                                                  username=user.name) \ |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|                             .AndReturn(scoped) |                                      tenant_id=self.data.tenant_two.id, | ||||||
|  |                                      insecure=False, | ||||||
|         client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, |                                      token=unscoped.auth_token, | ||||||
|                       tenant_id=self.data.tenant_two.id, |                                      debug=False) \ | ||||||
|                       insecure=False, |                 .AndReturn(self.keystone_client_scoped) | ||||||
|                       token=sc.get_token()['id']) \ |  | ||||||
|                 .AndReturn(self.keystone_client) |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
|  |         url = reverse('login') | ||||||
|  |  | ||||||
|  |         response = self.client.get(url) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |         response = self.client.post(url, form_data) | ||||||
|  |         self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) | ||||||
|  |  | ||||||
|  |         old_region = sc.get_endpoints()['compute'][0]['region'] | ||||||
|  |         self.assertEqual(self.client.session['services_region'], old_region) | ||||||
|  |  | ||||||
|  |         region = sc.get_endpoints()['compute'][1]['region'] | ||||||
|  |         url = reverse('switch_services_region', args=[region]) | ||||||
|  |  | ||||||
|  |         form_data['region_name'] = region | ||||||
|  |  | ||||||
|  |         if next: | ||||||
|  |             form_data.update({REDIRECT_FIELD_NAME: next}) | ||||||
|  |  | ||||||
|  |         response = self.client.get(url, form_data) | ||||||
|  |  | ||||||
|  |         if next: | ||||||
|  |             expected_url = 'http://testserver%s' % next | ||||||
|  |             self.assertEqual(response['location'], expected_url) | ||||||
|  |         else: | ||||||
|  |             self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) | ||||||
|  |  | ||||||
|  |         self.assertEqual(self.client.session['services_region'], region) | ||||||
|  |  | ||||||
|  |     def test_switch_region_with_next(self, next=None): | ||||||
|  |         self.test_switch_region(next='/next_url') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OpenStackAuthTestsV3(test.TestCase): | ||||||
|  |     def setUp(self): | ||||||
|  |         super(OpenStackAuthTestsV3, self).setUp() | ||||||
|  |         self.mox = mox.Mox() | ||||||
|  |         self.data = data_v3() | ||||||
|  |         self.ks_client_module = client_v3 | ||||||
|  |         endpoint = settings.OPENSTACK_KEYSTONE_URL | ||||||
|  |         self.keystone_client_unscoped = self.ks_client_module.Client( | ||||||
|  |             endpoint=endpoint, | ||||||
|  |             auth_ref=self.data.unscoped_access_info) | ||||||
|  |         self.keystone_client_scoped = self.ks_client_module.Client( | ||||||
|  |             endpoint=endpoint, | ||||||
|  |             auth_ref=self.data.scoped_access_info) | ||||||
|  |         settings.OPENSTACK_API_VERSIONS['identity'] = 3 | ||||||
|  |         settings.OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v3" | ||||||
|  |  | ||||||
|  |     def tearDown(self): | ||||||
|  |         self.mox.UnsetStubs() | ||||||
|  |         self.mox.VerifyAll() | ||||||
|  |  | ||||||
|  |     def test_login(self): | ||||||
|  |         projects = [self.data.project_one, self.data.project_two] | ||||||
|  |         user = self.data.user | ||||||
|  |         unscoped = self.data.unscoped_access_info | ||||||
|  |  | ||||||
|  |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|  |                      'password': user.password, | ||||||
|  |                      'username': user.name} | ||||||
|  |  | ||||||
|  |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|  |         self.mox.StubOutWithMock(self.keystone_client_unscoped.projects, | ||||||
|  |                                  "list") | ||||||
|  |  | ||||||
|  |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                                      password=user.password, | ||||||
|  |                                      username=user.name, | ||||||
|  |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      debug=False)\ | ||||||
|  |                 .AndReturn(self.keystone_client_unscoped) | ||||||
|  |         self.keystone_client_unscoped.projects.list(user=user.id) \ | ||||||
|  |                 .AndReturn(projects) | ||||||
|  |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                                      tenant_id=self.data.project_two.id, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      token=unscoped.auth_token, | ||||||
|  |                                      debug=False) \ | ||||||
|  |                 .AndReturn(self.keystone_client_scoped) | ||||||
|  |  | ||||||
|  |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
|  |         url = reverse('login') | ||||||
|  |  | ||||||
|  |         # GET the page to set the test cookie. | ||||||
|  |         response = self.client.get(url, form_data) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |         # POST to the page to log in. | ||||||
|  |         response = self.client.post(url, form_data) | ||||||
|  |         self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) | ||||||
|  |  | ||||||
|  |     def test_no_tenants(self): | ||||||
|  |         user = self.data.user | ||||||
|  |  | ||||||
|  |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|  |                      'password': user.password, | ||||||
|  |                      'username': user.name} | ||||||
|  |  | ||||||
|  |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|  |         self.mox.StubOutWithMock(self.keystone_client_unscoped.projects, | ||||||
|  |                                  "list") | ||||||
|  |  | ||||||
|  |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                                      password=user.password, | ||||||
|  |                                      username=user.name, | ||||||
|  |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      debug=False)\ | ||||||
|  |                 .AndReturn(self.keystone_client_unscoped) | ||||||
|  |         self.keystone_client_unscoped.projects.list(user=user.id) \ | ||||||
|  |                 .AndReturn([]) | ||||||
|  |  | ||||||
|  |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
|  |         url = reverse('login') | ||||||
|  |  | ||||||
|  |         # GET the page to set the test cookie. | ||||||
|  |         response = self.client.get(url, form_data) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |         # POST to the page to log in. | ||||||
|  |         response = self.client.post(url, form_data) | ||||||
|  |         self.assertTemplateUsed(response, 'auth/login.html') | ||||||
|  |         self.assertContains(response, | ||||||
|  |                             'You are not authorized for any projects.') | ||||||
|  |  | ||||||
|  |     def test_invalid_credentials(self): | ||||||
|  |         user = self.data.user | ||||||
|  |  | ||||||
|  |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|  |                      'password': "invalid", | ||||||
|  |                      'username': user.name} | ||||||
|  |  | ||||||
|  |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|  |  | ||||||
|  |         exc = keystone_exceptions.Unauthorized(401) | ||||||
|  |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                                      password="invalid", | ||||||
|  |                                      username=user.name, | ||||||
|  |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      debug=False).AndRaise(exc) | ||||||
|  |  | ||||||
|  |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
|  |         url = reverse('login') | ||||||
|  |  | ||||||
|  |         # GET the page to set the test cookie. | ||||||
|  |         response = self.client.get(url, form_data) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |         # POST to the page to log in. | ||||||
|  |         response = self.client.post(url, form_data) | ||||||
|  |         self.assertTemplateUsed(response, 'auth/login.html') | ||||||
|  |         self.assertContains(response, "Invalid user name or password.") | ||||||
|  |  | ||||||
|  |     def test_exception(self): | ||||||
|  |         user = self.data.user | ||||||
|  |  | ||||||
|  |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|  |                      'password': user.password, | ||||||
|  |                      'username': user.name} | ||||||
|  |  | ||||||
|  |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|  |  | ||||||
|  |         exc = keystone_exceptions.ClientException(500) | ||||||
|  |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                                      password=user.password, | ||||||
|  |                                      username=user.name, | ||||||
|  |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      debug=False).AndRaise(exc) | ||||||
|  |  | ||||||
|  |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
|  |         url = reverse('login') | ||||||
|  |  | ||||||
|  |         # GET the page to set the test cookie. | ||||||
|  |         response = self.client.get(url, form_data) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |         # POST to the page to log in. | ||||||
|  |         response = self.client.post(url, form_data) | ||||||
|  |  | ||||||
|  |         self.assertTemplateUsed(response, 'auth/login.html') | ||||||
|  |         self.assertContains(response, | ||||||
|  |                             ("An error occurred authenticating. Please try " | ||||||
|  |                              "again later.")) | ||||||
|  |  | ||||||
|  |     def test_switch(self, next=None): | ||||||
|  |         project = self.data.project_two | ||||||
|  |         projects = [self.data.project_one, self.data.project_two] | ||||||
|  |         user = self.data.user | ||||||
|  |         unscoped = self.data.unscoped_access_info | ||||||
|  |         scoped = self.data.scoped_access_info | ||||||
|  |         sc = self.data.service_catalog | ||||||
|  |  | ||||||
|  |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|  |                      'username': user.name, | ||||||
|  |                      'password': user.password} | ||||||
|  |  | ||||||
|  |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|  |         self.mox.StubOutWithMock(self.keystone_client_unscoped.projects, | ||||||
|  |                                  "list") | ||||||
|  |  | ||||||
|  |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                                      password=user.password, | ||||||
|  |                                      username=user.name, | ||||||
|  |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      debug=False) \ | ||||||
|  |                 .AndReturn(self.keystone_client_unscoped) | ||||||
|  |         self.keystone_client_unscoped.projects.list(user=user.id) \ | ||||||
|  |                 .AndReturn(projects) | ||||||
|  |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                                      tenant_id=self.data.project_two.id, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      token=unscoped.auth_token, | ||||||
|  |                                      debug=False) \ | ||||||
|  |                 .AndReturn(self.keystone_client_scoped) | ||||||
|  |         self.ks_client_module.Client(auth_url=sc.url_for(), | ||||||
|  |                                      tenant_id=project.id, | ||||||
|  |                                      token=scoped.auth_token, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      debug=False) \ | ||||||
|  |                 .AndReturn(self.keystone_client_scoped) | ||||||
|  |  | ||||||
|  |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
|  |         url = reverse('login') | ||||||
|  |  | ||||||
|  |         response = self.client.get(url) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |         response = self.client.post(url, form_data) | ||||||
|  |         self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) | ||||||
|  |  | ||||||
|  |         url = reverse('switch_tenants', args=[project.id]) | ||||||
|  |  | ||||||
|  |         scoped['project']['id'] = self.data.project_two.id | ||||||
|  |  | ||||||
|  |         if next: | ||||||
|  |             form_data.update({REDIRECT_FIELD_NAME: next}) | ||||||
|  |  | ||||||
|  |         response = self.client.get(url, form_data) | ||||||
|  |  | ||||||
|  |         if next: | ||||||
|  |             expected_url = 'http://testserver%s' % next | ||||||
|  |             self.assertEqual(response['location'], expected_url) | ||||||
|  |         else: | ||||||
|  |             self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) | ||||||
|  |  | ||||||
|  |         self.assertEqual(self.client.session['token'].project['id'], | ||||||
|  |                          scoped.project_id) | ||||||
|  |  | ||||||
|  |     def test_switch_with_next(self): | ||||||
|  |         self.test_switch(next='/next_url') | ||||||
|  |  | ||||||
|  |     def test_switch_region(self, next=None): | ||||||
|  |  | ||||||
|  |         projects = [self.data.project_one, self.data.project_two] | ||||||
|  |         user = self.data.user | ||||||
|  |         unscoped = self.data.unscoped_access_info | ||||||
|  |         sc = self.data.service_catalog | ||||||
|  |  | ||||||
|  |         form_data = {'region': settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                      'domain': DEFAULT_DOMAIN, | ||||||
|  |                      'username': user.name, | ||||||
|  |                      'password': user.password} | ||||||
|  |  | ||||||
|  |         self.mox.StubOutWithMock(self.ks_client_module, "Client") | ||||||
|  |         self.mox.StubOutWithMock(self.keystone_client_unscoped.projects, | ||||||
|  |                                  "list") | ||||||
|  |  | ||||||
|  |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                                      password=user.password, | ||||||
|  |                                      username=user.name, | ||||||
|  |                                      user_domain_name=DEFAULT_DOMAIN, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      debug=False) \ | ||||||
|  |                 .AndReturn(self.keystone_client_unscoped) | ||||||
|  |         self.keystone_client_unscoped.projects.list(user=user.id) \ | ||||||
|  |                 .AndReturn(projects) | ||||||
|  |         self.ks_client_module.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL, | ||||||
|  |                                      tenant_id=self.data.project_two.id, | ||||||
|  |                                      insecure=False, | ||||||
|  |                                      token=unscoped.auth_token, | ||||||
|  |                                      debug=False) \ | ||||||
|  |                 .AndReturn(self.keystone_client_scoped) | ||||||
|  |  | ||||||
|         self.mox.ReplayAll() |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| from django.conf.urls.defaults import patterns, url | from django.conf.urls.defaults import patterns, url | ||||||
|  |  | ||||||
| from .utils import patch_middleware_get_user | from .utils import patch_middleware_get_user | ||||||
| from .views import switch_region |  | ||||||
|  |  | ||||||
| patch_middleware_get_user() | patch_middleware_get_user() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,24 +4,23 @@ import logging | |||||||
| from django.contrib.auth.models import AnonymousUser | from django.contrib.auth.models import AnonymousUser | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
|  |  | ||||||
| from keystoneclient.v2_0 import client as keystone_client |  | ||||||
| from keystoneclient import exceptions as keystone_exceptions | from keystoneclient import exceptions as keystone_exceptions | ||||||
|  |  | ||||||
| from .utils import check_token_expiration, is_ans1_token | from .utils import check_token_expiration | ||||||
|  | from .utils import get_keystone_version | ||||||
|  | from .utils import get_project_list | ||||||
|  | from .utils import is_ans1_token | ||||||
|  |  | ||||||
|  |  | ||||||
| LOG = logging.getLogger(__name__) | LOG = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
| def set_session_from_user(request, user): | def set_session_from_user(request, user): | ||||||
|     if is_ans1_token(user.token.id): |     request.session['token'] = user.token | ||||||
|         hashed_token = hashlib.md5(user.token.id).hexdigest() |  | ||||||
|         user.token._info['token']['id'] = hashed_token |  | ||||||
|     if 'token_list' not in request.session: |     if 'token_list' not in request.session: | ||||||
|         request.session['token_list'] = [] |         request.session['token_list'] = [] | ||||||
|     token_tuple = (user.endpoint, user.token.id) |     token_tuple = (user.endpoint, user.token.id) | ||||||
|     request.session['token_list'].append(token_tuple) |     request.session['token_list'].append(token_tuple) | ||||||
|     request.session['token'] = user.token._info |  | ||||||
|     request.session['user_id'] = user.id |     request.session['user_id'] = user.id | ||||||
|     request.session['region_endpoint'] = user.endpoint |     request.session['region_endpoint'] = user.endpoint | ||||||
|     request.session['services_region'] = user.services_region |     request.session['services_region'] = user.services_region | ||||||
| @@ -31,17 +30,65 @@ def create_user_from_token(request, token, endpoint, services_region=None): | |||||||
|     return User(id=token.user['id'], |     return User(id=token.user['id'], | ||||||
|                 token=token, |                 token=token, | ||||||
|                 user=token.user['name'], |                 user=token.user['name'], | ||||||
|                 tenant_id=token.tenant['id'], |                 user_domain_id=token.user_domain_id, | ||||||
|                 tenant_name=token.tenant['name'], |                 project_id=token.project['id'], | ||||||
|  |                 project_name=token.project['name'], | ||||||
|  |                 domain_id=token.domain['id'], | ||||||
|  |                 domain_name=token.domain['name'], | ||||||
|                 enabled=True, |                 enabled=True, | ||||||
|                 service_catalog=token.serviceCatalog, |                 service_catalog=token.serviceCatalog, | ||||||
|                 roles=token.user['roles'], |                 roles=token.roles, | ||||||
|                 endpoint=endpoint, |                 endpoint=endpoint, | ||||||
|                 services_region=services_region) |                 services_region=services_region) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Token(object): | ||||||
|  |     """Token object that encapsulates the auth_ref (AccessInfo)from keystone | ||||||
|  |        client. | ||||||
|  |  | ||||||
|  |        Added for maintaining backward compatibility with horizon that expects | ||||||
|  |        Token object in the user object. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, auth_ref): | ||||||
|  |         # User-related attributes | ||||||
|  |         user = {} | ||||||
|  |         user['id'] = auth_ref.user_id | ||||||
|  |         user['name'] = auth_ref.username | ||||||
|  |         self.user = user | ||||||
|  |         self.user_domain_id = auth_ref.user_domain_id | ||||||
|  |  | ||||||
|  |         #Token-related attributes | ||||||
|  |         self.id = auth_ref.auth_token | ||||||
|  |         if is_ans1_token(self.id): | ||||||
|  |             self.id = hashlib.md5(self.id).hexdigest() | ||||||
|  |         self.expires = auth_ref.expires | ||||||
|  |  | ||||||
|  |         # Project-related attributes | ||||||
|  |         project = {} | ||||||
|  |         project['id'] = auth_ref.project_id | ||||||
|  |         project['name'] = auth_ref.project_name | ||||||
|  |         self.project = project | ||||||
|  |         self.tenant = self.project | ||||||
|  |  | ||||||
|  |         # Domain-related attributes | ||||||
|  |         domain = {} | ||||||
|  |         domain['id'] = auth_ref.domain_id | ||||||
|  |         domain['name'] = auth_ref.domain_name | ||||||
|  |         self.domain = domain | ||||||
|  |  | ||||||
|  |         if auth_ref.version == 'v2.0': | ||||||
|  |             self.roles = auth_ref['user']['roles'] | ||||||
|  |         else: | ||||||
|  |             self.roles = auth_ref['roles'] | ||||||
|  |  | ||||||
|  |         if get_keystone_version() < 3: | ||||||
|  |             self.serviceCatalog = auth_ref.get('serviceCatalog', []) | ||||||
|  |         else: | ||||||
|  |             self.serviceCatalog = auth_ref.get('catalog', []) | ||||||
|  |  | ||||||
|  |  | ||||||
| class User(AnonymousUser): | class User(AnonymousUser): | ||||||
|     """ A User class with some extra special sauce for Keystone. |     """A User class with some extra special sauce for Keystone. | ||||||
|  |  | ||||||
|     In addition to the standard Django user attributes, this class also has |     In addition to the standard Django user attributes, this class also has | ||||||
|     the following: |     the following: | ||||||
| @@ -50,14 +97,29 @@ class User(AnonymousUser): | |||||||
|  |  | ||||||
|         The Keystone token object associated with the current user/tenant. |         The Keystone token object associated with the current user/tenant. | ||||||
|  |  | ||||||
|  |         The token object is deprecated, user auth_ref instead. | ||||||
|  |  | ||||||
|     .. attribute:: tenant_id |     .. attribute:: tenant_id | ||||||
|  |  | ||||||
|         The id of the Keystone tenant for the current user/token. |         The id of the Keystone tenant for the current user/token. | ||||||
|  |  | ||||||
|  |         The tenant_id keyword argument is deprecated, use project_id instead. | ||||||
|  |  | ||||||
|     .. attribute:: tenant_name |     .. attribute:: tenant_name | ||||||
|  |  | ||||||
|         The name of the Keystone tenant for the current user/token. |         The name of the Keystone tenant for the current user/token. | ||||||
|  |  | ||||||
|  |         The tenant_name keyword argument is deprecated, use project_name | ||||||
|  |         instead. | ||||||
|  |  | ||||||
|  |     .. attribute:: project_id | ||||||
|  |  | ||||||
|  |         The id of the Keystone project for the current user/token. | ||||||
|  |  | ||||||
|  |     .. attribute:: project_name | ||||||
|  |  | ||||||
|  |         The name of the Keystone project for the current user/token. | ||||||
|  |  | ||||||
|     .. attribute:: service_catalog |     .. attribute:: service_catalog | ||||||
|  |  | ||||||
|         The ``ServiceCatalog`` data returned by Keystone. |         The ``ServiceCatalog`` data returned by Keystone. | ||||||
| @@ -66,17 +128,35 @@ class User(AnonymousUser): | |||||||
|  |  | ||||||
|         A list of dictionaries containing role names and ids as returned |         A list of dictionaries containing role names and ids as returned | ||||||
|         by Keystone. |         by Keystone. | ||||||
|  |  | ||||||
|  |     .. attribute:: services_region | ||||||
|  |  | ||||||
|  |         A list of non-identity service endpoint regions extracted from the | ||||||
|  |         service catalog. | ||||||
|  |  | ||||||
|  |     .. attribute:: user_domain_id | ||||||
|  |  | ||||||
|  |         The domain id of the current user. | ||||||
|  |  | ||||||
|  |     .. attribute:: domain_id | ||||||
|  |  | ||||||
|  |         The id of the Keystone domain scoped for the current user/token. | ||||||
|  |  | ||||||
|     """ |     """ | ||||||
|     def __init__(self, id=None, token=None, user=None, tenant_id=None, |     def __init__(self, id=None, token=None, user=None, tenant_id=None, | ||||||
|                  service_catalog=None, tenant_name=None, roles=None, |                  service_catalog=None, tenant_name=None, roles=None, | ||||||
|                  authorized_tenants=None, endpoint=None, enabled=False, |                  authorized_tenants=None, endpoint=None, enabled=False, | ||||||
|                  services_region=None): |                  services_region=None, user_domain_id=None, domain_id=None, | ||||||
|  |                  domain_name=None, project_id=None, project_name=None): | ||||||
|         self.id = id |         self.id = id | ||||||
|         self.pk = id |         self.pk = id | ||||||
|         self.token = token |         self.token = token | ||||||
|         self.username = user |         self.username = user | ||||||
|         self.tenant_id = tenant_id |         self.user_domain_id = user_domain_id | ||||||
|         self.tenant_name = tenant_name |         self.domain_id = domain_id | ||||||
|  |         self.domain_name = domain_name | ||||||
|  |         self.project_id = project_id or tenant_id | ||||||
|  |         self.project_name = project_name or tenant_name | ||||||
|         self.service_catalog = service_catalog |         self.service_catalog = service_catalog | ||||||
|         self._services_region = services_region or \ |         self._services_region = services_region or \ | ||||||
|                                     self.default_services_region() |                                     self.default_services_region() | ||||||
| @@ -85,6 +165,10 @@ class User(AnonymousUser): | |||||||
|         self.enabled = enabled |         self.enabled = enabled | ||||||
|         self._authorized_tenants = authorized_tenants |         self._authorized_tenants = authorized_tenants | ||||||
|  |  | ||||||
|  |         # List of variables to be deprecated. | ||||||
|  |         self.tenant_id = self.project_id | ||||||
|  |         self.tenant_name = self.project_name | ||||||
|  |  | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.username |         return self.username | ||||||
|  |  | ||||||
| @@ -131,14 +215,15 @@ class User(AnonymousUser): | |||||||
|             endpoint = self.endpoint |             endpoint = self.endpoint | ||||||
|             token = self.token |             token = self.token | ||||||
|             try: |             try: | ||||||
|                 client = keystone_client.Client(username=self.username, |                 self._authorized_tenants = get_project_list( | ||||||
|                                                 auth_url=endpoint, |                     user_id=self.id, | ||||||
|                                                 token=token.id, |                     auth_url=endpoint, | ||||||
|                                                 insecure=insecure) |                     token=token.id, | ||||||
|                 self._authorized_tenants = client.tenants.list() |                     insecure=insecure, | ||||||
|  |                     debug=settings.DEBUG) | ||||||
|             except (keystone_exceptions.ClientException, |             except (keystone_exceptions.ClientException, | ||||||
|                     keystone_exceptions.AuthorizationFailure): |                     keystone_exceptions.AuthorizationFailure): | ||||||
|                 LOG.exception('Unable to retrieve tenant list.') |                 LOG.exception('Unable to retrieve project list.') | ||||||
|         return self._authorized_tenants or [] |         return self._authorized_tenants or [] | ||||||
|  |  | ||||||
|     @authorized_tenants.setter |     @authorized_tenants.setter | ||||||
|   | |||||||
| @@ -5,7 +5,9 @@ from django.contrib import auth | |||||||
| from django.contrib.auth.models import AnonymousUser | from django.contrib.auth.models import AnonymousUser | ||||||
| from django.contrib.auth import middleware | from django.contrib.auth import middleware | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| from django.utils.dateparse import parse_datetime |  | ||||||
|  | from keystoneclient.v2_0 import client as client_v2 | ||||||
|  | from keystoneclient.v3 import client as client_v3 | ||||||
|  |  | ||||||
|  |  | ||||||
| """ | """ | ||||||
| @@ -49,7 +51,7 @@ def check_token_expiration(token): | |||||||
|  |  | ||||||
|     Returns ``True`` if the token has not yet expired, otherwise ``False``. |     Returns ``True`` if the token has not yet expired, otherwise ``False``. | ||||||
|     """ |     """ | ||||||
|     expiration = parse_datetime(token.expires) |     expiration = token.expires | ||||||
|     if settings.USE_TZ and timezone.is_naive(expiration): |     if settings.USE_TZ and timezone.is_naive(expiration): | ||||||
|         # Presumes that the Keystone is using UTC. |         # Presumes that the Keystone is using UTC. | ||||||
|         expiration = timezone.make_aware(expiration, timezone.utc) |         expiration = timezone.make_aware(expiration, timezone.utc) | ||||||
| @@ -121,3 +123,28 @@ def is_safe_url(url, host=None): | |||||||
|         return False |         return False | ||||||
|     netloc = urlparse.urlparse(url)[1] |     netloc = urlparse.urlparse(url)[1] | ||||||
|     return not netloc or netloc == host |     return not netloc or netloc == host | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Helper for figuring out keystone version | ||||||
|  | # Implementation will change when API version discovery is available | ||||||
|  | def get_keystone_version(): | ||||||
|  |     return getattr(settings, 'OPENSTACK_API_VERSIONS', {}).get('identity', 2.0) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_keystone_client(): | ||||||
|  |     if get_keystone_version() < 3: | ||||||
|  |         return client_v2 | ||||||
|  |     else: | ||||||
|  |         return client_v3 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_project_list(*args, **kwargs): | ||||||
|  |     if get_keystone_version() < 3: | ||||||
|  |         client = get_keystone_client().Client(*args, **kwargs) | ||||||
|  |         return client.tenants.list() | ||||||
|  |     else: | ||||||
|  |         auth_url = kwargs.get('auth_url', '').replace('v2.0', 'v3') | ||||||
|  |         kwargs['auth_url'] = auth_url | ||||||
|  |         client = get_keystone_client().Client(*args, **kwargs) | ||||||
|  |         client.management_url = auth_url | ||||||
|  |         return client.projects.list(user=kwargs.get('user_id')) | ||||||
|   | |||||||
| @@ -18,11 +18,13 @@ try: | |||||||
| except ImportError: | except ImportError: | ||||||
|     from .utils import is_safe_url |     from .utils import is_safe_url | ||||||
|  |  | ||||||
| from keystoneclient.v2_0 import client as keystone_client | from keystoneclient.v2_0 import client as keystone_client_v2 | ||||||
| from keystoneclient import exceptions as keystone_exceptions | from keystoneclient import exceptions as keystone_exceptions | ||||||
|  |  | ||||||
| from .forms import Login | from .forms import Login | ||||||
| from .user import set_session_from_user, create_user_from_token | from .user import set_session_from_user, create_user_from_token, Token | ||||||
|  | from .utils import get_keystone_client | ||||||
|  | from .utils import get_keystone_version | ||||||
|  |  | ||||||
|  |  | ||||||
| LOG = logging.getLogger(__name__) | LOG = logging.getLogger(__name__) | ||||||
| @@ -88,34 +90,45 @@ def delete_all_tokens(token_list): | |||||||
|         try: |         try: | ||||||
|             endpoint = token_tuple[0] |             endpoint = token_tuple[0] | ||||||
|             token = token_tuple[1] |             token = token_tuple[1] | ||||||
|             client = keystone_client.Client(endpoint=endpoint, |             if get_keystone_version() < 3: | ||||||
|                                             token=token, |                 client = keystone_client_v2.Client(endpoint=endpoint, | ||||||
|                                             insecure=insecure) |                                                 token=token, | ||||||
|             client.tokens.delete(token=token) |                                                 insecure=insecure, | ||||||
|  |                                                 debug=settings.DEBUG) | ||||||
|  |                 client.tokens.delete(token=token) | ||||||
|  |             else: | ||||||
|  |                 # FIXME: KS-client does not have delete token available | ||||||
|  |                 # Need to add this later when it is exposed. | ||||||
|  |                 pass | ||||||
|         except keystone_exceptions.ClientException as e: |         except keystone_exceptions.ClientException as e: | ||||||
|             LOG.info('Could not delete token') |             LOG.info('Could not delete token') | ||||||
|  |  | ||||||
|  |  | ||||||
| @login_required | @login_required | ||||||
| def switch(request, tenant_id, redirect_field_name=REDIRECT_FIELD_NAME): | def switch(request, tenant_id, redirect_field_name=REDIRECT_FIELD_NAME): | ||||||
|     """ Switches an authenticated user from one tenant to another. """ |     """ Switches an authenticated user from one project to another. """ | ||||||
|     LOG.debug('Switching to tenant %s for user "%s".' |     LOG.debug('Switching to tenant %s for user "%s".' | ||||||
|               % (tenant_id, request.user.username)) |               % (tenant_id, request.user.username)) | ||||||
|     insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) |     insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) | ||||||
|     endpoint = request.user.endpoint |     endpoint = request.user.endpoint | ||||||
|     client = keystone_client.Client(endpoint=endpoint, |  | ||||||
|                                     insecure=insecure) |  | ||||||
|     try: |     try: | ||||||
|         token = client.tokens.authenticate(tenant_id=tenant_id, |         if get_keystone_version() >= 3: | ||||||
|                                         token=request.user.token.id) |             endpoint = endpoint.replace('v2.0', 'v3') | ||||||
|         msg = 'Tenant switch successful for user "%(username)s".' % \ |  | ||||||
|  |         client = get_keystone_client().Client(tenant_id=tenant_id, | ||||||
|  |                                               token=request.user.token.id, | ||||||
|  |                                               auth_url=endpoint, | ||||||
|  |                                               insecure=insecure, | ||||||
|  |                                               debug=settings.DEBUG) | ||||||
|  |         auth_ref = client.auth_ref | ||||||
|  |         msg = 'Project switch successful for user "%(username)s".' % \ | ||||||
|             {'username': request.user.username} |             {'username': request.user.username} | ||||||
|         LOG.info(msg) |         LOG.info(msg) | ||||||
|     except keystone_exceptions.ClientException: |     except keystone_exceptions.ClientException: | ||||||
|         msg = 'Tenant switch failed for user "%(username)s".' % \ |         msg = 'Project switch failed for user "%(username)s".' % \ | ||||||
|             {'username': request.user.username} |             {'username': request.user.username} | ||||||
|         LOG.warning(msg) |         LOG.warning(msg) | ||||||
|         token = None |         auth_ref = None | ||||||
|         LOG.exception('An error occurred while switching sessions.') |         LOG.exception('An error occurred while switching sessions.') | ||||||
|  |  | ||||||
|     # Ensure the user-originating redirection url is safe. |     # Ensure the user-originating redirection url is safe. | ||||||
| @@ -124,8 +137,8 @@ def switch(request, tenant_id, redirect_field_name=REDIRECT_FIELD_NAME): | |||||||
|     if not is_safe_url(url=redirect_to, host=request.get_host()): |     if not is_safe_url(url=redirect_to, host=request.get_host()): | ||||||
|         redirect_to = settings.LOGIN_REDIRECT_URL |         redirect_to = settings.LOGIN_REDIRECT_URL | ||||||
|  |  | ||||||
|     if token: |     if auth_ref: | ||||||
|         user = create_user_from_token(request, token, endpoint) |         user = create_user_from_token(request, Token(auth_ref), endpoint) | ||||||
|         set_session_from_user(request, user) |         set_session_from_user(request, user) | ||||||
|     return shortcuts.redirect(redirect_to) |     return shortcuts.redirect(redirect_to) | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Lin Hua Cheng
					Lin Hua Cheng