Permalink
Browse files

Add models for Report and Group.

  • Loading branch information...
James Socol
James Socol committed Jul 12, 2012
1 parent 149e266 commit 84c42e6c6efe95b2b7e19abae2a4616a7ef11a8e
Showing with 227 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +27 −0 csp/admin.py
  3. +76 −0 csp/models.py
  4. 0 examples/csp-project/__init__.py
  5. +50 −0 examples/csp-project/settings.py
  6. +15 −0 examples/csp-project/urls.py
  7. +50 −0 fabfile.py
  8. +8 −0 requirements.txt
View
@@ -1,3 +1,4 @@
*.pyc
*.egg-info
+*.db
dist
View
@@ -0,0 +1,27 @@
+from django.contrib import admin
+
+from csp.models import Group, Report
+
+
+class GroupAdmin(admin.ModelAdmin):
+ fields = ('name', 'identifier',)
+ list_display = ('name', 'identifier', 'count',)
+ readonly_fields = ('identifier',)
+
+ def has_add_permission(*a, **kw):
+ return False
+
+
+class ReportAdmin(admin.ModelAdmin):
+ date_hierarchy = 'reported'
+ list_display = ('get_identifier', 'document_uri', 'blocked_uri',
+ 'violated_directive', 'referrer', 'reported')
+ readonly_fields = ('group', 'document_uri', 'blocked_uri', 'referrer',
+ 'violated_directive', 'original_policy', 'reported')
+
+ def has_add_permission(*a, **kw):
+ return False
+
+
+admin.site.register(Group, GroupAdmin)
+admin.site.register(Report, ReportAdmin)
View
@@ -0,0 +1,76 @@
+from datetime import datetime
+import hashlib
+import json
+
+from django.db import models
+
+
+__all__ = ['Group', 'Report']
+
+
+class Group(models.Model):
+ """A group of similar violation reports."""
+ name = models.CharField(max_length=200, verbose_name='Report Group',
+ help_text='A human-readable name for a group.')
+ identifier = models.CharField(max_length=40, verbose_name='Group Hash',
+ help_text='A unique identifier for a group.',
+ unique=True)
+
+ def __unicode__(self):
+ return self.name
+
+ @classmethod
+ def get_or_create(cls, report):
+ """Given a CSP report, find an existing group or create a new one."""
+ ident = report.get_identifier()
+ try:
+ return cls.objects.get(identifier=ident)
+ except cls.DoesNotExist as e:
+ # We're going to have to create a new group.
+ pass
+ name = u'%s - %s' % (report.document_uri, report.violated_directive)
+ group = cls(name=name, identifier=ident)
+ # TODO: Either use a signal or send a notice here.
+ group.save()
+ return group
+
+ def count(self):
+ if not hasattr(self, '_count'):
+ self._count = self.report_set.count()
+ return self._count
+
+
+class Report(models.Model):
+ """A representation of one report."""
+ group = models.ForeignKey(Group, null=True, blank=True)
+ document_uri = models.URLField(max_length=400, db_index=True)
+ blocked_uri = models.URLField(max_length=400, null=True, blank=True,
+ db_index=True)
+ referrer = models.URLField(max_length=400, null=True, blank=True)
+ violated_directive = models.CharField(max_length=1000, null=True,
+ blank=True, db_index=True)
+ original_policy = models.TextField(null=True, blank=True)
+ reported = models.DateTimeField(default=datetime.now, db_index=True)
+
+
+ @classmethod
+ def create(cls, report):
+ """If passed a JSON blob in the report kwarg, use it."""
+ report = json.loads(report)['csp-report']
+ kw.update((k.replace('-', '_'), v) for k, v in report.items())
+ return cls(**kw)
+
+ def __unicode__(self):
+ return self.get_identifier()
+
+ def get_identifier(self):
+ if not hasattr(self, '_ident'):
+ ident = u'%s %s %s' % (self.document_uri, self.blocked_uri,
+ self.violated_directive)
+ self._ident = hashlib.sha1(ident).hexdigest()
+ return self._ident
+
+ def save(self, *a, **kw):
+ if not self.group:
+ self.group = Group.get_or_create(self)
+ super(Report, self).save(*a, **kw)
No changes.
@@ -0,0 +1,50 @@
+import os
+
+# Make filepaths relative to settings.
+ROOT = os.path.dirname(os.path.abspath(__file__))
+path = lambda *a: os.path.join(ROOT, *a)
+
+DEBUG = True
+TEMPLATE_DEBUG = True
+
+JINJA_CONFIG = {}
+
+SITE_ID = 1
+
+TEST_RUNNER = 'django_nose.runner.NoseTestSuiteRunner'
+
+DATABASES = {
+ 'default': {
+ 'NAME': 'test.db',
+ 'ENGINE': 'django.db.backends.sqlite3',
+ }
+}
+
+INSTALLED_APPS = (
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django_nose',
+ 'south',
+ 'csp',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+ROOT_URLCONF = 'csp-project.urls'
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+ 'django.contrib.auth.context_processors.auth',
+ 'django.core.context_processors.request',
+)
+
+WAFFLE_FLAG_DEFAULT = False
+WAFFLE_SWITCH_DEFAULT = False
+WAFFLE_SAMPLE_DEFAULT = False
+WAFFLE_OVERRIDE = False
@@ -0,0 +1,15 @@
+from django.conf.urls.defaults import patterns, url, include
+from django.contrib import admin
+from django.http import HttpResponseNotFound, HttpResponseServerError
+
+from csp import views
+
+
+handler404 = lambda r: HttpResponseNotFound()
+handler500 = lambda r: HttpResponseServerError()
+
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ (r'^admin/', include(admin.site.urls))
+)
View
@@ -0,0 +1,50 @@
+"""
+Creating standalone Django apps is a PITA because you're not in a project, so
+you don't have a settings.py file. I can never remember to define
+DJANGO_SETTINGS_MODULE, so I run these commands which get the right env
+automatically.
+"""
+import functools
+import os
+
+from fabric.api import local as _local
+
+
+NAME = os.path.basename(os.path.dirname(__file__))
+ROOT = os.path.abspath(os.path.dirname(__file__))
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'csp-project.settings'
+os.environ['PYTHONPATH'] = os.pathsep.join([ROOT,
+ os.path.join(ROOT, 'examples')])
+
+_local = functools.partial(_local, capture=False)
+
+
+def shell():
+ """Start a Django shell with the test settings."""
+ _local('django-admin.py shell')
+
+
+def test():
+ """Run the Waffle test suite."""
+ _local('django-admin.py test')
+
+
+def serve():
+ """Start the Django dev server."""
+ _local('django-admin.py runserver 0:8000')
+
+
+def syncdb():
+ """Create a database for testing in the shell or server."""
+ _local('django-admin.py syncdb')
+
+
+def schema():
+ """Create a schema migration for any changes."""
+ _local('django-admin.py schemamigration waffle --auto')
+
+
+def migrate():
+ """Update a testing database with south."""
+ _local('django-admin.py migrate')
View
@@ -0,0 +1,8 @@
+# These are required to run the tests.
+nose
+mock
+fabric
+Django==1.3.1
+south
+-e git+git://github.com/jbalogh/test-utils.git#egg=test-utils
+-e git+git://github.com/jbalogh/django-nose.git#egg=django-nose

0 comments on commit 84c42e6

Please sign in to comment.