diff --git a/inventory/__init__.py b/inventory/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/inventory/admin.py b/inventory/admin.py
new file mode 100644
index 00000000..d594b496
--- /dev/null
+++ b/inventory/admin.py
@@ -0,0 +1,11 @@
+from django.contrib import admin
+from inventory.models import Inventory, InventoryItem
+
+class InventoryAdmin(admin.ModelAdmin):
+ list_display = ('machine', 'datestamp', 'sha256hash')
+
+class InventoryItemAdmin(admin.ModelAdmin):
+ list_display = ('name', 'version', 'path')
+
+admin.site.register(Inventory, InventoryAdmin)
+admin.site.register(InventoryItem, InventoryItemAdmin)
\ No newline at end of file
diff --git a/inventory/migrations/0001_initial.py b/inventory/migrations/0001_initial.py
new file mode 100644
index 00000000..2d260153
--- /dev/null
+++ b/inventory/migrations/0001_initial.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('server', '0014_auto_20150817_1646'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Inventory',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('datestamp', models.DateTimeField(auto_now=True)),
+ ('sha256hash', models.CharField(max_length=64)),
+ ('machine', models.ForeignKey(to='server.Machine')),
+ ],
+ options={
+ 'ordering': ['datestamp'],
+ },
+ ),
+ migrations.CreateModel(
+ name='InventoryItem',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=255)),
+ ('version', models.CharField(max_length=32)),
+ ('bundleid', models.CharField(max_length=255)),
+ ('bundlename', models.CharField(max_length=255)),
+ ('path', models.TextField()),
+ ('machine', models.ForeignKey(to='server.Machine')),
+ ],
+ options={
+ 'ordering': ['name', '-version'],
+ },
+ ),
+ ]
diff --git a/inventory/migrations/__init__.py b/inventory/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/inventory/models.py b/inventory/models.py
new file mode 100644
index 00000000..f8d4eadd
--- /dev/null
+++ b/inventory/models.py
@@ -0,0 +1,21 @@
+from django.db import models
+from server.models import *
+# Create your models here.
+
+class Inventory(models.Model):
+ machine = models.ForeignKey(Machine)
+ datestamp = models.DateTimeField(auto_now=True)
+ sha256hash = models.CharField(max_length=64)
+ class Meta:
+ ordering = ['datestamp']
+
+
+class InventoryItem(models.Model):
+ machine = models.ForeignKey(Machine)
+ name = models.CharField(max_length=255)
+ version = models.CharField(max_length=32)
+ bundleid = models.CharField(max_length=255)
+ bundlename = models.CharField(max_length=255)
+ path = models.TextField()
+ class Meta:
+ ordering = ['name', '-version']
\ No newline at end of file
diff --git a/inventory/templates/inventory/index.html b/inventory/templates/inventory/index.html
new file mode 100644
index 00000000..bd307cc6
--- /dev/null
+++ b/inventory/templates/inventory/index.html
@@ -0,0 +1,70 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load dashboard_extras %}
+
+{% block script %}
+
+
+{% endblock %}
+
+{% block nav %}
+
Back
+
+
+{% endblock %}
+{% block content %}
+
+
+
+
+ Application Inventory
+
+
+
+
+
+
+
+ Name |
+ Version |
+ Bundle ID |
+
+
+
+ {% for item in inventory|dictsort:'name' %}
+
+
+
+ {{ item.name }}
+
+ |
+
+ {{ item.version }} |
+ {{ item.bundleid }} |
+
+ {% endfor %}
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/inventory/tests.py b/inventory/tests.py
new file mode 100644
index 00000000..7ce503c2
--- /dev/null
+++ b/inventory/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/inventory/urls.py b/inventory/urls.py
new file mode 100644
index 00000000..1544d30e
--- /dev/null
+++ b/inventory/urls.py
@@ -0,0 +1,10 @@
+from django.conf.urls import url
+from inventory import views
+
+urlpatterns = [
+ url(r'^submit/$', views.inventory_submit),
+ url(r'^hash/(?P.+)/$', views.inventory_hash),
+ url(r'^business_unit/(?P.+)/$', views.bu_inventory),
+ url(r'^machine_group/(?P.+)/$', views.machine_group_inventory),
+ url(r'^$', views.index),
+]
diff --git a/inventory/views.py b/inventory/views.py
new file mode 100644
index 00000000..88bc2bbf
--- /dev/null
+++ b/inventory/views.py
@@ -0,0 +1,183 @@
+from django.http import HttpResponse, HttpRequest, HttpResponseRedirect
+from django.template import RequestContext, Template, Context
+from django.shortcuts import render_to_response
+from django.core.context_processors import csrf
+from django.views.decorators.csrf import csrf_exempt
+from django.core.urlresolvers import reverse
+from django.http import Http404
+#from django.contrib.auth import authenticate, login, logout
+from django.contrib.auth.decorators import login_required, permission_required
+from django.conf import settings
+from django import forms
+from django.db.models import Q
+from django.db.models import Count
+from server import utils
+from django.shortcuts import render_to_response, get_object_or_404, redirect
+
+import plistlib
+import base64
+import bz2
+import hashlib
+import json
+
+from datetime import datetime
+import urllib2
+from xml.etree import ElementTree
+
+from models import *
+from server.models import *
+
+def decode_to_string(base64bz2data):
+ '''Decodes an inventory submission, which is a plist-encoded
+ list, compressed via bz2 and base64 encoded.'''
+ try:
+ bz2data = base64.b64decode(base64bz2data)
+ return bz2.decompress(bz2data)
+ except Exception:
+ return ''
+
+def unique_apps(inventory):
+ found = []
+ for inventory_item in inventory:
+ found_flag = False
+ for found_item in found:
+ if (inventory_item.name == found_item['name'] and
+ inventory_item.version == found_item['version'] and
+ inventory_item.bundleid == found_item['bundleid'] and
+ inventory_item.bundlename == found_item['bundlename'] and
+ inventory_item.path == found_item['path']):
+ found_flag = True
+ break
+ if found_flag == False:
+ found_item = {}
+ found_item['name'] = inventory_item.name
+ found_item['version'] = inventory_item.version
+ found_item['bundleid'] = inventory_item.bundleid
+ found_item['bundlename'] = inventory_item.bundlename
+ found_item['path'] = inventory_item.path
+ found.append(found_item)
+ return found
+
+@csrf_exempt
+def inventory_submit(request):
+ if request.method != 'POST':
+ raise Http404
+
+ # list of bundleids to ignore
+ bundleid_ignorelist = [
+ 'com.apple.print.PrinterProxy'
+ ]
+ submission = request.POST
+ serial = submission.get('serial')
+ machine = None
+ if serial:
+ try:
+ machine = Machine.objects.get(serial=serial)
+ except Machine.DoesNotExist:
+ raise Http404
+
+ compressed_inventory = submission.get('base64bz2inventory')
+ if compressed_inventory:
+ compressed_inventory = compressed_inventory.replace(" ", "+")
+ inventory_str = decode_to_string(compressed_inventory)
+ try:
+ inventory_list = plistlib.readPlistFromString(inventory_str)
+ except Exception:
+ inventory_list = None
+ if inventory_list:
+ try:
+ inventory_meta = Inventory.objects.get(machine=machine)
+ except Inventory.DoesNotExist:
+ inventory_meta = Inventory(machine=machine)
+ inventory_meta.sha256hash = \
+ hashlib.sha256(inventory_str).hexdigest()
+ # clear existing inventoryitems
+ machine.inventoryitem_set.all().delete()
+ # insert current inventory items
+ for item in inventory_list:
+ # skip items in bundleid_ignorelist.
+ if not item.get('bundleid') in bundleid_ignorelist:
+ i_item = machine.inventoryitem_set.create(
+ name=item.get('name', ''),
+ version=item.get('version', ''),
+ bundleid=item.get('bundleid', ''),
+ bundlename=item.get('CFBundleName', ''),
+ path=item.get('path', '')
+ )
+ machine.last_inventory_update = datetime.now()
+ inventory_meta.save()
+ machine.save()
+ return HttpResponse(
+ "Inventory submmitted for %s.\n" %
+ submission.get('serial'))
+
+ return HttpResponse("No inventory submitted.\n")
+
+
+def inventory_hash(request, serial):
+ sha256hash = ''
+ machine = None
+ if serial:
+ try:
+ machine = Machine.objects.get(serial=serial)
+ inventory_meta = Inventory.objects.get(machine=machine)
+ sha256hash = inventory_meta.sha256hash
+ except (Machine.DoesNotExist, Inventory.DoesNotExist):
+ pass
+ else:
+ raise Http404
+ return HttpResponse(sha256hash)
+
+
+@login_required
+def index(request):
+ # This really should just select on the BU's the user has access to like the
+ # Main page, but this will do for now
+ user = request.user
+ user_level = user.userprofile.level
+ if user_level != 'GA':
+ return redirect(index)
+ inventory = InventoryItem.objects.all()
+ found = unique_apps(inventory)
+
+ c = {'user': request.user, 'inventory': found, 'page':'front', 'request': request }
+ return render_to_response('inventory/index.html', c, context_instance=RequestContext(request))
+
+@login_required
+def bu_inventory(request, bu_id):
+ user = request.user
+ user_level = user.userprofile.level
+ business_unit = get_object_or_404(BusinessUnit, pk=bu_id)
+ if business_unit not in user.businessunit_set.all() and user_level != 'GA':
+ print 'not letting you in ' + user_level
+ return redirect(index)
+ # Get the groups within the Business Unit
+ machines = utils.getBUmachines(bu_id)
+
+ inventory = []
+ for machine in machines:
+ for item in machine.inventoryitem_set.all():
+ inventory.append(item)
+
+ found = unique_apps(inventory)
+ c = {'user': request.user, 'inventory': found, 'page':'business_unit', 'business_unit':business_unit, 'request': request}
+ return render_to_response('inventory/index.html', c, context_instance=RequestContext(request))
+
+@login_required
+def machine_group_inventory(request, group_id):
+ user = request.user
+ user_level = user.userprofile.level
+ machine_group = get_object_or_404(MachineGroup, pk=group_id)
+ business_unit = machine_group.business_unit
+ if business_unit not in user.businessunit_set.all() and user_level != 'GA':
+ print 'not letting you in ' + user_level
+ return redirect(index)
+
+ inventory = []
+ for machine in machine_group.machine_set.all():
+ for item in machine.inventoryitem_set.all():
+ inventory.append(item)
+
+ found = unique_apps(inventory)
+ c = {'user': request.user, 'inventory': found, 'page':'business_unit', 'business_unit':business_unit, 'request': request}
+ return render_to_response('inventory/index.html', c, context_instance=RequestContext(request))
\ No newline at end of file
diff --git a/sal/system_settings.py b/sal/system_settings.py
index f54c49e3..83712df3 100644
--- a/sal/system_settings.py
+++ b/sal/system_settings.py
@@ -180,6 +180,7 @@
'sal',
'server',
'api',
+ 'inventory',
'bootstrap3',
)
diff --git a/sal/urls.py b/sal/urls.py
index c3bba379..38eb3029 100644
--- a/sal/urls.py
+++ b/sal/urls.py
@@ -18,7 +18,8 @@
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
- (r'^api/', include('api.urls'))
+ (r'^api/', include('api.urls')),
+ (r'^inventory/', include('inventory.urls'))
#url(r'^$', 'namer.views.index', name='home'),
)
diff --git a/server/migrations/0014_auto_20150817_1646.py b/server/migrations/0014_auto_20150817_1646.py
new file mode 100644
index 00000000..370b3afe
--- /dev/null
+++ b/server/migrations/0014_auto_20150817_1646.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('server', '0013_auto_20150816_1652'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='osqueryresult',
+ name='unix_time',
+ field=models.IntegerField(default=1),
+ preserve_default=False,
+ ),
+ ]
diff --git a/server/plugins/topprocesses/templates/front.html b/server/plugins/topprocesses/templates/front.html
index c211176e..db917d51 100644
--- a/server/plugins/topprocesses/templates/front.html
+++ b/server/plugins/topprocesses/templates/front.html
@@ -8,14 +8,16 @@
diff --git a/server/plugins/topprocesses/templates/id.html b/server/plugins/topprocesses/templates/id.html
index b38e5bbf..aa1c6348 100644
--- a/server/plugins/topprocesses/templates/id.html
+++ b/server/plugins/topprocesses/templates/id.html
@@ -8,14 +8,16 @@
diff --git a/server/plugins/topprocesses/topprocesses.py b/server/plugins/topprocesses/topprocesses.py
index a35bed6f..8c47dc89 100644
--- a/server/plugins/topprocesses/topprocesses.py
+++ b/server/plugins/topprocesses/topprocesses.py
@@ -38,7 +38,6 @@ def show_widget(self, page, machines=None, theid=None):
info = OSQueryColumn.objects.filter(osquery_result__name='pack_sal_top_processes').filter(osquery_result__machine=machines).filter(column_name='name').values('column_data').annotate(data_count=Count('column_data')).order_by('-data_count')[:100:1]
else:
info = []
-
c = Context({
'title': 'Top Processes',
'data': info,
@@ -51,6 +50,6 @@ def filter_machines(self, machines, data):
# You will be passed a QuerySet of machines, you then need to perform some filtering based on the 'data' part of the url from the show_widget output. Just return your filtered list of machines and the page title.
machines = machines.filter(osquery_results__osquery_columns__column_data__exact=data).filter(osquery_results__name__exact='pack_sal_top_processes').distinct()
- print machines
+
return machines, 'Machines running '+data
\ No newline at end of file
diff --git a/server/templates/server/bu_dashboard.html b/server/templates/server/bu_dashboard.html
index 53517861..4e977cfc 100755
--- a/server/templates/server/bu_dashboard.html
+++ b/server/templates/server/bu_dashboard.html
@@ -10,6 +10,7 @@
{% if user.userprofile.level == 'GA' %}
Back
{% endif %}
+ Application Inventory
{{business_unit}}
diff --git a/server/templates/server/group_dashboard.html b/server/templates/server/group_dashboard.html
index c58ade7b..a79c2e2d 100755
--- a/server/templates/server/group_dashboard.html
+++ b/server/templates/server/group_dashboard.html
@@ -7,7 +7,7 @@
{% block nav %}
- Back
-
+- Application Inventory
-
{{business_unit}}
diff --git a/server/templates/server/index.html b/server/templates/server/index.html
index 65910471..7980149c 100755
--- a/server/templates/server/index.html
+++ b/server/templates/server/index.html
@@ -15,6 +15,9 @@
{% endif %}
{% endif %}
{% endif %}
+ {% if user.userprofile.level == 'GA' %}
+ - Application Inventory
+ {% endif %}
-
Business Units