Commit c71eb95d authored by Sofia Papagiannaki's avatar Sofia Papagiannaki

snf-django-lib: Fix proxy issue

The ``X-Forwarded-Host`` header should not be forwared to the target
because django appends its value to the Host header and
results in validate_host() failure.
parent a5d257fb
......@@ -34,40 +34,39 @@
from urlparse import urlunsplit, urlsplit
from django.http import urlencode
from django.views.decorators.csrf import csrf_exempt
from snf_django.lib import api
from snf_django.lib.api import faults, utils, api_method, get_token
from astakos.im.models import Service
from .util import user_from_token, rename_meta_key, json_response, xml_response
from astakos.im.models import Service, AstakosUser
from .util import user_from_token, json_response, xml_response
import logging
logger = logging.getLogger(__name__)
@api.api_method(http_method="GET", token_required=True, user_required=False,
logger=logger)
@api_method(http_method="GET", token_required=True, user_required=False,
logger=logger)
@user_from_token # Authenticate user!!
def get_endpoints(request, token):
if token != api.get_token(request):
raise api.faults.Forbidden()
if token != get_token(request):
raise faults.Forbidden()
belongsTo = request.GET.get('belongsTo')
if belongsTo and belongsTo != request.user.uuid:
raise api.faults.BadRequest()
raise faults.BadRequest()
marker = request.GET.get('marker', 0)
limit = request.GET.get('limit', 10000)
endpoints = list(Service.objects.all().order_by('id').\
filter(id__gt=marker)[:limit].\
values('name', 'url', 'api_url', 'id', 'type'))
endpoints = list(Service.objects.all().order_by('id').
filter(id__gt=marker)[:limit].
values('name', 'url', 'api_url', 'id', 'type'))
for e in endpoints:
e['api_url'] = e['api_url'] or e['url']
e['internalURL'] = e['url']
e['publicURL'] = e['admiURL'] = e['internalURL'] = e['api_url']
e['SNF:uiURL'] = e['url']
e['region'] = e['name']
rename_meta_key(e, 'api_url', 'adminURL')
rename_meta_key(e, 'url', 'publicURL')
e.pop('api_url')
if endpoints:
parts = list(urlsplit(request.path))
......@@ -83,3 +82,54 @@ def get_endpoints(request, token):
return xml_response(result, 'api/endpoints.xml')
else:
return json_response(result)
@csrf_exempt
@api_method(http_method="POST", token_required=False, user_required=False,
logger=logger)
def authenticate(request):
req = utils.get_request_dict(request)
uuid = None
try:
token_id = req['auth']['token']['id']
except KeyError:
try:
token_id = req['auth']['passwordCredentials']['password']
uuid = req['auth']['passwordCredentials']['username']
except KeyError:
raise faults.BadRequest('Malformed request')
if token_id is None:
raise faults.BadRequest('Malformed request')
try:
user = AstakosUser.objects.get(auth_token=token_id)
except AstakosUser.DoesNotExist:
raise faults.Unauthorized('Invalid token')
if uuid is not None:
if user.uuid != uuid:
raise faults.Unauthorized('Invalid credentials')
access = {}
access['token'] = {'id': user.auth_token,
'expires': utils.isoformat(user.auth_token_expires),
'tenant': {'id': user.uuid, 'name': user.realname}}
access['user'] = {'id': user.uuid, 'name': user.realname,
'roles': list(user.groups.values('id', 'name')),
'roles_links': []}
access['serviceCatalog'] = []
append = access['serviceCatalog'].append
for s in Service.objects.all().order_by('id'):
append({'name': s.name, 'type': s.type,
'endpoints': [{'adminURL': s.api_url,
'publicURL': s.api_url,
'internalUrl': s.api_url,
'SNF:uiURL': s.url,
'region': s.name}]})
if request.serialization == 'xml':
return xml_response(access, 'api/access.xml')
else:
return json_response(access)
......@@ -59,5 +59,6 @@ urlpatterns += patterns(
urlpatterns += patterns(
'astakos.api.tokens',
url(r'tokens/?$', 'authenticate'),
url(r'tokens/(?P<token>.+?)/endpoints', 'get_endpoints'),
)
......@@ -32,13 +32,12 @@
# or implied, of GRNET S.A.
from astakos.im.tests.common import *
from astakos.im.activation_backends import get_backend
from django.test import TestCase
from urllib import quote
from urlparse import urlparse, parse_qs
#from xml.dom import minidom
from xml.dom import minidom
import json
......@@ -388,19 +387,153 @@ class TokensApiTest(TestCase):
assert self.user2.is_active is True
Service(name='service1', url='http://localhost/service1',
api_url='http://localhost/api/service1').save()
api_url='http://localhost/api/service1',
type='service1').save()
Service(name='service2', url='http://localhost/service2',
api_url='http://localhost/api/service2').save()
api_url='http://localhost/api/service2',
type='service2').save()
Service(name='service3', url='http://localhost/service3',
api_url='http://localhost/api/service3').save()
api_url='http://localhost/api/service3',
type='service3').save()
def test_get_endpoints(self):
def test_authenticate(self):
client = Client()
# Check no token
url = '/astakos/api/tokens/%s/endpoints' % quote(self.user1.auth_token)
r = client.get(url)
# Check not allowed method
url = '/astakos/api/tokens'
r = client.get(url, post_data={})
self.assertEqual(r.status_code, 400)
# Malformed request
url = '/astakos/api/tokens'
r = client.post(url, post_data={})
self.assertEqual(r.status_code, 400)
# Check unsupported xml input
url = '/astakos/api/tokens'
post_data = """
<?xml version="1.0" encoding="UTF-8"?>
<auth xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://docs.openstack.org/identity/api/v2.0"
tenantName="%s">
<passwordCredentials username="%s" password="%s"/>
</auth>""" % (self.user1.uuid, self.user1.uuid,
self.user1.auth_token)
r = client.post(url, post_data, content_type='application/xml')
self.assertEqual(r.status_code, 400)
body = json.loads(r.content)
self.assertEqual(body['badRequest']['message'],
"Unsupported Content-type: 'application/xml'")
# Check malformed request: missing password
url = '/astakos/api/tokens'
post_data = """{"auth":{"passwordCredentials":{"username":"%s"},
"tenantName":"%s"}}""" % (
self.user1.uuid, self.user1.uuid)
r = client.post(url, post_data, content_type='application/json')
self.assertEqual(r.status_code, 400)
body = json.loads(r.content)
self.assertEqual(body['badRequest']['message'],
'Malformed request')
# Check malformed request: missing username
url = '/astakos/api/tokens'
post_data = """{"auth":{"passwordCredentials":{"password":"%s"},
"tenantName":"%s"}}""" % (
self.user1.auth_token, self.user1.uuid)
r = client.post(url, post_data, content_type='application/json')
self.assertEqual(r.status_code, 400)
body = json.loads(r.content)
self.assertEqual(body['badRequest']['message'],
'Malformed request')
# Check invalid pass
url = '/astakos/api/tokens'
post_data = """{"auth":{"passwordCredentials":{"username":"%s",
"password":"%s"},
"tenantName":"%s"}}""" % (
self.user1.uuid, '', self.user1.uuid)
r = client.post(url, post_data, content_type='application/json')
self.assertEqual(r.status_code, 401)
body = json.loads(r.content)
self.assertEqual(body['unauthorized']['message'],
'Invalid token')
# Check inconsistent pass
url = '/astakos/api/tokens'
post_data = """{"auth":{"passwordCredentials":{"username":"%s",
"password":"%s"},
"tenantName":"%s"}}""" % (
self.user1.uuid, self.user2.auth_token, self.user1.uuid)
r = client.post(url, post_data, content_type='application/json')
self.assertEqual(r.status_code, 401)
body = json.loads(r.content)
self.assertEqual(body['unauthorized']['message'],
'Invalid credentials')
# Check invalid json data
url = '/astakos/api/tokens'
r = client.post(url, "not json", content_type='application/json')
self.assertEqual(r.status_code, 400)
body = json.loads(r.content)
self.assertEqual(body['badRequest']['message'], 'Invalid JSON data')
# Check auth with token
url = '/astakos/api/tokens'
post_data = """{"auth":{"token": {"id":"%s"},
"tenantName":"%s"}}""" % (
self.user1.auth_token, self.user1.uuid)
r = client.post(url, post_data, content_type='application/json')
self.assertEqual(r.status_code, 200)
self.assertTrue(r['Content-Type'].startswith('application/json'))
try:
body = json.loads(r.content)
except Exception, e:
self.fail(e)
# Check successful json response
url = '/astakos/api/tokens'
post_data = """{"auth":{"passwordCredentials":{"username":"%s",
"password":"%s"},
"tenantName":"%s"}}""" % (
self.user1.uuid, self.user1.auth_token, self.user1.uuid)
r = client.post(url, post_data, content_type='application/json')
self.assertEqual(r.status_code, 200)
self.assertTrue(r['Content-Type'].startswith('application/json'))
try:
body = json.loads(r.content)
except Exception, e:
self.fail(e)
try:
token = body['token']['id']
user = body['user']['id']
service_catalog = body['serviceCatalog']
except KeyError:
self.fail('Invalid response')
self.assertEqual(token, self.user1.auth_token)
self.assertEqual(user, self.user1.uuid)
self.assertEqual(len(service_catalog), 3)
# Check successful xml response
url = '/astakos/api/tokens'
headers = {'HTTP_ACCEPT': 'application/xml'}
post_data = """{"auth":{"passwordCredentials":{"username":"%s",
"password":"%s"},
"tenantName":"%s"}}""" % (
self.user1.uuid, self.user1.auth_token, self.user1.uuid)
r = client.post(url, post_data, content_type='application/json',
**headers)
self.assertEqual(r.status_code, 200)
self.assertTrue(r['Content-Type'].startswith('application/xml'))
try:
body = minidom.parseString(r.content)
except Exception, e:
self.fail(e)
def test_get_endpoints(self):
client = Client()
# Check in active user token
inactive_user = AstakosUser.objects.create(email='test3')
......@@ -414,7 +547,6 @@ class TokensApiTest(TestCase):
r = client.get(url)
self.assertEqual(r.status_code, 401)
# Check forbidden
url = '/astakos/api/tokens/%s/endpoints' % quote(self.user1.auth_token)
headers = {'HTTP_X_AUTH_TOKEN': AstakosUser.objects.create(
......@@ -422,7 +554,6 @@ class TokensApiTest(TestCase):
r = client.get(url, **headers)
self.assertEqual(r.status_code, 401)
# Check bad request method
url = '/astakos/api/tokens/%s/endpoints' % quote(self.user1.auth_token)
r = client.post(url)
......
......@@ -47,7 +47,7 @@ import urlparse
#
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10
# Connection and MUST NOT be communicated by proxies over further connections
EXCLUDE_HEADERS = ['Host', 'Cookie', 'Connection']
EXCLUDE_HEADERS = ['Host', 'Cookie', 'Connection', 'X-Forwarded-Host']
def proxy(request, target):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment