Skip to content

Commit

Permalink
Enough work for today.
Browse files Browse the repository at this point in the history
New models, some tests - some of them are failing, will investigate tomorrow.
Created route /group-field
Rename GroupCustomField => GroupField
Rename GroupCustomFieldValue => GroupMemberField
...
  • Loading branch information
TheBirdie authored and TheBirdie committed Jan 26, 2016
1 parent acccbe9 commit ade628c
Show file tree
Hide file tree
Showing 13 changed files with 277 additions and 19 deletions.
2 changes: 2 additions & 0 deletions sigma/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
from sigma_core.views.group import GroupViewSet
from sigma_core.views.group_user import GroupUserViewSet
from sigma_core.views.group_member import GroupMemberViewSet
from sigma_core.views.group_field import GroupFieldViewSet

router = routers.DefaultRouter()

router.register(r'user', UserViewSet)
router.register(r'group', GroupViewSet)
router.register(r'group-member', GroupMemberViewSet)
router.register(r'group-field', GroupFieldViewSet)

urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
Expand Down
29 changes: 29 additions & 0 deletions sigma_core/migrations/0011_auto_20160126_1524.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-26 14:24
from __future__ import unicode_literals

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


class Migration(migrations.Migration):

dependencies = [
('sigma_core', '0010_auto_20160126_1436'),
]

operations = [
migrations.CreateModel(
name='GroupMemberCustomField',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.CharField(max_length=255)),
('field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='sigma_core.GroupCustomField')),
('membership', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='values', to='sigma_core.GroupMember')),
],
),
migrations.AlterUniqueTogether(
name='groupmembercustomfield',
unique_together=set([('membership', 'field')]),
),
]
60 changes: 60 additions & 0 deletions sigma_core/migrations/0012_auto_20160126_2142.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-26 20:42
from __future__ import unicode_literals

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


class Migration(migrations.Migration):

dependencies = [
('sigma_core', '0011_auto_20160126_1524'),
]

operations = [
migrations.CreateModel(
name='GroupField',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('validator_values', models.CharField(max_length=1024)),
],
),
migrations.RemoveField(
model_name='groupcustomfield',
name='validator',
),
migrations.AlterUniqueTogether(
name='groupmembercustomfield',
unique_together=set([]),
),
migrations.RemoveField(
model_name='groupmembercustomfield',
name='field',
),
migrations.RemoveField(
model_name='groupmembercustomfield',
name='membership',
),
migrations.RemoveField(
model_name='group',
name='custom_fields',
),
migrations.DeleteModel(
name='GroupCustomField',
),
migrations.DeleteModel(
name='GroupMemberCustomField',
),
migrations.AddField(
model_name='groupfield',
name='group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='sigma_core.Group'),
),
migrations.AddField(
model_name='groupfield',
name='validator',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='sigma_core.Validator'),
),
]
21 changes: 7 additions & 14 deletions sigma_core/models/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,8 @@

from dry_rest_permissions.generics import allow_staff_or_superuser

# class GroupManager(models.Manager):
# # TODO: Determine whether 'memberships' fields needs to be retrieved every time or not...
# def get_queryset(self):
# return super(GroupManager, self).get_queryset().prefetch_related('memberships')

from sigma_core.models.custom_field import CustomField

class GroupCustomField(CustomField):
class Meta:
pass
# Generated fields:
# values
from sigma_core.models.group_field import GroupField


class Group(models.Model):
Expand Down Expand Up @@ -45,7 +35,6 @@ class Meta:
name = models.CharField(max_length=254)
visibility = models.CharField(max_length=64, choices=VISIBILITY_CHOICES, default=VIS_PRIVATE)
type = models.CharField(max_length=64, choices=TYPE_CHOICES, default=TYPE_BASIC)
custom_fields = models.ManyToManyField(GroupCustomField, related_name = '+')

# The permission a member has upon joining
# A value of -1 means that no one can join the group.
Expand All @@ -67,15 +56,19 @@ class Meta:
# Related fields:
# - invited_users (model User)
# - memberships (model UserGroup)

# objects = GroupManager()
# - fields (model GroupField)
# TODO: Determine whether 'memberships' fields needs to be retrieved every time or not...

def can_anyone_join(self):
return self.default_member_rank >= 0

def __str__(self):
return "%s (%s)" % (self.name, self.get_type_display())

################################################################
# PERMISSIONS #
################################################################

# Perms for admin site
def has_perm(self, perm, obj=None):
return True
Expand Down
30 changes: 30 additions & 0 deletions sigma_core/models/group_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from django.db import models

from sigma_core.models.custom_field import CustomField


class GroupField(CustomField):
class Meta:
pass

group = models.ForeignKey('Group', related_name='fields')
# Generated fields:
# values

################################################################
# PERMISSIONS #
################################################################

@staticmethod
def has_read_permission(request):
return request.user.is_authenticated()

def has_object_read_permission(self, request):
return request.user.is_authenticated() and request.user.is_group_member(self.group)

@staticmethod
def has_write_permission(request):
return request.user.is_authenticated()

def has_object_write_permission(self, request):
return request.user.is_authenticated() and request.user.is_group_admin(self.group)
10 changes: 6 additions & 4 deletions sigma_core/models/group_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
from dry_rest_permissions.generics import allow_staff_or_superuser

from sigma_core.models.user import User
from sigma_core.models.group import Group


class GroupMember(models.Model):
"""
Expand All @@ -15,13 +13,16 @@ class Meta:
# TODO: Make a primary key once Django supports it
unique_together = (("user", "group"),)

user = models.ForeignKey(User, related_name='memberships')
group = models.ForeignKey(Group, related_name='memberships')
user = models.ForeignKey('User', related_name='memberships')
group = models.ForeignKey('Group', related_name='memberships')
created = models.DateTimeField(auto_now_add=True)
join_date = models.DateField(blank=True, null=True)
leave_date = models.DateField(blank=True, null=True)
perm_rank = models.SmallIntegerField(blank=False, default=1)

# Related fields:
# - values (model GroupMemberField)

def can_invite(self):
return self.perm_rank >= self.group.req_rank_invite

Expand Down Expand Up @@ -56,6 +57,7 @@ def has_write_permission(request):
@staticmethod
@allow_staff_or_superuser
def has_create_permission(request):
from sigma_core.models.group import Group
if request.data.get('user') and request.user.id != request.data.get('user'):
return False
try:
Expand Down
17 changes: 17 additions & 0 deletions sigma_core/models/group_member_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.db import models

from sigma_core.models.custom_field import CustomField
from sigma_core.models.user import User

class GroupMemberField(models.Model):
class Meta:
unique_together = (("membership", "field"),)

membership = models.ForeignKey('GroupMember', related_name='values')
fields = models.ForeignKey('GroupField', related_name='+')
value = models.CharField(max_length=CustomField.FIELD_VALUE_MAX_LENGTH)

def has_object_read_permission(self, request):
return self.membership.user.is_group_member(self.membership.group)
def has_object_write_permission(self, request):
return self.membership.user.is_group_member(self.membership.group) and self.membership.user == request.user
5 changes: 5 additions & 0 deletions sigma_core/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ def can_modify_group_infos(self, group):
mem = self.get_group_membership(group)
return mem is not None and mem.perm_rank >= group.req_rank_modify_group_infos

def is_group_admin(self, group):
from sigma_core.models.group_member import GroupMember
mem = self.get_group_membership(group)
return mem is not None and mem.perm_rank == Group.ADMINISTRATOR_RANK


################################################################
# PERMISSIONS #
Expand Down
8 changes: 7 additions & 1 deletion sigma_core/models/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from django.core.validators import validate_email


def get_validator_by_name(name):
"""
For given validator name, returns associated functions array:
Expand All @@ -21,15 +22,19 @@ def get_validator_by_name(name):
Returns None if there is no validator matching that name.
"""

# Validators functions
def text_validate_fields(fields):
re.compile(fields['regex']) # TODO: Is it safe to call re.compile with user input ? Possible infinite loops ... ?
# TODO: Is it safe to call re.compile with user input ? Possible infinite loops ... ?
re.compile(fields['regex'])
fields['message']
return True

# Protection against Evil Regex. Only 50ms to evaluate regex - should be enough.
@timeout_decorator.timeout(0.05, use_signals=False)
def regex_check_timeout(regex, error_message, input):
RegexValidator(regex, error_message)(input)

def text_validate_input(fields, input):
if (fields['regex']):
try:
Expand All @@ -56,6 +61,7 @@ def text_validate_input(fields, input):
except KeyError:
return None


class Validator(models.Model):
class Meta:
pass
Expand Down
14 changes: 14 additions & 0 deletions sigma_core/serializers/group_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from rest_framework import serializers

from sigma_core.models.user import User
from sigma_core.models.group_field import GroupField
from sigma_core.models.group import Group

class GroupFieldSerializer(serializers.ModelSerializer):
class Meta:
model = GroupField

group = serializers.PrimaryKeyRelatedField(queryset=Group.objects.all())

class GroupFieldCreateSerializer(GroupFieldSerializer):
pass
8 changes: 8 additions & 0 deletions sigma_core/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from sigma_core.models.user import User
from sigma_core.models.group import Group
from sigma_core.models.group_member import GroupMember
from sigma_core.models.group_field import GroupField

faker = FakerFactory.create('fr_FR')

Expand All @@ -30,6 +31,13 @@ class Meta:
name = factory.Sequence(lambda n: 'Group %d' % n)


class GroupFieldFactory(factory.django.DjangoModelFactory):
class Meta:
model = GroupField

name = factory.Sequence(lambda n: 'Field %d' % n)


class GroupMemberFactory(factory.django.DjangoModelFactory):
class Meta:
model = GroupMember
Expand Down
67 changes: 67 additions & 0 deletions sigma_core/tests/test_group_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import json

from django.core import mail

from rest_framework import status
from rest_framework.test import APITestCase

from sigma_core.models.user import User
from sigma_core.models.group import Group
from sigma_core.models.group_member import GroupMember
from sigma_core.models.group_field import GroupField
from sigma_core.models.validator import Validator
from sigma_core.tests.factories import UserFactory, AdminUserFactory, GroupFactory, GroupMemberFactory


class GroupFieldTests(APITestCase):
@classmethod
def setUpTestData(self):
super(APITestCase, self).setUpTestData()

# Routes
self.group_field_url = "/group-field/"

# Group open to anyone
self.group = GroupFactory()
self.group.save()

# Users already in group
# User[0]: Not in Group
# User[1]: Requested join, not accepted
# User[2]: Group member
# User[3]: Group admin
self.users = [UserFactory(), UserFactory(), UserFactory(), UserFactory()]
# Associated GroupMember
self.group_member = [
None,
GroupMember(user=self.users[1], group=self.group, perm_rank=0),
GroupMember(user=self.users[2], group=self.group, perm_rank=1),
GroupMember(user=self.users[3], group=self.group, perm_rank=Group.ADMINISTRATOR_RANK)
]

# Misc
self.new_field_data = {"group": self.group.id,
"name": "Example Group Field",
"validator": Validator.VALIDATOR_NONE,
"validator_values": {}}

#################### TEST GROUP FIELD CREATION ########################
def try_create(self, user):
self.client.force_authenticate(user=user)
resp = self.client.post(self.group_field_url, self.new_field_data)
return resp.status_code

def test_create_not_authed(self):
self.assertEqual(self.try_create(None), status.HTTP_401_UNAUTHORIZED)

def test_create_not_group_member(self):
self.assertEqual(self.try_create(self.users[0]), status.HTTP_403_FORBIDDEN)

def test_create_not_group_accepted(self):
self.assertEqual(self.try_create(self.users[1]), status.HTTP_403_FORBIDDEN)

def test_create_not_group_admin(self):
self.assertEqual(self.try_create(self.users[2]), status.HTTP_403_FORBIDDEN)

def test_create_ok(self):
self.assertEqual(self.try_create(self.users[3]), status.HTTP_201_CREATED)
Loading

0 comments on commit ade628c

Please sign in to comment.