diff --git a/CHANGELOG.md b/CHANGELOG.md index 34322b206..b29fc7712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ GovReady-Q Release Notes ======================== +v0.9.9 (August 12, 2021) +------------------------ + +**UI changes** + +* Improve speed control selection auto-complete. +* Various improvements to domponent add statement form: better alignment, validate control selected before saving, show/hide "Add component statement" button appropriately. + +**Developer changes** + +* Move creation of users in first_run to earlier in script. +* Use faster bulk_create importing components. + +**Data changes** + +* Update sample components to OSCAL 1.0.0. +* Change CatalogData JSONFields to Django JSONField for better searching options. +* Import components and their statements even when catalog not found or statement control ids are not found in referenced catalog. + + v0.9.8 (August 09, 2021) ------------------------ diff --git a/VERSION b/VERSION index eeab19d3b..8031930ee 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.9.8 +v0.9.9 diff --git a/controls/migrations/0059_auto_20210811_0001.py b/controls/migrations/0059_auto_20210811_0001.py new file mode 100644 index 000000000..3ec1a3a1d --- /dev/null +++ b/controls/migrations/0059_auto_20210811_0001.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.5 on 2021-08-11 00:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('controls', '0058_catalogdata_baselines_json'), + ] + + operations = [ + migrations.AlterField( + model_name='catalogdata', + name='baselines_json', + field=models.JSONField(blank=True, help_text='JSON object representing the baselines for the catalog.', null=True), + ), + migrations.AlterField( + model_name='catalogdata', + name='catalog_json', + field=models.JSONField(blank=True, help_text='JSON object representing the OSCAL-formatted control catalog.', null=True), + ), + ] diff --git a/controls/oscal.py b/controls/oscal.py index a769286a8..9591f11db 100644 --- a/controls/oscal.py +++ b/controls/oscal.py @@ -9,7 +9,6 @@ import auto_prefetch from django.db import models from django.utils.functional import cached_property -from jsonfield import JSONField CATALOG_PATH = os.path.join(os.path.dirname(__file__), 'data', 'catalogs') @@ -17,8 +16,8 @@ class CatalogData(auto_prefetch.Model): catalog_key = models.CharField(max_length=100, help_text="Unique key for catalog", unique=True, blank=False, null=False) - catalog_json = JSONField(blank=True, null=True, help_text="JSON object representing the OSCAL-formatted control catalog.") - baselines_json = JSONField(blank=True, null=True, help_text="JSON object representing the baselines for the catalog.") + catalog_json = models.JSONField(blank=True, null=True, help_text="JSON object representing the OSCAL-formatted control catalog.") + baselines_json = models.JSONField(blank=True, null=True, help_text="JSON object representing the baselines for the catalog.") created = models.DateTimeField(auto_now_add=True, db_index=True) updated = models.DateTimeField(auto_now=True, db_index=True) @@ -52,7 +51,7 @@ def _load_catalog_json(self, catalog_key): def _build_index(self): """Build a small catalog_index from metadata""" index = [] - for catalog_key in self._list_catalog_keys(): + for catalog_key in self.catalog_keys: catalog = self._load_catalog_json(catalog_key) index.append( {'id': catalog['id'], 'catalog_key': catalog_key, 'catalog_key_display': catalog_key.replace("_", " "), @@ -67,7 +66,7 @@ def list_catalogs(self): """ List catalog objects """ - return [Catalog.GetInstance(catalog_key=key) for key in Catalogs()._list_catalog_keys()] + return [Catalog.GetInstance(catalog_key=key) for key in self.catalog_keys] def uhash(obj): """Return a positive hash code""" diff --git a/controls/utilities.py b/controls/utilities.py index 304d7a7ad..81a475271 100644 --- a/controls/utilities.py +++ b/controls/utilities.py @@ -68,26 +68,27 @@ def oscalize_control_id(cl_id): return cl_id - -def oscalize_catalog_key(catalogkey): - """ Covers empty catalog key case. Otherwise, outputs an oscal standard catalog key from various common formats for catalog keys - NIST_SP_800_53_rev4 --> NIST_SP-800-53_rev4 +def oscalize_catalog_key(catalogkey=None): + """Outputs an oscal standard catalog key from various common formats for catalog keys + Covers empty catalog key case + Example: NIST_SP_800_53_rev4 --> NIST_SP-800-53_rev4 """ + DEFAULT_CATALOG_KEY = 'NIST_SP-800-53_rev5' + if catalogkey is None or catalogkey=='': + return DEFAULT_CATALOG_KEY # If coming with reference to some path to catalog json file # (e.g. '../../../nist.gov/SP800-53/rev4/json/NIST_SP-800-53_rev4_catalog.json', 'FedRAMP_rev4_HIGH-baseline_profile.json') if ".json" in catalogkey: catalogkey = catalogkey.split('/')[-1].split('_catalog.json')[0].split(".json")[0] - # A default catalog key - if catalogkey=='': - catalogkey = 'NIST_SP-800-53_rev4' # Handle the default improperly formatted control id if catalogkey.count("_") > 2 and "800" in catalogkey: split_key_list = catalogkey.split("_800_") catalogkey = split_key_list[0] + "-800-" + split_key_list[1] + # TODO: Handle other cases + #if catalogkey in ['NIST_SP-800-53_rev4', 'NIST_SP-800-53_rev4', 'CMMC_ver1', 'NIST_SP-800-171_rev1']: return catalogkey - def get_control_statement_part(control_stmnt_id): """ Parses part from control statement id ra-5_smt.a --> a diff --git a/controls/views.py b/controls/views.py index 51f9948f3..ff27dcaa2 100644 --- a/controls/views.py +++ b/controls/views.py @@ -738,6 +738,15 @@ def statement_id_from_control(control_id, part_id): return f"{control_id}" + def generate_source(self, src_str): + """Return a valid catalog source given string""" + DEFAULT_SOURCE = "NIST_SP-800-53_rev5" + if not src_str: + return DEFAULT_SOURCE + # TODO: Handle other cases + source = src_str + return source + def as_json(self): # Build OSCAL # Example: https://github.com/usnistgov/OSCAL/blob/master/src/content/ssp-example/json/example-component.json @@ -765,10 +774,10 @@ def as_json(self): "components": [ { "uuid": comp_uuid, - "type": self.element.component_type.lower() if self.element.component_type is not None else "software", - "title": self.element.full_name or self.element.name, + "type": self.element.component_type.lower() if self.element.component_type is not None else "software", + "title": self.element.full_name or self.element.name, "description": self.element.description, - "responsible-roles": responsible_roles, # TODO: gathering party-uuids, just filling for now + "responsible-roles": responsible_roles, # TODO: gathering party-uuids, just filling for now "control-implementations": control_implementations } ] @@ -813,7 +822,7 @@ def as_json(self): for sid_class, requirements in by_class.items(): control_implementation = { "uuid":str(uuid4()),# TODO: Not sure if this should implemented or just generated here. - "source": smt.source if smt.source else "Govready", + "source": self.generate_source(smt.source if smt.source else None), "description": f"This is a partial implementation of the {sid_class} catalog, focusing on the control enhancement {requirements[0].get('control-id')}.", "implemented-requirements": [req for req in requirements] } @@ -897,17 +906,6 @@ def import_components_as_json(self, import_name, json_object, request=None, exis new_import_record = self.create_import_record(import_name, created_components, existing_import_record=existing_import_record) return new_import_record - # def find_import_record_by_name(self, import_name): - # """Returns most recent existing import record by name - - # @type import_name: str - # @param import_name: Name of import file (if it exists) - # """ - - # found_import_record = ImportRecord.objects.filter(name=import_name).last() - - # return found_import_record - def create_import_record(self, import_name, components, existing_import_record=False): """Associates components and statements to an import record @@ -969,15 +967,11 @@ def create_component(self, component_json): logger.info(f"Component {new_component.name} created with UUID {new_component.uuid}.") control_implementation_statements = component_json.get('control-implementations', None) - catalog = "missing" - # If there is an data in the control-implementations key + # catalog = "missing" + # If there data exists the OSCAL component's control-implementations key if control_implementation_statements: - # For each element if there is a source and the oscalized key is in the available keys - # Then create statements otherwise it will return an empty list for control_element in control_implementation_statements: - if 'source' in control_element: - if oscalize_catalog_key(control_element['source']) in Catalogs().catalog_keys: - catalog = oscalize_catalog_key(control_element['source']) + catalog = oscalize_catalog_key(control_element.get('source', None)) created_statements = self.create_control_implementation_statements(catalog, control_element, new_component) # If there are no valid statements in the json object if created_statements == []: @@ -1000,55 +994,27 @@ def create_control_implementation_statements(self, catalog_key, control_element, @returns: New statement objects created """ - statements_created = [] - if catalog_key == "missing": - logger.info(f"Control Catalog {catalog_key} missing skipping Statement creation...") - return statements_created + new_statements = [] implemented_reqs = control_element['implemented-requirements'] if 'implemented-requirements' in control_element else [] for implemented_control in implemented_reqs: - - control_id = implemented_control['control-id'] if 'control-id' in implemented_control else '' - #statements = implemented_control['statements'] if 'statements' in implemented_control else '' - if self.control_exists_in_catalog(catalog_key, control_id): - new_statement = Statement.objects.create( - sid=control_id, - sid_class=catalog_key, - pid=get_control_statement_part(control_id), - source=control_element['source'] if 'source' in control_element else catalog_key, - uuid=control_element['uuid'] if 'uuid' in control_element else uuid.uuid4(), - body=implemented_control['description'] if 'description' in implemented_control else '', - statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name, - remarks=implemented_control['remarks'] if 'remarks' in implemented_control else '', - status=implemented_control['status'] if 'status' in implemented_control else None, - producer_element=parent_component, - ) - - logger.info(f"New statement with UUID {new_statement.uuid} created.") - statements_created.append(new_statement) - - else: - logger.info(f"Control {control_id} doesn't exist in catalog {catalog_key}. Skipping Statement...") - + control_id = implemented_control['control-id'] if 'control-id' in implemented_control else 'missing' + new_statement = Statement( + sid=control_id, + sid_class=catalog_key, + pid=get_control_statement_part(control_id), + source=catalog_key, + uuid=control_element['uuid'] if 'uuid' in control_element else uuid.uuid4(), + body=implemented_control['description'] if 'description' in implemented_control else '', + statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name, + remarks=implemented_control['remarks'] if 'remarks' in implemented_control else '', + status=implemented_control['status'] if 'status' in implemented_control else None, + producer_element=parent_component, + ) + logger.info(f"New statement with UUID {new_statement.uuid} being created.") + new_statements.append(new_statement) + statements_created = Statement.objects.bulk_create(new_statements) return statements_created - def control_exists_in_catalog(self, catalog_key, control_id): - """Searches for the presence of a specific control id in a catalog. - - @type catalog_key: str - @param catalog_key: Catalog Key - @type control_id: str - @param control_id: Control id - @rtype: bool - @returns: True if control id exists in the catalog. False otherwise - """ - - if catalog_key not in Catalogs()._list_catalog_keys(): - return False - else: - catalog = Catalog.GetInstance(catalog_key) - control = catalog.get_control_by_id(control_id) - return True if control is not None else False - @login_required def add_selected_components(system, import_record): """Add a component from the library or a compliance app to the project and its statements using the import record""" @@ -1159,7 +1125,6 @@ def system_element_control(request, system_id, element_id, catalog_key, control_ } return render(request, "systems/element_detail_control.html", context) - def edit_component_state(request, system_id, element_id): """ Edit system component state @@ -1340,18 +1305,14 @@ def component_library_component(request, element_id): def api_controls_select(request): """Return list of controls in json for select2 options from all control catalogs""" - cl_id = request.GET.get('q', None).lower() - # Search control catalogs in a loop and add results to an array + cl_id = request.GET.get('q', None) + oscal_ctl_id = oscalize_control_id(cl_id) + catalogs_containing_cl_id = CatalogData.objects.filter(Q(catalog_json__catalog__groups__contains=[{"controls": [{"id": oscal_ctl_id}]}]) | + Q(catalog_json__catalog__groups__contains=[{"controls": [{"controls": [{"id": oscal_ctl_id}]}] }] )) cxs = [] - catalogs = Catalogs() - for ck in catalogs._list_catalog_keys(): - cx = Catalog.GetInstance(catalog_key=ck) - ctr = cx.get_control_by_id(cl_id) - # TODO: Better representation of control ids for case-insensitive searching insteading of listing ids in both cases - # TODO: OSCALizing control id? - if ctr: - cxs.append({'id': ctr['id'], 'title': ctr['title'], 'class': ctr['class'], 'catalog_key_display': cx.catalog_key_display, 'display_text': f"{ctr['id']} - {ctr['title']} - {cx.catalog_key_display} - ({ctr['id'].upper()})"}) - cxs.sort(key = operator.itemgetter('id', 'catalog_key_display')) + for catalog in catalogs_containing_cl_id: + catalog_key_display = catalog.catalog_key.replace("_", " ") + cxs.append({"id": oscal_ctl_id, 'catalog_key_display': catalog_key_display, 'display_text': f"{oscal_ctl_id} - {catalog_key_display} - {cl_id}"}) status = "success" message = "Sending list." return JsonResponse( {"status": status, "message": message, "data": {"controls": cxs} }) diff --git a/q-files/vendors/govready/components/OSCAL/cybrary.json b/q-files/vendors/govready/components/OSCAL/cybrary.json index f1858b6f0..35fb2e102 100644 --- a/q-files/vendors/govready/components/OSCAL/cybrary.json +++ b/q-files/vendors/govready/components/OSCAL/cybrary.json @@ -1,74 +1,82 @@ { "component-definition": { - "uuid": "e7b37ba4-ce0d-46bb-8900-231003dba10c", + "uuid": "343d3b0a-30a7-4196-b2b9-4bb3df85e04d", "metadata": { - "title": "Cybrary Component-to-Control Narratives", - "published": "2021-02-15T10:10:57+00:00", - "last-modified": "2021-01-05T03:40:57+00:00", - "version": "string", - "oscal-version": "1.0.0-rc1" + "title": "Cybrary", + "last-modified": "2021-03-23T23:45:28+00:00", + "version": "2021-03-23T23:45:28+00:00", + "oscal-version": "1.0.0", + "parties": [ + { + "uuid": "49e75032-c17a-4b57-9afd-cf7f23fc1dee", + "type": "organization", + "name": "Main" + } + ] }, - "components": { - "a42932ab-7aa9-4c34-a643-2840b6630a19": { + "components": [ + { + "uuid": "a42932ab-7aa9-4c34-a643-2840b6630a19", + "type": "software", "title": "Cybrary", - "type": "system_element", "description": "Training course", + "responsible-roles": [ + { + "role-id": "supplier", + "party-uuids": [ + "49e75032-c17a-4b57-9afd-cf7f23fc1dee" + ] + } + ], "control-implementations": [ { - "uuid": "8a8f9643-2c68-4f87-a47d-70d8af924e73", + "uuid": "f986c0f5-47b3-4c06-aa74-e7d0d3683f8f", "source": "NIST_SP-800-53_rev4", - "description": "Partial implementation of NIST_SP-800-53_rev4", + "description": "This is a partial implementation of the at-2 catalog, focusing on the control enhancement at-2.", "implemented-requirements": [ { - "uuid": "8a9856df-69b4-4b5c-9709-169b34ef06b9", - "control-id": "at-2", - "description": "", - "remarks": "", - "statements": { - "at-2_smt": { - "uuid": "3d9091a6-1315-4fb9-94c7-f138e0d93191", - "description": "Cybrary provides a complete catalog of security awareness and role-based security training. Cybrary also tracks the training completed by each user. Cybrary content can be tailored for annual security awareness.", - "remarks": "" - } - } + "uuid": "3d9091a6-1315-4fb9-94c7-f138e0d93191", + "description": "Cybrary provides a complete catalog of security awareness and role-based security training. Cybrary also tracks the training completed by each user. Cybrary content can be tailored for annual security awareness.", + "control-id": "at-2" }, { - "uuid": "a25c123b-b78d-4833-b7f9-7826676699be", - "control-id": "at-3", - "description": "", - "remarks": "", - "statements": { - "at-3_smt": { - "uuid": "08a5bcb1-80ff-4c44-8f5f-28d24c796e63", - "description": "Role base training implementation narrative.", - "remarks": "" - } - } + "uuid": "3d9091a6-1315-4fb9-94c7-f138e0d93191", + "description": "Cybrary provides a complete catalog of security awareness, role-based security training, and vendor training. Cybrary also tracks the training completed by each user. Cybrary content can be tailored for annual security awareness. \r\nCybrary version 4.5 is being used by system Admins.", + "control-id": "at-2" } ] }, { - "uuid": "ed191423-96b6-4a8f-98b9-de07b953d36d", - "source": "NIST_SP-800-53_rev5", - "description": "Partial implementation of NIST_SP-800-53_rev5", + "uuid": "5a8952e7-1846-49e1-9a4d-bd58f23610e4", + "source": "NIST_SP-800-53_rev4", + "description": "This is a partial implementation of the at-2.2 catalog, focusing on the control enhancement at-2.2.", "implemented-requirements": [ { - "uuid": "28383ff1-8870-484e-b31b-3d00bfd6fbae", - "control-id": "at-2.2", - "description": "", - "remarks": "", - "statements": { - "at-2.2_smt": { - "uuid": "ddba3705-efbd-446f-8ac1-2c68f1b28e55", - "description": "THis is how Cybrary helps with Insider Threat.", - "remarks": "" - } - } + "uuid": "ddba3705-efbd-446f-8ac1-2c68f1b28e55", + "description": "THis is how Cybrary helps with Insider Threat.", + "control-id": "at-2.2" + } + ] + }, + { + "uuid": "04507036-0a47-4612-b39a-576e60df614c", + "source": "NIST_SP-800-53_rev4", + "description": "This is a partial implementation of the at-3 catalog, focusing on the control enhancement at-3.", + "implemented-requirements": [ + { + "uuid": "08a5bcb1-80ff-4c44-8f5f-28d24c796e63", + "description": "Role base training implementation narrative.", + "control-id": "at-3" + }, + { + "uuid": "08a5bcb1-80ff-4c44-8f5f-28d24c796e63", + "description": "Role base training implementation narrative. sdfsdfdf", + "control-id": "at-3" } ] } ] } - } + ] } } \ No newline at end of file diff --git a/q-files/vendors/govready/components/OSCAL/ilias.json b/q-files/vendors/govready/components/OSCAL/ilias.json index f08d98e1b..9ccdaba75 100644 --- a/q-files/vendors/govready/components/OSCAL/ilias.json +++ b/q-files/vendors/govready/components/OSCAL/ilias.json @@ -1,131 +1,72 @@ { "component-definition": { - "uuid": "cd85235a-fb97-456f-8ce7-a629aff0c958", + "uuid": "93d8ad10-029e-42bc-a3d0-9bf432e99ed3", "metadata": { - "title": "ILIAS Component-to-Control Narratives", - "published": "2021-02-15T10:11:51+00:00", - "last-modified": "2021-01-14T20:17:57+00:00", - "version": "string", - "oscal-version": "1.0.0-rc1" + "title": "ILIAS", + "last-modified": "2021-06-11T14:12:34+00:00", + "version": "2021-06-11T14:12:34+00:00", + "oscal-version": "1.0.0", + "parties": [ + { + "uuid": "03d2810a-9a11-4b2f-98fa-49a6911c2248", + "type": "organization", + "name": "Main" + } + ] }, - "components": { - "2408c133-dbca-4354-98c7-356cf29fd376": { + "components": [ + { + "uuid": "a42932ab-7aa9-4c34-a643-2840b6630a19", + "type": "software", "title": "ILIAS", - "type": "system_element", - "description": "ILIAS is an open-source web-based learning management system. It supports learning content management and tools for collaboration, communication, evaluation and assessment.", + "description": "ILIAS Training course", + "responsible-roles": [ + { + "role-id": "supplier", + "party-uuids": [ + "03d2810a-9a11-4b2f-98fa-49a6911c2248" + ] + } + ], "control-implementations": [ { - "uuid": "9964ade5-64a3-49e0-860e-280b4fe51061", + "uuid": "b5ab3785-5889-4d58-9bd0-19545f04fdb6", "source": "NIST_SP-800-53_rev4", - "description": "Partial implementation of NIST_SP-800-53_rev4", + "description": "This is a partial implementation of the at-2 catalog, focusing on the control enhancement at-2.", "implemented-requirements": [ { - "uuid": "38928627-01e7-4bdd-8e2a-57d5063814d8", - "control-id": "ac-14", - "description": "", - "remarks": "", - "statements": { - "ac-14_smt": { - "uuid": "98c0285e-424e-4e01-bbd7-e5fc0c038d98", - "description": "The anonymous user role has the least access to the site of all roles. The website does not allow anonymous users to register an account for themselves.", - "remarks": "" - } - } - }, - { - "uuid": "43d0f753-aeb7-413a-b004-200586586636", - "control-id": "ac-2", - "description": "", - "remarks": "", - "statements": { - "ac-2_smt.a": { - "uuid": "eba06f28-03d4-47be-9e3d-5ec0c26cf403", - "description": "Ilias provides user accounts for individuals who participate in visiting, contributing to and administering the site with the following roles:\r\n\r\n- Anonymous user \u2013 Readers of the site who either do not have an account or are not logged in.\r\n\r\n- Guest \u2013 This role has limited visibility and read permissions\r\n\r\n- User - Standard role for registered users. This role grants read access to most objects.\r\n\r\n- Administrator - This role has all permissions enabled by default.", - "remarks": "" - }, - "ac-2_smt.d": { - "uuid": "48fec236-3eb3-4b4c-9cbb-2e9caa4c0ccf", - "description": "Ilias' permissions and role-based access controls are built-in. Each role within Ilias can only access the pages and controls for which their privilege allows.", - "remarks": "" - }, - "ac-2_smt.g": { - "uuid": "eaa9459c-dcf1-4d16-a2a2-653ef594cb0c", - "description": "Ilias monitors the usage of information accounts in a log on the server.", - "remarks": "" - } - } - }, - { - "uuid": "7e82ac55-ace2-44ac-a148-c51ff44a526f", - "control-id": "ac-3", - "description": "", - "remarks": "", - "statements": { - "ac-3_smt": { - "uuid": "102aab65-79f2-491f-a70c-38f9d8ff1264", - "description": "Access control in Ilias is enforced by authentication via Shibboleth single sing on (SSO) for every type of user except Anonymous user. The user\u2019s privileges, permissions, and access are provided on the principle of least privilege.\r\n\r\nThe anonymous user role has the least access to the site of all roles. The website does not allow anonymous users to register an account for themselves. Project Administrators, HR Managers, and Org Managers are the only roles that can create new user accounts.", - "remarks": "" - } - } - }, - { - "uuid": "ddc4fd5a-2513-4f96-bca7-54804a3317d4", - "control-id": "ac-8", - "description": "", - "remarks": "", - "statements": { - "ac-8_smt": { - "uuid": "e55c6f41-b3a8-4307-bca9-7b0ed2343357", - "description": "System Use Notification is inherited from the Project.", - "remarks": "" - } - } - }, + "uuid": "3d9091a6-1315-4fb9-94c7-f138e0d93191", + "description": "ILIAS provides a complete catalog of security awareness and role-based security training. ILIAS also tracks the training completed by each user. ILIAS content can be tailored for annual security awareness.", + "control-id": "at-2" + } + ] + }, + { + "uuid": "58d71d4b-ce81-4608-afc7-c0892ddae0fe", + "source": "NIST_SP-800-53_rev4", + "description": "This is a partial implementation of the at-2.2 catalog, focusing on the control enhancement at-2.2.", + "implemented-requirements": [ { - "uuid": "df1c055d-ac05-4fee-9e3d-192ce93c111d", - "control-id": "au-2", - "description": "", - "remarks": "", - "statements": { - "au-2_smt.a": { - "uuid": "36cffd7f-cbaa-4dc5-8192-714cbaad84ac", - "description": "Transaction logs are generated by the Apache web server, Ilias CMS, MySQL database and PHP page processing. Specifically, the following server, application, database and network device audit log events are captured:\r\n\r\n- Apache access log: Contains a list of requests for your website that have bypassed Varnish. These requests include pages, theme files, and static media files.\r\n\r\n- Apache error log: Records any Apache-level issues. The issues reported here are usually caused by general server issues, including capacity problems, .htaccess problems, and missing files.\r\n\r\n- Ilias page request log: Records all Ilias page loads on your website.\r\n\r\n- Ilias log: Records Ilias-related actions on your website. The log is recorded on your server.\r\n\r\n- MySQL slow query log: Contains a list of MySQL queries that have taken longer than one second to complete.\r\n\r\n- PHP error log: Records any issues that occur during the PHP processing portion of a page load. Issues reported here are usually caused by a website\u2019s code, configuration, or content.", - "remarks": "" - }, - "au-2_smt.b": { - "uuid": "37fdf156-e3bb-450a-9d16-f83ea1c251a6", - "description": "All security-related issues and events, including requests for server log analysis, are recorded in CivicActions' JIRA tracking system.", - "remarks": "" - }, - "au-2_smt.c": { - "uuid": "c42f3ad0-35f7-4a72-abcf-625e338b0bad", - "description": "CivicActions has extensive experience and specialization as a host of websites that are built using the Ilias web learning platform. Should the need for additional logging become evident, we have the ability to do so by modifying the website's source code to insert additional Ilias logging hooks.", - "remarks": "" - }, - "au-2_smt.d": { - "uuid": "141d07ff-d89f-4928-9716-7f7d07d9e9ca", - "description": "Information captured in the transaction logs includes, but is not limited to, the following auditable events:\r\n- Failed login attempts\r\n- Successful login attempts\r\n- New user account creation\r\n- Password reset instructions mailed\r\n- User logins via a one-time login link\r\n- Content creation\r\n- Content publishing\r\n- Web page not found\r\n- Website configuration changes\r\n- System administration activities\r\n- Slow query logs.\r\n- PHP error logs: Captures any errors logged during execution of the PHP programming language. implementation_status: \r\n", - "remarks": "" - } - } - }, + "uuid": "ddba3705-efbd-446f-8ac1-2c68f1b28e55", + "description": "This is how ILIAS helps with Insider Threat training.", + "control-id": "at-2.2" + } + ] + }, + { + "uuid": "ac73dc57-d1cb-49b4-9996-fd13cc238061", + "source": "NIST_SP-800-53_rev4", + "description": "This is a partial implementation of the at-3 catalog, focusing on the control enhancement at-3.", + "implemented-requirements": [ { - "uuid": "ac993eb7-6b53-4d24-a94d-9d70daf82af7", - "control-id": "au-3", - "description": "", - "remarks": "", - "statements": { - "au-3_smt": { - "uuid": "8fff922a-897c-45f1-b36f-c58cc65af92c", - "description": "The logs collected for Ilias sites include the following types of information:\r\n\r\n- IP number of the request originator\r\n\r\n- Timestamp\r\n\r\n- Username\r\n\r\n- Ilias log message (if applicable)\r\n\r\n- Unique numerical ID of the content being modified (for content creation, modification and deletion events)\r\n\r\nWhen auditing an Ilias incident, CivicActions' developers aggregate log sources from multiple servers into the Graylog dashboard so that all log entries for a single managed security incident can be analyzed in a single document. Log sources are sorted, filtered and reviewed. Application logs are maintained primarily for an after-the-fact investigation of critical systems or security events.\r\n", - "remarks": "" - } - } + "uuid": "08a5bcb1-80ff-4c44-8f5f-28d24c796e63", + "description": "Role base training implementation narrative.", + "control-id": "at-3" } ] } ] } - } + ] } } \ No newline at end of file diff --git a/siteapp/management/commands/first_run.py b/siteapp/management/commands/first_run.py index e3601dcd6..4360149a3 100644 --- a/siteapp/management/commands/first_run.py +++ b/siteapp/management/commands/first_run.py @@ -41,6 +41,88 @@ def handle(self, *args, **options): if not Organization.objects.all().exists() and not Organization.objects.filter(name="main").exists(): org = Organization.objects.create(name="main", slug="main") + # Create GovReady admin users, if specified in local/environment.json + if len(settings.GOVREADY_ADMINS): + for admin_user in settings.GOVREADY_ADMINS: + username = admin_user["username"] + if not User.objects.filter(username=username).exists(): + user = User.objects.create(username=username, is_superuser=True, is_staff=True) + user.set_password(admin_user["password"]) + user.email = admin_user["email"] + user.save() + print("Created administrator account: username '{}' with email '{}'.".format( + user.username, + user.email + )) + # Create the first portfolio + portfolio = user.create_default_portfolio_if_missing() + print("Created administrator portfolio {}".format(portfolio.title)) + else: + print("\n[INFO] Skipping create admin account '{}' - username already exists.\n".format( + username + )) + + # Create default users, if specified in local/environment.json otherwise read from SSM parameter store + users = settings.GOVREADY_USERS + if len(settings.GOVREADY_USERS): + # TODO: iterate for each environment. Need to modularize this loop. + for reg_user in users: + username = reg_user["username"] + if not User.objects.filter(username=username).exists(): + user = User.objects.create(username=username, is_superuser=False, is_staff=False) + user.set_password(reg_user["password"]) + user.email = reg_user["email"] + user.save() + print("Created regular user account: username '{}' with email '{}'.".format( + user.username, + user.email + )) + # Create the first portfolio + portfolio = user.create_default_portfolio_if_missing() + print("Created regular user portfolio {}".format(portfolio.title)) + else: + print("\n[INFO] Skipping create account '{}' - username already exists.\n".format( + username + )) + + # Create the first user. + if not User.objects.filter(is_superuser=True).exists(): + if not options['non_interactive']: + print("Let's create your first Q user. This user will have superuser privileges in the Q administrative interface.") + call_command('createsuperuser') + else: + # Create an "admin" account with a random pwd and + # print it on stdout. + user = User.objects.create(username="admin", is_superuser=True, is_staff=True) + password = User.objects.make_random_password(length=12) + user.set_password(password) + user.save() + print("Created administrator account (username: {}) with password: {}".format( + user.username, + password + )) + # Get the admin user - it was just created and should be the only admin user. + user = User.objects.filter(is_superuser=True).get() + + # Create the first portfolio + portfolio = Portfolio.objects.create(title=user.username) + portfolio.assign_owner_permissions(user) + print("Created administrator portfolio {}".format(portfolio.title)) + + # Add the user to the org's help squad and reviewers lists. + try: + user + except NameError: + print("[INFO] Admin already added to Help Squad and Reviewers") + else: + if user not in org.help_squad.all(): org.help_squad.add(user) + if user not in org.reviewers.all(): org.reviewers.add(user) + print("[INFO] Admin added to Help Squad and Reviewers") + + else: + # One or more superusers already exists + print("\n[INFO] Superuser(s) already exists, not creating default admin superuser. Did you specify 'govready_admins' in 'local/environment.json'? Did you specify an admin or are you connecting to a persistent database?\n") + # Load the default control catalogs and baselines CATALOG_PATH = os.path.join(os.path.dirname(__file__),'..','..','..','controls','data','catalogs') BASELINE_PATH = os.path.join(os.path.dirname(__file__),'..','..','..','controls','data','baselines') @@ -133,87 +215,5 @@ def handle(self, *args, **options): oscal_component_json = f.read() result = ComponentImporter().import_components_as_json(import_name, oscal_component_json) - # Create GovReady admin users, if specified in local/environment.json - if len(settings.GOVREADY_ADMINS): - for admin_user in settings.GOVREADY_ADMINS: - username = admin_user["username"] - if not User.objects.filter(username=username).exists(): - user = User.objects.create(username=username, is_superuser=True, is_staff=True) - user.set_password(admin_user["password"]) - user.email = admin_user["email"] - user.save() - print("Created administrator account: username '{}' with email '{}'.".format( - user.username, - user.email - )) - # Create the first portfolio - portfolio = user.create_default_portfolio_if_missing() - print("Created administrator portfolio {}".format(portfolio.title)) - else: - print("\n[INFO] Skipping create admin account '{}' - username already exists.\n".format( - username - )) - - # Create default users, if specified in local/environment.json otherwise read from SSM parameter store - users = settings.GOVREADY_USERS - if len(settings.GOVREADY_USERS): - # TODO: iterate for each environment. Need to modularize this loop. - for reg_user in users: - username = reg_user["username"] - if not User.objects.filter(username=username).exists(): - user = User.objects.create(username=username, is_superuser=False, is_staff=False) - user.set_password(reg_user["password"]) - user.email = reg_user["email"] - user.save() - print("Created regular user account: username '{}' with email '{}'.".format( - user.username, - user.email - )) - # Create the first portfolio - portfolio = user.create_default_portfolio_if_missing() - print("Created regular user portfolio {}".format(portfolio.title)) - else: - print("\n[INFO] Skipping create account '{}' - username already exists.\n".format( - username - )) - - # Create the first user. - if not User.objects.filter(is_superuser=True).exists(): - if not options['non_interactive']: - print("Let's create your first Q user. This user will have superuser privileges in the Q administrative interface.") - call_command('createsuperuser') - else: - # Create an "admin" account with a random pwd and - # print it on stdout. - user = User.objects.create(username="admin", is_superuser=True, is_staff=True) - password = User.objects.make_random_password(length=12) - user.set_password(password) - user.save() - print("Created administrator account (username: {}) with password: {}".format( - user.username, - password - )) - # Get the admin user - it was just created and should be the only admin user. - user = User.objects.filter(is_superuser=True).get() - - # Create the first portfolio - portfolio = Portfolio.objects.create(title=user.username) - portfolio.assign_owner_permissions(user) - print("Created administrator portfolio {}".format(portfolio.title)) - - # Add the user to the org's help squad and reviewers lists. - try: - user - except NameError: - print("[INFO] Admin already added to Help Squad and Reviewers") - else: - if user not in org.help_squad.all(): org.help_squad.add(user) - if user not in org.reviewers.all(): org.reviewers.add(user) - print("[INFO] Admin added to Help Squad and Reviewers") - - else: - # One or more superusers already exists - print("\n[INFO] Superuser(s) already exists, not creating default admin superuser. Did you specify 'govready_admins' in 'local/environment.json'? Did you specify an admin or are you connecting to a persistent database?\n") - # Provide feedback to user print("GovReady-Q configuration complete.") diff --git a/siteapp/tests.py b/siteapp/tests.py index 79b93ea5b..04b25125a 100644 --- a/siteapp/tests.py +++ b/siteapp/tests.py @@ -497,6 +497,7 @@ def test_login(self): def test_new_user_account_settings(self): # Log in as the user, who is new. Complete the account settings. + # NOTE TODO: These tests will be replaced by a new user account settings in late summer 2021 self._login() @@ -513,11 +514,11 @@ def test_new_user_account_settings(self): wait_for_sleep_after(lambda: self.click_element("#save-button")) # - We're on the module finished page. - wait_for_sleep_after(lambda: self.assertNodeNotVisible('#return-to-project')) - wait_for_sleep_after(lambda: self.click_element("#return-to-projects")) + # wait_for_sleep_after(lambda: self.assertNodeNotVisible('#return-to-project')) + # wait_for_sleep_after(lambda: self.click_element("#return-to-projects")) - wait_for_sleep_after(lambda: self.assertRegex(self.browser.title, "Your Compliance Projects")) - wait_for_sleep_after(lambda: self.assertNodeNotVisible('#please-complete-account-settings')) + # wait_for_sleep_after(lambda: self.assertRegex(self.browser.title, "Your Compliance Projects")) + # wait_for_sleep_after(lambda: self.assertNodeNotVisible('#please-complete-account-settings')) def test_static_pages(self): self.browser.get(self.url("/privacy")) diff --git a/templates/components/element_detail_tabs.html b/templates/components/element_detail_tabs.html index 1a8510eb0..4c07ed85f 100644 --- a/templates/components/element_detail_tabs.html +++ b/templates/components/element_detail_tabs.html @@ -181,7 +181,6 @@