Add unit tests to check the issues found in the bug bounty report
This commit is contained in:
parent
79cd70cbd8
commit
338e4cfcfc
|
|
@ -1,4 +1,5 @@
|
||||||
import graphene
|
import graphene
|
||||||
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
|
|
||||||
class HiddenForMixin:
|
class HiddenForMixin:
|
||||||
|
|
@ -18,3 +19,12 @@ class VisibleForMixin:
|
||||||
|
|
||||||
class HiddenAndVisibleForMixin(HiddenForMixin, VisibleForMixin):
|
class HiddenAndVisibleForMixin(HiddenForMixin, VisibleForMixin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GraphqlNodeMixin:
|
||||||
|
def default_node_name(self):
|
||||||
|
return f'{self.__class__.__name__}Node'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def graphql_id(self):
|
||||||
|
return to_global_id(self.default_node_name(), self.id)
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,9 @@ from django.db import models
|
||||||
from django_extensions.db.models import TitleSlugDescriptionModel
|
from django_extensions.db.models import TitleSlugDescriptionModel
|
||||||
from graphql_relay import to_global_id
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
|
from core.mixins import GraphqlNodeMixin
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
class GraphqlNodeMixin:
|
|
||||||
def default_node_name(self):
|
|
||||||
return f'{self.__class__.__name__}Node'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def graphql_id(self):
|
|
||||||
return to_global_id(self.default_node_name(), self.id)
|
|
||||||
|
|
||||||
class Project(TitleSlugDescriptionModel, GraphqlNodeMixin):
|
class Project(TitleSlugDescriptionModel, GraphqlNodeMixin):
|
||||||
objectives = models.TextField(blank=True)
|
objectives = models.TextField(blank=True)
|
||||||
appearance = models.CharField(blank=True, null=False, max_length=255)
|
appearance = models.CharField(blank=True, null=False, max_length=255)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from graphene_django import DjangoObjectType
|
||||||
from api.utils import get_by_id_or_slug
|
from api.utils import get_by_id_or_slug
|
||||||
from portfolio.models import Project, ProjectEntry
|
from portfolio.models import Project, ProjectEntry
|
||||||
from users.models import Role, UserRole, User
|
from users.models import Role, UserRole, User
|
||||||
|
from users.schema import PublicUserNode
|
||||||
|
|
||||||
|
|
||||||
class ProjectEntryNode(DjangoObjectType):
|
class ProjectEntryNode(DjangoObjectType):
|
||||||
|
|
@ -19,6 +20,7 @@ class ProjectNode(DjangoObjectType):
|
||||||
pk = graphene.Int()
|
pk = graphene.Int()
|
||||||
entries_count = graphene.Int()
|
entries_count = graphene.Int()
|
||||||
entries = graphene.List(ProjectEntryNode)
|
entries = graphene.List(ProjectEntryNode)
|
||||||
|
owner = graphene.Field(PublicUserNode)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Project
|
model = Project
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from graphene.test import Client
|
||||||
from graphql_relay import to_global_id
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
from api.schema import schema
|
from api.schema import schema
|
||||||
|
from core.tests.base_test import SkillboxTestCase
|
||||||
from portfolio.factories import ProjectFactory
|
from portfolio.factories import ProjectFactory
|
||||||
from users.factories import SchoolClassFactory
|
from users.factories import SchoolClassFactory
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
@ -10,7 +11,7 @@ from users.services import create_users
|
||||||
from api.test_utils import create_client, DefaultUserTestCase
|
from api.test_utils import create_client, DefaultUserTestCase
|
||||||
from portfolio.models import Project
|
from portfolio.models import Project
|
||||||
|
|
||||||
class ProjectQuery(TestCase):
|
class ProjectQuery(SkillboxTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
create_users()
|
create_users()
|
||||||
self.teacher = User.objects.get(username='teacher')
|
self.teacher = User.objects.get(username='teacher')
|
||||||
|
|
@ -49,7 +50,6 @@ class ProjectQuery(TestCase):
|
||||||
self.assertEqual(Project.objects.count(), 0)
|
self.assertEqual(Project.objects.count(), 0)
|
||||||
|
|
||||||
def test_should_not_be_able_to_delete_other_projects(self):
|
def test_should_not_be_able_to_delete_other_projects(self):
|
||||||
|
|
||||||
self.assertEqual(Project.objects.count(), 1)
|
self.assertEqual(Project.objects.count(), 1)
|
||||||
request = RequestFactory().get('/')
|
request = RequestFactory().get('/')
|
||||||
request.user = self.student2
|
request.user = self.student2
|
||||||
|
|
@ -58,6 +58,41 @@ class ProjectQuery(TestCase):
|
||||||
result = self.client.execute(self.mutation, variables=self.variables)
|
result = self.client.execute(self.mutation, variables=self.variables)
|
||||||
self.assertEqual(result.get('errors')[0]['message'], 'Permission denied: Incorrect project')
|
self.assertEqual(result.get('errors')[0]['message'], 'Permission denied: Incorrect project')
|
||||||
|
|
||||||
|
def test_should_not_be_able_to_edit_other_projects(self):
|
||||||
|
self.assertEqual(Project.objects.count(), 1)
|
||||||
|
request = RequestFactory().get('/')
|
||||||
|
request.user = self.student2
|
||||||
|
self.client = Client(schema=schema, context_value=request)
|
||||||
|
mutation = '''
|
||||||
|
mutation UpdateProjectMutation($input: UpdateProjectInput!){
|
||||||
|
updateProject(input: $input) {
|
||||||
|
project {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
# project:
|
||||||
|
# {
|
||||||
|
# title: String
|
||||||
|
# description: String
|
||||||
|
# objectives: String
|
||||||
|
# appearance: String
|
||||||
|
# id: ID!
|
||||||
|
# final: Boolean
|
||||||
|
# }
|
||||||
|
input = {
|
||||||
|
'project': {
|
||||||
|
'id': self.project1.graphql_id,
|
||||||
|
'title': 'BAD! THIS IS BAD!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.get_client(self.student2).get_result(mutation, variables={
|
||||||
|
'input': input
|
||||||
|
})
|
||||||
|
self.assertIsNotNone(result.errors)
|
||||||
|
self.assertTrue('Permission' in result.errors)
|
||||||
|
|
||||||
|
|
||||||
class ProjectMutationsTestCase(DefaultUserTestCase):
|
class ProjectMutationsTestCase(DefaultUserTestCase):
|
||||||
def test_add_project(self):
|
def test_add_project(self):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
from core.tests.base_test import SkillboxTestCase
|
from core.tests.base_test import SkillboxTestCase
|
||||||
from portfolio.factories import ProjectFactory
|
from portfolio.factories import ProjectFactory
|
||||||
from portfolio.models import Project
|
from portfolio.models import Project
|
||||||
|
|
@ -13,7 +15,7 @@ query ProjectQuery($id: ID!) {
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ProjectQueryTestCaswe(SkillboxTestCase):
|
class ProjectQueryTestCase(SkillboxTestCase):
|
||||||
def _test_direct_project_access(self, user: User, should_have_access: bool):
|
def _test_direct_project_access(self, user: User, should_have_access: bool):
|
||||||
result = self.get_client(user).get_result(project_query, variables={
|
result = self.get_client(user).get_result(project_query, variables={
|
||||||
'id': self.project1.graphql_id
|
'id': self.project1.graphql_id
|
||||||
|
|
@ -30,6 +32,7 @@ class ProjectQueryTestCaswe(SkillboxTestCase):
|
||||||
school_class2 = SchoolClassFactory(users=[self.teacher2, self.student2])
|
school_class2 = SchoolClassFactory(users=[self.teacher2, self.student2])
|
||||||
|
|
||||||
self.project1 = ProjectFactory(student=self.student1)
|
self.project1 = ProjectFactory(student=self.student1)
|
||||||
|
self.project1_id = to_global_id('ProjectNode', self.project1.id)
|
||||||
self.query = '''
|
self.query = '''
|
||||||
query ProjectsQuery {
|
query ProjectsQuery {
|
||||||
projects {
|
projects {
|
||||||
|
|
@ -113,3 +116,19 @@ class ProjectQueryTestCaswe(SkillboxTestCase):
|
||||||
self._test_direct_project_access(self.teacher2, False)
|
self._test_direct_project_access(self.teacher2, False)
|
||||||
# non-owner can't access project
|
# non-owner can't access project
|
||||||
self._test_direct_project_access(self.student2, False)
|
self._test_direct_project_access(self.student2, False)
|
||||||
|
|
||||||
|
def test_project_owner(self):
|
||||||
|
query = """
|
||||||
|
query ProjectQuery($id: ID!) {
|
||||||
|
project(id: $id) {
|
||||||
|
id
|
||||||
|
owner {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
result = self.get_client(self.student1).get_result(query, variables={
|
||||||
|
'id': self.project1.graphql_id
|
||||||
|
})
|
||||||
|
self.assertIsNotNone(result.errors)
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,11 @@ from wagtail.core.fields import StreamField
|
||||||
|
|
||||||
from books.blocks import DocumentBlock, ImageUrlBlock, LinkBlock, VideoBlock
|
from books.blocks import DocumentBlock, ImageUrlBlock, LinkBlock, VideoBlock
|
||||||
from books.models import TextBlock
|
from books.models import TextBlock
|
||||||
|
from core.mixins import GraphqlNodeMixin
|
||||||
from users.models import SchoolClass
|
from users.models import SchoolClass
|
||||||
|
|
||||||
|
|
||||||
class Room(TitleSlugDescriptionModel):
|
class Room(TitleSlugDescriptionModel, GraphqlNodeMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Raum'
|
verbose_name = 'Raum'
|
||||||
verbose_name_plural = 'Räume'
|
verbose_name_plural = 'Räume'
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ from graphql_relay import from_global_id
|
||||||
|
|
||||||
from core.tests.base_test import SkillboxTestCase
|
from core.tests.base_test import SkillboxTestCase
|
||||||
from core.tests.helpers import GQLResult
|
from core.tests.helpers import GQLResult
|
||||||
|
from rooms.models import Room
|
||||||
|
|
||||||
|
|
||||||
class GQLRoom:
|
class GQLRoom:
|
||||||
def __init__(self, room_data):
|
def __init__(self, room_data):
|
||||||
|
|
@ -30,29 +32,30 @@ class AddRoomResult:
|
||||||
class NewRoomMutationTestCase(SkillboxTestCase):
|
class NewRoomMutationTestCase(SkillboxTestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.createDefault()
|
self.createDefault()
|
||||||
|
self.mutation = """
|
||||||
|
mutation AddRoom($input: AddRoomInput!){
|
||||||
|
addRoom(input: $input) {
|
||||||
|
room {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
entryCount
|
||||||
|
appearance
|
||||||
|
description
|
||||||
|
schoolClass {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
def test_create_new_room(self):
|
def test_create_new_room(self):
|
||||||
mutation = """
|
self.assertEqual(Room.objects.count(), 0)
|
||||||
mutation AddRoom($input: AddRoomInput!){
|
|
||||||
addRoom(input: $input) {
|
|
||||||
room {
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
title
|
|
||||||
entryCount
|
|
||||||
appearance
|
|
||||||
description
|
|
||||||
schoolClass {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
title = 'some title'
|
title = 'some title'
|
||||||
appearance='blue'
|
appearance='blue'
|
||||||
res = self.get_client().execute(mutation, variables={
|
res = self.get_client().execute(self.mutation, variables={
|
||||||
'input': {
|
'input': {
|
||||||
'room': {
|
'room': {
|
||||||
'title': title,
|
'title': title,
|
||||||
|
|
@ -68,5 +71,23 @@ mutation AddRoom($input: AddRoomInput!){
|
||||||
self.assertEqual(room.appearance, appearance)
|
self.assertEqual(room.appearance, appearance)
|
||||||
self.assertIsNone(room.description)
|
self.assertIsNone(room.description)
|
||||||
self.assertEqual(int(from_global_id(room.school_class.get('id'))[1]), self.teacher.selected_class.id)
|
self.assertEqual(int(from_global_id(room.school_class.get('id'))[1]), self.teacher.selected_class.id)
|
||||||
|
self.assertEqual(Room.objects.count(), 1)
|
||||||
|
|
||||||
|
def test_create_new_room_for_other_school_class(self):
|
||||||
|
self.assertEqual(Room.objects.count(), 0)
|
||||||
|
result = self.get_client(self.teacher2).get_result(self.mutation, variables={
|
||||||
|
'input': {
|
||||||
|
'room': {
|
||||||
|
'title': 'BIG NO NO!',
|
||||||
|
# description
|
||||||
|
'schoolClass': {
|
||||||
|
'id': self.school_class.graphql_id
|
||||||
|
},
|
||||||
|
'appearance': 'red'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.assertIsNotNone(result.errors)
|
||||||
|
self.assertTrue('Permission' in result.errors)
|
||||||
|
self.assertEqual(Room.objects.count(), 0)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@ from graphql_relay import to_global_id
|
||||||
|
|
||||||
from api.schema import schema
|
from api.schema import schema
|
||||||
from core.factories import UserFactory
|
from core.factories import UserFactory
|
||||||
|
from core.tests.base_test import SkillboxTestCase
|
||||||
from rooms.factories import RoomEntryFactory, RoomFactory
|
from rooms.factories import RoomEntryFactory, RoomFactory
|
||||||
from rooms.models import RoomEntry
|
from rooms.models import RoomEntry
|
||||||
from users.factories import SchoolClassFactory
|
from users.factories import SchoolClassFactory
|
||||||
|
|
||||||
|
|
||||||
class RoomEntryMutationsTestCase(TestCase):
|
class RoomEntryMutationsTestCase(SkillboxTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = UserFactory(username='aschi')
|
self.user = UserFactory(username='aschi')
|
||||||
self.another_user = UserFactory(username='pesche')
|
self.another_user = UserFactory(username='pesche')
|
||||||
|
|
@ -17,6 +18,8 @@ class RoomEntryMutationsTestCase(TestCase):
|
||||||
s = SchoolClassFactory(users=[self.user, self.another_user])
|
s = SchoolClassFactory(users=[self.user, self.another_user])
|
||||||
s2 = SchoolClassFactory(users=[self.yet_another_user])
|
s2 = SchoolClassFactory(users=[self.yet_another_user])
|
||||||
self.room_entry = RoomEntryFactory(author=self.user, room=RoomFactory(school_class=s))
|
self.room_entry = RoomEntryFactory(author=self.user, room=RoomFactory(school_class=s))
|
||||||
|
self.room = self.room_entry.room
|
||||||
|
self.first_school_class = s
|
||||||
|
|
||||||
request = RequestFactory().get('/')
|
request = RequestFactory().get('/')
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
|
|
@ -135,3 +138,47 @@ class RoomEntryMutationsTestCase(TestCase):
|
||||||
entry = RoomEntry.objects.get(pk=self.room_entry.pk)
|
entry = RoomEntry.objects.get(pk=self.room_entry.pk)
|
||||||
self.assertIsNotNone(result.get('errors'))
|
self.assertIsNotNone(result.get('errors'))
|
||||||
self.assertEqual(entry.title, self.room_entry.title)
|
self.assertEqual(entry.title, self.room_entry.title)
|
||||||
|
|
||||||
|
def test_add_room_entry_not_owner_from_other_class(self):
|
||||||
|
self.assertEqual(RoomEntry.objects.count(), 1)
|
||||||
|
mutation = """
|
||||||
|
fragment RoomEntryParts on RoomEntryNode {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
contents
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
avatarUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mutation AddRoomEntry($input: AddRoomEntryInput!){
|
||||||
|
addRoomEntry(input: $input) {
|
||||||
|
roomEntry {
|
||||||
|
...RoomEntryParts
|
||||||
|
}
|
||||||
|
errors
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# input:
|
||||||
|
# title = graphene.String(required=True)
|
||||||
|
# contents = graphene.List(ContentElementInput)
|
||||||
|
# room = graphene.ID(required=True)
|
||||||
|
room_entry = {
|
||||||
|
'title': 'Bad Actor!',
|
||||||
|
'room': self.room.graphql_id
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self.get_client(self.yet_another_user).get_result(mutation, variables={
|
||||||
|
'input': {
|
||||||
|
'roomEntry': room_entry
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.assertIsNotNone(result.errors)
|
||||||
|
self.assertTrue('Permission' in result.errors)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from django.utils.functional import cached_property
|
||||||
from django.utils.timezone import is_aware, make_aware
|
from django.utils.timezone import is_aware, make_aware
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from core.mixins import GraphqlNodeMixin
|
||||||
from users.licenses import MYSKILLBOX_LICENSES
|
from users.licenses import MYSKILLBOX_LICENSES
|
||||||
from users.managers import LicenseManager, RoleManager, UserManager, UserRoleManager
|
from users.managers import LicenseManager, RoleManager, UserManager, UserRoleManager
|
||||||
|
|
||||||
|
|
@ -176,7 +177,7 @@ class Team(GroupWithCode):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class SchoolClass(GroupWithCode):
|
class SchoolClass(GroupWithCode, GraphqlNodeMixin):
|
||||||
users = models.ManyToManyField(get_user_model(), related_name='school_classes', blank=True,
|
users = models.ManyToManyField(get_user_model(), related_name='school_classes', blank=True,
|
||||||
through='users.SchoolClassMember')
|
through='users.SchoolClassMember')
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue