Add proper permission checks for crashreports and heartbeats
diff --git a/crashreports/apps.py b/crashreports/apps.py
index e8d6894..d651203 100644
--- a/crashreports/apps.py
+++ b/crashreports/apps.py
@@ -2,5 +2,6 @@
 
 from django.apps import AppConfig
 
+
 class crashreportsConfig(AppConfig):
     name = 'crashreports'
diff --git a/crashreports/models.py b/crashreports/models.py
index ebf9c4b..8ac1222 100644
--- a/crashreports/models.py
+++ b/crashreports/models.py
@@ -1,20 +1,26 @@
-    # -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 from django.db import models
-import datetime
+
 from django.contrib.auth.models import User
 from taggit.managers import TaggableManager
+
 import uuid
 
+
 class Device(models.Model):
     # for every device there is a django user
-    uuid           = models.CharField(max_length=64, unique=True, default=uuid.uuid4, editable=False)
-    user           = models.OneToOneField(User, related_name='Hiccup_Device', on_delete=models.CASCADE, unique=True)
-    imei           = models.CharField(max_length=32, null=True, blank=True)
-    board_date     = models.DateTimeField(null=True, blank= True)
-    chipset        = models.CharField(max_length=200, null=True, blank= True)
-    tags           = TaggableManager(blank=True)
+    uuid = models.CharField(max_length=64, unique=True,
+                            default=uuid.uuid4, editable=False)
+    user = models.OneToOneField(
+        User, related_name='Hiccup_Device', on_delete=models.CASCADE,
+        unique=True)
+    imei = models.CharField(max_length=32, null=True, blank=True)
+    board_date = models.DateTimeField(null=True, blank=True)
+    chipset = models.CharField(max_length=200, null=True, blank=True)
+    tags = TaggableManager(blank=True)
     last_heartbeat = models.DateTimeField(null=True, blank=True)
-    token          = models.CharField(max_length=200, null=True, blank=True)
+    token = models.CharField(max_length=200, null=True, blank=True)
+
 
 def crashreport_file_name(instance, filename):
     return '/'.join([
@@ -24,32 +30,40 @@
         str(instance.crashreport.date),
         filename])
 
+
 class Crashreport(models.Model):
-    device            = models.ForeignKey(Device, on_delete=models.CASCADE)
-    is_fake_report    = models.BooleanField(default=False)
-    app_version       = models.IntegerField()
-    uptime            = models.CharField(max_length=200)
+    device = models.ForeignKey(Device, on_delete=models.CASCADE)
+    is_fake_report = models.BooleanField(default=False)
+    app_version = models.IntegerField()
+    uptime = models.CharField(max_length=200)
     build_fingerprint = models.CharField(max_length=200)
-    boot_reason       = models.CharField(max_length=200)
-    power_on_reason   = models.CharField(max_length=200)
-    power_off_reason  = models.CharField(max_length=200)
-    date              = models.DateTimeField()
+    boot_reason = models.CharField(max_length=200)
+    power_on_reason = models.CharField(max_length=200)
+    power_off_reason = models.CharField(max_length=200)
+    date = models.DateTimeField()
     tags = TaggableManager(blank=True)
+
     def _get_uuid(self):
         "Returns the person's full name."
         return self.device.uuid
     uuid = property(_get_uuid)
-    
+
 
 class LogFile(models.Model):
-    logfile_type      = models.TextField(max_length=36)
-    device       = models.ForeignKey(Device, on_delete=models.CASCADE)
-    crashreport       = models.ForeignKey(Crashreport, on_delete=models.CASCADE)
-    crashreport_file  = models.FileField(upload_to=crashreport_file_name)
+    logfile_type = models.TextField(max_length=36)
+    device = models.ForeignKey(Device, on_delete=models.CASCADE)
+    crashreport = models.ForeignKey(Crashreport, on_delete=models.CASCADE)
+    crashreport_file = models.FileField(upload_to=crashreport_file_name)
 
-class HeartBeat(models.Model):    
+
+class HeartBeat(models.Model):
     device = models.ForeignKey(Device, on_delete=models.CASCADE)
     app_version = models.IntegerField()
     uptime = models.CharField(max_length=200)
     build_fingerprint = models.CharField(max_length=200)
     date = models.DateTimeField()
+
+    def _get_uuid(self):
+        "Returns the person's full name."
+        return self.device.uuid
+    uuid = property(_get_uuid)
diff --git a/crashreports/permissions.py b/crashreports/permissions.py
new file mode 100644
index 0000000..5f48caa
--- /dev/null
+++ b/crashreports/permissions.py
@@ -0,0 +1,29 @@
+from crashreports.models import Device
+from rest_framework.permissions import BasePermission
+
+
+class HasRightsOrIsDeviceOwnerDeviceCreation(BasePermission):
+
+    def has_permission(self, request, view):
+        # if user has all permissions for crashreport return true
+        if (request.user.has_perm('crashreports.add_crashreport')
+                and request.user.has_perm('crashreports.change_crashreport')
+                and request.user.has_perm('crashreports.del_crashreport')
+                and request.user.has_perm('heartbeat.add_crashreport')
+                and request.user.has_perm('heartbeat.change_crashreport')
+                and request.user.has_perm('heartbeat.del_crashreport')):
+            return True
+        # special case:
+        # user is the owner of a device. in this case creations are allowed.
+        # we have to check if the device with the supplied uuid indeed
+        # belongs to the user
+        if request.method == 'POST':
+            try:
+                device = Device.objects.get(user=request.user)
+            except:
+                return False
+            if ('uuid' not in request.data):
+                return False
+            if (request.data['uuid'] == device.uuid):
+                return True
+        return False
diff --git a/crashreports/rest_api_crashreports.py b/crashreports/rest_api_crashreports.py
index 83b94e4..d23821c 100644
--- a/crashreports/rest_api_crashreports.py
+++ b/crashreports/rest_api_crashreports.py
@@ -1,38 +1,12 @@
-from rest_framework import filters
 from rest_framework import generics
-from rest_framework.response import Response
-
-from rest_framework.decorators import api_view
-from rest_framework.decorators import permission_classes
-
-from rest_framework.permissions import AllowAny
-
-from rest_framework.permissions import IsAdminUser
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.permissions import AllowAny
-
-
-from rest_framework.permissions import BasePermission
-
-from rest_framework.authtoken.models import Token
-
 from crashreports.models import Crashreport
+from crashreports.permissions import HasRightsOrIsDeviceOwnerDeviceCreation
 from crashreports.serializers import CrashReportSerializer
 
-# class IsCreationAndHasCreationRights(BasePermission):
-#     def has_permission(self, request, view):
-#         if not request.user.is_authenticated():
-#             if view.action == 'create':
-#                 if request.user.has_permission(""):
-#                     return True
-#                 return False
-#         else:
-#             return True
-# 
 
 class ListCreateCrashReport(generics.ListCreateAPIView):
     queryset = Crashreport.objects.all()
     paginate_by = 20
-    permission_classes = (IsAuthenticated, )
+    permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
     serializer_class = CrashReportSerializer
     pass
diff --git a/crashreports/rest_api_heartbeats.py b/crashreports/rest_api_heartbeats.py
new file mode 100644
index 0000000..88a79f5
--- /dev/null
+++ b/crashreports/rest_api_heartbeats.py
@@ -0,0 +1,12 @@
+from rest_framework import generics
+from crashreports.models import HeartBeat
+from crashreports.serializers import HeartBeatSerializer
+from crashreports.permissions import HasRightsOrIsDeviceOwnerDeviceCreation
+
+
+class ListCreateHeartBeat(generics.ListCreateAPIView):
+    queryset = HeartBeat.objects.all()
+    paginate_by = 20
+    permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
+    serializer_class = HeartBeatSerializer
+    pass
diff --git a/crashreports/serializers.py b/crashreports/serializers.py
index 5ccf3b8..1ef8dbd 100644
--- a/crashreports/serializers.py
+++ b/crashreports/serializers.py
@@ -1,16 +1,16 @@
 from rest_framework import serializers
-from rest_framework.serializers import PrimaryKeyRelatedField
-from rest_framework.serializers import CharField
-from rest_framework.serializers import ObjectDoesNotExist
+
 from rest_framework.exceptions import NotFound
 from crashreports.models import Crashreport
 from crashreports.models import Device
+from crashreports.models import HeartBeat
 from rest_framework import permissions
 
 
 class CrashReportSerializer(serializers.ModelSerializer):
     permission_classes = (permissions.AllowAny,)
     uuid = serializers.CharField(max_length=64)
+
     class Meta:
         model = Crashreport
         exclude = ('device',)
@@ -22,12 +22,33 @@
             raise NotFound(detail="uuid does not exist")
         validated_data.pop('uuid', None)
         report = Crashreport(**validated_data)
-        report.device=device
+        report.device = device
         report.save()
         return report
-        
+
+
+class HeartBeatSerializer(serializers.ModelSerializer):
+    permission_classes = (permissions.AllowAny,)
+    uuid = serializers.CharField(max_length=64)
+
+    class Meta:
+        model = HeartBeat
+        exclude = ('device',)
+
+    def create(self, validated_data):
+        try:
+            device = Device.objects.get(uuid=validated_data['uuid'])
+        except:
+            raise NotFound(detail="uuid does not exist")
+        validated_data.pop('uuid', None)
+        heartbeat = HeartBeat(**validated_data)
+        heartbeat.device = device
+        heartbeat.save()
+        return heartbeat
+
 
 class DeviceSerializer(serializers.ModelSerializer):
     permission_classes = (permissions.IsAdminUser,)
+
     class Meta:
         model = Device
diff --git a/crashreports/tests.py b/crashreports/tests.py
index 3eb6854..43acf1d 100644
--- a/crashreports/tests.py
+++ b/crashreports/tests.py
@@ -1,8 +1,7 @@
-from django.test import TestCase
+from django.contrib.auth.models import User
 from rest_framework.test import APITestCase
 from rest_framework.test import APIClient
-from django.contrib.auth.models import User
-from rest_framework.test import force_authenticate
+import datetime
 
 # Create your tests here.
 
@@ -69,31 +68,16 @@
         client = APIClient()
         client.login(username='myuser', password='test')
         url = "/hiccup/api/v1/devices/{}/".format(self.uuid_to_delete)
-        request = client.delete(url, {})
-        # self.assertEqual(request.status_code, 204)
-        # request = client.delete("/hiccup/api/v1/devices/{}/".format(self.uuid_to_delete),{})
-        # self.assertEqual(request.status_code, 404)
+        request = client.delete(
+            url.format(self.uuid_to_delete), {})
+        self.assertEqual(request.status_code, 204)
+        request = client.delete(
+            url.format(self.uuid_to_delete), {})
+        self.assertEqual(request.status_code, 404)
         client.logout()
 
-import datetime
 
-
-def create_dummy_crash_report(uuid="not set"):
-    return {
-        'uuid': uuid,
-        'is_fake_report': 0,
-        'app_version': 2,
-        'uptime': "2 Hours",
-        'build_fingerprint': "models.CharField(max_length=200)",
-        'boot_reason': "models.CharField(max_length=200)",
-        'power_on_reason': "models.CharField(max_length=200)",
-        'power_off_reason': "models.CharField(max_length=200)",
-        'date': str(datetime.datetime(year=2016, month=1, day=1))
-    }
-
-from crashreports.models import Crashreport
-
-class CreateCrashreportTestCase(APITestCase):
+class HeartbeatListTestCase(APITestCase):
 
     def setUp(self):
         self.password = "test"
@@ -106,37 +90,112 @@
         request = self.client.post("/hiccup/api/v1/devices/register/", {})
         self.other_uuid = request.data['uuid']
         self.other_token = request.data['token']
-        self.crashreport = create_dummy_crash_report(self.uuid)
+        self.data = self.create_dummy_data(self.uuid)
+        self.url = "/hiccup/api/v1/heartbeats/"
 
-    def test_create_crashreport_no_auth(self):
+    def create_dummy_data(self, uuid="not set"):
+        return {
+            'uuid': uuid,
+            'app_version': 2,
+            'uptime': "2 Hours",
+            'build_fingerprint': "models.CharField(max_length=200)",
+            'date': str(datetime.datetime(year=2016, month=1, day=1))
+        }
+
+    def test_create_no_auth(self):
         client = APIClient()
-        request = client.post("/hiccup/api/v1/crashreports/", self.crashreport)
+        request = client.post(self.url, self.data)
         self.assertEqual(request.status_code, 401)
 
-    def test_create_crashreport_as_admin(self):
+    def test_create_as_admin(self):
         client = APIClient()
         client.login(username='myuser', password='test')
-        request = client.post("/hiccup/api/v1/crashreports/", self.crashreport)
+        request = client.post(self.url, self.data)
         self.assertEqual(request.status_code, 201)
         client.logout()
 
-    def test_create_crashreport_as_admin_not_existing_device(self):
+    def test_create_as_admin_not_existing_device(self):
         client = APIClient()
         client.login(username='myuser', password='test')
-        request = client.post("/hiccup/api/v1/crashreports/", create_dummy_crash_report())
+        request = client.post(self.url,
+                              self.create_dummy_data())
         self.assertEqual(request.status_code, 404)
         client.logout()
-        
-    def test_create_crashreport_as_uuid_owner(self):
+
+    def test_create_as_uuid_owner(self):
         client = APIClient()
         client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
-        request = client.post("/hiccup/api/v1/crashreports/",  create_dummy_crash_report(self.uuid))
+        request = client.post(self.url,
+                              self.create_dummy_data(self.uuid))
         self.assertEqual(request.status_code, 201)
         client.credentials()
-    
-    def test_create_crashreport_as_uuid_not_owner(self):
+
+    def test_create_as_uuid_not_owner(self):
         client = APIClient()
         client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
-        request = client.post("/hiccup/api/v1/crashreports/",  create_dummy_crash_report(self.other_uuid))
-        self.assertEqual(request.status_code, 403) 
+        request = client.post(self.url,
+                              self.create_dummy_data(self.other_uuid))
+        self.assertEqual(request.status_code, 403)
         client.credentials()
+
+    def post_heartbeats(self, client, heartbeat, count=5):
+        for i in range(count):
+            client.post(self.url, heartbeat)
+
+    def test_list(self):
+        count = 5
+        client = APIClient()
+        client.login(username='myuser', password='test')
+        self.post_heartbeats(client, self.data, count)
+        request = client.get(self.url)
+        self.assertEqual(request.status_code, 200)
+        self.assertTrue(len(request.data) >= count)
+        client.logout()
+
+    def test_list_noauth(self):
+        count = 5
+        client = APIClient()
+        client.login(username='myuser', password='test')
+        self.post_heartbeats(client, self.data, count)
+        client.logout()
+        request = client.get(self.url)
+        self.assertEqual(request.status_code, 401)
+
+    def test_list_device_owner(self):
+        count = 5
+        client = APIClient()
+        client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
+        self.post_heartbeats(client, self.data, count)
+        request = client.get(self.url)
+        client.logout()
+        self.assertEqual(request.status_code, 403)
+
+
+class CrashreportListTestCase(HeartbeatListTestCase):
+
+    def setUp(self):
+        self.password = "test"
+        self.admin = User.objects.create_superuser(
+            'myuser', 'myemail@test.com', self.password)
+        # we need a device
+        request = self.client.post("/hiccup/api/v1/devices/register/", {})
+        self.uuid = request.data['uuid']
+        self.token = request.data['token']
+        request = self.client.post("/hiccup/api/v1/devices/register/", {})
+        self.other_uuid = request.data['uuid']
+        self.other_token = request.data['token']
+        self.data = self.create_dummy_data(self.uuid)
+        self.url = "/hiccup/api/v1/crashreports/"
+
+    def create_dummy_data(self, uuid="not set"):
+        return {
+            'uuid': uuid,
+            'is_fake_report': 0,
+            'app_version': 2,
+            'uptime': "2 Hours",
+            'build_fingerprint': "models.CharField(max_length=200)",
+            'boot_reason': "models.CharField(max_length=200)",
+            'power_on_reason': "models.CharField(max_length=200)",
+            'power_off_reason': "models.CharField(max_length=200)",
+            'date': str(datetime.datetime(year=2016, month=1, day=1))
+        }
diff --git a/crashreports/urls.py b/crashreports/urls.py
index a81f242..2120b9d 100644
--- a/crashreports/urls.py
+++ b/crashreports/urls.py
@@ -2,15 +2,16 @@
 from . import views
 from . import rest_api_devices
 from . import rest_api_crashreports
-
-
+from . import rest_api_heartbeats
 
 urlpatterns = [
-
     url(r'^api/v1/crashreports/$', rest_api_crashreports.ListCreateCrashReport.as_view(), name='api_v1_crashreports'),
     # url(r'^api/v1/crashreports/(?P<pk>[0-9]+)/$', views.index, name='api_v1_crashreports_single'),    
-    # url(r'^api/v1/crashreports/(?P<pk>[0-9]+)/logfiles/$', views.index, name='api_v1_crashreports_single_logfiles'),
+    #url(r'^api/v1/crashreports/(?P<pk>[0-9]+)/logfiles/$', views.index, name='api_v1_crashreports_single_logfiles'),
     # url(r'^api/v1/crashreports/(?P<pk>[0-9]+)/logfiles/(?P<pk>[0-9]+)/$', views.index, name='api_v1_crashreports_single_logfiles_single'),
+    
+    url(r'^api/v1/heartbeats/$', rest_api_heartbeats.ListCreateHeartBeat.as_view(), name='api_v1_heartbeat'),
+
     # 
     # url(r'^api/v1//logfiles/$',views.index, name='api_v1_logfiles'),
     # url(r'^api/v1//logfiles/(?P<pk>[0-9]+)/$',views.index, name='api_v1_logfiles_single')
diff --git a/crashreports/views.py b/crashreports/views.py
index a25b071..804bd1b 100644
--- a/crashreports/views.py
+++ b/crashreports/views.py
@@ -1,48 +1,14 @@
 # -*- coding: utf-8 -*-
-import datetime
 import django_filters
-import os
-import time
-
-from django.conf import settings
-from django.contrib.auth.decorators import login_required
-from django.core.urlresolvers import reverse
-from django.db.models import Count
-from django.http import Http404
-from django.http import HttpResponse
-from django.http import HttpResponseRedirect
-from django.shortcuts import render
-from django.shortcuts import render_to_response
-from django.template import loader
-from django.views.decorators.csrf import csrf_exempt
-from django.views.static import serve
-
-from ratelimit.decorators import ratelimit
-
 from rest_framework import filters
-from rest_framework import generics
 from rest_framework.permissions import BasePermission
 from rest_framework import viewsets
-
-import rest_framework
-
 from crashreports.models import Crashreport
-
 from crashreports.serializers import CrashReportSerializer
 
-# @login_required
-# def serve_saved_crashreport (request, path):
-#     if settings.DEBUG == False:
-#         response = HttpResponse()
-#         response["Content-Disposition"] = "attachment; filename={0}".format(
-#             os.path.basename(path))
-#         response['X-Accel-Redirect'] = "/hiccup/protected/{0}".format(path)
-#         return response
-#     else:
-#         return serve(request, os.path.basename(path), os.path.dirname(settings.BASE_DIR + "/crashreport_uploads/" + path))
-
 
 class IsCreationOrIsAuthenticated(BasePermission):
+
     def has_permission(self, request, view):
         if not request.user.is_authenticated():
             if view.action == 'create':
@@ -53,24 +19,28 @@
             return True
 
 
-
 class ListFilter(django_filters.Filter):
+
     def filter(self, qs, value):
         value_list = value.split(u',')
-        return super(ListFilter, self).filter(qs, django_filters.fields.Lookup(value_list, 'in'))
+        return super(ListFilter, self).filter(
+            qs, django_filters.fields.Lookup(value_list, 'in'))
 
 
 class CrashreportFilter(filters.FilterSet):
     start_date = django_filters.DateTimeFilter(name="date", lookup_expr='gte')
     end_date = django_filters.DateTimeFilter(name="date", lookup_expr='lte')
     boot_reason = ListFilter(name='boot_reason')
+
     class Meta:
         model = Crashreport
-        fields = ['build_fingerprint','boot_reason', 'power_on_reason', 'power_off_reason']
+        fields = ['build_fingerprint', 'boot_reason',
+                  'power_on_reason', 'power_off_reason']
+
 
 class CrashreportViewSet(viewsets.ModelViewSet):
     queryset = Crashreport.objects.all()
-    serializer_class =  CrashReportSerializer
+    serializer_class = CrashReportSerializer
     permission_classes = [IsCreationOrIsAuthenticated]
     filter_backends = (filters.DjangoFilterBackend,)
     filter_class = CrashreportFilter