Initial commit.

This commit is contained in:
Gabriel Hurley 2012-07-03 02:25:01 -07:00
commit 9962375e0c
20 changed files with 928 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.pyc
*.egg
*.egg-info
.DS_STORE
doc/build
build
dist

30
LICENSE Normal file
View File

@ -0,0 +1,30 @@
Copyright (c) 2012, Gabriel Hurley
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the author nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

31
README.rst Normal file
View File

@ -0,0 +1,31 @@
=====================
Django OpenStack Auth
=====================
Django OpenStack Auth is a pluggable Django authentication backend that
works with Django's ``contrib.auth`` framework to authenticate a user against
OpenStack's Keystone Identity API.
The current version is designed to work with the Keystone V2 API.
Installation
============
Installing is quick and easy:
#. Run ``pip install django_openstack_auth``.
#. Add ``openstack_auth`` to ``settings.INSTALLED_APPS``.
#. Add ``'keystone_auth.backend.KeystoneBackend'`` to your
``settings.AUTHENTICATION_BACKENDS``, e.g.::
AUTHENTICATION_BACKENDS = ('keystone_auth.backend.KeystoneBackend',)
#. Configure your API endpoint(s) in ``settings.py``::
OPENSTACK_KEYSTONE_URL = "http://example.com:5000/v2.0"
#. Include ``'keystone_auth.urls'`` somewhere in your ``urls.py`` file.
#. Use it as you would any other Django auth backend.

View File

@ -0,0 +1,2 @@
# following PEP 386
__version__ = "1.0"

92
openstack_auth/backend.py Normal file
View File

@ -0,0 +1,92 @@
""" Module defining the Django auth backend class for the Keystone API. """
import logging
from django.utils.translation import ugettext as _
from keystoneclient.v2_0 import client as keystone_client
from keystoneclient import exceptions as keystone_exceptions
from keystoneclient.v2_0.tokens import Token, TokenManager
from .exceptions import KeystoneAuthException
from .user import create_user_from_token
LOG = logging.getLogger(__name__)
KEYSTONE_CLIENT_ATTR = "_keystoneclient"
class KeystoneBackend(object):
def get_user(self, user_id):
if user_id == self.request.session["user_id"]:
token = Token(TokenManager(None),
self.request.session['token'],
loaded=True)
endpoint = self.request.session['region_endpoint']
return create_user_from_token(self.request, token, endpoint)
else:
return None
def authenticate(self, request=None, username=None, password=None,
tenant=None, auth_url=None):
""" Authenticates a user via the Keystone Identity API. """
LOG.debug('Beginning user authentication for user "%s".' % username)
try:
client = keystone_client.Client(username=username,
password=password,
tenant_id=tenant,
auth_url=auth_url)
unscoped_token_data = {"token": client.service_catalog.get_token()}
unscoped_token = Token(TokenManager(None),
unscoped_token_data,
loaded=True)
except keystone_exceptions.Unauthorized:
msg = _('Invalid user name or password.')
raise KeystoneAuthException(msg)
except keystone_exceptions.ClientException:
msg = _("An error occurred authenticating. "
"Please try again later.")
raise KeystoneAuthException(msg)
# FIXME: Log in to default tenant when the Keystone API returns it...
# For now we list all the user's tenants and iterate through.
try:
tenants = client.tenants.list()
except keystone_exceptions.ClientException:
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:
token = client.tokens.authenticate(username=username,
token=unscoped_token.id,
tenant_id=tenant.id)
break
except keystone_exceptions.ClientException:
token = None
if token is None:
msg = _("Unable to authenticate to any available projects.")
raise KeystoneAuthException(msg)
# If we made it here we succeeded. Create our User!
user = create_user_from_token(request, token, client.management_url)
if request is not None:
request.session['unscoped_token'] = unscoped_token.id
request.user = user
# Support client caching to save on auth calls.
setattr(request, KEYSTONE_CLIENT_ATTR, client)
LOG.debug('Authentication completed for user "%s".' % username)
return user

View File

@ -0,0 +1,3 @@
class KeystoneAuthException(Exception):
""" Generic error class to identify and catch our own errors. """
pass

59
openstack_auth/forms.py Normal file
View File

@ -0,0 +1,59 @@
from django import forms
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import ugettext as _
from django.views.decorators.debug import sensitive_variables
from .exceptions import KeystoneAuthException
class Login(AuthenticationForm):
""" Form used for logging in a user.
Handles authentication with Keystone, choosing a tenant, and fetching
a scoped token token for that tenant.
"""
region = forms.ChoiceField(label=_("Region"), required=False)
username = forms.CharField(label=_("User Name"))
password = forms.CharField(label=_("Password"),
widget=forms.PasswordInput(render_value=False))
tenant = forms.CharField(required=False, widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
super(Login, self).__init__(*args, **kwargs)
self.fields['region'].choices = self.get_region_choices()
if len(self.fields['region'].choices) == 1:
self.fields['region'].initial = self.fields['region'].choices[0][0]
self.fields['region'].widget = forms.widgets.HiddenInput()
@staticmethod
def get_region_choices():
default_region = (settings.OPENSTACK_KEYSTONE_URL, "Default Region")
return getattr(settings, 'AVAILABLE_REGIONS', [default_region])
@sensitive_variables()
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
region = self.cleaned_data.get('region')
tenant = self.cleaned_data.get('tenant')
if not tenant:
tenant = None
if not (username and password):
# Don't authenticate, just let the other validators handle it.
return self.cleaned_data
try:
self.user_cache = authenticate(request=self.request,
username=username,
password=password,
tenant=tenant,
auth_url=region)
except KeystoneAuthException as exc:
self.request.session.flush()
raise forms.ValidationError(exc)
self.check_for_test_cookie()
return self.cleaned_data

View File

View File

@ -0,0 +1,112 @@
import uuid
from datetime import timedelta
from django.utils import datetime_safe
from keystoneclient.v2_0.roles import Role, RoleManager
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.service_catalog import ServiceCatalog
class TestDataContainer(object):
""" Arbitrary holder for test data in an object-oriented fashion. """
pass
def generate_test_data():
''' Builds a set of test_data data as returned by Keystone. '''
test_data = TestDataContainer()
keystone_service = {
'type': 'identity',
'name': 'keystone',
'endpoints_links': [],
'endpoints': [
{
'region': 'RegionOne',
'adminURL': 'http://admin.localhost:35357/v2.0',
'internalURL': 'http://internal.localhost:5000/v2.0',
'publicURL': 'http://public.localhost:5000/v2.0'
}
]
}
# Users
user_dict = {'id': uuid.uuid4().hex,
'name': 'gabriel',
'email': 'gabriel@example.com',
'password': 'swordfish',
'token': '',
'enabled': True}
test_data.user = User(UserManager(None), user_dict, loaded=True)
# Tenants
tenant_dict_1 = {'id': uuid.uuid4().hex,
'name': 'tenant_one',
'description': '',
'enabled': True}
tenant_dict_2 = {'id': uuid.uuid4().hex,
'name': '',
'description': '',
'enabled': False}
test_data.tenant_one = Tenant(TenantManager(None),
tenant_dict_1,
loaded=True)
test_data.tenant_two = Tenant(TenantManager(None),
tenant_dict_2,
loaded=True)
# Roles
role_dict = {'id': uuid.uuid4().hex,
'name': 'Member'}
test_data.role = Role(RoleManager, role_dict)
# Tokens
tomorrow = datetime_safe.datetime.now() + timedelta(days=1)
expiration = datetime_safe.datetime.isoformat(tomorrow)
scoped_token_dict = {
'token': {
'id': uuid.uuid4().hex,
'expires': expiration,
'tenant': tenant_dict_1,
'tenants': [tenant_dict_1, tenant_dict_2]},
'user': {
'id': user_dict['id'],
'name': user_dict['name'],
'roles': [role_dict]},
'serviceCatalog': [keystone_service]
}
test_data.scoped_token = Token(TokenManager(None),
scoped_token_dict,
loaded=True)
unscoped_token_dict = {
'token': {
'id': uuid.uuid4().hex,
'expires': expiration},
'user': {
'id': user_dict['id'],
'name': user_dict['name'],
'roles': [role_dict]},
'serviceCatalog': [keystone_service]
}
test_data.unscoped_token = Token(TokenManager(None),
unscoped_token_dict,
loaded=True)
# Service Catalog
test_data.service_catalog = ServiceCatalog({
'serviceCatalog': [keystone_service],
'token': {
'id': scoped_token_dict['token']['id'],
'expires': scoped_token_dict['token']['expires'],
'user_id': user_dict['id'],
'tenant_id': tenant_dict_1['id']
}
})
return test_data

View File

View File

@ -0,0 +1,47 @@
#!/usr/bin/env python
import os
import sys
from django.conf import settings
if not settings.configured:
settings.configure(
DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3'}},
INSTALLED_APPS=[
'django',
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'openstack_auth',
'openstack_auth.tests'
],
MIDDLEWARE_CLASSES=[
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware'
],
AUTHENTICATION_BACKENDS=['openstack_auth.backend.KeystoneBackend'],
OPENSTACK_KEYSTONE_URL="http://localhost:5000/v2.0",
ROOT_URLCONF='openstack_auth.tests.urls',
LOGIN_REDIRECT_URL='/'
)
from django.test.simple import DjangoTestSuiteRunner
def run(*test_args):
if not test_args:
test_args = ['tests']
parent = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"..",
"..",
)
sys.path.insert(0, parent)
failures = DjangoTestSuiteRunner().run_tests(test_args)
sys.exit(failures)
if __name__ == '__main__':
run(*sys.argv[1:])

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<form action="." method="POST">{{ csrf_token }}
{{ form.as_p }}
</form>
</body>
</html>

View File

@ -0,0 +1,202 @@
from django import test
from django.conf import settings
from django.core.urlresolvers import reverse
from keystoneclient import exceptions as keystone_exceptions
from keystoneclient.v2_0 import client
import mox
from .data import generate_test_data
class OpenStackAuthTests(test.TestCase):
def setUp(self):
super(OpenStackAuthTests, self).setUp()
self.mox = mox.Mox()
self.data = generate_test_data()
endpoint = settings.OPENSTACK_KEYSTONE_URL
self.keystone_client = client.Client(endpoint=endpoint)
self.keystone_client.service_catalog = self.data.service_catalog
def tearDown(self):
self.mox.UnsetStubs()
self.mox.VerifyAll()
def test_login(self):
tenants = [self.data.tenant_one, self.data.tenant_two]
user = self.data.user
sc = self.data.service_catalog
form_data = {'region': settings.OPENSTACK_KEYSTONE_URL,
'password': user.password,
'username': user.name}
self.mox.StubOutWithMock(client, "Client")
self.mox.StubOutWithMock(self.keystone_client.tenants, "list")
self.mox.StubOutWithMock(self.keystone_client.tokens, "authenticate")
client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL,
password=user.password,
username=user.name,
tenant_id=None).AndReturn(self.keystone_client)
self.keystone_client.tenants.list().AndReturn(tenants)
self.keystone_client.tokens.authenticate(tenant_id=tenants[1].id,
token=sc.get_token()['id'],
username=user.name) \
.AndReturn(self.data.scoped_token)
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,
'password': user.password,
'username': user.name}
self.mox.StubOutWithMock(client, "Client")
self.mox.StubOutWithMock(self.keystone_client.tenants, "list")
client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL,
password=user.password,
username=user.name,
tenant_id=None).AndReturn(self.keystone_client)
self.keystone_client.tenants.list().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,
'password': "invalid",
'username': user.name}
self.mox.StubOutWithMock(client, "Client")
exc = keystone_exceptions.Unauthorized(401)
client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL,
password="invalid",
username=user.name,
tenant_id=None).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,
'password': user.password,
'username': user.name}
self.mox.StubOutWithMock(client, "Client")
exc = keystone_exceptions.ClientException(500)
client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL,
password=user.password,
username=user.name,
tenant_id=None).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):
tenant = self.data.tenant_two
tenants = [self.data.tenant_one, self.data.tenant_two]
user = self.data.user
scoped = self.data.scoped_token
sc = self.data.service_catalog
form_data = {'region': settings.OPENSTACK_KEYSTONE_URL,
'username': user.name,
'password': user.password}
self.mox.StubOutWithMock(client, "Client")
self.mox.StubOutWithMock(self.keystone_client.tenants, "list")
self.mox.StubOutWithMock(self.keystone_client.tokens, "authenticate")
client.Client(auth_url=settings.OPENSTACK_KEYSTONE_URL,
password=user.password,
username=user.name,
tenant_id=None).AndReturn(self.keystone_client)
self.keystone_client.tenants.list().AndReturn(tenants)
self.keystone_client.tokens.authenticate(tenant_id=tenants[1].id,
token=sc.get_token()['id'],
username=user.name) \
.AndReturn(scoped)
client.Client(endpoint=settings.OPENSTACK_KEYSTONE_URL) \
.AndReturn(self.keystone_client)
self.keystone_client.tokens.authenticate(tenant_id=tenant.id,
token=sc.get_token()['id']) \
.AndReturn(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=[tenant.id])
scoped.tenant['id'] = self.data.tenant_two._info
sc.catalog['token']['id'] = self.data.tenant_two.id
form_data['tenant_id'] = tenant.id
response = self.client.get(url, form_data)
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
self.assertEqual(self.client.session['tenant_id'],
scoped.tenant['id'])

View File

@ -0,0 +1,13 @@
from django.conf.urls.defaults import patterns, include, url
from django.views.generic import TemplateView
from openstack_auth.utils import patch_middleware_get_user
patch_middleware_get_user()
urlpatterns = patterns('',
url(r"", include('openstack_auth.urls')),
url(r"^$", TemplateView.as_view(template_name="auth/blank.html"))
)

13
openstack_auth/urls.py Normal file
View File

@ -0,0 +1,13 @@
from django.conf.urls.defaults import patterns, url
from .utils import patch_middleware_get_user
patch_middleware_get_user()
urlpatterns = patterns('openstack_auth.views',
url(r"^login/$", "login", name='login'),
url(r"^logout/$", 'logout', name='logout'),
url(r'^switch/(?P<tenant_id>[^/]+)/$', 'switch', name='switch_tenants')
)

124
openstack_auth/user.py Normal file
View File

@ -0,0 +1,124 @@
from django.contrib.auth.models import AnonymousUser
from keystoneclient.v2_0 import client as keystone_client
from keystoneclient import exceptions as keystone_exceptions
from .utils import check_token_expiration
def set_session_from_user(request, user):
request.session['serviceCatalog'] = user.service_catalog
request.session['tenant'] = user.tenant_name
request.session['tenant_id'] = user.tenant_id
request.session['token'] = user.token._info
request.session['username'] = user.username
request.session['user_id'] = user.id
request.session['roles'] = user.roles
request.session['region_endpoint'] = user.endpoint
def create_user_from_token(request, token, endpoint):
return User(id=token.user['id'],
token=token,
user=token.user['name'],
tenant_id=token.tenant['id'],
tenant_name=token.tenant['name'],
enabled=True,
service_catalog=token.serviceCatalog,
roles=token.user['roles'],
endpoint=endpoint)
class User(AnonymousUser):
""" A User class with some extra special sauce for Keystone.
In addition to the standard Django user attributes, this class also has
the following:
.. attribute:: token
The Keystone token object associated with the current user/tenant.
.. attribute:: tenant_id
The id of the Keystone tenant for the current user/token.
.. attribute:: tenant_name
The name of the Keystone tenant for the current user/token.
.. attribute:: service_catalog
The ``ServiceCatalog`` data returned by Keystone.
.. attribute:: roles
A list of dictionaries containing role names and ids as returned
by Keystone.
"""
def __init__(self, id=None, token=None, user=None, tenant_id=None,
service_catalog=None, tenant_name=None, roles=None,
authorized_tenants=None, endpoint=None, enabled=False):
self.id = id
self.token = token
self.username = user
self.tenant_id = tenant_id
self.tenant_name = tenant_name
self.service_catalog = service_catalog
self.roles = roles or []
self.endpoint = endpoint
self.enabled = enabled
self._authorized_tenants = authorized_tenants
def __unicode__(self):
return self.username
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self.username)
def is_authenticated(self):
""" Checks for a valid token that has not yet expired. """
return self.token is not None and check_token_expiration(self.token)
@property
def is_active(self):
return self.enabled
@property
def is_superuser(self):
"""
Evaluates whether this user has admin privileges. Returns
``True`` or ``False``.
"""
for role in self.roles:
if role['name'].lower() == 'admin':
return True
return False
@property
def authorized_tenants(self):
""" Returns a memoized list of tenants this user may access. """
if self.is_authenticated() and self._authorized_tenants is None:
endpoint = self.endpoint
token = self.token
try:
client = keystone_client.Client(username=self.username,
auth_url=endpoint,
token=token.id)
authd = client.tenants.list()
except keystone_exceptions.ClientException:
authd = []
self._authorized_tenants = authd
return self._authorized_tenants or []
@authorized_tenants.setter
def authorized_tenants(self, tenant_list):
self._authorized_tenants = tenant_list
def save(*args, **kwargs):
# Presume we can't write to Keystone.
pass
def delete(*args, **kwargs):
# Presume we can't write to Keystone.
pass

55
openstack_auth/utils.py Normal file
View File

@ -0,0 +1,55 @@
from django.conf import settings
from django.contrib import auth
from django.contrib.auth.models import AnonymousUser
from django.contrib.auth import middleware
from django.utils import timezone
from django.utils.dateparse import parse_datetime
"""
We need the request object to get the user, so we'll slightly modify the
existing django.contrib.auth.get_user method. To do so we update the
auth middleware to point to our overridden method.
Calling the "patch_middleware_get_user" method somewhere like our urls.py
file takes care of hooking it in appropriately.
"""
def middleware_get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = get_user(request)
return request._cached_user
def get_user(request):
try:
user_id = request.session[auth.SESSION_KEY]
backend_path = request.session[auth.BACKEND_SESSION_KEY]
backend = auth.load_backend(backend_path)
backend.request = request
user = backend.get_user(user_id) or AnonymousUser()
except KeyError:
user = AnonymousUser()
return user
def patch_middleware_get_user():
middleware.get_user = middleware_get_user
auth.get_user = get_user
""" End Monkey-Patching. """
def check_token_expiration(token):
expiration = parse_datetime(token.expires)
if settings.USE_TZ and timezone.is_naive(expiration):
# Presumes that the Keystone is using UTC.
expiration = timezone.make_aware(expiration, timezone.utc)
# In case we get an unparseable expiration timestamp, return False
# so you can't have a "forever" token just by breaking the expires param.
if expiration:
return expiration > timezone.now()
else:
return False

75
openstack_auth/views.py Normal file
View File

@ -0,0 +1,75 @@
import logging
from django import shortcuts
from django.conf import settings
from django.contrib.auth.views import (login as django_login,
logout_then_login as django_logout)
from django.contrib.auth.decorators import login_required
from django.views.decorators.debug import sensitive_post_parameters
from django.utils.functional import curry
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from keystoneclient.v2_0 import client as keystone_client
from .forms import Login
from .user import set_session_from_user, create_user_from_token
LOG = logging.getLogger(__name__)
@sensitive_post_parameters()
@csrf_protect
@never_cache
def login(request):
# Get our initial region for the form.
initial = {}
current_region = request.session.get('region_endpoint', None)
requested_region = request.GET.get('region', None)
regions = dict(getattr(settings, "AVAILABLE_REGIONS", []))
if requested_region in regions and requested_region != current_region:
initial.update({'region': requested_region})
if request.method == "POST":
form = curry(Login, request)
else:
form = curry(Login, initial=initial)
if request.is_ajax():
template_name = 'auth/_login.html'
extra_context = {'hide': True}
else:
template_name = 'auth/login.html'
extra_context = {}
res = django_login(request,
template_name=template_name,
authentication_form=form,
extra_context=extra_context)
# Set the session data here because django's session key rotation
# will erase it if we set it earlier.
if request.user.is_authenticated():
set_session_from_user(request, request.user)
region = request.user.endpoint
region_name = dict(Login.get_region_choices()).get(region)
request.session['region_endpoint'] = region
request.session['region_name'] = region_name
return res
def logout(request):
return django_logout(request)
@login_required
def switch(request, tenant_id):
LOG.debug('Switching to tenant %s for user "%s".'
% (tenant_id, request.user.username))
endpoint = request.user.endpoint
client = keystone_client.Client(endpoint=endpoint)
token = client.tokens.authenticate(tenant_id=tenant_id,
token=request.user.token.id)
user = create_user_from_token(request, token, endpoint)
set_session_from_user(request, user)
return shortcuts.redirect(settings.LOGIN_REDIRECT_URL)

52
setup.py Executable file
View File

@ -0,0 +1,52 @@
import os
import re
import codecs
from setuptools import setup, find_packages
def read(*parts):
return codecs.open(os.path.join(os.path.dirname(__file__), *parts)).read()
def find_version(*file_paths):
version_file = read(*file_paths)
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
setup(
name="django_openstack_auth",
version=find_version("openstack_auth", "__init__.py"),
url='http://django_openstack_auth.readthedocs.org/',
license='BSD',
description=("A Django authentication backend for use with the "
"OpenStack Keystone Identity backend."),
long_description=read('README.rst'),
author='Gabriel Hurley',
author_email='gabriel@strikeawe.com',
packages=find_packages(),
include_package_data=True,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
],
zip_safe=False,
install_requires=[
'django >= 1.4',
'python-keystoneclient'
],
tests_require=[
'mox',
],
test_suite='openstack_auth.tests.run_tests.run'
)