Skip to content
This repository was archived by the owner on Sep 16, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion backend/device_registry/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from device_registry.serializers import DeviceListSerializer
from device_registry.authentication import MTLSAuthentication
from device_registry.serializers import EnrollDeviceSerializer, PairingKeyListSerializer, UpdatePairingKeySerializer
from device_registry.serializers import SnoozeActionSerializer
from .models import Device, DeviceInfo, FirewallState, PortScan, Credential, Tag, PairingKey, GlobalPolicy

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -94,6 +95,7 @@ def post(self, request, *args, **kwargs):
device.deb_packages_hash = deb_packages['hash']
device.set_deb_packages(deb_packages['packages'], os_release)
device.os_release = os_release
device.snoozed_actions = [] # Cleanup snoozed actions list.
device_info_object, _ = DeviceInfo.objects.get_or_create(device=device)
device_info_object.device__last_ping = timezone.now()
device_info_object.device_operating_system_version = data.get('device_operating_system_version')
Expand Down Expand Up @@ -134,7 +136,7 @@ def post(self, request, *args, **kwargs):

device.update_trust_score = True
device.save(update_fields=['last_ping', 'agent_version', 'audit_files', 'deb_packages_hash',
'update_trust_score', 'os_release', 'auto_upgrades'])
'update_trust_score', 'os_release', 'auto_upgrades', 'snoozed_actions'])

if datastore_client:
task_key = datastore_client.key('Ping')
Expand Down Expand Up @@ -997,3 +999,15 @@ def list(self, request, *args, **kwargs):
payload = {'data': serializer.data}
payload.update(self.ajax_info)
return Response(payload)


class SnoozeActionView(APIView):
"""Snooze particular recommended action for given devices list."""

def post(self, request, *args, **kwargs):
serializer = SnoozeActionSerializer(data=request.data, context={'user': request.user})
serializer.is_valid(raise_exception=True)
devices = request.user.devices.filter(pk__in=serializer.validated_data['device_ids'])
for dev in devices:
dev.snooze_action(serializer.validated_data['action_id'])
return Response(status=status.HTTP_200_OK)
19 changes: 19 additions & 0 deletions backend/device_registry/migrations/0066_device_snoozed_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 2.2.6 on 2019-10-18 09:12

import django.contrib.postgres.fields.jsonb
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('device_registry', '0065_device_auto_upgrades'),
]

operations = [
migrations.AddField(
model_name='device',
name='snoozed_actions',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list),
),
]
44 changes: 32 additions & 12 deletions backend/device_registry/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@
DEBIAN_SUITES = ('jessie', 'stretch', 'buster') # Supported Debian suite names.


class RecommendedActions(Enum):
default_credentials = 1
permissive_policy = 2
vulnerable_packages = 3
insecure_services = 4
sshd_config_issues = 5
auto_updates = 6


def get_bootstrap_color(val):
if val <= 33:
return 'danger'
Expand Down Expand Up @@ -106,6 +115,12 @@ class Device(models.Model):
audit_files = JSONField(blank=True, default=list)
os_release = JSONField(blank=True, default=dict)
auto_upgrades = models.BooleanField(null=True, blank=True)
snoozed_actions = JSONField(blank=True, default=list)

def snooze_action(self, action_id):
if action_id not in self.snoozed_actions:
self.snoozed_actions.append(action_id)
self.save(update_fields=['snoozed_actions'])

@property
def auto_upgrades_enabled(self):
Expand All @@ -125,6 +140,7 @@ def distribution(self):
distro_name = distro.capitalize()
return f"{distro_name} {full_version}"

@property
def sshd_issues(self):
if self.audit_files:
for file_info in self.audit_files:
Expand All @@ -133,7 +149,6 @@ def sshd_issues(self):
for k, v in file_info['issues'].items():
issues.append((k, v, SSHD_CONFIG_PARAMS_SAFE_VALUES[k]))
return issues
return None

@property
def certificate_expired(self):
Expand Down Expand Up @@ -245,12 +260,18 @@ def hostname(self):
@property
def actions_count(self):
if hasattr(self, 'firewallstate') and hasattr(self, 'portscan'):
return sum((self.deviceinfo.default_password is True,
self.firewallstate.policy != FirewallState.POLICY_ENABLED_BLOCK,
bool(self.vulnerable_packages and self.vulnerable_packages.exists()),
bool(self.insecure_services),
bool(self.sshd_issues()),
self.auto_upgrades_enabled is False))
return sum((self.deviceinfo.default_password is True and
RecommendedActions.default_credentials.value not in self.snoozed_actions,
self.firewallstate.policy != FirewallState.POLICY_ENABLED_BLOCK and
RecommendedActions.permissive_policy.value not in self.snoozed_actions,
self.vulnerable_packages is not None and self.vulnerable_packages.exists() and
RecommendedActions.vulnerable_packages.value not in self.snoozed_actions,
self.insecure_services is not None and self.insecure_services.exists() and
RecommendedActions.insecure_services.value not in self.snoozed_actions,
bool(self.sshd_issues) and
RecommendedActions.sshd_config_issues.value not in self.snoozed_actions,
self.auto_upgrades_enabled is False and
RecommendedActions.auto_updates.value not in self.snoozed_actions))

COEFFICIENTS = {
'app_armor_enabled': .5,
Expand Down Expand Up @@ -575,18 +596,17 @@ def save(self, *args, **kwargs):

# Temporary POJO to showcase recommended actions template.
class Action:
def __init__(self, action_id, title, description, actions):
def __init__(self, title, description, snoozing_info):
"""
Args:
action_id: Action Id.
title: Actions title.
description: Action description.
actions (str[]): List of available actions.
snoozing_info: a 2-elements list with an action id as a 1st element
and the list of affected device ids as a 2nd element.
"""
self.id = action_id
self.title = title
self.description = description
self.actions = actions
self.snoozing_info = snoozing_info


def average_trust_score(user):
Expand Down
17 changes: 16 additions & 1 deletion backend/device_registry/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from rest_framework import serializers
from rest_framework.utils.representation import smart_repr

from .models import Device, DeviceInfo, Credential, Tag, PairingKey
from .models import Device, DeviceInfo, Credential, Tag, PairingKey, RecommendedActions


class RequiredValidator(object):
Expand Down Expand Up @@ -228,3 +228,18 @@ def to_representation(self, instance):
'url': reverse('device_actions', args=[instance.pk])
}
return representation


class SnoozeActionSerializer(serializers.Serializer):
device_ids = serializers.ListField(child=serializers.IntegerField(), allow_empty=False)
action_id = serializers.IntegerField()

def validate_action_id(self, value):
if value not in RecommendedActions._value2member_map_:
raise serializers.ValidationError('Invalid recommended action id')
return value

def validate_device_ids(self, value):
if Device.objects.filter(owner=self.context['user'], pk__in=value).count() < len(value):
raise serializers.ValidationError('Invalid device id(s) provided')
return value
48 changes: 15 additions & 33 deletions backend/device_registry/templates/actions.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,12 @@ <h1>Recommended Actions{% if device_name %} for {{ device_name }}{% endif %}</h1
{% for action in actions %}
<div class="alert alert-primary alert-outline alert-dismissible alert-recommended" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"
onclick="dismissAction({{ action.id }})">
onclick="snoozeAction('{{ action.snoozing_info.0 }}', {{ action.snoozing_info.1 }})">
<span aria-hidden="true">×</span>
</button>
<div class="alert-message">
<h3 class="alert-heading">{{ action.title|safe }}</h3>
<p>{{ action.description|safe }}</p>
{% if device_name and action.actions %}
<hr>
<div class="btn-list text-center">
{% for button_template in action.actions %}
{% include button_template %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endfor %}
Expand All @@ -46,33 +38,23 @@ <h5 class="card-title mb-0">Well Done!</h5>
{% block scripts %}
{{ block.super }}
<script>
/**
* Generate the Action Url
* @param actionId
* @param actionType
* @returns {string} Action Url.
*/
function getActionUrl(actionId, actionType) {
// TODO: return the real URL
return "";
}

/**
* Submit an action to the web server
* @param actionId
* @param actionType
* @return {void}
*/
function submitAction(actionId, actionType) {
$.post(getActionUrl(actionId, actionType))
}

/**
* Dismiss an action. Used by the close icon on the alerts.
* @param actionId
* Snooze an action. Used by the close icon on the alerts.
* @param id - action id
* @param devices - list of device ids
*/
function dismissAction(actionId) {
submitAction(actionId, 'DISMISS')
function snoozeAction(id, devices) {
$.ajax({
url: '/snooze-action/',
type:"POST",
data: JSON.stringify({
action_id: id,
device_ids: devices
}),
contentType: "application/json; charset=utf-8",
dataType: "json"
});
}
</script>
<script>
Expand Down
Loading