Revision 244c552b

b/runtests-venvs.sh
59 59
rm bin/vncauthproxy.py
60 60
echo "running django tests..." >&2
61 61
export SYNNEFO_SETTINGS_DIR=/etc/lala
62
snf-manage test aai admin api db helpdesk invitations logic userdata --settings=synnefo.settings.test
62
snf-manage test admin api db logic userdata --settings=synnefo.settings.test
63 63
cd ..
64 64
deactivate
65 65

  
b/runtests.sh
38 38
set -e
39 39

  
40 40
echo "Running snf-app tests..." >&2
41
snf-manage test aai admin api db helpdesk logic userdata --settings=synnefo.settings.test
41
snf-manage test admin api db logic userdata --settings=synnefo.settings.test
42 42

  
43 43
echo "Running snf-ganeti-tools tests..." >&2
44 44
./snf-ganeti-tools/test/synnefo.ganeti_unittest.py
/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
            "auth_token": "46e427d657b20defe352804f0eb6f8a2",
10
            "auth_token_created": "2011-04-07 09:17:14",
11
            "auth_token_expires": "2015-04-07 09:17:14",
12
            "type": "STUDENT",
13
            "created": "2011-02-06"
14
   	    }
15
    }
16
]
/dev/null
1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or without
4
# modification, are permitted provided that the following conditions
5
# are met:
6
#
7
#   1. Redistributions of source code must retain the above copyright
8
#      notice, this list of conditions and the following disclaimer.
9
#
10
#  2. Redistributions in binary form must reproduce the above copyright
11
#     notice, this list of conditions and the following disclaimer in the
12
#     documentation and/or other materials provided with the distribution.
13
#
14
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
# SUCH DAMAGE.
25
#
26
# The views and conclusions contained in the software and documentation are
27
# those of the authors and should not be interpreted as representing official
28
# policies, either expressed or implied, of GRNET S.A.
29

  
30
from django.conf import settings
31
from django.http import HttpResponse, HttpResponseRedirect
32
from django.utils.cache import patch_vary_headers
33
from synnefo.db.models import SynnefoUser
34
from synnefo.aai.shibboleth import Tokens, register_shibboleth_user
35
import time
36

  
37
DONT_CHECK = getattr(settings, "AAI_SKIP_AUTH_URLS", ['/api'])
38

  
39
class SynnefoAuthMiddleware(object):
40

  
41
    def process_request(self, request):
42

  
43
        for path in DONT_CHECK:
44
            if request.path.startswith(path):
45
                return
46

  
47
        # Special case for testing purposes, delivers the cookie for the
48
        # test user on first access
49
        if settings.BYPASS_AUTHENTICATION and \
50
           request.GET.get('test') is not None:
51
            try:
52
                u = SynnefoUser.objects.get(
53
                    auth_token=settings.BYPASS_AUTHENTICATION_SECRET_TOKEN)
54
            except SynnefoUser.DoesNotExist:
55
                raise Exception("No user found with token matching "
56
                                "BYPASS_AUTHENTICATION_SECRET_TOKEN.")
57
            return self._redirect_shib_auth_user(user = u)
58

  
59
        token = None
60

  
61
        # Try to find token in a cookie
62
        token = request.COOKIES.get('X-Auth-Token', None)
63

  
64
        # Try to find token in request header
65
        if not token:
66
            token = request.META.get('HTTP_X_AUTH_TOKEN', None)
67

  
68
        if token:
69
            # token was found, retrieve user from backing store
70
            try:
71
                user = SynnefoUser.objects.get(auth_token=token)
72

  
73
            except SynnefoUser.DoesNotExist:
74
                return HttpResponseRedirect(settings.LOGIN_URL)
75
            # check user's auth token validity
76
            if (time.time() -
77
                time.mktime(user.auth_token_expires.timetuple())) > 0:
78
                # the user's token has expired, prompt to re-login
79
                return HttpResponseRedirect(settings.LOGIN_URL)
80

  
81
            request.user = user
82
            return
83

  
84
        # token was not found but user authenticated by Shibboleth
85
        if Tokens.SHIB_EPPN in request.META and \
86
           Tokens.SHIB_SESSION_ID in request.META:
87
            try:
88
                user = SynnefoUser.objects.get(uniq=request.META[Tokens.SHIB_EPPN])
89
                return self._redirect_shib_auth_user(user)
90
            except SynnefoUser.DoesNotExist:
91
                if register_shibboleth_user(request.META):
92
                    user = SynnefoUser.objects.get(uniq=request.META[Tokens.SHIB_EPPN])
93
                    return self._redirect_shib_auth_user(user)
94
                else:
95
                    return HttpResponseRedirect(settings.LOGIN_URL)
96

  
97
        if settings.TEST and 'TEST-AAI' in request.META:
98
            return HttpResponseRedirect(settings.LOGIN_URL)
99

  
100
        if request.path.endswith(settings.LOGIN_URL):
101
            # avoid redirect loops
102
            return
103
        else:
104
            # no authentication info found in headers, redirect back
105
            return HttpResponseRedirect(settings.LOGIN_URL)
106

  
107
    def process_response(self, request, response):
108
        # Tell proxies and other interested parties that the request varies
109
        # based on X-Auth-Token, to avoid caching of results
110
        patch_vary_headers(response, ('X-Auth-Token',))
111
        return response
112

  
113
    def _redirect_shib_auth_user(self, user):
114
        expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
115

  
116
        response = HttpResponse()
117
        response.set_cookie('X-Auth-Token', value=user.auth_token,
118
                            expires=expire_fmt, path='/')
119
        response['X-Auth-Token'] = user.auth_token
120
        response['Location'] = settings.APP_INSTALL_URL
121
        response.status_code = 302
122
        return response
123

  
124

  
125
def add_url_exception(url):
126
    if not url in DONT_CHECK:
127
        DONT_CHECK.append(url)
/dev/null
1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or without
4
# modification, are permitted provided that the following conditions
5
# are met:
6
#
7
#   1. Redistributions of source code must retain the above copyright
8
#      notice, this list of conditions and the following disclaimer.
9
#
10
#  2. Redistributions in binary form must reproduce the above copyright
11
#     notice, this list of conditions and the following disclaimer in the
12
#     documentation and/or other materials provided with the distribution.
13
#
14
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
# SUCH DAMAGE.
25

  
26
#
27
# The views and conclusions contained in the software and documentation are
28
# those of the authors and should not be interpreted as representing official
29
# policies, either expressed or implied, of GRNET S.A.
30

  
31
# Business Logic for working with sibbolleth users
32

  
33
from synnefo.logic import users
34

  
35
class Tokens:
36
    # these are mapped by the Shibboleth SP software
37
    SHIB_EPPN = "eppn" # eduPersonPrincipalName
38
    SHIB_NAME = "Shib-InetOrgPerson-givenName"
39
    SHIB_SURNAME = "Shib-Person-surname"
40
    SHIB_CN = "Shib-Person-commonName"
41
    SHIB_DISPLAYNAME = "Shib-InetOrgPerson-displayName"
42
    SHIB_EP_AFFILIATION = "Shib-EP-Affiliation"
43
    SHIB_SESSION_ID = "Shib-Session-ID"
44

  
45

  
46
class NoUniqueToken(BaseException):
47
    def __init__(self, msg):
48
        self.msg = msg
49

  
50

  
51
class NoRealName(BaseException):
52
    def __init__(self, msg):
53
        self.msg = msg
54

  
55

  
56
def register_shibboleth_user(tokens):
57
    """Registers a Shibboleth user using the input hash as a source for data."""
58

  
59
    if Tokens.SHIB_DISPLAYNAME in tokens:
60
        realname = tokens[Tokens.SHIB_DISPLAYNAME]
61
    elif Tokens.SHIB_CN in tokens:
62
        realname = tokens[Tokens.SHIB_CN]
63
    elif Tokens.SHIB_NAME in tokens and Tokens.SHIB_SURNAME in tokens:
64
        realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
65
    else:
66
        raise NoRealName("Authentication does not return the user's name")
67

  
68
    try:
69
        affiliation = tokens[Tokens.SHIB_EP_AFFILIATION]
70
    except KeyError:
71
        affiliation = 'member'
72

  
73
    try:
74
        eppn = tokens[Tokens.SHIB_EPPN]
75
    except KeyError:
76
        raise NoUniqueToken("Authentication does not return a unique token")
77

  
78
    if affiliation == 'student':
79
        users.register_student(realname, '' , eppn)
80
    else:
81
        # this includes faculty but also staff, alumni, member, other, ...
82
        users.register_professor(realname, '' , eppn)
83

  
84
    return True
/dev/null
1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or without
4
# modification, are permitted provided that the following conditions
5
# are met:
6
#
7
#   1. Redistributions of source code must retain the above copyright
8
#      notice, this list of conditions and the following disclaimer.
9
#
10
#  2. Redistributions in binary form must reproduce the above copyright
11
#     notice, this list of conditions and the following disclaimer in the
12
#     documentation and/or other materials provided with the distribution.
13
#
14
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
# SUCH DAMAGE.
25
#
26
# The views and conclusions contained in the software and documentation are
27
# those of the authors and should not be interpreted as representing official
28
# policies, either expressed or implied, of GRNET S.A.
29

  
30
# Provides automated tests for aai module. The tests
31

  
32
from django.test import TestCase
33
from django.test.client import Client
34
from django.conf import settings
35

  
36
from synnefo.db.models import SynnefoUser
37

  
38
from datetime import datetime, timedelta
39

  
40
from synnefo.aai.shibboleth import Tokens
41

  
42

  
43
class AaiTestCase(TestCase):
44
    fixtures = ['users', 'api_test_data', 'auth_test_data']
45
    apibase = '/api/v1.1'
46

  
47
    def setUp(self):
48
        self.client = Client()
49

  
50
    def test_shibboleth_correct_request(self):
51
        """test request that should succeed and register a user
52
        """
53
        response = self.client.get('/index.html', {},
54
                                   **{Tokens.SHIB_NAME: 'Jimmy',
55
                                      Tokens.SHIB_EPPN: 'jh@gmail.com',
56
                                      Tokens.SHIB_CN: 'Jimmy Hendrix',
57
                                      Tokens.SHIB_SESSION_ID: '123321',
58
                                      'TEST-AAI' : 'true'})
59
        user = None
60
        try:
61
            user = SynnefoUser.objects.get(uniq = "jh@gmail.com")
62
        except SynnefoUser.DoesNotExist:
63
            self.assertNotEqual(user, None)
64
        self.assertNotEqual(user, None)
65
        self.assertEquals(response.status_code, 302)
66
        self.assertEquals(response['Location'], settings.APP_INSTALL_URL)
67
        self.assertTrue('X-Auth-Token' in response)
68
        self.assertEquals(response['X-Auth-Token'], user.auth_token)
69
        #self.assertNotEquals(response.cookies['X-Auth-Token'].find(user.auth_token), -1)
70

  
71
    def test_shibboleth_no_uniq_request(self):
72
        """test a request with no unique field
73
        """
74
        response = self.client.get('/index.html', {},
75
                               **{Tokens.SHIB_NAME: 'Jimmy',
76
                                  Tokens.SHIB_CN: 'Jimmy Hendrix',
77
                                  'TEST-AAI': 'true'})
78
        self._test_redirect(response)
79

  
80
    def test_shibboleth_expired_token(self):
81
        """ test request from expired token
82
        """
83
        user = SynnefoUser.objects.get(uniq="test@synnefo.gr")
84
        self.assertNotEqual(user.auth_token_expires, None)
85
        user.auth_token_expires = datetime.now()
86
        user.save()
87
        response = self.client.get('/index.html', {},
88
                               **{'X-Auth-Token': user.auth_token,
89
                                  'TEST-AAI': 'true'})
90
        self._test_redirect(response)
91

  
92
    def test_shibboleth_redirect(self):
93
        """ test redirect to Sibboleth page
94
        """
95
        response = self.client.get('/index.html', {}, **{'TEST-AAI': 'true'})
96
        self._test_redirect(response)
97

  
98
    def test_shibboleth_auth(self):
99
        """ test authentication with X-Auth-Token
100
        """
101
        user = SynnefoUser.objects.get(uniq="test@synnefo.gr")
102
        response = self.client.get('/index.html', {},
103
                               **{'X-Auth-Token': user.auth_token,
104
                                  'TEST-AAI': 'true'})
105
        self.assertTrue(response.status_code, 200)
106
        self.assertTrue('Vary' in response)
107
        self.assertTrue('X-Auth-Token' in response['Vary'])
108

  
109
    def test_auth_cookie(self):
110
        user = SynnefoUser.objects.get(uniq = "test@synnefo.gr")
111
        self.client.cookies['X-Auth-Token'] = user.auth_token
112
        response = self.client.get('/', {},
113
                                   **{'X-Auth-Token': user.auth_token,
114
                                      'TEST-AAI' : 'true'})
115
        self.assertTrue(response.status_code, 200)
116
        self.assertTrue('Vary' in response)
117
        self.assertTrue('X-Auth-Token' in response['Vary'])
118

  
119
    def _test_redirect(self, response):
120
        self.assertEquals(response.status_code, 302)
121
        self.assertTrue('Location' in response)
122
        self.assertTrue(response['Location'].startswith(settings.LOGIN_URL))
123

  
b/snf-app/synnefo/admin/api.py
37 37
from django.http import HttpResponse
38 38

  
39 39
from synnefo.admin.views import requires_admin
40
from synnefo.db import models
40
from synnefo.db.models import VirtualMachine
41 41

  
42 42

  
43 43
@requires_admin
44 44
def servers_info(request, server_id):
45
    server = models.VirtualMachine.objects.get(id=server_id)
45
    server = VirtualMachine.objects.get(id=server_id)
46 46
    reply = {
47 47
    	'name': server.name,
48 48
    	'ref': '#'}
49 49
    return HttpResponse(json.dumps(reply), content_type='application/json')
50

  
51

  
52
@requires_admin
53
def users_info(request, user_id):
54
    user = models.SynnefoUser.objects.get(id=user_id)
55
    reply = {
56
    	'name': user.name,
57
    	'ref': reverse('synnefo.admin.views.users_info', args=(user_id,))}
58
    return HttpResponse(json.dumps(reply), content_type='application/json')
b/snf-app/synnefo/admin/static/main.js
21 21
	append_item('append-server', '/admin/api/servers/');
22 22
}
23 23

  
24
function append_user() {
25
	append_item('append-user', '/admin/api/users/');
26
}
27

  
28

  
29 24
$(function() {
30 25
	$('table.id-sorted').tablesorter({ sortList: [[0, 0]] });
31 26

  
32 27
	$('tr.row-template').hide();
33 28
	$('div.alert-message').hide();
34 29

  
35
	append_user();
36
	$('.append-user').change(function() {
37
		append_user();
38
	});
39

  
40 30
	append_server();
41 31
	$('.append-server').change(function() {
42 32
		append_server();
b/snf-app/synnefo/admin/templates/images_info.html
33 33
      <div class="clearfix">
34 34
        <label for="image-owner">Owner ID</label>
35 35
        <div class="input input-append">
36
          <input class="small append-user" id="image-owner" name="owner" value="{{ image.owner.id }}" type="text" />
36
          <input class="small" id="image-owner" name="owner" value="{{ image.userid }}" type="text" />
37 37
          <label class="add-on"><a href=""></a></label>
38 38
        </div>
39 39
      </div>
b/snf-app/synnefo/admin/templates/images_list.html
19 19
    <tr>
20 20
      <td><a href="{% url synnefo.admin.views.images_info image.id %}">{{ image.id }}</a></td>
21 21
      <td><a href="{% url synnefo.admin.views.images_info image.id %}">{{ image.name }}</a></td>
22
      <td>{% if image.owner %}<a href="{% url synnefo.admin.views.users_info image.owner.id %}">{{ image.owner.name }}</a>{% endif %}
23
      </td>
22
      <td>{{ image.userid|default:"" }}</td>
24 23
      <td>{{ image.state }}</td>
25 24
      <td>{{ image.backend_id }}</td>
26 25
      <td>{{ image.format }}</td>
b/snf-app/synnefo/admin/templates/images_register.html
13 13
  <div class="clearfix">
14 14
    <label for="image-owner">Owner ID</label>
15 15
    <div class="input input-append">
16
      <input class="small append-user" id="image-owner" name="owner" value="{{ image.owner.id }}" type="text" />
16
      <input class="small" id="image-owner" name="owner" value="{{ image.userid }}" type="text" />
17 17
      <label class="add-on"><a href=""></a></label>
18 18
    </div>
19 19
  </div>
b/snf-app/synnefo/admin/templates/servers_list.html
19 19
    <tr>
20 20
      <td>{{ server.id }}</td>
21 21
      <td>{{ server.name }}</td>
22
      <td><a href="{% url synnefo.admin.views.users_info server.owner.id %}">{{ server.owner.name }}</a></td>
22
      <td>{{ server.userid }}</td>
23 23
      <td>{{ server.operstate }}</td>
24 24
      <td><a href="{% url synnefo.admin.views.flavors_info server.flavor.id %}">{{ server.flavor.name }}</a></td>
25 25
      <td><a href="{% url synnefo.admin.views.images_info server.imageid %}">{{ server.imageid }}</a></td>
/dev/null
1
{% extends "base.html" %}
2

  
3
{% block body %}
4

  
5
<form action="{% url synnefo.admin.views.users_modify user.id %}" method="post">
6
  <div class="clearfix">
7
    <label for="user-id">ID</label>
8
    <div class="input">
9
      <span class="uneditable-input" id="user-id">{{ user.id }}</span>
10
    </div>
11
  </div>
12

  
13
  <div class="clearfix">
14
    <label for="user-name">Name</label>
15
    <div class="input">
16
      <input class="medium" id="user-name" name="name" value="{{ user.name }}" type="text" />
17
    </div>
18
  </div>
19

  
20
  <div class="clearfix">
21
    <label for="user-realname">Real Name</label>
22
    <div class="input">
23
      <input class="large" id="user-realname" name="realname" value="{{ user.realname }}" type="text" />
24
    </div>
25
  </div>
26

  
27
  <div class="clearfix">
28
    <label for="user-uniq">Uniq</label>
29
    <div class="input">
30
      <input class="large" id="user-uniq" name="uniq" value="{{ user.uniq }}" type="text" />
31
    </div>
32
  </div>
33

  
34
  <div class="clearfix">
35
    <label for="user-type">Type</label>
36
    <div class="input">
37
      <select class="medium" id="user-type" name="type">
38
        {% for type in types %}
39
        <option{% ifequal type user.type %} selected{% endifequal %}>{{ type }}</option>
40
        {% endfor %}
41
      </select>
42
    </div>
43
  </div>
44

  
45
  <div class="clearfix">
46
    <label for="user-state">State</label>
47
    <div class="input">
48
      <select class="medium" id="user-state" name="state">
49
        {% for state in states %}
50
        <option{% ifequal state user.state %} selected{% endifequal %}>{{ state }}</option>
51
        {% endfor %}
52
      </select>
53
    </div>
54
  </div>
55

  
56
  <div class="clearfix">
57
    <label for="user-created">Created</label>
58
    <div class="input">
59
      <span class="uneditable-input" id="user-created">{{ user.created }}</span>
60
    </div>
61
  </div>
62

  
63
  <div class="clearfix">
64
    <label for="user-updated">Updated</label>
65
    <div class="input">
66
      <span class="uneditable-input" id="user-updated">{{ user.updated }}</span>
67
    </div>
68
  </div>
69

  
70
  <div class="actions">
71
    <button type="submit" class="btn primary">Save Changes</button>
72
    <button type="reset" class="btn">Reset</button>
73
  </div>
74
</form>
75
{% endblock body %}
/dev/null
1
{% extends "base.html" %}
2

  
3
{% block body %}
4
<table class="zebra-striped id-sorted">
5
  <thead>
6
    <tr>
7
      <th>ID</th>
8
      <th>Name</th>
9
      <th>Real Name</th>
10
      <th>Uniq</th>
11
      <th>Type</th>
12
      <th>State</th>
13
      <th>Updated</th>
14
    </tr>
15
  </thead>
16
  <tbody>
17
    {% for user in users %}
18
    <tr>
19
      <td><a href="{% url synnefo.admin.views.users_info user.id %}">{{ user.id }}</a></td>
20
      <td><a href="{% url synnefo.admin.views.users_info user.id %}">{{ user.name }}</a></td>
21
      <td>{{ user.realname }}</td>
22
      <td>{{ user.uniq }}</td>
23
      <td>{{ user.type }}</td>
24
      <td>{{ user.state }}</td>
25
      <td>{{ user.updated }}</td>
26
    </tr>
27
    {% endfor %}
28
  </tbody>
29
</table>
30
{% endblock body %}
b/snf-app/synnefo/admin/urls.py
50 50
    (r'^/images/(\d+)/modify/?$', 'images_modify'),
51 51

  
52 52
    (r'^/servers/?$', 'servers_list'),
53

  
54
    (r'^/users/?$', 'users_list'),
55
    (r'^/users/(\d+)/?$', 'users_info'),
56
    (r'^/users/(\d+)/modify/?$', 'users_modify'),
57
    (r'^/users/(\d+)/delete/?$', 'users_delete'),
58 53
)
59 54

  
60 55
urlpatterns += patterns('synnefo.admin.api',
b/snf-app/synnefo/admin/views.py
77 77
@requires_admin
78 78
def index(request):
79 79
    stats = {}
80
    stats['users'] = models.SynnefoUser.objects.count()
81 80
    stats['images'] = models.Image.objects.exclude(state='DELETED').count()
82 81
    stats['flavors'] = models.Flavor.objects.count()
83 82
    stats['vms'] = models.VirtualMachine.objects.filter(deleted=False).count()
......
187 186
        image = models.Image()
188 187
        image.state = 'ACTIVE'
189 188
        image.name = request.POST.get('name')
190
        owner_id = request.POST.get('owner') or None
191
        image.owner = owner_id and models.SynnefoUser.objects.get(id=owner_id)
189
        image.userid = request.POST.get('owner')
192 190
        image.backend_id = request.POST.get('backend')
193 191
        image.format = request.POST.get('format')
194 192
        image.public = True if request.POST.get('public') else False
......
221 219
    image = models.Image.objects.get(id=image_id)
222 220
    image.name = request.POST.get('name')
223 221
    image.state = request.POST.get('state')
224
    owner_id = request.POST.get('owner') or None
225
    image.owner = owner_id and models.SynnefoUser.objects.get(id=owner_id)
226
    vm_id = request.POST.get('sourcevm') or None
222
    image.userid = request.POST.get('owner')
223
    vm_id = request.POST.get('sourcevm')
227 224
    image.sourcevm = vm_id and models.VirtualMachine.objects.get(id=vm_id)
228 225
    image.backend_id = request.POST.get('backend')
229 226
    image.format = request.POST.get('format')
......
258 255
                    all_states=sorted(all_states),
259 256
                    filters=filters)
260 257
    return HttpResponse(html)
261

  
262

  
263
@requires_admin
264
def users_list(request):
265
    all_states = set(x[0] for x in models.SynnefoUser.ACCOUNT_STATE)
266
    default = all_states - set(['DELETED'])
267
    filters = get_filters(request, 'users_filters', all_states, default)
268
    
269
    users = models.SynnefoUser.objects.all()
270
    for state in all_states - filters:
271
        users = users.exclude(state=state)
272
    
273
    html = render('users_list.html', 'users',
274
                    users=users.order_by('id'),
275
                    all_states=sorted(all_states),
276
                    filters=filters)
277
    return HttpResponse(html)
278

  
279

  
280
@requires_admin
281
def users_info(request, user_id):
282
    user = models.SynnefoUser.objects.get(id=user_id)
283
    types = [x[0] for x in models.SynnefoUser.ACCOUNT_TYPE]
284
    if not user.type:
285
        types = [''] + types
286
    states = [x[0] for x in models.SynnefoUser.ACCOUNT_STATE]
287
    html = render('users_info.html', 'users',
288
                    user=user, types=types, states=states)
289
    return HttpResponse(html)
290

  
291

  
292
@requires_admin
293
def users_modify(request, user_id):
294
    user = models.SynnefoUser.objects.get(id=user_id)
295
    user.name = request.POST.get('name')
296
    user.realname = request.POST.get('realname')
297
    user.uniq = request.POST.get('uniq')
298
    user.type = request.POST.get('type')
299
    user.state = request.POST.get('state')
300
    user.save()
301
    log.info('User %s modified User %s', request.user.name, user.name)
302
    return redirect(users_info, user.id)
303

  
304

  
305
@requires_admin
306
def users_delete(request, user_id):
307
    user = models.SynnefoUser.objects.get(id=user_id)
308
    user.delete()
309
    log.info('User %s deleted User %s', request.user.name, user.name)
310
    return redirect(users_list)
b/snf-app/synnefo/api/fixtures/api_test_data.json
247 247
        "pk": 1,
248 248
        "fields": {
249 249
            "name": "Debian Squeeze",
250
            "owner": 1,
250
            "userid": "test",
251 251
            "created": "2011-02-06 00:00:00",
252 252
            "updated": "2011-02-06 00:00:00",
253 253
            "state": "ACTIVE"
......
257 257
        "model": "db.VirtualMachine",
258 258
        "pk": 1001,
259 259
        "fields": {
260
            "owner": 1,
260
            "userid": "test",
261 261
            "name": "snf-1001",
262 262
            "created": "2011-02-06 00:00:00",
263 263
            "updated": "2011-02-06 00:00:00",
......
272 272
        "model": "db.VirtualMachine",
273 273
        "pk": 1002,
274 274
        "fields": {
275
            "owner": 1,
275
            "userid": "test",
276 276
            "name": "snf-1002",
277 277
            "created": "2011-02-10 00:00:00",
278 278
            "updated": "2011-02-10 00:00:00",
......
287 287
        "model": "db.VirtualMachine",
288 288
        "pk": 1003,
289 289
        "fields": {
290
            "owner": 1,
290
            "userid": "test",
291 291
            "name": "snf-1003",
292 292
            "created": "2011-02-10 00:00:00",
293 293
            "updated": "2011-02-10 00:00:00",
......
302 302
        "model": "db.VirtualMachine",
303 303
        "pk": 1004,
304 304
        "fields": {
305
            "owner": 1,
305
            "userid": "test",
306 306
            "name": "snf-1004",
307 307
            "created": "2011-02-10 00:00:00",
308 308
            "updated": "2011-02-10 00:00:00",
......
321 321
            "created": "2011-02-10 00:00:00",
322 322
            "updated": "2011-02-10 00:00:00",
323 323
            "state": "ACTIVE",
324
            "owner" : 1,
324
            "userid" : "test",
325 325
            "sourcevm": 1001
326 326
        }
327 327
    }
b/snf-app/synnefo/api/middleware.py
1
from django.http import HttpResponse
2
from synnefo.db.models import SynnefoUser
3
from django.utils.cache import patch_vary_headers
4
import time
1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
5 33

  
6
class ApiAuthMiddleware(object):
34
from django.utils.cache import patch_vary_headers
7 35

  
8
    auth_token = "X-Auth-Token"
9
    auth_user  = "X-Auth-User"
10
    auth_key   = "X-Auth-Key"
11 36

  
37
class ApiAuthMiddleware(object):
12 38
    def process_request(self, request):
13
        if not request.path.startswith('/api/') :
14
            return
15

  
16
        token = None
17

  
18
        # Try to find token in a cookie
19
        token = request.COOKIES.get('X-Auth-Token', None)
20

  
21
        # Try to find token in request header
22
        if not token:
23
            token = request.META.get('HTTP_X_AUTH_TOKEN', None)
24

  
25
        if token:
26
            user = None
27
            # Retrieve user from DB or other caching mechanism
28
            try:
29
                user = SynnefoUser.objects.get(auth_token=token)
30
            except SynnefoUser.DoesNotExist:
31
                user = None
32

  
33
            # Check user's auth token
34
            if user and (time.time() -
35
                time.mktime(user.auth_token_expires.timetuple())) > 0:
36
                # The user's token has expired, re-login
37
                user = None
38

  
39
            request.user = user
40
            return
41

  
42
        # A Rackspace API authentication request
43
        if self.auth_user in request.META and \
44
           self.auth_key in request.META and \
45
           'GET' == request.method:
46
            # This is here merely for compatibility with the Openstack API.
47
            # All normal users should authenticate through Shibboleth. Admin
48
            # users or other selected users could use this as a bypass
49
            # mechanism
50
            user = SynnefoUser.objects\
51
                    .filter(name = request.META[self.auth_user]) \
52
                    .filter(uniq = request.META[self.auth_key])
53

  
54
            response = HttpResponse()
55
            if user.count() <= 0:
56
                response.status_code = 401
57
            else:
58
                response.status_code = 204
59
                response['X-Auth-Token'] = user[0].auth_token
60
                # TODO: set the following fields when we do have this info
61
                response['X-Server-Management-Url'] = ""
62
                response['X-Storage-Url'] = ""
63
                response['X-CDN-Management-Url'] = ""
64
            return response
65

  
66 39
        request.user = None
67 40

  
68 41
    def process_response(self, request, response):
......
70 43
        # based on X-Auth-Token, to avoid caching of results
71 44
        patch_vary_headers(response, ('X-Auth-Token',))
72 45
        return response
73

  
b/snf-app/synnefo/api/tests.py
49 49

  
50 50
class AaiClient(Client):
51 51
    def request(self, **request):
52
        request['HTTP_X_AUTH_TOKEN'] = \
53
            settings.BYPASS_AUTHENTICATION_SECRET_TOKEN
52
        request['HTTP_X_AUTH_TOKEN'] = '0000'
54 53
        return super(AaiClient, self).request(**request)
55 54

  
56 55

  
......
160 159
        request = {
161 160
                    "server": {
162 161
                        "name": "new-server-test",
163
                        "owner": 1,
162
                        "userid": "test",
164 163
                        "imageRef": 1,
165 164
                        "flavorRef": 1,
166 165
                        "metadata": {
......
342 341
        self.assertEqual(response.status_code, 201)
343 342

  
344 343

  
345
def create_users(n=1):
346
    for i in range(n):
347
        SynnefoUser.objects.create(name='User %d' % i)
348

  
349

  
350 344
def create_flavors(n=1):
351 345
    for i in range(n):
352 346
        Flavor.objects.create(
......
356 350

  
357 351

  
358 352
def create_images(n=1):
359
    owner = SynnefoUser.objects.all()[0]
360 353
    for i in range(n):
361 354
        Image.objects.create(
362 355
            name='Image %d' % (i + 1),
363 356
            state='ACTIVE',
364
            owner=owner)
357
            owner='test')
365 358

  
366 359

  
367 360
def create_image_metadata(n=1):
......
374 367

  
375 368

  
376 369
def create_servers(n=1):
377
    owner = SynnefoUser.objects.all()[0]
378 370
    flavors = Flavor.objects.all()
379 371
    images = Image.objects.all()
380 372
    for i in range(n):
381 373
        VirtualMachine.objects.create(
382 374
            name='Server %d' % (i + 1),
383
            owner=owner,
375
            owner='test',
384 376
            imageid=choice(images).id,
385 377
            hostid=str(i),
386 378
            flavor=choice(flavors))
......
410 402

  
411 403

  
412 404
class BaseTestCase(TestCase):
413
    USERS = 0
414 405
    FLAVORS = 1
415 406
    IMAGES = 1
416 407
    SERVERS = 1
......
420 411

  
421 412
    def setUp(self):
422 413
        self.client = AaiClient()
423
        create_users(self.USERS)
424 414
        create_flavors(self.FLAVORS)
425 415
        create_images(self.IMAGES)
426 416
        create_image_metadata(self.IMAGE_METADATA)
b/snf-app/synnefo/api/util.py
177 177
    try:
178 178
        image_id = int(image_id)
179 179
        image = Image.objects.get(id=image_id)
180
        if not image.public and image.owner != owner:
180
        if not image.public and image.userid != owner:
181 181
            raise ItemNotFound('Image not found.')
182 182
        return image
183 183
    except ValueError:
b/snf-app/synnefo/app_settings/__init__.py
1 1
synnefo_web_apps = [
2
    'synnefo.aai',
3 2
    'synnefo.admin',
4 3
    'synnefo.api',
5 4
    'synnefo.ui',
6 5
    'synnefo.db',
7 6
    'synnefo.logic',
8
    'synnefo.helpdesk',
9 7
    'synnefo.plankton',
10 8
    'synnefo.ui.userdata',
11 9
]
12 10

  
13 11
synnefo_web_middleware = [
14 12
    {'after': 'django.middleware.locale.LocaleMiddleware', 'insert': [
15
        'synnefo.aai.middleware.SynnefoAuthMiddleware',
16
        'synnefo.api.middleware.ApiAuthMiddleware',
17
        'synnefo.helpdesk.middleware.HelpdeskMiddleware'
13
        'synnefo.api.middleware.ApiAuthMiddleware'
18 14
        ]
19 15
    }
20 16
]
b/snf-app/synnefo/app_settings/default/__init__.py
38 38
from synnefo.app_settings.default.plankton import *
39 39
from synnefo.app_settings.default.ui import *
40 40
from synnefo.app_settings.default.userdata import *
41
from synnefo.app_settings.default.aai import *
42 41
from synnefo.app_settings.default.reconciliation import *
43
from synnefo.app_settings.default.helpdesk import *
44 42
from synnefo.app_settings.default.tests import *
45 43

  
/dev/null
1
# -*- coding: utf-8 -*-
2
from synnefo.util.entry_points import extend_list_from_entry_point
3
#
4
# AAI configuration
5
#####################
6

  
7
# Unauthenticated HTTP requests to the UI get redirected to this URL
8
LOGIN_URL = "/login"
9

  
10
# Set the expiration time of newly created auth tokens
11
# to be this many hours after their creation time.
12
AUTH_TOKEN_DURATION = 30 * 24
13

  
14
# Enable receiving a temporary auth token (using the ?test URL parameter) that
15
# bypasses the authentication mechanism.
16
#
17
# Make sure there is an actual user in the db whose token matches
18
# BYPASS_AUTHENTICATION_SECRET_TOKEN.
19
#
20
# WARNING, ACHTUNG, README, etc: DO NOT ENABLE THIS ON DEPLOYED VERSIONS!
21
#
22
BYPASS_AUTHENTICATION = False
23
BYPASS_AUTHENTICATION_SECRET_TOKEN = '5e41595e9e884543fa048e07c1094d74'
24

  
25
# Urls that bypass Shibboleth authentication
26
AAI_SKIP_AUTH_URLS = ['/api', '/plankton']
27
AAI_SKIP_AUTH_URLS = extend_list_from_entry_point(AAI_SKIP_AUTH_URLS, \
28
        'synnefo', 'web_skip_urls')
/dev/null
1
# -*- coding: utf-8 -*-
2
#
3
# Helpdesk application
4
#
5

  
6
# Duration for temporary auth tokens, created for impersonating a registered
7
# user by helpdesk staff.
8
HELPDESK_TOKEN_DURATION_MIN = 30
9

  
10
# IP addresses of the machines allowed to connect as help desk
11
HELPDESK_ALLOWED_IPS = ("127.0.0.1",)
12

  
b/snf-app/synnefo/app_settings/urls.py
37 37
    (r'^ui/', include('synnefo.ui.urls')),
38 38
    (r'^admin/', include('synnefo.admin.urls')),
39 39
    (r'^api/', include('synnefo.api.urls')),
40
    (r'^helpdesk/?', include('synnefo.helpdesk.urls')),
41 40
    (r'^plankton/', include('synnefo.plankton.urls')),
42 41
)
43 42

  
b/snf-app/synnefo/db/fixtures/db_test_data.json
1 1
[
2 2
    {
3
        "model": "db.SynnefoUser",
4
        "pk": 30000,
5
        "fields": {
6
            "name": "testdbuser",
7
            "realname" :"test db user",
8
            "uniq" :"test@synnefo.gr",
9
            "auth_token": "5e41595e9e884543fa048e07c1094d74",
10
            "auth_token_created": "2009-04-07 09:17:14",
11
            "auth_token_expires": "2015-04-07 09:17:14",
12
            "type": "STUDENT",
13
            "created": "2011-02-06"
14
   	    }
15
    },
16
    {
17 3
        "model": "db.Flavor",
18 4
        "pk": 30000,
19 5
        "fields": {
......
39 25
            "updated": "2011-02-06 00:00:00",
40 26
            "created": "2011-02-06 00:00:00",
41 27
            "state": "ACTIVE",
42
            "owner" : 30000
28
            "userid" : "test"
43 29
        }
44 30
    },
45 31
    {
......
64 50
        "model": "db.VirtualMachine",
65 51
        "pk": 30000,
66 52
        "fields": {
67
            "owner": 30000,
53
            "userid": "test",
68 54
            "name": "snf-1001",
69 55
            "created": "2009-02-06 00:00:00",
70 56
            "charged": "2010-02-06 00:00:00",
......
78 64
        "model": "db.VirtualMachine",
79 65
        "pk": 30001,
80 66
        "fields": {
81
            "owner": 30000,
67
            "userid": "test",
82 68
            "name": "snf-1002",
83 69
            "created": "2009-02-06 00:00:00",
84 70
            "charged": "2010-02-06 00:00:00",
......
89 75
        }
90 76
    },
91 77
    {
92
        "model": "db.Disk",
93
        "pk": 30000,
94
        "fields": {
95
            "name": "My_Music",
96
            "created": "2011-02-10 00:00:00",
97
            "size" : 20,
98
            "vm" : 30000,
99
            "owner" : 30000
100
        }
101
    },
102
    {
103 78
        "model": "db.VirtualMachine",
104 79
        "pk": 30002,
105 80
        "fields": {
106
            "owner": 30000,
81
            "userid": "test",
107 82
            "name": "snf-test-building",
108 83
            "created": "2009-02-06 00:00:00",
109 84
            "charged": "2010-02-06 00:00:00",
/dev/null
1
[
2
    {
3
        "model": "db.Disk",
4
        "pk": 1,
5
        "fields": {
6
            "name": "My_Music",
7
            "created": "2011-02-10 00:00:00",
8
            "updated": "2011-02-10 00:00:00",
9
            "size" : "20",
10
            "vm" : "1001",
11
            "owner" : "1"
12
        }
13
    }, 
14
    {
15
        "model": "db.Disk",
16
        "pk": 2,
17
        "fields": {
18
            "name": "My_Videos",
19
            "created": "2011-02-10 00:00:00",
20
            "updated": "2011-02-10 00:00:00",
21
            "size" : "300",
22
            "vm" : "1001",
23
            "owner" : "1"
24
        }
25
    } 
26
] 
b/snf-app/synnefo/db/fixtures/images.json
4 4
        "pk": 1,
5 5
        "fields": {
6 6
            "name": "Debian Base",
7
            "owner": 1,
7
            "userid": "test",
8 8
            "created": "2011-02-06 00:00:00",
9 9
            "updated": "2011-06-29 13:14:00",
10 10
            "state": "ACTIVE",
......
63 63
        "pk": 2,
64 64
        "fields": {
65 65
            "name": "Debian Desktop",
66
            "owner": 1,
66
            "userid": "test",
67 67
            "created": "2011-06-29 19:33:00",
68 68
            "updated": "2011-02-06 19:33:00",
69 69
            "state": "ACTIVE",
......
122 122
        "pk": 3,
123 123
        "fields": {
124 124
            "name": "Ubuntu",
125
            "owner": 1,
125
            "userid": "test",
126 126
            "created": "2011-06-30 16:45:00",
127 127
            "updated": "2011-06-30 16:45:00",
128 128
            "state": "ACTIVE",
......
181 181
        "pk": 4,
182 182
        "fields": {
183 183
            "name": "Kubuntu",
184
            "owner": 1,
184
            "userid": "test",
185 185
            "created": "2011-06-30 16:45:00",
186 186
            "updated": "2011-06-30 16:45:00",
187 187
            "state": "ACTIVE",
......
240 240
        "pk": 5,
241 241
        "fields": {
242 242
            "name": "Fedora Desktop",
243
            "owner": 1,
243
            "userid": "test",
244 244
            "created": "2011-06-30 18:54:00",
245 245
            "updated": "2011-06-30 18:54:00",
246 246
            "state": "ACTIVE",
......
299 299
        "pk": 6,
300 300
        "fields": {
301 301
            "name": "CentOS",
302
            "owner": 1,
302
            "userid": "test",
303 303
            "created": "2011-06-09 00:00:00",
304 304
            "updated": "2011-06-09 00:00:00",
305 305
            "state": "ACTIVE",
......
358 358
        "pk": 7,
359 359
        "fields": {
360 360
            "name": "Windows",
361
            "owner": 1,
361
            "userid": "test",
362 362
            "created": "2011-06-09 00:00:00",
363 363
            "updated": "2011-06-09 00:00:00",
364 364
            "state": "ACTIVE",
/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
            "auth_token": "5e41595e9e884543fa048e07c1094d74",
10
            "auth_token_created": "2011-05-10",
11
            "auth_token_expires": "2015-05-10",
12
            "type": "ADMIN",
13
            "created": "2011-05-10"
14
   	    }
15
    },
16
    {
17
        "model": "db.SynnefoUser",
18
        "pk": 2,
19
        "fields": {
20
            "name": "helpdesk",
21
            "realname" :"HelpDesk user",
22
            "uniq" :"helpdesk@synnefo.grnet.gr",
23
            "auth_token": "cd7c3c159507515ac495c54bee3ff8d1",
24
            "auth_token_created": "2011-05-10",
25
            "auth_token_expires": "2015-05-10",
26
            "type": "HELPDESK",
27
            "created": "2011-05-10"
28
   	    }
29
    }
30
] 
b/snf-app/synnefo/db/fixtures/vms.json
13 13
        "model": "db.VirtualMachine",
14 14
        "pk": 1001,
15 15
        "fields": {
16
            "owner": 1,
16
            "userid": "test",
17 17
            "name": "snf-1001",
18 18
            "created": "2011-02-06 00:00:00",
19 19
            "updated": "2011-02-06 00:00:00",
......
28 28
        "model": "db.VirtualMachine",
29 29
        "pk": 1002,
30 30
        "fields": {
31
            "owner": 1,
31
            "userid": "test",
32 32
            "name": "snf-1002",
33 33
            "created": "2011-02-10 00:00:00",
34 34
            "updated": "2011-02-10 00:00:00",
......
43 43
        "model": "db.VirtualMachine",
44 44
        "pk": 1003,
45 45
        "fields": {
46
            "owner": 1,
46
            "userid": "test",
47 47
            "name": "snf-1003",
48 48
            "created": "2011-02-10 00:00:00",
49 49
            "updated": "2011-02-10 00:00:00",
......
57 57
    {
58 58
        "model": "db.VirtualMachine",
59 59
        "pk": 1004,
60
        "owner": 1,
60
        "userid": "test",
61 61
        "fields": {
62
            "owner": 1,
62
            "userid": "test",
63 63
            "name": "snf-1004",
64 64
            "created": "2011-02-10 00:00:00",
65 65
            "updated": "2011-02-10 00:00:00",
......
87 87
            "created": "2011-02-10 00:00:00",
88 88
            "updated": "2011-02-10 00:00:00",
89 89
            "state": "ACTIVE",
90
            "owner": 1,
90
            "userid": "test",
91 91
            "sourcevm": 1001
92 92
        }
93 93
    }
b/snf-app/synnefo/db/migrations/0026_auto__del_legacy_fields.py
23 23
        # Deleting model 'Limit'
24 24
        db.delete_table('db_limit')
25 25

  
26
        # Deleting model 'Debit'
27
        db.delete_table('db_debit')
28

  
26 29
        # Deleting field 'SynnefoUser.credit'
27 30
        db.delete_column('db_synnefouser', 'credit')
28 31

  
......
81 84
        ))
82 85
        db.send_create_signal('db', ['Limit'])
83 86

  
87
        # Adding model 'Debit'
88
        db.create_table('db_debit', (
89
            ('description', self.gf('django.db.models.fields.TextField')()),
90
            ('when', self.gf('django.db.models.fields.DateTimeField')()),
91
            ('vm', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['db.VirtualMachine'])),
92
            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['db.SynnefoUser'])),
93
            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
94
        ))
95
        db.send_create_signal('db', ['Debit'])
96

  
84 97
        # Adding field 'SynnefoUser.credit'
85 98
        db.add_column('db_synnefouser', 'credit', self.gf('django.db.models.fields.IntegerField')(default=0), keep_default=False)
86 99

  
......
89 102

  
90 103

  
91 104
    models = {
92
        'db.debit': {
93
            'Meta': {'object_name': 'Debit'},
94
            'description': ('django.db.models.fields.TextField', [], {}),
95
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
96
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']"}),
97
            'vm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.VirtualMachine']"}),
98
            'when': ('django.db.models.fields.DateTimeField', [], {})
99
        },
100 105
        'db.disk': {
101 106
            'Meta': {'object_name': 'Disk'},
102 107
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
......
193 198
            'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}),
194 199
            'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
195 200
            'buildpercentage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
196
            'charged': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 1, 23, 13, 34, 33, 344061)'}),
201
            'charged': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 1, 23, 14, 29, 44, 160884)'}),
197 202
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
198 203
            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
199 204
            'flavor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Flavor']"}),
b/snf-app/synnefo/db/migrations/0027_auto__del_disk__add_field_userid.py
1
# encoding: utf-8
2
import datetime
3
from south.db import db
4
from south.v2 import SchemaMigration
5
from django.db import models
6

  
7
class Migration(SchemaMigration):
8

  
9
    def forwards(self, orm):
10
        
11
        # Deleting model 'Disk'
12
        db.delete_table('db_disk')
13

  
14
        # Adding field 'VirtualMachine.userid'
15
        db.add_column('db_virtualmachine', 'userid', self.gf('django.db.models.fields.CharField')(default='', max_length=100), keep_default=False)
16

  
17
        # Adding field 'Image.userid'
18
        db.add_column('db_image', 'userid', self.gf('django.db.models.fields.CharField')(default='', max_length=100), keep_default=False)
19

  
20
        # Adding field 'Network.userid'
21
        db.add_column('db_network', 'userid', self.gf('django.db.models.fields.CharField')(default='', max_length=100), keep_default=False)
22

  
23

  
24
    def backwards(self, orm):
25
        
26
        # Adding model 'Disk'
27
        db.create_table('db_disk', (
28
            ('vm', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['db.VirtualMachine'], null=True, blank=True)),
29
            ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
30
            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
31
            ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
32
            ('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['db.SynnefoUser'], null=True, blank=True)),
33
            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
34
            ('size', self.gf('django.db.models.fields.PositiveIntegerField')()),
35
        ))
36
        db.send_create_signal('db', ['Disk'])
37

  
38
        # Deleting field 'VirtualMachine.userid'
39
        db.delete_column('db_virtualmachine', 'userid')
40

  
41
        # Deleting field 'Image.userid'
42
        db.delete_column('db_image', 'userid')
43

  
44
        # Deleting field 'Network.userid'
45
        db.delete_column('db_network', 'userid')
46

  
47

  
48
    models = {
49
        'db.flavor': {
50
            'Meta': {'unique_together': "(('cpu', 'ram', 'disk'),)", 'object_name': 'Flavor'},
51
            'cpu': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
52
            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
53
            'disk': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
54
            'disk_template': ('django.db.models.fields.CharField', [], {'default': "'drbd'", 'max_length': '32'}),
55
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
56
            'ram': ('django.db.models.fields.IntegerField', [], {'default': '0'})
57
        },
58
        'db.image': {
59
            'Meta': {'object_name': 'Image'},
60
            'backend_id': ('django.db.models.fields.CharField', [], {'default': "'debian_base'", 'max_length': '50'}),
61
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
62
            'format': ('django.db.models.fields.CharField', [], {'default': "'dump'", 'max_length': '30'}),
63
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
64
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
65
            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']", 'null': 'True', 'blank': 'True'}),
66
            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
67
            'sourcevm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.VirtualMachine']", 'null': 'True'}),
68
            'state': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff