Skip to content

Commit

Permalink
Add Ownership of a namespace
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyle Rockman committed Sep 17, 2017
1 parent b52df8d commit c52e776
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 26 deletions.
4 changes: 3 additions & 1 deletion estate/assets/js/components/DashboardListView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ class DashboardListView extends React.Component {
let data = []
each(this.props.data, (item) => {
if (item) {
item.link = <Link className="btn btn-default btn-xs glyphicon glyphicon-pencil" to={ urljoin(this.props.match.url, item.slug) } />
if (item.is_owner) {
item.link = <Link className="btn btn-default btn-xs glyphicon glyphicon-pencil" to={ urljoin(this.props.match.url, item.slug) } />
}
item.modified = new Date(Date.parse(item.modified)).toLocaleString()
data.push(item)
}
Expand Down
15 changes: 9 additions & 6 deletions estate/assets/js/components/TerraformNamespaceItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ class TerraformNamespaceItem extends React.Component {
this.props.unlockNamespace(this.props.namespace.pk)
}
createFilePane(props) {
var locked = this.props.namespace.is_uneditable
var locked = this.props.namespace.is_readonly
var index = findIndex(this.state.files, {slug: props.match.params.file})
if (index == -1){
return null
Expand All @@ -259,7 +259,7 @@ class TerraformNamespaceItem extends React.Component {
)
}
createFileList() {
var locked = this.props.namespace.is_uneditable
var locked = this.props.namespace.is_readonly
var url = this.props.match.url
var count = 0
var elements = []
Expand Down Expand Up @@ -307,7 +307,7 @@ class TerraformNamespaceItem extends React.Component {
)
}
createTemplatePane(props) {
var locked = this.props.namespace.is_uneditable
var locked = this.props.namespace.is_readonly
var index = findIndex(this.state.templates, {slug: props.match.params.template})
if (index == -1){
return null
Expand Down Expand Up @@ -349,7 +349,7 @@ class TerraformNamespaceItem extends React.Component {
)
}
createTemplateList() {
var locked = this.props.namespace.is_uneditable
var locked = this.props.namespace.is_readonly
var url = this.props.match.url
var count = 0
var elements = []
Expand Down Expand Up @@ -446,7 +446,7 @@ class TerraformNamespaceItem extends React.Component {
)
}
createExperimentPane() {
var locked = this.props.namespace.is_uneditable
var locked = this.props.namespace.is_readonly
var data = this.props.experimentOutput
var output = join(data.output, "")
return (
Expand Down Expand Up @@ -501,9 +501,12 @@ class TerraformNamespaceItem extends React.Component {
if (this.props.namespace == null) {
return null
}
if (this.props.namespace.is_owner == false){
return null
}
const url = this.props.match.url
const namespace = this.props.namespace
const locked = namespace.is_uneditable
const locked = namespace.is_readonly
return (
<div>
<div className="col-xs-2 sidebar">
Expand Down
12 changes: 12 additions & 0 deletions estate/assets/js/components/TerraformNamespaceList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ const TerraformNamespacesTableColumns = [
accessor: "description",
sortable: false,
},
{
Header: "Owning Group",
accessor: "owner",
maxWidth: 200,
sortable: false,
},
{
Header: "Locked",
accessor: "locking_user",
maxWidth: 200,
sortable: false,
},
{
Header: "Modified",
accessor: "modified",
Expand Down
20 changes: 19 additions & 1 deletion estate/core/views/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import absolute_import
from django.db.models.query import QuerySet
from rest_framework import serializers, decorators, response
from rest_framework import serializers, decorators, response, permissions


class HistoricalSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -31,3 +31,21 @@ def history(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance.history.all(), many=True, is_history=True)
return response.Response(serializer.data)


class IsOwner(permissions.BasePermission):

def has_object_permission(self, request, view, obj):
if obj.owner:
return request.user.groups.filter(name=obj.owner).count() == 1
else:
return True


class OwnsNamespace(permissions.BasePermission):

def has_object_permission(self, request, view, obj):
if obj.namespace.owner:
return request.user.groups.filter(name=obj.namespace.owner).count() == 1
else:
return True
3 changes: 1 addition & 2 deletions estate/settings/drf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def api_exception_handler(exc, context):
'PAGE_SIZE': 10,
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
Expand Down Expand Up @@ -62,7 +61,7 @@ def api_exception_handler(exc, context):
},
'LOGIN_URL': 'rest_framework:login',
'LOGOUT_URL': 'rest_framework:logout',
'USE_SESSION_AUTH': True,
'USE_SESSION_AUTH': False,
'APIS_SORTER': 'alpha',
'JSON_EDITOR': True,
'VALIDATOR_URL': None
Expand Down
4 changes: 2 additions & 2 deletions estate/terraform/admin/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@


class NamespaceAdmin(admin.ModelAdmin):
list_display = ['pk', 'title', 'description', 'modified']
list_editable = ['title', 'description']
list_display = ['pk', 'title', 'owner', 'locked', 'locking_user']
list_editable = ['title', 'owner']
list_filter = ['title']
search_fields = ['slug', 'title']
list_per_page = 10
Expand Down
42 changes: 42 additions & 0 deletions estate/terraform/migrations/0009_auto_20170917_0156.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-09-17 01:56
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('terraform', '0008_auto_20170905_2028'),
]

operations = [
migrations.AlterField(
model_name='historicalnamespace',
name='locked',
field=models.BooleanField(default=False, verbose_name='locked'),
),
migrations.AlterField(
model_name='historicalnamespace',
name='owner',
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='auth.Group'),
),
migrations.AlterField(
model_name='namespace',
name='locked',
field=models.BooleanField(default=False, verbose_name='locked'),
),
migrations.AlterField(
model_name='namespace',
name='locking_user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='locked_namespaces', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='namespace',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='namespaces', to='auth.Group'),
),
]
6 changes: 3 additions & 3 deletions estate/terraform/models/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@


class Namespace(EstateAbstractBase):
owner = models.CharField(_('owner'), max_length=80)
locked = models.BooleanField(default=False)
locking_user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
owner = models.ForeignKey("auth.Group", related_name="namespaces", null=True, blank=True)
locked = models.BooleanField(_('locked'), default=False)
locking_user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="locked_namespaces", null=True, blank=True)
# TODO: Add tags

history = HistoricalRecordsWithoutDelete(excluded_fields=['slug'])
Expand Down
13 changes: 11 additions & 2 deletions estate/terraform/views/file.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import absolute_import
from django.apps import apps
from rest_framework import serializers, viewsets
from estate.core.views import HistoricalSerializer, HistoryMixin
from rest_framework import serializers, viewsets, filters
from estate.core.views import HistoricalSerializer, HistoryMixin, OwnsNamespace

Namespace = apps.get_model('terraform.Namespace')
File = apps.get_model('terraform.File')
Expand All @@ -20,9 +20,18 @@ class Meta:
historical_fields = ("pk", "slug", "title", "namespace", "description", "content", "disable")


class FileFilter(filters.FilterSet):

class Meta:
model = File
fields = ["title", "namespace"]


class FileApiView(HistoryMixin, viewsets.ModelViewSet):
queryset = File.objects.all()
serializer_class = FileSerializer
filter_class = FileFilter
permission_classes = (OwnsNamespace, )
filter_fields = ('slug',)
search_fields = ('title',)
ordering_fields = ('title', 'created', 'modified')
21 changes: 15 additions & 6 deletions estate/terraform/views/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import django_filters
from django.apps import apps
from rest_framework import serializers, viewsets, filters, decorators, response
from estate.core.views import HistoricalSerializer, HistoryMixin
from estate.core.views import HistoricalSerializer, HistoryMixin, IsOwner
from .file import FileSerializer
from .template import TemplateInstanceSerializer
from ..terraform import Terraform
Expand All @@ -13,22 +13,30 @@

class NamespaceSerializer(HistoricalSerializer):
description = serializers.CharField(default="", allow_blank=True)
owner = serializers.CharField(default="", allow_blank=True)
owner = serializers.SlugRelatedField(slug_field="name", read_only=True)
files = FileSerializer(many=True, read_only=True, is_history=True)
templates = TemplateInstanceSerializer(many=True, read_only=True, is_history=True)
locking_user = serializers.SlugRelatedField(slug_field="username", read_only=True)
is_owner = serializers.SerializerMethodField(read_only=True)
is_unlockable = serializers.SerializerMethodField(read_only=True)
is_uneditable = serializers.SerializerMethodField(read_only=True)
is_readonly = serializers.SerializerMethodField(read_only=True)

class Meta:
model = Namespace
fields = ("pk", "slug", "title", "description", "owner", "files", "templates", "locked", "locking_user", "is_unlockable", "is_uneditable", "created", "modified")
fields = ("pk", "slug", "title", "description", "owner", "files", "templates", "locked", "locking_user", "is_owner", "is_unlockable", "is_readonly", "created", "modified")
historical_fields = ("pk", "slug", "title", "description", "owner", "locked", "locking_user", "historical_files", "historical_templates")

def get_is_owner(self, instance):
if instance.owner:
return self.context["request"].user.groups.filter(name=instance.owner).count() == 1
else:
return True

def get_is_unlockable(self, instance):
return instance.is_unlockable(self.context["request"].user)
is_owner = self.get_is_owner(instance)
return all([instance.is_unlockable(self.context["request"].user), is_owner])

def get_is_uneditable(self, instance):
def get_is_readonly(self, instance):
result = False
if instance.locked is True:
if instance.is_unlockable(self.context["request"].user) is not True:
Expand All @@ -51,6 +59,7 @@ class NamespaceApiView(HistoryMixin, viewsets.ModelViewSet):
queryset = Namespace.objects.all()
serializer_class = NamespaceSerializer
filter_class = NamespaceFilter
permission_classes = (IsOwner, )
search_fields = ('title',)
ordering_fields = ('title', 'created', 'modified')

Expand Down
19 changes: 16 additions & 3 deletions estate/terraform/views/template.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import absolute_import
import json
from django.apps import apps
from rest_framework import serializers, viewsets, decorators, exceptions, status, response
from rest_framework import serializers, viewsets, decorators, exceptions, status, response, filters
from semantic_version import Version
from estate.core.views import HistoricalSerializer, HistoryMixin
from estate.core.views import HistoricalSerializer, HistoryMixin, OwnsNamespace
from estate.core import renderer

Namespace = apps.get_model("terraform.Namespace")
Expand All @@ -20,15 +20,19 @@ class TemplateSerializer(HistoricalSerializer):
version_increment = serializers.ChoiceField(choices=["major", "minor", "patch", "initial"], write_only=True)
body = serializers.CharField(default="", allow_blank=True, validators=[renderer.is_valid_template])
body_mode = serializers.SerializerMethodField()
is_owner = serializers.SerializerMethodField()

class Meta:
model = Template
fields = ("pk", "slug", "title", "description", "version", "version_increment", "json_schema", "ui_schema", "body", "body_mode", "created", "modified")
fields = ("pk", "slug", "title", "description", "version", "version_increment", "json_schema", "ui_schema", "body", "body_mode", "is_owner", "created", "modified")
historical_fields = ("pk", "slug", "title", "description", "version", "json_schema", "ui_schema", "body")

def get_body_mode(self, instance):
return renderer.get_style(instance.body)

def get_is_owner(self, instance):
return True

def create(self, validated_data):
version_increment = validated_data.pop("version_increment", "initial")
if version_increment != "initial":
Expand Down Expand Up @@ -123,9 +127,18 @@ def update(self, instance, validated_data):
return super(TemplateInstanceSerializer, self).update(instance, validated_data)


class TemplateInstanceFilter(filters.FilterSet):

class Meta:
model = TemplateInstance
fields = ["title", "namespace"]


class TemplateInstanceApiView(HistoryMixin, viewsets.ModelViewSet):
queryset = TemplateInstance.objects.all()
serializer_class = TemplateInstanceSerializer
filter_class = TemplateInstanceFilter
permission_classes = (OwnsNamespace, )
filter_fields = ("slug",)
search_fields = ("title",)
ordering_fields = ("title", "created", "modified")
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"rc-select": "6.8.6",
"react": "15.5.4",
"@skidding/react-codemirror": "^1.0.0",
"react-diff": "0.0.7",
"react-dom": "15.5.4",
"react-jsonschema-form": "0.48.2",
"react-hot-loader": "3.0.0-beta.6",
Expand Down

0 comments on commit c52e776

Please sign in to comment.