Commit e4141022 by Őry Máté

Merge remote-tracking branch 'origin/feature-new-saml-group'

parents a499689c 94e3de4b
......@@ -96,3 +96,8 @@ for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': 'DEBUG'}
CRISPY_FAIL_SILENTLY = not DEBUG
# propagate exceptions from signals
if DEBUG:
from django.dispatch import Signal
Signal.send_robust = Signal.send
......@@ -33,7 +33,7 @@ from crispy_forms.layout import (
from crispy_forms.utils import render_field
from django import forms
from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm
from django.forms.widgets import TextInput
from django.forms.widgets import TextInput, HiddenInput
from django.template import Context
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
......@@ -44,7 +44,7 @@ from storage.models import Disk, DataStore
from vm.models import (
InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance
)
from .models import Profile
from .models import Profile, GroupProfile
class VmCustomizeForm(forms.Form):
......@@ -315,51 +315,80 @@ class VmCustomizeForm(forms.Form):
class GroupCreateForm(forms.ModelForm):
description = forms.CharField(label=_("Description"), required=False,
widget=forms.Textarea(attrs={'rows': 3}))
def __init__(self, *args, **kwargs):
new_groups = kwargs.pop('new_groups', None)
super(GroupCreateForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper.layout = Layout(
Div(
Div(
AnyTag(
'h4',
HTML(_("Name")),
),
css_class="col-sm-10",
),
css_class="row",
),
Div(
Div(
Field('name', id="group-create-name"),
css_class="col-sm-10",
),
css_class="row",
),
choices = [('', '--')]
if new_groups:
choices += [(g, g) for g in new_groups if len(g) <= 64]
self.fields['org_id'] = forms.ChoiceField(
# TRANSLATORS: directory like in LDAP
choices=choices, required=False, label=_('Directory identifier'))
if not new_groups:
self.fields['org_id'].widget = HiddenInput()
Div( # buttons
Div(
AnyTag( # tip: don't try to use Button class
"button",
AnyTag(
"i",
css_class="icon-play"
),
HTML(" Create"),
css_id="vm-create-submit",
css_class="btn btn-success",
def save(self, commit=True):
if not commit:
raise AttributeError('Committing is mandatory.')
group = super(GroupCreateForm, self).save()
),
css_class="col-sm-5",
),
css_class="row",
),
)
profile = group.profile
# multiple blanks were not be unique unlike NULLs are
profile.org_id = self.cleaned_data['org_id'] or None
profile.description = self.cleaned_data['description']
profile.save()
return group
@property
def helper(self):
helper = FormHelper(self)
helper.add_input(Submit("submit", _("Create")))
helper.form_tag = False
return helper
class Meta:
model = Group
fields = ['name', ]
fields = ('name', )
class GroupProfileUpdateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
new_groups = kwargs.pop('new_groups', None)
superuser = kwargs.pop('superuser', False)
super(GroupProfileUpdateForm, self).__init__(*args, **kwargs)
if not superuser:
choices = [('', '--')]
if new_groups:
choices += [(g, g) for g in new_groups if len(g) <= 64]
self.fields['org_id'] = forms.ChoiceField(
choices=choices, required=False,
label=_('Directory identifier'))
if not new_groups:
self.fields['org_id'].widget = HiddenInput()
self.fields['description'].widget = forms.Textarea(attrs={'rows': 3})
@property
def helper(self):
helper = FormHelper(self)
helper.add_input(Submit("submit", _("Save")))
helper.form_tag = False
return helper
def save(self, commit=True):
profile = super(GroupProfileUpdateForm, self).save(commit=False)
profile.org_id = self.cleaned_data['org_id'] or None
if commit:
profile.save()
return profile
class Meta:
model = GroupProfile
fields = ('description', 'org_id')
class HostForm(forms.ModelForm):
......
......@@ -104,7 +104,12 @@ class GroupProfile(AclBase):
org_id = CharField(
unique=True, blank=True, null=True, max_length=64,
help_text=_('Unique identifier of the group at the organization.'))
description = TextField()
description = TextField(blank=True)
def save(self, *args, **kwargs):
if not self.org_id:
self.org_id = None
super(GroupProfile, self).save(*args, **kwargs)
@classmethod
def search(cls, name):
......@@ -162,7 +167,8 @@ if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
logger.debug("org_id of %s already added to user %s's profile",
value, sender.username)
memberatrs = getattr(settings, 'SAML_GROUP_ATTRIBUTES', [])
for group in chain(*[attributes[i] for i in memberatrs]):
for group in chain(*[attributes[i]
for i in memberatrs if i in attributes]):
try:
g = GroupProfile.search(group)
except Group.DoesNotExist:
......@@ -173,7 +179,8 @@ if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
g.user_set.add(sender)
owneratrs = getattr(settings, 'SAML_GROUP_OWNER_ATTRIBUTES', [])
for group in chain(*[attributes[i] for i in owneratrs]):
for group in chain(*[attributes[i]
for i in owneratrs if i in attributes]):
try:
g = GroupProfile.search(group)
except Group.DoesNotExist:
......
......@@ -5,7 +5,7 @@
}
</style>
<form method="POST" action="/dashboard/group/create/">
<form method="POST" action="{% url "dashboard.views.group-create" %}">
{% csrf_token %}
{% crispy form %}
</form>
{% extends "dashboard/base.html" %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
......@@ -19,6 +20,9 @@
</div>
<div id="group-details-h1-name">
{{ group.name }}
{% if group.groupprofile.org_id %}
<small>{{group.groupprofile.org_id}}</small>
{% endif %}
</div>
</h1>
<div class="group-details-help js-hidden">
......@@ -38,6 +42,12 @@
<div class="col-md-12" id="group-detail-pane">
<div class="panel panel-default" id="group-detail-panel">
<div class="tab-content panel-body" id="group-form-body">
<form method="POST" action="{% url "dashboard.views.group-update" pk=group.pk %}">
{% csrf_token %}
{% crispy group_profile_form %}
</form>
<h3>{% trans "User list"|capfirst %}</h3>
<form action="" method="post">{% csrf_token %}
<table class="table table-striped table-with-form-fields table-bordered" id="group-detail-user-table">
......
{% extends "dashboard/base.html" %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="body-content">
<div class="panel panel-default" style="margin-top: 60px;">
<div class="panel-heading">
<h3 class="no-margin">
{% trans "Update group" %}
</h3>
</div>
<div class="panel-body">
<form method="POST" action="">
{% csrf_token %}
{% crispy form %}
</form>
</div>
</div>
</div>
{% endblock %}
......@@ -31,7 +31,7 @@ from .views import (
VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmMigrateView,
VmRenewView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView,
GroupRemoveAclUserView, GroupRemoveAclGroupView, GroupRemoveUserView,
GroupCreate,
GroupCreate, GroupProfileUpdate,
TemplateChoose,
UserCreationView,
get_vm_screenshot,
......@@ -121,6 +121,8 @@ urlpatterns = patterns(
name='dashboard.views.node-graph'),
url(r'^group/(?P<pk>\d+)/$', GroupDetailView.as_view(),
name='dashboard.views.group-detail'),
url(r'^group/(?P<pk>\d+)/update/$', GroupProfileUpdate.as_view(),
name='dashboard.views.group-update'),
url(r'^group/(?P<pk>\d+)/acl/$', GroupAclUpdateView.as_view(),
name='dashboard.views.group-acl'),
url(r'^notifications/$', NotificationView.as_view(),
......
......@@ -17,6 +17,7 @@
from __future__ import unicode_literals, absolute_import
from itertools import chain
from os import getenv
import json
import logging
......@@ -58,7 +59,7 @@ from braces.views._access import AccessMixin
from .forms import (
CircleAuthenticationForm, DiskAddForm, HostForm, LeaseForm, MyProfileForm,
NodeForm, TemplateForm, TraitForm, VmCustomizeForm, GroupCreateForm,
UserCreationForm,
UserCreationForm, GroupProfileUpdateForm,
CirclePasswordChangeForm
)
......@@ -75,6 +76,7 @@ from firewall.models import Vlan, Host, Rule
from .models import Favourite, Profile, GroupProfile
logger = logging.getLogger(__name__)
saml_available = hasattr(settings, "SAML_CONFIG")
def search_user(keyword):
......@@ -87,6 +89,39 @@ def search_user(keyword):
return User.objects.get(email=keyword)
class GroupCodeMixin(object):
@classmethod
def get_available_group_codes(cls, request):
newgroups = []
if saml_available:
from djangosaml2.cache import StateCache, IdentityCache
from djangosaml2.conf import get_config
from djangosaml2.views import _get_subject_id
from saml2.client import Saml2Client
state = StateCache(request.session)
conf = get_config(None, request)
client = Saml2Client(conf, state_cache=state,
identity_cache=IdentityCache(request.session),
logger=logger)
subject_id = _get_subject_id(request.session)
identity = client.users.get_identity(subject_id,
check_not_on_or_after=False)
if identity:
attributes = identity[0]
owneratrs = getattr(
settings, 'SAML_GROUP_OWNER_ATTRIBUTES', [])
for group in chain(*[attributes[i]
for i in owneratrs if i in attributes]):
try:
GroupProfile.search(group)
except Group.DoesNotExist:
newgroups.append(group)
return newgroups
class FilterMixin(object):
def get_queryset_filters(self):
......@@ -692,6 +727,8 @@ class GroupDetailView(CheckedDetailView):
context['group'] = self.object
context['users'] = self.object.user_set.all()
context['acl'] = get_group_acl_data(self.object)
context['group_profile_form'] = GroupProfileUpdate.get_form_object(
self.request, self.object.profile)
return context
def post(self, request, *args, **kwargs):
......@@ -1565,10 +1602,9 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
return redirect(path)
class GroupCreate(LoginRequiredMixin, TemplateView):
class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
form_class = GroupCreateForm
form = None
def get_template_names(self):
if self.request.is_ajax():
......@@ -1580,7 +1616,8 @@ class GroupCreate(LoginRequiredMixin, TemplateView):
if not request.user.has_module_perms('auth'):
raise PermissionDenied()
if form is None:
form = self.form_class()
form = self.form_class(
new_groups=self.get_available_group_codes(request))
context = self.get_context_data(**kwargs)
context.update({
'template': 'dashboard/group-create.html',
......@@ -1593,7 +1630,8 @@ class GroupCreate(LoginRequiredMixin, TemplateView):
def post(self, request, *args, **kwargs):
if not request.user.has_module_perms('auth'):
raise PermissionDenied()
form = self.form_class(request.POST)
form = self.form_class(
request.POST, new_groups=self.get_available_group_codes(request))
if not form.is_valid():
return self.get(request, form, *args, **kwargs)
form.cleaned_data
......@@ -1608,6 +1646,55 @@ class GroupCreate(LoginRequiredMixin, TemplateView):
return redirect(savedform.profile.get_absolute_url())
class GroupProfileUpdate(SuccessMessageMixin, GroupCodeMixin,
LoginRequiredMixin, UpdateView):
form_class = GroupProfileUpdateForm
model = Group
success_message = _('Group is successfully updated.')
@classmethod
def get_available_group_codes(cls, request, extra=None):
result = super(GroupProfileUpdate, cls).get_available_group_codes(
request)
if extra and extra not in result:
result += [extra]
return result
def get_object(self):
group = super(GroupProfileUpdate, self).get_object()
profile = group.profile
if not profile.has_level(self.request.user, 'owner'):
raise PermissionDenied
else:
return profile
@classmethod
def get_form_object(cls, request, instance, *args, **kwargs):
kwargs['instance'] = instance
kwargs['new_groups'] = cls.get_available_group_codes(
request, instance.org_id)
kwargs['superuser'] = request.user.is_superuser
return cls.form_class(*args, **kwargs)
def get(self, request, form=None, *args, **kwargs):
self.object = self.get_object()
if form is None:
form = self.get_form_object(request, self.object)
return super(GroupProfileUpdate, self).get(
request, form, *args, **kwargs)
def post(self, request, *args, **kwargs):
if not request.user.has_module_perms('auth'):
raise PermissionDenied()
self.object = self.get_object()
form = self.get_form_object(request, self.object, self.request.POST)
if not form.is_valid():
return self.form_invalid(form)
form.save()
return self.form_valid(form)
class VmDelete(LoginRequiredMixin, DeleteView):
model = Instance
template_name = "dashboard/confirm/base-delete.html"
......@@ -1880,9 +1967,9 @@ class VmMassDelete(LoginRequiredMixin, View):
logger.info('Tried to delete instance #%d without owner '
'permission by %s.', i.pk,
unicode(request.user))
raise PermissionDenied() # no need for rollback or proper
# error message, this can't
# normally happen.
# no need for rollback or proper error message, this can't
# normally happen:
raise PermissionDenied()
try:
i.destroy.async(user=request.user)
names.append(i.name)
......@@ -2416,7 +2503,7 @@ class NotificationView(LoginRequiredMixin, TemplateView):
def circle_login(request):
authentication_form = CircleAuthenticationForm
extra_context = {
'saml2': hasattr(settings, "SAML_CONFIG")
'saml2': saml_available,
}
response = login(request, authentication_form=authentication_form,
extra_context=extra_context)
......
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