Commit a170119d by Bach Dániel

Merge branch 'feature-template-fixes' into 'master'

Template ACL fixes

👌 tests

fixes #195
fixes #194
fixes #208
parents f0353c75 ce33b51c
...@@ -260,6 +260,7 @@ THIRD_PARTY_APPS = ( ...@@ -260,6 +260,7 @@ THIRD_PARTY_APPS = (
'taggit', 'taggit',
'statici18n', 'statici18n',
'django_sshkey', 'django_sshkey',
'autocomplete_light',
) )
# Apps specific for this project go here. # Apps specific for this project go here.
......
import autocomplete_light
from django.utils.translation import ugettext as _
from .views import AclUpdateView
class AclUserAutocomplete(autocomplete_light.AutocompleteGenericBase):
search_fields = (
('^first_name', 'last_name', 'username', '^email', 'profile__org_id'),
('^name', 'groupprofile__org_id'),
)
autocomplete_js_attributes = {'placeholder': _("Name of group or user")}
def choices_for_request(self):
user = self.request.user
self.choices = (AclUpdateView.get_allowed_users(user),
AclUpdateView.get_allowed_groups(user))
return super(AclUserAutocomplete, self).choices_for_request()
autocomplete_light.register(AclUserAutocomplete)
...@@ -27,6 +27,7 @@ from django.contrib.auth.models import User, Group ...@@ -27,6 +27,7 @@ from django.contrib.auth.models import User, Group
from django.core.validators import URLValidator from django.core.validators import URLValidator
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
import autocomplete_light
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import ( from crispy_forms.layout import (
Layout, Div, BaseInput, Field, HTML, Submit, Fieldset, TEMPLATE_PACK, Layout, Div, BaseInput, Field, HTML, Submit, Fieldset, TEMPLATE_PACK,
...@@ -44,7 +45,6 @@ from django.core.urlresolvers import reverse_lazy ...@@ -44,7 +45,6 @@ from django.core.urlresolvers import reverse_lazy
from django_sshkey.models import UserKey from django_sshkey.models import UserKey
from firewall.models import Vlan, Host from firewall.models import Vlan, Host
from storage.models import Disk
from vm.models import ( from vm.models import (
InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance
) )
...@@ -78,7 +78,7 @@ class VmCustomizeForm(forms.Form): ...@@ -78,7 +78,7 @@ class VmCustomizeForm(forms.Form):
amount = forms.IntegerField(min_value=0, initial=1) amount = forms.IntegerField(min_value=0, initial=1)
disks = forms.ModelMultipleChoiceField( disks = forms.ModelMultipleChoiceField(
queryset=None, required=True) queryset=None, required=False)
networks = forms.ModelMultipleChoiceField( networks = forms.ModelMultipleChoiceField(
queryset=None, required=False) queryset=None, required=False)
...@@ -91,8 +91,7 @@ class VmCustomizeForm(forms.Form): ...@@ -91,8 +91,7 @@ class VmCustomizeForm(forms.Form):
super(VmCustomizeForm, self).__init__(*args, **kwargs) super(VmCustomizeForm, self).__init__(*args, **kwargs)
# set displayed disk and network list # set displayed disk and network list
self.fields['disks'].queryset = Disk.get_objects_with_level( self.fields['disks'].queryset = self.template.disks.all()
'user', self.user).exclude(type="qcow2-snap")
self.fields['networks'].queryset = Vlan.get_objects_with_level( self.fields['networks'].queryset = Vlan.get_objects_with_level(
'user', self.user) 'user', self.user)
...@@ -596,6 +595,10 @@ class TemplateForm(forms.ModelForm): ...@@ -596,6 +595,10 @@ class TemplateForm(forms.ModelForm):
n = self.instance.interface_set.values_list("vlan", flat=True) n = self.instance.interface_set.values_list("vlan", flat=True)
self.initial['networks'] = n self.initial['networks'] = n
if self.instance.pk and not self.instance.has_level(self.user,
'owner'):
self.allowed_fields = ()
else:
self.allowed_fields = ( self.allowed_fields = (
'name', 'access_method', 'description', 'system', 'tags') 'name', 'access_method', 'description', 'system', 'tags')
if self.user.has_perm('vm.change_template_resources'): if self.user.has_perm('vm.change_template_resources'):
...@@ -675,6 +678,11 @@ class TemplateForm(forms.ModelForm): ...@@ -675,6 +678,11 @@ class TemplateForm(forms.ModelForm):
@property @property
def helper(self): def helper(self):
submit_kwargs = {}
if self.instance.pk and not self.instance.has_level(self.user,
'owner'):
submit_kwargs['disabled'] = None
helper = FormHelper() helper = FormHelper()
helper.layout = Layout( helper.layout = Layout(
Field("name"), Field("name"),
...@@ -739,7 +747,7 @@ class TemplateForm(forms.ModelForm): ...@@ -739,7 +747,7 @@ class TemplateForm(forms.ModelForm):
Field("tags"), Field("tags"),
), ),
) )
helper.add_input(Submit('submit', 'Save changes')) helper.add_input(Submit('submit', 'Save changes', **submit_kwargs))
return helper return helper
class Meta: class Meta:
...@@ -1178,6 +1186,11 @@ class UserCreationForm(OrgUserCreationForm): ...@@ -1178,6 +1186,11 @@ class UserCreationForm(OrgUserCreationForm):
return user return user
class AclUserAddForm(forms.Form):
name = forms.CharField(widget=autocomplete_light.TextWidget(
'AclUserAutocomplete', attrs={'class': 'form-control'}))
class UserKeyForm(forms.ModelForm): class UserKeyForm(forms.ModelForm):
name = forms.CharField(required=True, label=_('Name')) name = forms.CharField(required=True, label=_('Name'))
key = forms.CharField( key = forms.CharField(
......
...@@ -142,6 +142,11 @@ class Profile(Model): ...@@ -142,6 +142,11 @@ class Profile(Model):
def __unicode__(self): def __unicode__(self):
return self.get_display_name() return self.get_display_name()
class Meta:
permissions = (
('use_autocomplete', _('Can use autocomplete.')),
)
class FutureMember(Model): class FutureMember(Model):
org_id = CharField(max_length=64, help_text=_( org_id = CharField(max_length=64, help_text=_(
......
...@@ -70,6 +70,7 @@ ...@@ -70,6 +70,7 @@
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script> <script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script> <script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script>
{% include 'autocomplete_light/static.html' %}
{% block extra_script %} {% block extra_script %}
{% endblock %} {% endblock %}
......
...@@ -10,10 +10,12 @@ ...@@ -10,10 +10,12 @@
<div class="label label-danger"{% if user.is_superuser %} title="{{ d.get_latest_activity_result }}"{% endif %}>{% trans "failed" %}</div> <div class="label label-danger"{% if user.is_superuser %} title="{{ d.get_latest_activity_result }}"{% endif %}>{% trans "failed" %}</div>
{% endif %} {% endif %}
{% else %}<span class="disk-list-disk-percentage" data-disk-pk="{{ d.pk }}">{{ d.get_download_percentage }}</span>%{% endif %} {% else %}<span class="disk-list-disk-percentage" data-disk-pk="{{ d.pk }}">{{ d.get_download_percentage }}</span>%{% endif %}
{% if is_owner != False %}
<a href="{% url "dashboard.views.disk-remove" pk=d.pk %}?next={{ request.path }}" <a href="{% url "dashboard.views.disk-remove" pk=d.pk %}?next={{ request.path }}"
data-disk-pk="{{ d.pk }}" class="btn btn-xs btn-danger pull-right disk-remove" data-disk-pk="{{ d.pk }}" class="btn btn-xs btn-danger pull-right disk-remove"
{% if not long_remove %}title="{% trans "Remove" %}"{% endif %} {% if not long_remove %}title="{% trans "Remove" %}"{% endif %}
> >
<i class="fa fa-times"></i>{% if long_remove %} {% trans "Remove" %}{% endif %} <i class="fa fa-times"></i>{% if long_remove %} {% trans "Remove" %}{% endif %}
</a> </a>
{% endif %}
<div style="clear: both;"></div> <div style="clear: both;"></div>
{% load i18n %}
<form action="{{ acl.url }}" method="post">{% csrf_token %}
<table class="table table-striped table-with-form-fields" id="{{table_id}}">
<thead>
<tr>
<th></th>
<th>{% trans "Who" %}</th>
<th>{% trans "What" %}</th>
<th><i class="fa fa-times"></i></th>
</tr>
</thead>
<tbody>
{% for i in acl.users %}
<tr>
<td>
<i class="fa fa-user"></i>
</td>
<td>
<a href="{% url "dashboard.views.profile" username=i.user.username %}"
title="{{ i.user.username }}">
{% include "dashboard/_display-name.html" with user=i.user show_org=True %}
</a>
</td>
<td>
<select class="form-control" name="perm-u-{{i.user.id}}"{% if i.level not in acl.allowed_levels %} disabled{% endif %}>
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%}
{% if id not in acl.allowed_levels %} disabled{% endif %}
value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td>
<input type="checkbox" name="remove-u-{{i.user.id}}" title="{% trans "Remove" %}"/>
</td>
</tr>
{% endfor %}
{% for i in acl.groups %}
<tr>
<td><i class="fa fa-group"></i></td>
<td>
<a href="{% url "dashboard.views.group-detail" pk=i.group.pk %}">
{{i.group}}
</a>
</td>
<td>
<select class="form-control" name="perm-g-{{i.group.id}}{% if i.level not in acl.allowed_levels %} disabled{% endif %}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%}
{% if id not in acl.allowed_levels %} disabled{% endif %}
value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td>
<input type="checkbox" name="remove-g-{{i.group.id}}" title="{% trans "Remove" %}"/>
</td>
</tr>
{% endfor %}
<tr><td><i class="fa fa-plus"></i></td>
<td>{{aclform.name }}</td>
<td><select class="form-control" name="level">
{% for id, name in acl.levels %}
{% if id in acl.allowed_levels %}
<option value="{{id}}">{{name}}</option>
{% endif %}
{% endfor %}
</select></td><td></td>
</tr>
</tbody>
</table>
<div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div>
</form>
...@@ -104,76 +104,7 @@ ...@@ -104,76 +104,7 @@
<hr /> <hr />
<h3 id="group-detail-perm-header">{% trans "Access permissions"|capfirst %}</h3> <h3 id="group-detail-perm-header">{% trans "Access permissions"|capfirst %}</h3>
<form action="{{acl.url}}" method="post">{% csrf_token %} {% include "dashboard/_manage_access.html" with table_id="group-detail-perm-table" %}
<table class="table table-striped table-with-form-fields table-bordered" id="group-detail-perm-table">
<thead>
<tr>
<th></th><th>{% trans "Who" %}</th><th>{% trans "What" %}</th><th>{% trans "Remove" %}</th>
</tr>
</thead>
<tbody>
{% for i in acl.users %}
<tr>
<td>
<i class="fa fa-user"></i>
</td>
<td>
<a href="{% url "dashboard.views.profile" username=i.user.username %}" title="{{ i.user.username }}"
>{% include "dashboard/_display-name.html" with user=i.user show_org=True %}</a>
</td>
<td>
<select class="form-control" name="perm-u-{{i.user.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td class="user-remove"><a data-group_pk="{{ group.pk }}" data-member_pk="{{i.user.pk }}" href="{% url "dashboard.views.remove-acluser" member_pk=i.user.pk group_pk=group.pk %}" class="real-link delete-from-group btn btn-link btn-xs"><i class="fa fa-times"><span class="sr-only">{% trans "remove" %}</span></i></a></td>
</tr>
{% endfor %}
{% for i in acl.groups %}
<tr>
<td>
<i class="fa fa-group"></i>
</td>
<td>
<a href="{% url "dashboard.views.group-detail" pk=i.group.pk %}">{{ i.group }}</a>
</td>
<td>
<select class="form-control" name="perm-g-{{ i.group.pk }}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td class="user-remove"><a data-group_pk="{{ i.pk }}"data-member_pk="{{i.group.pk }}" href="{% url "dashboard.views.remove-aclgroup" member_pk=i.group.pk group_pk=group.pk %}" class="real-link delete-from-group btn btn-link btn-xs"><i class="fa fa-times"><span class="sr-only">{% trans "remove" %}</span></i></a>
</td>
</tr>
{% endfor %}
<tr>
<td><i class="fa fa-plus"></i></td>
<td>
<input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}">
</td>
<td>
<select class="form-control" name="perm-new">
{% for id, name in acl.levels %}
<option value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td></td>
</tr>
</tbody>
</table>
<div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div>
</form>
{% if user.is_superuser %} {% if user.is_superuser %}
<hr /> <hr />
......
...@@ -29,75 +29,7 @@ ...@@ -29,75 +29,7 @@
<h4 class="no-margin"><i class="fa fa-group"></i> {% trans "Manage access" %}</h4> <h4 class="no-margin"><i class="fa fa-group"></i> {% trans "Manage access" %}</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form action="{% url "dashboard.views.template-acl" pk=object.pk %}" method="post">{% csrf_token %} {% include "dashboard/_manage_access.html" with table_id="template-access-table" %}
<table class="table table-striped table-with-form-fields" id="template-access-table">
<thead>
<tr>
<th></th>
<th>{% trans "Who" %}</th>
<th>{% trans "What" %}</th>
<th><i class="fa fa-times"></i></th>
</tr>
</thead>
<tbody>
{% for i in acl.users %}
<tr>
<td>
<i class="fa fa-user"></i>
</td>
<td>
<a href="{% url "dashboard.views.profile" username=i.user.username %}"
title="{{ i.user.username }}">
{% include "dashboard/_display-name.html" with user=i.user show_org=True %}
</a>
</td>
<td>
<select class="form-control" name="perm-u-{{i.user.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td>
<input type="checkbox" name="remove-u-{{i.user.id}}" title="{% trans "Remove" %}"/>
</td>
</tr>
{% endfor %}
{% for i in acl.groups %}
<tr>
<td><i class="fa fa-group"></i></td>
<td>
<a href="{% url "dashboard.views.group-detail" pk=i.group.pk %}">
{{i.group}}
</a>
</td>
<td>
<select class="form-control" name="perm-g-{{i.group.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td>
<input type="checkbox" name="remove-g-{{i.group.id}}" title="{% trans "Remove" %}"/>
</td>
</tr>
{% endfor %}
<tr><td><i class="fa fa-plus"></i></td>
<td><input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}"></td>
<td><select class="form-control" name="perm-new">
{% for id, name in acl.levels %}
<option value="{{id}}">{{name}}</option>
{% endfor %}
</select></td><td></td>
</tr>
</tbody>
</table>
<div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div>
</form>
</div> </div>
</div> </div>
......
...@@ -14,64 +14,4 @@ ...@@ -14,64 +14,4 @@
{% endif %} {% endif %}
</p> </p>
<h3>{% trans "Permissions"|capfirst %}</h3> <h3>{% trans "Permissions"|capfirst %}</h3>
<form action="{{acl.url}}" method="post">{% csrf_token %} {% include "dashboard/_manage_access.html" with table_id="vm-access-table" %}
<table class="table table-striped table-with-form-fields" id="vm-access-table">
<thead><tr>
<th></th>
<th>{% trans "Who" %}</th>
<th>{% trans "What" %}</th>
<th>{% trans "Remove" %}</th>
</tr></thead>
<tbody>
{% for i in acl.users %}
<tr>
<td><i class="fa fa-user"></i></td>
<td>
<a href="{% url "dashboard.views.profile" username=i.user.username %}" title="{{ i.user.username }}"
>{% include "dashboard/_display-name.html" with user=i.user show_org=True %}</a>
</td>
<td>
<select class="form-control" name="perm-u-{{i.user.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td>
<input type="checkbox" name="remove-u-{{i.user.id}}"/>
</td>
</tr>
{% endfor %}
{% for i in acl.groups %}
<tr>
<td><i class="fa fa-group"></i></td>
<td>
<a href="{% url "dashboard.views.group-detail" pk=i.group.pk %}"
>{{ i.group.name }}</a>
</td>
<td>
<select class="form-control" name="perm-g-{{i.group.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select></td>
<td>
<input type="checkbox" name="remove-g-{{i.group.id}}"/>
</td>
</tr>
{% endfor %}
<tr><td><i class="fa fa-plus"></i></td>
<td><input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}"></td>
<td><select class="form-control" name="perm-new">
{% for id, name in acl.levels %}
<option value="{{id}}">{{name}}</option>
{% endfor %}
</select></td><td></td>
</tr>
</tbody>
</table>
<div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div>
</form>
...@@ -30,6 +30,7 @@ from django.utils import baseconv ...@@ -30,6 +30,7 @@ from django.utils import baseconv
from ..models import Profile from ..models import Profile
from ..views import InstanceActivityDetail, InstanceActivity from ..views import InstanceActivityDetail, InstanceActivity
from ..views import vm_ops, Instance, UnsubscribeFormView from ..views import vm_ops, Instance, UnsubscribeFormView
from ..views import AclUpdateView
from .. import views from .. import views
...@@ -429,6 +430,79 @@ class RenewViewTest(unittest.TestCase): ...@@ -429,6 +430,79 @@ class RenewViewTest(unittest.TestCase):
view.as_view()(request, pk=1234)['location']) view.as_view()(request, pk=1234)['location'])
class AclUpdateViewTest(unittest.TestCase):
def test_has_next_level(self):
data = {None: 'user', 'user': 'operator', 'operator': 'owner',
'owner': 'owner'}
for k, v in data.items():
inst = MagicMock(spec=Instance)
inst.has_level.return_value = True
inst.ACL_LEVELS = Instance.ACL_LEVELS
self.assertTrue(AclUpdateView.has_next_level('dummy', inst, k))
inst.has_level.assert_called_with('dummy', v)
def test_set_level_mod_owner(self):
with patch('dashboard.views.messages') as msg:
request = FakeRequestFactory(POST={})
inst = MagicMock(spec=Instance)
inst.owner = request.user
v = AclUpdateView()
v.instance = inst
v.request = request
v.get_level = MagicMock(return_value='owner')
v.check_auth = MagicMock(side_effect=Exception(''))
v.set_level(request.user, 'user')
v.get_level.assert_called_with(request.user)
assert not v.check_auth.called
assert msg.warning.called
def test_set_level_permitted(self):
data = (('user', 'owner', ('user', 'operator', 'owner'), False),
(None, None, ('user', ), True),
('user', None, ('user', ), True),
(None, 'user', ('user', ), True),
('operator', 'owner', ('user', 'operator'), True),
(None, 'user', ('user', 'operator'), False))
for old_level, new_level, allowed_levels, fail in data:
with patch('dashboard.views.messages') as msg:
def has_level(user, level):
return level in allowed_levels
request = FakeRequestFactory(POST={})
inst = MagicMock(spec=Instance)
inst.has_level.side_effect = has_level
inst.ACL_LEVELS = Instance.ACL_LEVELS
v = AclUpdateView()
v.instance = inst
v.request = request
v.is_owner = True
v.get_level = MagicMock(return_value=old_level)
v.set_level(request.user, new_level)
v.get_level.assert_called_with(request.user)
assert (new_level == old_level) ^ inst.has_level.called
assert fail ^ inst.set_level.called
assert fail ^ msg.success.called
def test_readd(self):
request = FakeRequestFactory(POST={'name': 'user0', 'level': 'user'})
with patch('dashboard.views.messages') as msg:
with patch.object(AclUpdateView, 'get_object') as go:
view = AclUpdateView.as_view()
inst = MagicMock(spec=Instance)
go.return_value = inst
view(request)
assert msg.warning.called
def FakeRequestFactory(user=None, **kwargs): def FakeRequestFactory(user=None, **kwargs):
''' FakeRequestFactory, FakeMessages and FakeRequestContext are good for ''' FakeRequestFactory, FakeMessages and FakeRequestContext are good for
mocking out django views; they are MUCH faster than the Django test client. mocking out django views; they are MUCH faster than the Django test client.
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
from __future__ import absolute_import from __future__ import absolute_import
from django.conf.urls import patterns, url, include from django.conf.urls import patterns, url, include
import autocomplete_light
from vm.models import Instance from vm.models import Instance
from .views import ( from .views import (
AclUpdateView, FavouriteView, GroupAclUpdateView, GroupDelete, AclUpdateView, FavouriteView, GroupAclUpdateView, GroupDelete,
...@@ -30,7 +31,7 @@ from .views import ( ...@@ -30,7 +31,7 @@ from .views import (
TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView, TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView,
VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete,
DiskRemoveView, get_disk_download_status, InterfaceDeleteView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView,
GroupRemoveAclUserView, GroupRemoveAclGroupView, GroupRemoveUserView, GroupRemoveUserView,
GroupRemoveFutureUserView, GroupRemoveFutureUserView,
GroupCreate, GroupProfileUpdate, GroupCreate, GroupProfileUpdate,
TemplateChoose, TemplateChoose,
...@@ -43,7 +44,10 @@ from .views import ( ...@@ -43,7 +44,10 @@ from .views import (
LeaseAclUpdateView, LeaseAclUpdateView,
) )
autocomplete_light.autodiscover()
urlpatterns = patterns( urlpatterns = patterns(
'', '',
url(r'^$', IndexView.as_view(), name="dashboard.index"), url(r'^$', IndexView.as_view(), name="dashboard.index"),
url(r'^lease/(?P<pk>\d+)/$', LeaseDetail.as_view(), url(r'^lease/(?P<pk>\d+)/$', LeaseDetail.as_view(),
...@@ -151,12 +155,6 @@ urlpatterns = patterns( ...@@ -151,12 +155,6 @@ urlpatterns = patterns(
name="dashboard.views.profile"), name="dashboard.views.profile"),
url(r'^profile/(?P<username>[^/]+)/use_gravatar/$', toggle_use_gravatar), url(r'^profile/(?P<username>[^/]+)/use_gravatar/$', toggle_use_gravatar),
url(r'^group/(?P<group_pk>\d+)/remove/acl/user/(?P<member_pk>\d+)/$',
GroupRemoveAclUserView.as_view(),
name="dashboard.views.remove-acluser"),
url(r'^group/(?P<group_pk>\d+)/remove/acl/group/(?P<member_pk>\d+)/$',
GroupRemoveAclGroupView.as_view(),
name="dashboard.views.remove-aclgroup"),
url(r'^group/(?P<group_pk>\d+)/remove/user/(?P<member_pk>\d+)/$', url(r'^group/(?P<group_pk>\d+)/remove/user/(?P<member_pk>\d+)/$',
GroupRemoveUserView.as_view(), GroupRemoveUserView.as_view(),
name="dashboard.views.remove-user"), name="dashboard.views.remove-user"),
...@@ -181,4 +179,5 @@ urlpatterns = patterns( ...@@ -181,4 +179,5 @@ urlpatterns = patterns(
url(r'^sshkey/create/$', url(r'^sshkey/create/$',
UserKeyCreate.as_view(), UserKeyCreate.as_view(),
name="dashboard.views.userkey-create"), name="dashboard.views.userkey-create"),
url(r'^autocomplete/', include('autocomplete_light.urls')),
) )
...@@ -23,61 +23,7 @@ ...@@ -23,61 +23,7 @@
<div class="page-header"> <div class="page-header">
<h3>{% trans "Manage access" %}</h3> <h3>{% trans "Manage access" %}</h3>
</div> </div>
<form action="{% url "network.vlan-acl" vid=vlan_vid %}" method="post">{% csrf_token %} {% include "dashboard/_manage_access.html" with table_id="vlan-access-table" %}
<table class="table table-striped table-with-form-fields" id="vlan-access-table">
<thead>
<tr>
<th></th>
<th>{% trans "Who" %}</th>
<th>{% trans "What" %}</th>
<th><i class="fa fa-times"></i></th>
</tr></thead>
<tbody>
{% for i in acl.users %}
<tr>
<td><i class="fa fa-user"></i></td><td>{{i.user}}</td>
<td>
<select class="form-control" name="perm-u-{{i.user.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td>
<input type="checkbox" name="remove-u-{{i.user.id}}" title="{% trans "Remove" %}"/>
</td>
</tr>
{% endfor %}
{% for i in acl.groups %}
<tr>
<td><i class="fa fa-group"></i></td><td>{{i.group}}</td>
<td>
<select class="form-control" name="perm-g-{{i.group.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td>
<input type="checkbox" name="remove-g-{{i.group.id}}" title="{% trans "Remove" %}"/>
</td>
</tr>
{% endfor %}
<tr><td><i class="fa fa-plus"></i></td>
<td><input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}"></td>
<td><select class="form-control" name="perm-new">
{% for id, name in acl.levels %}
<option value="{{id}}">{{name}}</option>
{% endfor %}
</select></td><td></td>
</tr>
</tbody>
</table>
<div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div>
</form>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
...@@ -63,9 +63,9 @@ class VlanAclTest(LoginMixin, TestCase): ...@@ -63,9 +63,9 @@ class VlanAclTest(LoginMixin, TestCase):
vlan = Vlan.objects.get(vid=1) vlan = Vlan.objects.get(vid=1)
self.assertEqual([], vlan.get_users_with_level()) self.assertEqual([], vlan.get_users_with_level())
resp = c.post("/network/vlans/1/acl/", { resp = c.post("/network/vlans/2/acl/", {
'perm-new-name': "user1", 'name': "user1",
'perm-new': "user", 'level': "user",
}) })
vlan = Vlan.objects.get(vid=1) vlan = Vlan.objects.get(vid=1)
...@@ -80,10 +80,10 @@ class VlanAclTest(LoginMixin, TestCase): ...@@ -80,10 +80,10 @@ class VlanAclTest(LoginMixin, TestCase):
vlan.set_level(self.u1, "user") vlan.set_level(self.u1, "user")
self.assertTrue((self.u1, "user") in vlan.get_users_with_level()) self.assertTrue((self.u1, "user") in vlan.get_users_with_level())
resp = c.post("/network/vlans/1/acl/", { resp = c.post("/network/vlans/2/acl/", {
'perm-u-%d' % self.u1.pk: "operator", 'perm-u-%d' % self.u1.pk: "operator",
'perm-new': "", 'level': "",
'perm-new-name': "", 'name': "",
}) })
self.assertTrue((self.u1, "operator") in vlan.get_users_with_level()) self.assertTrue((self.u1, "operator") in vlan.get_users_with_level())
...@@ -97,10 +97,10 @@ class VlanAclTest(LoginMixin, TestCase): ...@@ -97,10 +97,10 @@ class VlanAclTest(LoginMixin, TestCase):
vlan.set_level(self.u1, "user") vlan.set_level(self.u1, "user")
self.assertTrue((self.u1, "user") in vlan.get_users_with_level()) self.assertTrue((self.u1, "user") in vlan.get_users_with_level())
resp = c.post("/network/vlans/1/acl/", { resp = c.post("/network/vlans/2/acl/", {
'remove-u-%d' % self.u1.pk: "", 'remove-u-%d' % self.u1.pk: "",
'perm-new': "", 'level': "",
'perm-new-name': "", 'name': "",
}) })
self.assertTrue((self.u1, "user") not in vlan.get_users_with_level()) self.assertTrue((self.u1, "user") not in vlan.get_users_with_level())
......
...@@ -84,7 +84,7 @@ urlpatterns = patterns( ...@@ -84,7 +84,7 @@ urlpatterns = patterns(
url('^vlans/$', VlanList.as_view(), name='network.vlan_list'), url('^vlans/$', VlanList.as_view(), name='network.vlan_list'),
url('^vlans/create$', VlanCreate.as_view(), name='network.vlan_create'), url('^vlans/create$', VlanCreate.as_view(), name='network.vlan_create'),
url('^vlans/(?P<vid>\d+)/$', VlanDetail.as_view(), name='network.vlan'), url('^vlans/(?P<vid>\d+)/$', VlanDetail.as_view(), name='network.vlan'),
url('^vlans/(?P<vid>\d+)/acl/$', VlanAclUpdateView.as_view(), url('^vlans/(?P<pk>\d+)/acl/$', VlanAclUpdateView.as_view(),
name='network.vlan-acl'), name='network.vlan-acl'),
url('^vlans/delete/(?P<vid>\d+)/$', VlanDelete.as_view(), url('^vlans/delete/(?P<vid>\d+)/$', VlanDelete.as_view(),
name="network.vlan_delete"), name="network.vlan_delete"),
......
...@@ -42,6 +42,7 @@ from operator import itemgetter ...@@ -42,6 +42,7 @@ from operator import itemgetter
from itertools import chain from itertools import chain
import json import json
from dashboard.views import AclUpdateView from dashboard.views import AclUpdateView
from dashboard.forms import AclUserAddForm
class SuccessMessageMixin(FormMixin): class SuccessMessageMixin(FormMixin):
...@@ -629,19 +630,8 @@ class VlanList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView): ...@@ -629,19 +630,8 @@ class VlanList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
table_pagination = False table_pagination = False
def get_vlan_acl_data(obj):
levels = obj.ACL_LEVELS
users = obj.get_users_with_level()
users = [{'user': u, 'level': l} for u, l in users]
groups = obj.get_groups_with_level()
groups = [{'group': g, 'level': l} for g, l in groups]
return {'users': users, 'groups': groups, 'levels': levels}
class VlanAclUpdateView(AclUpdateView): class VlanAclUpdateView(AclUpdateView):
model = Vlan model = Vlan
slug_field = "vid"
slug_url_kwarg = "vid"
class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin, class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin,
...@@ -662,7 +652,9 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -662,7 +652,9 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin,
context['host_list'] = SmallHostTable(q) context['host_list'] = SmallHostTable(q)
context['vlan_vid'] = self.kwargs.get('vid') context['vlan_vid'] = self.kwargs.get('vid')
context['acl'] = get_vlan_acl_data(self.get_object()) context['acl'] = AclUpdateView.get_acl_data(
self.object, self.request.user, 'network.vlan-acl')
context['aclform'] = AclUserAddForm()
return context return context
success_url = reverse_lazy('network.vlan_list') success_url = reverse_lazy('network.vlan_list')
......
...@@ -31,7 +31,6 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -31,7 +31,6 @@ from django.utils.translation import ugettext_lazy as _
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from sizefield.models import FileSizeField from sizefield.models import FileSizeField
from acl.models import AclBase
from .tasks import local_tasks, storage_tasks from .tasks import local_tasks, storage_tasks
from celery.exceptions import TimeoutError from celery.exceptions import TimeoutError
from common.models import WorkerNotFound from common.models import WorkerNotFound
...@@ -76,15 +75,10 @@ class DataStore(Model): ...@@ -76,15 +75,10 @@ class DataStore(Model):
destroyed__isnull=False) if disk.is_deletable] destroyed__isnull=False) if disk.is_deletable]
class Disk(AclBase, TimeStampedModel): class Disk(TimeStampedModel):
"""A virtual disk. """A virtual disk.
""" """
ACL_LEVELS = (
('user', _('user')), # see all details
('operator', _('operator')),
('owner', _('owner')), # superuser, can delete, delegate perms
)
TYPES = [('qcow2-norm', 'qcow2 normal'), ('qcow2-snap', 'qcow2 snapshot'), TYPES = [('qcow2-norm', 'qcow2 normal'), ('qcow2-snap', 'qcow2 snapshot'),
('iso', 'iso'), ('raw-ro', 'raw read-only'), ('raw-rw', 'raw')] ('iso', 'iso'), ('raw-ro', 'raw read-only'), ('raw-rw', 'raw')]
name = CharField(blank=True, max_length=100, verbose_name=_("name")) name = CharField(blank=True, max_length=100, verbose_name=_("name"))
...@@ -225,15 +219,14 @@ class Disk(AclBase, TimeStampedModel): ...@@ -225,15 +219,14 @@ class Disk(AclBase, TimeStampedModel):
return any(i.state != 'STOPPED' for i in self.instance_set.all()) return any(i.state != 'STOPPED' for i in self.instance_set.all())
def get_appliance(self): def get_appliance(self):
"""Return an Instance or InstanceTemplate object where the disk is used """Return the Instance or InstanceTemplate object where the disk
is used
""" """
instance = self.instance_set.all() from vm.models import Instance
template = self.template_set.all() try:
app = list(instance) + list(template) return self.instance_set.get()
if len(app) > 0: except Instance.DoesNotExist:
return app[0] return self.template_set.get()
else:
return None
def get_exclusive(self): def get_exclusive(self):
"""Get an instance of the disk for exclusive usage. """Get an instance of the disk for exclusive usage.
......
...@@ -405,13 +405,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -405,13 +405,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
""" """
disks = template.disks.all() if disks is None else disks disks = template.disks.all() if disks is None else disks
for disk in disks:
if not disk.has_level(owner, 'user'):
raise PermissionDenied()
elif (disk.type == 'qcow2-snap'
and not disk.has_level(owner, 'owner')):
raise PermissionDenied()
networks = (template.interface_set.all() if networks is None networks = (template.interface_set.all() if networks is None
else networks) else networks)
......
...@@ -4,6 +4,7 @@ billiard==3.3.0.17 ...@@ -4,6 +4,7 @@ billiard==3.3.0.17
bpython==0.12 bpython==0.12
celery==3.1.11 celery==3.1.11
Django==1.6.3 Django==1.6.3
django-autocomplete-light==1.4.14
django-braces==1.4.0 django-braces==1.4.0
django-celery==3.1.10 django-celery==3.1.10
django-crispy-forms==1.4.0 django-crispy-forms==1.4.0
......
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 sign in to comment