Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration Request 5 #12

Merged
merged 51 commits into from
Jun 4, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
9399395
Renamed SWID measurement endpoint
dbrgn May 27, 2014
91188f2
Added coveralls
dbrgn May 27, 2014
81c3e54
Handle entities with multiple roles
dbrgn May 27, 2014
174e4b6
Improved & tested enforcement save view
dbrgn May 27, 2014
eba7db6
Added SQL_DEBUG setting
dbrgn May 27, 2014
b689dd3
Optimized database access in swid-measurement endpoint
dbrgn May 27, 2014
b4b3d76
Moved inventory und log view js files to swid app
cfaessler May 28, 2014
3cab39a
Added sorting to reported by device table
cfaessler May 27, 2014
cb4e1a4
Changed result table-headers from td to th
May 28, 2014
1ded3d0
Prevent dates and some titles from wrapping
May 28, 2014
d2abb28
Provided possibility to add file via gui
May 28, 2014
5507743
Moved swid log and inventory button to device report view
cfaessler May 28, 2014
4c4876f
Inital set of hash-params should not influence browser history
May 28, 2014
d85b593
Fixed some schema problems that occur with MySQL
dbrgn May 29, 2014
9fdcc2f
Added initial data fixtures
dbrgn May 29, 2014
ee59076
Enabled non-interactive mode on setpassword command
dbrgn May 29, 2014
7ee3c25
Attached event to datepicker button
May 29, 2014
2f12465
Added sorting to file paging
dbrgn May 29, 2014
1793786
Added markdown as dependency
dbrgn May 29, 2014
9076340
Added --no-cov option to runtests.py
dbrgn May 29, 2014
9e70f70
Use data param for submitting a SWID measurement or tag
dbrgn May 29, 2014
58e6af4
Paging for dynamic tables
May 29, 2014
d40d29e
Extended view to allow creation and removal of versions
cfaessler May 29, 2014
ad057b9
Added grouping by entity name to tag detail view
cfaessler May 27, 2014
d4c1f67
Implemented result highlighting in paging
dbrgn May 30, 2014
28e965c
Decoded filter query
May 31, 2014
cc15938
Added additional reverse lookup data to several views
May 30, 2014
43a5d3e
Changed swid tag import to allow entity name update
cfaessler May 29, 2014
c837747
Raise KeyError on invalid dynamic_params
dbrgn May 31, 2014
b1d5784
Added error callback to all dajaxice calls
dbrgn May 31, 2014
14c9957
Fixed most recent session in device report
May 31, 2014
15334e5
Renamed `swid_log` url to `log`
dbrgn Jun 1, 2014
02dffad
Require login for swid views and statistics
dbrgn Jun 1, 2014
680fdc3
Added a `fields` param to all API endpoints
dbrgn Jun 1, 2014
d2a2d3a
Revised SWID inventory
May 31, 2014
c19ef9d
Removed all users and sessions from default db
dbrgn Jun 1, 2014
9a71cd0
Switched to custom API permission class
dbrgn Jun 1, 2014
efb7611
Added CSRF_COOKIE_SECURE to settings.ini
dbrgn Jun 1, 2014
61c4297
Enabled clickjacking protection
dbrgn Jun 1, 2014
9df83b6
Added paging to potentially large tables
May 31, 2014
a7a7940
Added dajax wrapper and loading indicators
cfaessler May 31, 2014
233d686
Moved ajax-utils.js content to common.js
cfaessler Jun 2, 2014
8e5a44e
Added error handler proxy to remove ajax indicators on failure
cfaessler Jun 2, 2014
0215905
Added version column to regid detail view
cfaessler May 27, 2014
e8dc736
Reordered save and delete button in package view
cfaessler May 31, 2014
3779528
Added matching package link and highlighting to inventory view
cfaessler Jun 2, 2014
85570a0
Added package name and version column to log view
cfaessler Jun 2, 2014
2e0b47f
Avoided multiple ajax requests caused by hashchanged events
Jun 2, 2014
77800e2
Changed default timezone to UTC
dbrgn Jun 2, 2014
2dbf786
Fixed get_sessions_in_range
dbrgn May 31, 2014
75a4b67
Added frontend validation for creating files
Jun 2, 2014
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ script:
- cp config/settings.sample.ini config/settings.ini
- sed -i 's/DEBUG\s*=\s*0/DEBUG = 1/' config/settings.ini
- ./runtests.py
after_script:
- pip install --quiet --use-mirrors coveralls
- coveralls
31 changes: 28 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ strongTNC
=========

.. image:: https://travis-ci.org/strongswan/strongTNC.png?branch=master
:target: https://travis-ci.org/strongswan/strongTNC
:alt: Build status
:target: https://travis-ci.org/strongswan/strongTNC
:alt: Build status

.. image:: https://coveralls.io/repos/strongswan/strongTNC/badge.png?branch=master
:target: https://coveralls.io/r/strongswan/strongTNC
:alt: Test coverage

.. image:: https://landscape.io/github/strongswan/strongTNC/master/landscape.png
:target: https://landscape.io/github/strongswan/strongTNC/master
Expand Down Expand Up @@ -78,13 +82,18 @@ Now you can start the development server. ::

The web interface should be available on ``http://localhost:8000/``.

**Debugging**

If you want to use the django debug toolbar, install it via pip::

pip install django-debug-toolbar

Then start the server with the setting ``DEBUG_TOOLBAR = 1`` (in
Then start the server with the setting ``[debug] DEBUG_TOOLBAR = 1`` (in
``settings.ini``).

To print all executed SQL queries to stdout, start the server with the setting
``[debug] SQL_DEBUG = 1`` (in ``settings.ini``).


Testing
-------
Expand All @@ -97,6 +106,22 @@ Run the tests::

./runtests.py

Run a specific test file::

./runtests.py tests/<filename>

Run only tests matching a specific pattern::

./runtests.py -k <pattern>

Run only tests that failed the last time::

./runtests.py --lf

Run tests without coverage::

./runtests.py --no-cov

Setup a database with test data::

$ ./manage.py shell
Expand Down
37 changes: 37 additions & 0 deletions apps/api/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import, unicode_literals

try:
from djangorestframework_camel_case.parser import camel_to_underscore
except ImportError:
camel_to_underscore = lambda x: x


class DynamicFieldsMixin(object):
"""
A serializer mixin that takes an additional `fields` argument that controls
which fields should be displayed.

If the djangorestframework_camel_case package is installed, the field names
are converted from camelCase to under_scores.

Usage::

class MySerializer(DynamicFieldsMixin, serializers.HyperlinkedModelSerializer):
class Meta:
model = MyModel

"""
def __init__(self, *args, **kwargs):
super(DynamicFieldsMixin, self).__init__(*args, **kwargs)
request = self.context.get('request')
if request:
fields = request.QUERY_PARAMS.get('fields')
if fields:
fields = fields.split(',')
fields = map(camel_to_underscore, fields)
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
18 changes: 14 additions & 4 deletions apps/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rest_framework import routers

from apps.core.api_views import IdentityViewSet, SessionViewSet
from apps.swid.api_views import EntityViewSet, TagViewSet, TagAddView
from apps.swid.api_views import EntityViewSet, TagViewSet, TagAddView, SwidMeasurementView


# Create router
Expand All @@ -18,11 +18,21 @@
router.register(r'swid-entities', EntityViewSet)
router.register(r'swid-tags', TagViewSet)

# Generate URL configuration
# Generate basic URL configuration
urlpatterns = router.urls


# API URLs
# Register additional endpoints
urlpatterns += patterns('',
# Auth views
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),

# Add tags
url(r'^swid/add-tags/', TagAddView.as_view(), name='swid-add-tags'),
url(r'^swid/add-tags/\.(?P<format>[a-z0-9]+)', TagAddView.as_view(), name='swid-add-tags'),

# Register measurement
url(r'^sessions/(?P<pk>[^/]+)/swid-measurement/',
SwidMeasurementView.as_view(), name='session-swid-measurement'),
url(r'^sessions/(?P<pk>[^/]+)/swid-measurement/\.(?P<format>[a-z0-9]+)',
SwidMeasurementView.as_view(), name='session-swid-measurement'),
)
24 changes: 24 additions & 0 deletions apps/api/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
"""
Helper functions used for API-related tasks.
"""
from __future__ import print_function, division, absolute_import, unicode_literals

from rest_framework.response import Response


def make_message(message, status_code):
"""
Generate and return an API Response.

Args:
message:
The message to be returned in the response.
status_code:
The HTTP status code for the response.

Returns:
A :class:`rest_framework.response.Response` instance.

"""
return Response({'detail': message}, status=status_code)
27 changes: 19 additions & 8 deletions apps/auth/management/commands/setpassword.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,35 @@
Usage: ./manage.py setpassword [password]
"""

import sys
from getpass import getpass

from django.contrib.auth import get_user_model
from django.core.management.base import NoArgsCommand
from django.core.management.base import BaseCommand

from apps.auth.permissions import GlobalPermission


class Command(NoArgsCommand):
class Command(BaseCommand):
"""
Required class to be recognized by manage.py.
"""
help = 'Get or create admin-user and set password interactively'
help = 'Get or create admin-user and set password'
args = '[<readonly_password> <readwrite_password>]'

def handle_noargs(self, **kwargs):
self.process_user('admin-user', write_access=True)
self.process_user('readonly-user')
def handle(self, *args, **kwargs):
if len(args) == 0:
readonly_pw = admin_pw = None
elif len(args) == 2:
(readonly_pw, admin_pw) = args
else:
self.stderr.write('You must either specify both paswords, or none at all.')
sys.exit(1)
self.process_user('admin-user', write_access=True, pwd=admin_pw)
self.process_user('readonly-user', pwd=readonly_pw)
self.stdout.write('Passwords updated succesfully!')

def process_user(self, username, write_access=False):
def process_user(self, username, write_access=False, pwd=None):
"""
Get or create user, set password and set permissions.

Expand All @@ -46,7 +56,8 @@ def process_user(self, username, write_access=False):
self.stdout.write('--> User "%s" not found. Creating new user.' % username)

# Set password
pwd = getpass('--> Please enter a new password for %s: ' % username)
if pwd is None:
pwd = getpass('--> Please enter a new password for %s: ' % username)
user.set_password(pwd)
user.save()

Expand Down
22 changes: 22 additions & 0 deletions apps/auth/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

from rest_framework import permissions, exceptions


class GlobalPermissionManager(models.Manager):
"""
Expand Down Expand Up @@ -48,3 +50,23 @@ def save(self, *args, **kwargs):
# Assign the 'global_permission' content type to this permission
self.content_type = ct
super(GlobalPermission, self).save(*args, **kwargs)


class IsStaffOrHasWritePerm(permissions.BasePermission):
"""
Django Rest Framework permission class.

It allows access to the API if it has the `is_staff` flag set, or
if it has the global `auth.write_access` permission assigned. (See
`apps/auth/management/commands/setpassword.py` to see an example on
how to assign that permission programmatically.)

"""
def has_permission(self, request, view):
if not request.user.is_authenticated():
return False
if request.user.is_staff:
return True
if request.user.has_perm('auth.write_access'):
return True
return False
45 changes: 2 additions & 43 deletions apps/core/api_views.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import, unicode_literals

from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import viewsets

from apps.core import models as core_models
from apps.swid import models as swid_models
from apps.swid.utils import chunked_bulk_create
from . import models
from . import serializers
from . import models, serializers


class IdentityViewSet(viewsets.ReadOnlyModelViewSet):
Expand All @@ -20,38 +14,3 @@ class IdentityViewSet(viewsets.ReadOnlyModelViewSet):
class SessionViewSet(viewsets.ReadOnlyModelViewSet):
model = models.Session
serializer_class = serializers.SessionSerializer

@action()
def swid_measurement(self, request, pk=None):
""""
Link the given software-ids with the current session.
If no corresponding tag is available for one or more software-ids, return these software-ids
with HTTP status code 412 Precondition failed.

TODO: move this controller to separate file

"""
software_ids = request.DATA
found_tags = []
missing_tags = []

# Look for matching tags
for software_id in software_ids:
try:
tag = swid_models.Tag.objects.get(software_id=software_id)
found_tags.append(tag)
except swid_models.Tag.DoesNotExist:
missing_tags.append(software_id)

if missing_tags:
# Some tags are missing
return Response(data=missing_tags, status=status.HTTP_412_PRECONDITION_FAILED)
else:
# All tags are available: link them with a session
try:
session = core_models.Session.objects.get(pk=pk)
except core_models.Session.DoesNotExist:
data = {'status': 'error', 'message': 'Session with id "%s" not found.' % pk}
return Response(data=data, status=status.HTTP_404_NOT_FOUND)
chunked_bulk_create(session.tag_set, found_tags, 980)
return Response(data=[], status=status.HTTP_200_OK)
1 change: 0 additions & 1 deletion apps/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class Identity(models.Model):

class Meta:
db_table = 'identities'
unique_together = [('type', 'data')]
verbose_name_plural = 'identities'
ordering = ('data',)

Expand Down
5 changes: 3 additions & 2 deletions apps/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@

from rest_framework import serializers

from apps.api.mixins import DynamicFieldsMixin
from . import models


class IdentitySerializer(serializers.HyperlinkedModelSerializer):
class IdentitySerializer(DynamicFieldsMixin, serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Identity
fields = ('id', 'uri', 'type', 'data')


class SessionSerializer(serializers.HyperlinkedModelSerializer):
class SessionSerializer(DynamicFieldsMixin, serializers.HyperlinkedModelSerializer):
# PrimaryKey fields are only needed until endpoints exists
device = serializers.PrimaryKeyRelatedField()

Expand Down
24 changes: 0 additions & 24 deletions apps/devices/ajax.py

This file was deleted.

14 changes: 5 additions & 9 deletions apps/devices/device_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from apps.core.models import Session, Result
from apps.core.types import WorkItemType
from apps.policies.models import Policy
from apps.policies.models import Policy, Enforcement
from .models import Device, Group, Product


Expand Down Expand Up @@ -197,8 +197,8 @@ def report(request, deviceID):
context['inherit_set'] = list(current_device.get_inherit_set())

if context['session_count'] > 0:
latest_session = Session.objects.latest('time')
context['last_session'] = latest_session.time
latest_session = Session.objects.filter(device=deviceID).latest()
context['last_session'] = latest_session
context['last_user'] = latest_session.identity.data
context['last_result'] = latest_session.get_recommendation_display()
else:
Expand Down Expand Up @@ -232,13 +232,9 @@ def session(request, sessionID):
context = {}
context['session'] = session
context['title'] = _('Session details')
context['recommendation'] = Policy.action[session.recommendation]
context['recommendation'] = session.get_recommendation_display()

context['results'] = []
for result in session.results.all():
context['results'].append((result, Policy.action[result.recommendation]))
if result.policy.type == WorkItemType.SWIDT:
context['swid_measurement'] = result.session_id
context['results'] = session.results.all()

return render(request, 'devices/session.html', context)

Expand Down