Revision 25380811

b/aai/fixtures/auth_test_data.json
1
[
2
    {
3
        "model": "db.SynnefoUser",
4
        "pk": 1,
5
        "fields": {
6
            "name": "testdbuser",
7
            "realname" :"test db user",
8
            "uniq" :"test@synnefo.gr",
9
            "credit": 10,
10
            "auth_token": "46e427d657b20defe352804f0eb6f8a2",
11
            "auth_token_created": "2009-04-07 09:17:14",
12
            "type": "STUDENT",
13
            "created": "2011-02-06"
14
   	    }
15
    }
16
]
b/aai/middleware.py
1
from time import time
2
from django.conf import settings
3
from django.http import HttpResponse, HttpResponseRedirect
4
from synnefo.db.models import SynnefoUser
5
from synnefo.logic.shibboleth import Tokens, register_shibboleth_user
6
import time
7

  
8
class SynnefoAuthMiddleware(object):
9

  
10
    auth_token = "X-Auth-Token"
11
    auth_user  = "X-Auth-User"
12
    auth_key   = "X-Auth-Key"
13

  
14
    def process_request(self, request):
15

  
16
        if self.auth_token in request.META:
17
            user = None
18
            #Retrieve user from DB or other caching mechanism
19
            try:
20
                user = SynnefoUser.objects.get(auth_token = request.META[self.auth_token])
21
            except SynnefoUser.DoesNotExist:
22
                return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
23

  
24
            #Check user's auth token
25
            if (time.time() -
26
                time.mktime(user.auth_token_created.timetuple()) +
27
                settings.AUTH_TOKEN_DURATION * 3600) > 0:
28
                #The user's token has expired, re-login
29
                return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
30

  
31
            request.user = user
32
            return
33

  
34
        #A user authenticated by Shibboleth, must include a uniq id
35
        if Tokens.SIB_EDU_PERSON_PRINCIPAL_NAME in request.META:
36
            #We must somehow make sure that we only process
37
            #SIB headers when coming from a URL whitelist,
38
            #or a similar form of restriction
39
            if request.get_host() not in settings.SHIBBOLETH_WHITELIST.keys():
40
                return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
41

  
42
            user = None
43
            try:
44
                user = SynnefoUser.objects.get(
45
                    uniq = request.META[Tokens.SIB_EDU_PERSON_PRINCIPAL_NAME])
46
            except SynnefoUser.DoesNotExist:
47
                pass
48

  
49
            #No user with this id could be found in the database
50
            if user is None:
51
                #Attempt to register the incoming user
52
                if register_shibboleth_user(request.META):
53
                    user = SynnefoUser.objects.get(
54
                        uniq = request.META[Tokens.SIB_EDU_PERSON_PRINCIPAL_NAME])
55
                    response = HttpResponse()
56
                    response[self.auth_token] = user.auth_token
57
                    response['Location'] = "/"
58
                    response.status_code = 302
59
                    return response
60
                else:
61
                    return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
62

  
63
            #User and authentication token valid, user allowed to proceed
64
            return
65

  
66
        #An API authentication request
67
        if self.auth_user in request.META and self.auth_key in request.META and 'GET' == request.method:
68
            # This is here merely for compatibility with the Openstack API.
69
            # All normal users should authenticate through Sibbolleth. Admin
70
            # users or other selected users could use this as a bypass
71
            # mechanism
72
            user = SynnefoUser.objects\
73
                    .filter(name = request.META[self.auth_user]) \
74
                    .filter(uniq = request.META[self.auth_key])
75

  
76
            response = HttpResponse()
77
            if user.count() <= 0:
78
                response.status_code = 401
79
            else:
80
                response.status_code = 204
81
                response['X-Auth-Token'] = user[0].auth_token
82
                #TODO: set the following fields when we do have this info
83
                response['X-Server-Management-Url'] = ""
84
                response['X-Storage-Url'] = ""
85
                response['X-CDN-Management-Url'] = ""
86
            return response
87

  
88
        #No authentication info found in headers, redirect to Shibboleth
89
        return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
90

  
91
    def process_response(self, request, response):
92
        #Tell proxies and other interested parties that the
93
        #request varies based on the auth token, to avoid
94
        #caching of results
95
        response['Vary'] = self.auth_token
96
        return response
b/aai/tests.py
1
#
2
# Unit Tests for api
3
#
4
# Provides automated tests for api module
5
#
6
# Copyright 2011 Greek Research and Technology Network
7
#
8

  
9
from django.test import TestCase
10
from django.test.client import Client
11
from django.conf import settings
12

  
13
from synnefo.logic.shibboleth import Tokens, NoUniqueToken
14
from synnefo.db.models import SynnefoUser
15

  
16
from datetime import datetime, timedelta
17

  
18
class AuthTestCase(TestCase):
19
    fixtures = ['api_test_data', 'auth_test_data']
20
    apibase = '/api/v1.1'
21

  
22
    def setUp(self):
23
        self.client = Client()
24

  
25
    def test_shibboleth_correct_request(self):
26
        """test request that should succeed and register a user
27
        """
28
        response = self.client.get(self.apibase + '/servers', {},
29
                                   **{Tokens.SIB_GIVEN_NAME: 'Jimmy',
30
                                      Tokens.SIB_EDU_PERSON_PRINCIPAL_NAME: 'jh@gmail.com',
31
                                      Tokens.SIB_DISPLAY_NAME: 'Jimmy Hendrix'})
32
        user = None
33
        try:
34
            user = SynnefoUser.objects.get(uniq = "jh@gmail.com")
35
        except SynnefoUser.DoesNotExist:
36
            self.assertNotEqual(user, None)
37
        self.assertNotEqual(user, None)
38
        self.assertEquals(response.status_code, 302)
39
        self.assertEquals(response['Location'], "http://testserver/")
40
        self.assertTrue('X-Auth-Token' in response)
41
        self.assertEquals(response['X-Auth-Token'], user.auth_token)
42

  
43
    def test_shibboleth_no_uniq_request(self):
44
        """test a request with no unique field
45
        """
46
        response = self.client.get(self.apibase + '/servers', {},
47
                                    **{Tokens.SIB_GIVEN_NAME: 'Jimmy',
48
                                    Tokens.SIB_DISPLAY_NAME: 'Jimmy Hendrix'})
49
        self._test_redirect(response)
50

  
51
    def test_shibboleth_wrong_from_request(self):
52
        """ test request from wrong host
53
        """
54
        response = self.client.get(self.apibase + '/servers', {},
55
                                   **{Tokens.SIB_GIVEN_NAME: 'Jimmy',
56
                                      Tokens.SIB_EDU_PERSON_PRINCIPAL_NAME: 'jh@gmail.com',
57
                                      Tokens.SIB_DISPLAY_NAME: 'Jimmy Hendrix',
58
                                      'REMOTE_ADDR': '1.2.3.4',
59
                                      'SERVER_NAME': 'nohost.nodomain'})
60
        self._test_redirect(response)
61

  
62
    def test_shibboleth_expired_token(self):
63
        """ test request from expired token
64
        """
65
        user = SynnefoUser.objects.get(uniq = "test@synnefo.gr")
66
        self.assertNotEqual(user.auth_token_created, None)
67
        self._update_user_ts(user)
68
        response = self.client.get(self.apibase + '/servers', {},
69
                                   **{'X-Auth-Token': user.auth_token})
70
        self._test_redirect(response)
71

  
72
    def test_shibboleth_redirect(self):
73
        """ test redirect to Sibboleth page
74
        """
75
        response = self.client.get(self.apibase + '/servers')
76
        self._test_redirect(response)
77

  
78
    def test_shibboleth_auth(self):
79
        """ test authentication with X-Auth-Token
80
        """
81
        user = SynnefoUser.objects.get(uniq = "test@synnefo.gr")
82
        response = self.client.get(self.apibase + '/servers', {},
83
                                   **{'X-Auth-Token': user.auth_token})
84
        self.assertTrue(response.status_code, 200)
85
        self.assertTrue('Vary' in response)
86
        self.assertTrue('X-Auth-Token' in response['Vary'])
87

  
88
    def test_fail_oapi_auth(self):
89
        """ test authentication from not registered user using OpenAPI
90
        """
91
        response = self.client.get(self.apibase + '/servers', {},
92
                                   **{'X-Auth-User': 'notme',
93
                                      'X-Auth-Key': '0xdeadbabe'})
94
        self.assertEquals(response.status_code, 401)
95

  
96
    def test_oapi_auth(self):
97
        """authentication with user registration
98
        """
99
        response = self.client.get(self.apibase + '/', {},
100
                                   **{'X-Auth-User': 'testdbuser',
101
                                      'X-Auth-Key': 'test@synnefo.gr'})
102
        self.assertEquals(response.status_code, 204)
103
        self.assertNotEqual(response['X-Auth-Token'], None)
104
        self.assertEquals(response['X-Server-Management-Url'], '')
105
        self.assertEquals(response['X-Storage-Url'], '')
106
        self.assertEquals(response['X-CDN-Management-Url'], '')
107

  
108
    def _test_redirect(self, response):
109
        self.assertEquals(response.status_code, 302)
110
        self.assertTrue('Location' in response)
111
        self.assertEquals(response['Location'], settings.SHIBBOLETH_HOST)
112

  
113
    def _update_user_ts(self, user):
114
        user.auth_token_created = (datetime.now() -
115
                                   timedelta(hours = settings.AUTH_TOKEN_DURATION))
116
        user.save()
/dev/null
1
[
2
    {
3
        "model": "db.SynnefoUser",
4
        "pk": 1,
5
        "fields": {
6
            "name": "testdbuser",
7
            "realname" :"test db user",
8
            "uniq" :"test@synnefo.gr",
9
            "credit": 10,
10
            "auth_token": "46e427d657b20defe352804f0eb6f8a2",
11
            "auth_token_created": "2009-04-07 09:17:14",
12
            "type": "STUDENT",
13
            "created": "2011-02-06"
14
   	    }
15
    }
16
]
/dev/null
1
from time import time
2
from django.conf import settings
3
from django.http import HttpResponse, HttpResponseRedirect
4
from synnefo.db.models import SynnefoUser
5
from synnefo.logic.shibboleth import Tokens, register_shibboleth_user
6
import time
7

  
8
class SynnefoAuthMiddleware(object):
9

  
10
    auth_token = "X-Auth-Token"
11
    auth_user  = "X-Auth-User"
12
    auth_key   = "X-Auth-Key"
13

  
14
    def process_request(self, request):
15

  
16
        if self.auth_token in request.META:
17
            user = None
18
            #Retrieve user from DB or other caching mechanism
19
            try:
20
                user = SynnefoUser.objects.get(auth_token = request.META[self.auth_token])
21
            except SynnefoUser.DoesNotExist:
22
                return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
23

  
24
            #Check user's auth token
25
            if (time.time() -
26
                time.mktime(user.auth_token_created.timetuple()) +
27
                settings.AUTH_TOKEN_DURATION * 3600) > 0:
28
                #The user's token has expired, re-login
29
                return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
30

  
31
            request.user = user
32
            return
33

  
34
        #A user authenticated by Shibboleth, must include a uniq id
35
        if Tokens.SIB_EDU_PERSON_PRINCIPAL_NAME in request.META:
36
            #We must somehow make sure that we only process
37
            #SIB headers when coming from a URL whitelist,
38
            #or a similar form of restriction
39
            if request.get_host() not in settings.SHIBBOLETH_WHITELIST.keys():
40
                return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
41

  
42
            user = None
43
            try:
44
                user = SynnefoUser.objects.get(
45
                    uniq = request.META[Tokens.SIB_EDU_PERSON_PRINCIPAL_NAME])
46
            except SynnefoUser.DoesNotExist:
47
                pass
48

  
49
            #No user with this id could be found in the database
50
            if user is None:
51
                #Attempt to register the incoming user
52
                if register_shibboleth_user(request.META):
53
                    user = SynnefoUser.objects.get(
54
                        uniq = request.META[Tokens.SIB_EDU_PERSON_PRINCIPAL_NAME])
55
                    response = HttpResponse()
56
                    response[self.auth_token] = user.auth_token
57
                    response['Location'] = "/"
58
                    response.status_code = 302
59
                    return response
60
                else:
61
                    return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
62

  
63
            #User and authentication token valid, user allowed to proceed
64
            return
65
            
66
        #An API authentication request
67
        if self.auth_user in request.META and 'X-Auth-Key' in request.META \
68
           and '/v1.1' == request.path and 'GET' == request.method:
69
            # This is here merely for compatibility with the Openstack API.
70
            # All normal users should authenticate through Sibbolleth. Admin
71
            # users or other selected users could use this as a bypass
72
            # mechanism
73
            user = SynnefoUser.objects.filter(username = request.META[self.auth_user])
74
            
75
            return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
76

  
77
        #No authentication info found in headers, redirect to Shibboleth
78
        return HttpResponseRedirect(settings.SHIBBOLETH_HOST)
79

  
80
    def process_response(self, request, response):
81
        #Tell proxies and other interested parties that the
82
        #request varies based on the auth token, to avoid
83
        #caching of results
84
        response['Vary'] = self.auth_token
85
        return response
b/api/tests.py
13 13
from synnefo.db.models import VirtualMachine, VirtualMachineGroup
14 14
from synnefo.db.models import Flavor, Image
15 15
from synnefo.api.tests_redux import APIReduxTestCase
16
from synnefo.api.tests_auth import AuthTestCase
17 16

  
18 17
from synnefo.logic import utils
19 18

  
/dev/null
1
#
2
# Unit Tests for api
3
#
4
# Provides automated tests for api module
5
#
6
# Copyright 2011 Greek Research and Technology Network
7
#
8

  
9
from django.test import TestCase
10
from django.test.client import Client
11
from django.conf import settings
12

  
13
from synnefo.logic.shibboleth import Tokens, NoUniqueToken
14
from synnefo.db.models import SynnefoUser
15

  
16
from datetime import datetime, timedelta
17

  
18
class AuthTestCase(TestCase):
19
    fixtures = ['api_test_data', 'auth_test_data']
20
    apibase = '/api/v1.1'
21

  
22
    def setUp(self):
23
        self.client = Client()
24

  
25
    def test_shibboleth_correct_request(self):
26
        """test request that should succeed and register a user
27
        """
28
        response = self.client.get(self.apibase + '/servers', {},
29
                                   **{Tokens.SIB_GIVEN_NAME: 'Jimmy',
30
                                      Tokens.SIB_EDU_PERSON_PRINCIPAL_NAME: 'jh@gmail.com',
31
                                      Tokens.SIB_DISPLAY_NAME: 'Jimmy Hendrix'})
32
        user = None
33
        try:
34
            user = SynnefoUser.objects.get(uniq = "jh@gmail.com")
35
        except SynnefoUser.DoesNotExist:
36
            self.assertNotEqual(user, None)
37
        self.assertNotEqual(user, None)
38
        self.assertEquals(response.status_code, 302)
39
        self.assertEquals(response['Location'], "http://testserver/")
40
        self.assertTrue('X-Auth-Token' in response)
41
        self.assertEquals(response['X-Auth-Token'], user.auth_token)
42

  
43
    def test_shibboleth_no_uniq_request(self):
44
        """test a request with no unique field
45
        """
46
        response = self.client.get(self.apibase + '/servers', {},
47
                                    **{Tokens.SIB_GIVEN_NAME: 'Jimmy',
48
                                    Tokens.SIB_DISPLAY_NAME: 'Jimmy Hendrix'})
49
        self._test_redirect(response)
50

  
51
    def test_shibboleth_wrong_from_request(self):
52
        """ test request from wrong host
53
        """
54
        response = self.client.get(self.apibase + '/servers', {},
55
                                   **{Tokens.SIB_GIVEN_NAME: 'Jimmy',
56
                                      Tokens.SIB_EDU_PERSON_PRINCIPAL_NAME: 'jh@gmail.com',
57
                                      Tokens.SIB_DISPLAY_NAME: 'Jimmy Hendrix',
58
                                      'REMOTE_ADDR': '1.2.3.4',
59
                                      'SERVER_NAME': 'nohost.nodomain'})
60
        self._test_redirect(response)
61

  
62
    def test_shibboleth_expired_token(self):
63
        """ test request from expired token
64
        """
65
        user = SynnefoUser.objects.get(uniq = "test@synnefo.gr")
66
        self.assertNotEqual(user.auth_token_created, None)
67
        user.auth_token_created = (datetime.now() -
68
                                   timedelta(hours = settings.AUTH_TOKEN_DURATION))
69
        user.save()
70
        response = self.client.get(self.apibase + '/servers', {},
71
                                   **{'X-Auth-Token': user.auth_token})
72
        self._test_redirect(response)
73

  
74
    def test_shibboleth_redirect(self):
75
        """ test redirect to Sibboleth page
76
        """
77
        response = self.client.get(self.apibase + '/servers')
78
        self._test_redirect(response)
79

  
80
    def test_shibboleth_auth(self):
81
        """ test authentication with X-Auth-Token
82
        """
83
        user = SynnefoUser.objects.get(uniq = "test@synnefo.gr")
84
        response = self.client.get(self.apibase + '/servers', {},
85
                                   **{'X-Auth-Token': user.auth_token})
86
        self.assertTrue(response.status_code, 200)
87
        self.assertTrue('Vary' in response)
88
        self.assertTrue('X-Auth-Token' in response['Vary'])
89

  
90
    def test_fail_oapi_auth(self):
91
        """ test authentication from not registered user using OpenAPI
92
        """
93
        response = self.client.get(self.apibase + '/servers', {},
94
                                   **{'X-Auth-User': 'notme',
95
                                      'X-Auth-Key': '0xdeadbabe'})
96
        self.assertEquals(response.status_code, 401)
97

  
98
    def test_oapi_auth(self):
99
        """authentication with user registration
100
        """
101
        response = self.client.get(self.apibase + '/', {},
102
                                   **{'X-Auth-User': 'testuser',
103
                                      'X-Auth-Key': 'testuserpasswd'})
104
        self.assertEquals(response.status_code, 204)
105
        self.assertNotEqual(response['X-Auth-Token'], None)
106
        self.assertEquals(response['X-Server-Management-Url'], '')
107
        self.assertEquals(response['X-Storage-Url'], '')
108
        self.assertEquals(response['X-CDN-Management-Url'], '')
109

  
110
        #Check access now that we do have an auth token
111
        token = response['X-Auth-Token']
112
        response = self.client.get(self.apibase + '/servers/detail', {},
113
                                   **{'X-Auth-Token': token})
114
        self.assertEquals(response.status_code, 200)
115

  
116
    def _test_redirect(self, response):
117
        self.assertEquals(response.status_code, 302)
118
        self.assertTrue('Location' in response)
119
        self.assertEquals(response['Location'], settings.SHIBBOLETH_HOST)
/dev/null
1
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
2
#
3
# Copyright © 2010 Greek Research and Technology Network
4
#
5

  
6
from piston.handler import AnonymousBaseHandler
7
from django.http import HttpResponse
8
from django.core.urlresolvers import reverse
9

  
10
CURRENT_SERVER_VERSION = 'v1.0'
11

  
12
class AuthHandler(AnonymousBaseHandler):
13
    allowed_methods = ('GET',)
14

  
15
    def read(self, request):
16
        user = request.META.get('HTTP_X_AUTH_USER', None)
17
        key = request.META.get('HTTP_X_AUTH_KEY', None)
18
        if user is None or key is None:
19
            return HttpResponse(status=401)
20

  
21
        response = HttpResponse(status=204)
22

  
23
        # dummy auth
24
        response['X-Auth-Token'] = 'dummy-token'
25

  
26
        # return X-Server-Management's URL
27
        url = reverse('synnefo.api.urls.version_handler',
28
                kwargs={'number': CURRENT_SERVER_VERSION})
29
        url = request.build_absolute_uri(url)
30

  
31
        response['X-Server-Management-Url'] = url
32

  
33
        return response
/dev/null
1
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
2
#
3
# Copyright © 2010 Greek Research and Technology Network
4
#
5

  
6
from django.conf.urls.defaults import *
7
from piston.resource import Resource
8
from synnefo.auth.handlers import *
9

  
10
auth_handler = Resource(AuthHandler)
11

  
12
urlpatterns = patterns('',
13
    (r'^v1.0', auth_handler),
14
)
b/settings.py.dist
95 95

  
96 96
MIDDLEWARE_CLASSES = (
97 97
    'django.contrib.sessions.middleware.SessionMiddleware',
98
    #'django.contrib.auth.middleware.AuthenticationMiddleware',
98 99
    'django.middleware.locale.LocaleMiddleware',
99 100
    'django.middleware.common.CommonMiddleware',
100 101
    'synnefo.middleware.StripURLMiddleware',
101
    #'django.contrib.auth.middleware.AuthenticationMiddleware',
102 102
    #'synnefo.api.middleware.SynnefoAuthMiddleware',
103 103
    'django.contrib.messages.middleware.MessageMiddleware',
104 104
)
......
120 120
    'django.contrib.messages',
121 121
    'django.contrib.admin',
122 122
    # 'django.contrib.admindocs',
123
    'synnefo.auth',
123
    'synnefo.aai',
124 124
    'synnefo.api',
125 125
    'synnefo.ui',
126 126
    'synnefo.db',
......
164 164
SHIBBOLETH_HOST = "http://wayf.grnet.gr/"
165 165

  
166 166
SHIBBOLETH_WHITELIST = {
167
    'localhost' : '127.0.0.1'
167
    'localhost' : '127.0.0.1',
168
    'testserver' : '127.0.0.1'
168 169
}
169 170

  
170 171
#Number of hours during which a user token is active
171 172
AUTH_TOKEN_DURATION = 24
172

  

Also available in: Unified diff