Skip to content

Commit

Permalink
First look at inventory
Browse files Browse the repository at this point in the history
  • Loading branch information
grahamgilbert committed Aug 17, 2015
1 parent e17b863 commit 90ab6a3
Show file tree
Hide file tree
Showing 18 changed files with 374 additions and 6 deletions.
Empty file added inventory/__init__.py
Empty file.
11 changes: 11 additions & 0 deletions inventory/admin.py
Original file line number Diff line number Diff line change
@@ -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)
41 changes: 41 additions & 0 deletions inventory/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -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'],
},
),
]
Empty file.
21 changes: 21 additions & 0 deletions inventory/models.py
Original file line number Diff line number Diff line change
@@ -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']
70 changes: 70 additions & 0 deletions inventory/templates/inventory/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{% extends "base.html" %}
{% load i18n %}
{% load dashboard_extras %}

{% block script %}
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
$('.groupList').dataTable({
//"sDom": "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>",
// "iDisplayLength": 20,
"aLengthMenu": [[20, 50, -1], [20, 50, "All"]],
// "sPaginationType": "bootstrap",
// "bStateSave": true,
// "aaSorting": [[1,'asc']]
});
} );
</script>
<style type="text/css">
.tooltip-inner {
max-width: 1000px;
max-width: 1000px;
}
</style>
{% endblock %}

{% block nav %}
<li><a href="{{ request.META.HTTP_REFERER }}"><i class="fa fa-fw fa-chevron-left"></i> Back</a></li>


{% endblock %}
{% block content %}

<div class="panel panel-default">

<div class="panel-heading">
Application Inventory
</div>

<div class="panel-body">
<div class="table-responsive">
<table class="groupList table table-striped table-condensed"">
<thead>
<tr>
<th>Name</th>
<th>Version</th>
<th>Bundle ID</th>
</tr>
</thead>
<tbody>
{% for item in inventory|dictsort:'name' %}
<tr>
<td>
<a href="#" data-toggle="tooltip" data-placement="right" title="{{ item.path }}">
{{ item.name }}
</a>
</td>

<td>{{ item.version }}</td>
<td>{{ item.bundleid }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
3 changes: 3 additions & 0 deletions inventory/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
10 changes: 10 additions & 0 deletions inventory/urls.py
Original file line number Diff line number Diff line change
@@ -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<serial>.+)/$', views.inventory_hash),
url(r'^business_unit/(?P<bu_id>.+)/$', views.bu_inventory),
url(r'^machine_group/(?P<group_id>.+)/$', views.machine_group_inventory),
url(r'^$', views.index),
]
183 changes: 183 additions & 0 deletions inventory/views.py
Original file line number Diff line number Diff line change
@@ -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))
1 change: 1 addition & 0 deletions sal/system_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
'sal',
'server',
'api',
'inventory',
'bootstrap3',
)

Expand Down
3 changes: 2 additions & 1 deletion sal/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),

)
Expand Down
20 changes: 20 additions & 0 deletions server/migrations/0014_auto_20150817_1646.py
Original file line number Diff line number Diff line change
@@ -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,
),
]
Loading

0 comments on commit 90ab6a3

Please sign in to comment.