Skip to content

Commit

Permalink
Merge pull request #10 from aparsons/v0.0.6-dev
Browse files Browse the repository at this point in the history
v0.0.6
  • Loading branch information
aparsons committed May 15, 2016
2 parents 7bbfe0e + 0850296 commit 6366822
Show file tree
Hide file tree
Showing 33 changed files with 564 additions and 133 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ project/*/migrations/*
!project/boh/migrations/0003_v1_0_1.py
!project/boh/migrations/0004_v1_0_2.py
!project/boh/migrations/0005_v1_0_3.py
!project/boh/migrations/0006_v0_0_6.py
.idea

project/project/settings/dev_aparsons.py
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Bag of Holding

The **Bag of Holding** is an application security utility to assist in the organization and prioritization of software security activities.
The **Bag of Holding** is an application to assist in the organization and prioritization of software security activities.

Check out these talks which cover building your own AppSec pipeline:
* [Matt Tesauro - Taking AppSec to 11: Pipelines, DevOps and making things better](https://www.youtube.com/watch?v=LfVhB3EiDDs)
* [Matt Tesauro - Lessons From DevOps: Taking DevOps Practices Into Your AppSec Life](https://www.youtube.com/watch?v=tDnyFitE0y4)
* [Aaron Weaver - Building An AppSec Pipeline: Keeping Your Program, And Your Life, Sane](https://www.youtube.com/watch?v=1CDSOSl4DQU)
* [Matt Tesauro - Lessons From DevOps: Taking DevOps Practices Into Your AppSec Life](https://www.youtube.com/watch?v=tDnyFitE0y4)

Expand Down
20 changes: 20 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Bag of Holding Releases

## 0.0.6 - 5/15/2016

This release brings several highly requested features. You will find that custom fields can be defined and applied to applications. This functionality was developed so it could easily be applied to other models in the future. Another new piece of functionality is the filtering of metrics so yearly totals can be seen. Enjoy!

*Take note of the new github repository location.*

### What's New

* Metrics filtering by year
* Custom fields
* Application custom fields
* Dependencies
* Minor Bugfixes

### What's New

* ASVS Tracking for Applications
* Metrics Filtering By Year
* Various Bugfixes

## 0.0.5 - 4/20/2016

Maintenance release to update dependencies and remove stagnant code. This release serves as preparation for upcoming changes.
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bag-of-holding",
"version": "0.0.5",
"version": "0.0.6",
"private": true,
"ignore": [
"**/.*",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bag-of-holding",
"version": "0.0.5",
"version": "0.0.6",
"description": "The **Bag of Holding** is an application security utility to assist in the organization and prioritization of software security activities.",
"repository": {
"type": "git",
Expand Down
102 changes: 86 additions & 16 deletions project/boh/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.contrib import admin
from django.core.urlresolvers import reverse
from django.utils.html import format_html
from django.utils.translation import ugettext as _

from . import models

Expand All @@ -19,6 +21,11 @@ class EnvironmentLocationInline(admin.StackedInline):
extra = 0


class ApplicationCustomFieldValueInline(admin.StackedInline):
model = models.ApplicationCustomFieldValue
extra = 0


class RelationInline(admin.StackedInline):
model = models.Relation
fields = ['application', 'person', 'owner', 'emergency', 'notes']
Expand Down Expand Up @@ -59,26 +66,74 @@ class ActivityCommentInline(admin.StackedInline):
extra = 0


class ApplicationFileUploadInline(admin.StackedInline):
model = models.ApplicationFileUpload
extra = 0


class TagAdmin(admin.ModelAdmin):
list_display = ['name', 'color']
search_fields = ['^name']
list_display = ['name', 'color', 'sample', 'truncated_description']
search_fields = ['name']

def truncated_description(self, obj):
from django.utils.text import Truncator
return Truncator(obj.description).chars(40, truncate=' ...')
truncated_description.short_description = _('description')

def sample(self, obj):
return '<span style="background-color:#' + obj.color + '">Example</span>'
sample.allow_tags = True
admin.site.register(models.Tag, TagAdmin)


class CustomFieldAdmin(admin.ModelAdmin):
date_hierarchy = 'created_date'
fieldsets = (
(None, {
'fields': ('name', 'description'),
}),
(_('Identification'), {
'fields': ('key',),
}),
(_('Validation'), {
'fields': ('validation_regex', 'validation_description'),
}),
(_('Metadata'), {
'classes': ('collapse',),
'fields': ('created_date', 'modified_date'),
}),
)
list_display = ['name', 'truncated_description', 'key', 'validation_regex', 'created_date', 'modified_date']
list_filter = ['created_date', 'modified_date']
readonly_fields = ['created_date', 'modified_date']
search_fields = ['name', 'key']

def truncated_description(self, obj):
from django.utils.text import Truncator
return Truncator(obj.description).chars(40, truncate=' ...')
truncated_description.short_description = _('description')
admin.site.register(models.CustomField, CustomFieldAdmin)


class ApplicationCustomFieldValueAdmin(admin.ModelAdmin):
fieldsets = (
(None, {
'fields': ('application', 'custom_field', 'value'),
}),
(_('Metadata'), {
'classes': ('collapse',),
'fields': ('created_date', 'modified_date'),
}),
)
list_display = ['id', 'application', 'custom_field', 'value', 'created_date', 'modified_date']
list_filter = ['created_date', 'modified_date', 'custom_field__name', 'application']
readonly_fields = ['created_date', 'modified_date']
search_fields = ['custom_field__name', 'value', 'application__name', 'custom_field__key']
admin.site.register(models.ApplicationCustomFieldValue, ApplicationCustomFieldValueAdmin)


admin.site.register(models.Organization)


class DataElementAdmin(admin.ModelAdmin):
list_display = ['name', 'category', 'weight']
list_filter = ['category']
search_fields = ['^name']

admin.site.register(models.DataElement, DataElementAdmin)


Expand Down Expand Up @@ -121,7 +176,7 @@ class ApplicationAdmin(admin.ModelAdmin):
]
list_display = ['name', 'business_criticality', 'platform', 'lifecycle', 'origin', 'user_records', 'revenue', 'external_audience', 'internet_accessible', 'dcl_display', 'created_date', 'modified_date']
list_filter = ['business_criticality', 'platform', 'lifecycle', 'origin', 'external_audience', 'internet_accessible', 'tags', 'requestable']
inlines = [EnvironmentInline, RelationInline, EngagementInline, ApplicationFileUploadInline]
inlines = [EnvironmentInline, RelationInline, EngagementInline, ApplicationCustomFieldValueInline]
search_fields = ['^name']

def dcl_display(self, obj):
Expand Down Expand Up @@ -166,25 +221,44 @@ class EngagementAdmin(admin.ModelAdmin):

class ActivityTypeAdmin(admin.ModelAdmin):
list_display = ['name']

admin.site.register(models.ActivityType, ActivityTypeAdmin)


class ActivityAdmin(admin.ModelAdmin):
date_hierarchy = 'open_date'
fieldsets = [
(None, {'fields': ['engagement', 'activity_type', 'status', 'users']}),
('Advanced options', {
'classes': ['collapse'],
'fields': ['open_date', 'close_date', 'duration']
}),
]
list_display = ['__str__', 'status', 'activity_type', 'open_date', 'close_date', 'duration']
list_filter = ['status']
list_display = ['id', 'status', 'activity_type', 'application_link', 'users_list', 'engagement_link', 'open_date', 'close_date', 'duration']
list_filter = ['status', 'open_date', 'close_date', 'activity_type', 'users', 'engagement__application']
inlines = [ActivityCommentInline]
readonly_fields = ['duration']

def application_link(self, obj):
application = obj.engagement.application
application_url = reverse('admin:boh_application_change', args=(application.id,))
return '<a href="%s">%s</a>' % (application_url, application.name)
application_link.allow_tags = True
application_link.short_description = 'Application'

def users_list(self, obj):
return ', '.join(user.get_username() for user in obj.users.all())
users_list.short_description = 'Users'

def engagement_link(self, obj):
engagement = obj.engagement
engagement_url = reverse('admin:boh_engagement_change', args=(engagement.id,))
return '<a href="%s">%s</a>' % (engagement_url, engagement.id)
engagement_link.allow_tags = True
engagement_link.short_description = 'Engagement'

admin.site.register(models.Activity, ActivityAdmin)


admin.site.register(models.ThreadFix)


Expand Down Expand Up @@ -242,14 +316,10 @@ def reference_link(self, obj):
reference_link.admin_order_field = 'reference'
reference_link.allow_tags = True
reference_link.short_description = 'Reference'


admin.site.register(models.Regulation, RegulationAdmin)


class ServiceLevelAgreementAdmin(admin.ModelAdmin):
list_display = ['name', 'description']

admin.site.register(models.ServiceLevelAgreement, ServiceLevelAgreementAdmin)

admin.site.register(models.ExternalRequest)
14 changes: 14 additions & 0 deletions project/boh/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ class PageSizeForm(forms.Form):

# Dashboard

class MetricsYearForm(forms.Form):

def __init__(self, *args, **kwargs):
super(MetricsYearForm, self).__init__(*args, **kwargs)

engagement_years = models.Engagement.objects.distinct_years()
activity_years = models.Activity.objects.distinct_years()
years = engagement_years + list(set(activity_years) - set(engagement_years)) # Combine both lists
years.sort(reverse=True)
self.fields['year'] = forms.ChoiceField(choices=[('', 'All')] + [(year, year) for year in years], required=False)


class EngagementCoverageReportForm(forms.Form):
HTML_FORMAT = 'html'
CSV_FORMAT = 'csv'
Expand Down Expand Up @@ -211,6 +223,7 @@ class Meta:
model = models.Application
fields = ['service_level_agreements']


class ApplicationSettingsASVSForm(forms.ModelForm):
class Meta:
model = models.Application
Expand All @@ -222,6 +235,7 @@ class Meta:
'asvs_level_target': 'Target ASVS Level'
}


class ApplicationSettingsThreadFixForm(forms.ModelForm):
class Meta:
model = models.Application
Expand Down
39 changes: 34 additions & 5 deletions project/boh/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@ class ActivityTypeManager(models.Manager):


class ActivityTypeMetrics(models.Manager):
def stats(self):
def stats(self, year=None):
"""Returns counts of each activity and their average durations."""
from .models import Activity
results = self.get_queryset()
results = results.prefetch_related(Prefetch('activity_set', queryset=Activity.objects.all()))

activity_queryset = Activity.objects.all()
if year:
activity_queryset = activity_queryset.filter(open_date__year=year)

results = results.prefetch_related(Prefetch('activity_set', queryset=activity_queryset))
results = results.annotate(
pending_count=Sum(
Case(When(activity__status=Activity.PENDING_STATUS, then=1), output_field=IntegerField(), default=0)
Expand Down Expand Up @@ -55,14 +60,28 @@ class ActivityTypeQuerySet(models.QuerySet):


class EngagementManager(models.Manager):
pass
def distinct_years(self):
"""Returns a list of distinct years by open_date."""
results = self.get_queryset()
results = results.datetimes('open_date', 'year')

years_list = []

for result in results:
years_list.append(result.year)

return years_list


class EngagementMetrics(models.Manager):
def stats(self):
def stats(self, year=None):
"""Returns counts for each Engagement status and the average duration."""
from .models import Engagement
results = self.get_queryset()

if year:
results = results.filter(open_date__year=year)

results = results.aggregate(
pending_count=Sum(
Case(When(status=Engagement.PENDING_STATUS, then=1), output_field=IntegerField(), default=0)
Expand Down Expand Up @@ -91,7 +110,17 @@ def closed(self):


class ActivityManager(models.Manager):
pass
def distinct_years(self):
"""Returns a list of distinct years by open_date."""
results = self.get_queryset()
results = results.datetimes('open_date', 'year')

years_list = []

for result in results:
years_list.append(result.year)

return years_list


class ActivityQuerySet(models.QuerySet):
Expand Down
Loading

0 comments on commit 6366822

Please sign in to comment.