Skip to content

Commit

Permalink
Merge pull request #34 from lavalamp-/quickscan
Browse files Browse the repository at this point in the history
Quickscan
  • Loading branch information
lavalamp- committed Sep 19, 2017
2 parents 938e1d4 + bc6809c commit 452a132
Show file tree
Hide file tree
Showing 57 changed files with 3,093 additions and 139 deletions.
12 changes: 12 additions & 0 deletions lib/smtp/email_templates/order_completed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Dear [USER_NAME],
<br>
<br>
The recent scan for the organization [ORG_NAME] ([ORDER_UUID]) has completed. You can view the results of the scan at the following URL:
<br>
<br>
<a href="[DOMAIN_REPLACE]topography/[ORG_UUID]" target="_blank">View Data For [ORG_NAME]</a>
<br>
<br>
Regards,
<br>
The Web Sight Robot
9 changes: 9 additions & 0 deletions lib/smtp/email_templates/order_completed.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Dear [USER_NAME],

The recent scan for the organization [ORG_NAME] ([ORDER_UUID]) has completed. You can view the results of the scan at the following URL:

[DOMAIN_REPLACE]topography/[ORG_UUID]

Regards,

The Web Sight Robot
32 changes: 32 additions & 0 deletions lib/smtp/smtp.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,38 @@ def send_email_for_user_org_invite(self, user_name=None, user_email=None, org_na
plain_message = self.create_plain_message("user_org_invite", template_data)
self.send(user_email, subject, plain_message, html_message)

def send_emails_for_completed_order_to_users(
self,
order_uuid=None,
org_uuid=None,
org_name=None,
org_contact_tuples=None,
):
"""
Send all of the necessary emails for the completion of an order to the list of contacts.
:param order_uuid: The UUID of the order that finished.
:param org_uuid: The UUID of the organization that was scanned.
:param org_name: The name of the organization
:param org_contact_tuples: A list of tuples containing (1) the user's first name and (2) the user's email
address for all of the recipients of the order completion email.
:return: None
"""
subject = "The scan for organization %s has finished (%s)" % (
org_name,
order_uuid,
)
template_data = {
"[ORG_NAME]": org_name,
"[ORG_UUID]": org_uuid,
"[DOMAIN_REPLACE]": config.rest_domain,
"[ORDER_UUID]": order_uuid,
}
for first_name, email_address in org_contact_tuples:
template_data["[USER_NAME]"] = first_name
html_message = self.create_html_message("order_completed", template_data)
plain_message = self.create_plain_message("order_completed", template_data)
self.send(email_address, subject, plain_message, html_message)

def send_emails_for_placed_order(
self,
user_name=None,
Expand Down
1 change: 1 addition & 0 deletions lib/sqlalchemy/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
get_ports_to_scan_for_organization,
get_tcp_scan_ports_for_org,
get_udp_scan_ports_for_org,
get_user_tuples_for_organization,
update_network_scan,
update_network_scan_completed,
update_org_ip_address,
Expand Down
22 changes: 22 additions & 0 deletions lib/sqlalchemy/ops/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,28 @@ def get_udp_scan_ports_for_org(org_uuid=None, db_session=None):
return [x[0] for x in results]


#TESTME
def get_user_tuples_for_organization(org_uuid=None, db_session=None):
"""
Get a list of tuples containing (1) the first name and (2) the email address for all users associated with
the given organization.
:param org_uuid: The UUID of the organization to get the users for.
:param db_session: A SQLAlchemy session.
:return: A list of tuples containing (1) the first name and (2) the email address for all users
associated with the given organization.
"""
org_uuid = ConversionHelper.string_to_unicode(org_uuid)
auth_group = db_session.query(WsAuthGroup) \
.join(Organization, WsAuthGroup.organization_id == Organization.uuid) \
.filter(Organization.uuid == org_uuid) \
.filter(WsAuthGroup.name == u"org_read") \
.one()
to_return = []
for user in auth_group.users:
to_return.append((user.first_name, user.email))
return to_return


def update_network_scan(scan_uuid=None, update_dict=None, db_session=None):
"""
Update the referenced NetworkScan via the contents of the given dictionary.
Expand Down
3 changes: 1 addition & 2 deletions rest/models/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Network(BaseWsModel):

address = models.CharField(max_length=64, help_text="The network's base IP address.")
mask_length = models.IntegerField(help_text="The CIDR mask length.")
name = models.CharField(max_length=32, help_text="A name to associate with the network.")
name = models.CharField(max_length=32, help_text="A name to associate with the network.", null=True)
scanning_enabled = models.BooleanField(
default=True,
help_text="Whether or not to include this network in scans.",
Expand All @@ -42,7 +42,6 @@ class Network(BaseWsModel):
class Meta:
unique_together = (
("address", "mask_length", "organization"),
("name", "organization"),
("cidr_range", "organization"),
)

Expand Down
38 changes: 22 additions & 16 deletions rest/models/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,26 @@ def create(self, *args, **kwargs):
to_return = super(OrderManager, self).create(*args, **kwargs)
organization = kwargs.get("organization", None)
user = kwargs.get("user", None)
if organization:
to_return.scan_config = organization.scan_config.duplicate()
to_return.scan_config.user = user
to_return.scan_config.save()
else:
to_return.scan_config = ScanConfig.objects.create(
order=to_return,
organization=organization,
user=user,
)
scan_config = kwargs.get("scan_config", None)
if scan_config is None:
if organization:
to_return.scan_config = organization.scan_config.duplicate()
to_return.scan_config.user = user
to_return.scan_config.save()
else:
to_return.scan_config = ScanConfig.objects.create(
order=to_return,
organization=organization,
user=user,
)
return to_return

def create_from_user_and_organization(self, user=None, organization=None):
def create_from_user_and_organization(self, user=None, organization=None, scan_config=None):
"""
Create and return a new order object based on the contents of the given organization and user.
:param user: The user to populate data from.
:param organization: The organization to populate data from.
:param scan_config: The ScanConfig to associate with the newly-created order.
:return: The newly-created order.
"""
to_return = self.create(
Expand All @@ -56,6 +59,7 @@ def create_from_user_and_organization(self, user=None, organization=None):
scoped_endpoints_size=organization.monitored_networks_size,
user=user,
organization=organization,
scan_config=scan_config,
)
for network in organization.monitored_networks:
to_return.networks.create(network=network)
Expand All @@ -78,9 +82,9 @@ class Order(BaseWsModel):
started_at = models.DateTimeField(null=True)
completed_at = models.DateTimeField(null=True)
user_email = models.EmailField(null=False)
scoped_domains_count = models.IntegerField(null=False)
scoped_endpoints_count = models.IntegerField(null=False)
scoped_endpoints_size = models.IntegerField(null=False)
scoped_domains_count = models.IntegerField(null=False, default=0)
scoped_endpoints_count = models.IntegerField(null=False, default=0)
scoped_endpoints_size = models.IntegerField(null=False, default=0)
has_been_placed = models.BooleanField(default=False)

# Foreign Keys
Expand Down Expand Up @@ -135,9 +139,10 @@ def get_receipt_description(self):
to_return.append(separator)
return "\n".join([x.rjust(separator_len) for x in to_return])

def place_order(self):
def place_order(self, update_monitored=True):
"""
Place this order.
:param update_monitored: Whether or not to update monitored endpoints as having been scanned again.
:return: True if the order was placed successfully, False otherwise.
"""
from .payments import Receipt
Expand All @@ -148,7 +153,8 @@ def place_order(self):
description="Charge for order %s" % (self.uuid,)
)
self.receipt = receipt
self.organization.update_monitored_times_scanned()
if update_monitored:
self.organization.update_monitored_times_scanned()
self.has_been_placed = True
return True

Expand Down
41 changes: 41 additions & 0 deletions rest/models/scans.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from django.db import models
import json
from django.db.models import Q

from lib import JsonSerializableMixin, ConfigManager
from .base import BaseWsModel
Expand Down Expand Up @@ -53,6 +54,27 @@ def create(self, include_default_dns_record_types=True, include_default_scan_por
self.__create_default_scan_ports_for_config(scan_config)
return scan_config

def get_config_for_user(self, user=None, config_uuid=None, org_permission_level="org_read"):
"""
Get the referenced ScanConfig for the given user (assuming that the user has permission to access
the referenced ScanConfig).
:param user: The user to retrieve the ScanConfig for.
:param config_uuid: The UUID of the ScanConfig to retrieve.
:param org_permission_level: The level of permission associated with the organization that owns the
ScanConfig.
:return: The referenced ScanConfig if the user has permission to access it, otherwise a DoesNotExist
exception will be raised.
"""
if user.is_superuser:
query = self
else:
query = self.filter(
Q(user=user) |
Q(is_default=True) |
Q(organization__auth_groups__users=user, organization__auth_groups__name=org_permission_level)
)
return query.get(pk=config_uuid)

def write_defaults_to_file(self):
"""
Write the contents of all of the default ScanConfig objects to the ScanConfig JSON file.
Expand Down Expand Up @@ -293,6 +315,25 @@ class ScanConfig(BaseWsModel, JsonSerializableMixin):
help_text="Whether or not to gather information about user agent responses for a web application.",
)

# Completion

completion_web_hook_url = models.URLField(
null=True,
help_text="A URL to send an HTTP GET request to once an order has completed.",
)
completion_email_org_users = models.BooleanField(
null=False,
default=True,
help_text="Whether or not to send an email to all of the users associated with the organization that this "
"ScanConfig is associated with when an order has finished.",
)
completion_email_order_user = models.BooleanField(
null=False,
default=False,
help_text="Whether or not to send an email to the user that created the related order once the order "
"has finished.",
)

# Foreign Keys

order = models.OneToOneField(
Expand Down
1 change: 1 addition & 0 deletions rest/responses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
)

from .scans import (
WsQuickScanResponse,
WsScanConfigValidityResponse,
)

Expand Down
31 changes: 31 additions & 0 deletions rest/responses/scans.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,34 @@ def __init__(self, scan_config=None, *args, **kwargs):
"is_valid": scan_config.is_ready_to_place,
"errors": scan_config.get_ready_errors(),
}


class WsQuickScanResponse(WsBaseResponse):
"""
This is the response used to provide information about the results of attempting a quick scan
invocation.
"""

def __init__(
self,
order_uuid=None,
was_successful=True,
domains=[],
networks=[],
skipped=[],
description=None,
*args,
**kwargs
):
super(WsQuickScanResponse, self).__init__(*args, **kwargs)
if was_successful:
self.status_code = 201
else:
self.status_code = 400
self.data = {
"order_uuid": order_uuid,
"domains": domains,
"networks": networks,
"skipped": skipped,
"result": description,
}
4 changes: 4 additions & 0 deletions rest/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
)

from .dns import (
DnsRecordTypeRelatedSerializer,
DnsRecordTypeSerializer,
DomainNameSerializer,
)
Expand All @@ -31,6 +32,7 @@
OrganizationNetworkUploadRangeSerializer,
OrganizationDomainNameUploadRangeSerializer,
ScanPortSerializer,
ScanPortRelatedSerializer,
SetScanPortSerializer,
)

Expand All @@ -39,6 +41,8 @@
)

from .scans import (
OrganizationQuickScanSerializer,
ScanConfigChildrenSerializer,
ScanConfigSerializer,
)

Expand Down
22 changes: 21 additions & 1 deletion rest/serializers/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class DnsRecordTypeSerializer(WsBaseModelSerializer):

def validate_record_type(self, value):
"""
Validate that the contents of value represnt a valid DNS record type supported by
Validate that the contents of value represent a valid DNS record type supported by
this Web Sight deployment.
:param value: The value to validate.
:return: The validated value.
Expand All @@ -79,6 +79,26 @@ def validate_record_type(self, value):
)
return value

class Meta:
model = DnsRecordType
fields = (
"uuid",
"created",
"record_type",
)
read_only_fields = (
"uuid",
"created",
"scan_config",
)


class DnsRecordTypeRelatedSerializer(DnsRecordTypeSerializer):
"""
This is a serializer class for providing details about a DnsRecordType that includes the ability
to modify attachment to ScanConfig objects.
"""

class Meta:
model = DnsRecordType
fields = (
Expand Down
26 changes: 26 additions & 0 deletions rest/serializers/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,32 @@ def validate_protocol(self, value):
)
return value

class Meta:
model = ScanPort
fields = (
"port_number",
"protocol",
"added_by",
"included",
"created",
"uuid",
"organization",
)
read_only_fields = (
"added_by",
"included",
"scan_config",
"created",
"uuid",
)


class ScanPortRelatedSerializer(ScanPortSerializer):
"""
This is a serializer class for serializing data related to ScanPort models that includes the
ability to modify attachment to ScanConfig objects.
"""

class Meta:
model = ScanPort
fields = (
Expand Down
Loading

0 comments on commit 452a132

Please sign in to comment.