├── .c8rc ├── .dockerignore ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .nvmrc ├── .prettierrc ├── .releaserc ├── .vscode └── launch.json ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── USAGE.md ├── app ├── api │ └── definitions │ │ ├── components │ │ ├── assets.yml │ │ ├── authn.yml │ │ ├── campaigns.yml │ │ ├── collection-bundles.yml │ │ ├── collection-indexes.yml │ │ ├── collections.yml │ │ ├── data-components.yml │ │ ├── data-sources.yml │ │ ├── groups.yml │ │ ├── identities.yml │ │ ├── marking-definitions.yml │ │ ├── matrices.yml │ │ ├── mitigations.yml │ │ ├── notes.yml │ │ ├── references.yml │ │ ├── relationships.yml │ │ ├── sessions.yml │ │ ├── software.yml │ │ ├── stix-bundles.yml │ │ ├── stix-common.yml │ │ ├── system-configuration.yml │ │ ├── tactics.yml │ │ ├── teams.yml │ │ ├── techniques.yml │ │ ├── user-accounts.yml │ │ └── workspace.yml │ │ ├── openapi.yml │ │ └── paths │ │ ├── assets-paths.yml │ │ ├── attack-objects-paths.yml │ │ ├── authn-paths.yml │ │ ├── campaigns-paths.yml │ │ ├── collection-bundles-paths.yml │ │ ├── collection-indexes-paths.yml │ │ ├── collections-paths.yml │ │ ├── data-components-paths.yml │ │ ├── data-sources-paths.yml │ │ ├── groups-paths.yml │ │ ├── health-paths.yml │ │ ├── identities-paths.yml │ │ ├── marking-definitions-paths.yml │ │ ├── matrices-paths.yml │ │ ├── mitigations-paths.yml │ │ ├── notes-paths.yml │ │ ├── recent-activity-paths.yml │ │ ├── references-paths.yml │ │ ├── relationships-paths.yml │ │ ├── session-paths.yml │ │ ├── software-paths.yml │ │ ├── stix-bundles-paths.yml │ │ ├── system-configuration-paths.yml │ │ ├── tactics-paths.yml │ │ ├── teams-paths.yml │ │ ├── techniques-paths.yml │ │ └── user-accounts-paths.yml ├── config │ ├── allowed-values.json │ └── config.js ├── controllers │ ├── assets-controller.js │ ├── attack-objects-controller.js │ ├── authn-anonymous-controller.js │ ├── authn-oidc-controller.js │ ├── authn-service-controller.js │ ├── campaigns-controller.js │ ├── collection-bundles-controller.js │ ├── collection-indexes-controller.js │ ├── collections-controller.js │ ├── data-components-controller.js │ ├── data-sources-controller.js │ ├── groups-controller.js │ ├── health-controller.js │ ├── identities-controller.js │ ├── marking-definitions-controller.js │ ├── matrices-controller.js │ ├── mitigations-controller.js │ ├── notes-controller.js │ ├── recent-activity-controller.js │ ├── references-controller.js │ ├── relationships-controller.js │ ├── session-controller.js │ ├── software-controller.js │ ├── stix-bundles-controller.js │ ├── system-configuration-controller.js │ ├── tactics-controller.js │ ├── teams-controller.js │ ├── techniques-controller.js │ └── user-accounts-controller.js ├── exceptions │ └── index.js ├── index.js ├── lib │ ├── authenticated-request.js │ ├── authn-anonymous.js │ ├── authn-basic.js │ ├── authn-bearer.js │ ├── authn-configuration.js │ ├── authn-middleware.js │ ├── authn-oidc.js │ ├── authz-middleware.js │ ├── database-configuration.js │ ├── database-connection.js │ ├── database-in-memory.js │ ├── default-static-marking-definitions │ │ └── tlp.json │ ├── error-handler.js │ ├── linkById.js │ ├── logger.js │ ├── migration │ │ ├── migrate-database.js │ │ └── migration-config.js │ ├── model-names.js │ ├── regex.js │ ├── request-parameter-helper.js │ ├── requestId.js │ └── types.js ├── models │ ├── asset-model.js │ ├── attack-object-model.js │ ├── campaign-model.js │ ├── collection-index-model.js │ ├── collection-model.js │ ├── data-component-model.js │ ├── data-source-model.js │ ├── group-model.js │ ├── identity-model.js │ ├── marking-definition-model.js │ ├── matrix-model.js │ ├── mitigation-model.js │ ├── note-model.js │ ├── reference-model.js │ ├── relationship-model.js │ ├── software-model.js │ ├── subschemas │ │ ├── attack-pattern.js │ │ ├── stix-core.js │ │ └── workspace.js │ ├── system-configuration-model.js │ ├── tactic-model.js │ ├── team-model.js │ ├── technique-model.js │ └── user-account-model.js ├── repository │ ├── _abstract.repository.js │ ├── _base.repository.js │ ├── assets-repository.js │ ├── attack-objects-repository.js │ ├── campaigns-repository.js │ ├── collection-indexes-repository.js │ ├── collections-repository.js │ ├── data-components-repository.js │ ├── data-sources-repository.js │ ├── groups-repository.js │ ├── identities-repository.js │ ├── marking-definitions-repository.js │ ├── matrix-repository.js │ ├── mitigations-repository.js │ ├── notes-repository.js │ ├── recent-activity-repository.js │ ├── references-repository.js │ ├── relationships-repository.js │ ├── software-repository.js │ ├── system-configurations-repository.js │ ├── tactics-repository.js │ ├── teams-repository.js │ ├── techniques-repository.js │ └── user-accounts-repository.js ├── routes │ ├── assets-routes.js │ ├── attack-objects-routes.js │ ├── authn-anonymous-routes.js │ ├── authn-oidc-routes.js │ ├── authn-service-routes.js │ ├── campaigns-routes.js │ ├── collection-bundles-routes.js │ ├── collection-indexes-routes.js │ ├── collections-routes.js │ ├── data-components-routes.js │ ├── data-sources-routes.js │ ├── groups-routes.js │ ├── health-routes.js │ ├── identities-routes.js │ ├── index.js │ ├── marking-definitions-routes.js │ ├── matrices-routes.js │ ├── mitigations-routes.js │ ├── notes-routes.js │ ├── recent-activity-routes.js │ ├── references-routes.js │ ├── relationships-routes.js │ ├── session-routes.js │ ├── software-routes.js │ ├── stix-bundles-routes.js │ ├── system-configuration-routes.js │ ├── tactics-routes.js │ ├── teams-routes.js │ ├── techniques-routes.js │ └── user-accounts-routes.js ├── scheduler │ └── scheduler.js ├── services │ ├── _abstract.service.js │ ├── _base.service.js │ ├── assets-service.js │ ├── attack-objects-service.js │ ├── authentication-service.js │ ├── campaigns-service.js │ ├── collection-bundles-service │ │ ├── bundle-helpers.js │ │ ├── export-bundle.js │ │ ├── import-bundle.js │ │ ├── index.js │ │ └── validate-bundle.js │ ├── collection-indexes-service.js │ ├── collections-service.js │ ├── data-components-service.js │ ├── data-sources-service.js │ ├── groups-service.js │ ├── identities-service.js │ ├── index.js │ ├── marking-definitions-service.js │ ├── matrices-service.js │ ├── mitigations-service.js │ ├── notes-service.js │ ├── recent-activity-service.js │ ├── references-service.js │ ├── relationships-service.js │ ├── software-service.js │ ├── stix-bundles-service.js │ ├── system-configuration-service.js │ ├── tactics-service.js │ ├── teams-service.js │ ├── techniques-service.js │ └── user-accounts-service.js └── tests │ ├── api │ ├── assets │ │ └── assets.spec.js │ ├── attack-objects │ │ ├── attack-objects-1.json │ │ ├── attack-objects-2.json │ │ ├── attack-objects-pagination.spec.js │ │ └── attack-objects.spec.js │ ├── base-services │ │ └── _base.repository.spec.js │ ├── campaigns │ │ └── campaigns.spec.js │ ├── collection-bundles │ │ └── collection-bundles.spec.js │ ├── collection-indexes │ │ └── collection-indexes.spec.js │ ├── collections │ │ └── collections.spec.js │ ├── data-components │ │ ├── data-components-pagination.spec.js │ │ └── data-components.spec.js │ ├── data-sources │ │ ├── data-sources-pagination.spec.js │ │ └── data-sources.spec.js │ ├── groups │ │ ├── groups-input-validation.spec.js │ │ ├── groups-pagination.spec.js │ │ ├── groups.query.json │ │ ├── groups.query.spec.js │ │ └── groups.spec.js │ ├── identities │ │ └── identities.spec.js │ ├── marking-definitions │ │ └── marking-definitions.spec.js │ ├── matrices │ │ ├── matrices.spec.js │ │ └── matrices.techniques.tactics.json │ ├── mitigations │ │ ├── mitigations-pagination.spec.js │ │ └── mitigations.spec.js │ ├── notes │ │ └── notes.spec.js │ ├── recent-activity │ │ ├── recent-activity.spec.js │ │ └── sample-collection.json │ ├── references │ │ └── references.spec.js │ ├── relationships │ │ ├── relationships-pagination.spec.js │ │ └── relationships.spec.js │ ├── session │ │ └── session.spec.js │ ├── software │ │ ├── software-pagination.spec.js │ │ └── software.spec.js │ ├── stix-bundles │ │ └── stix-bundles.spec.js │ ├── system-configuration │ │ ├── create-object-identity.spec.js │ │ └── system-configuration.spec.js │ ├── tactics │ │ ├── tactics.spec.js │ │ ├── tactics.techniques.json │ │ └── tactics.techniques.spec.js │ ├── teams │ │ ├── teams-invalid.spec.js │ │ ├── teams.invalid.json │ │ ├── teams.spec.js │ │ └── teams.valid.json │ ├── techniques │ │ ├── techniques-pagination.spec.js │ │ ├── techniques.query.json │ │ ├── techniques.query.spec.js │ │ ├── techniques.spec.js │ │ ├── techniques.tactics.json │ │ └── techniques.tactics.spec.js │ └── user-accounts │ │ ├── user-accounts-invalid.spec.js │ │ ├── user-accounts.invalid.json │ │ ├── user-accounts.spec.js │ │ └── user-accounts.valid.json │ ├── authn │ ├── README.md │ ├── anonymous-authn.spec.js │ ├── basic-apikey-service-account.json │ ├── basic-apikey-service.spec.js │ ├── challenge-apikey-service-account.json │ ├── challenge-apikey-service.spec.js │ ├── oidc-authn.spec.js │ ├── oidc-client-credentials-service-account.json │ ├── oidc-client-credentials-service.spec.js │ └── oidc-register.spec.js │ ├── config │ ├── config.spec.js │ ├── test-config.json │ └── test-static-marking-definitions │ │ ├── empty-definitions.json │ │ ├── static-marking-definitions-1.json │ │ └── static-marking-definitions-2.json │ ├── fuzz │ └── user-accounts-fuzz.spec.js │ ├── import │ └── collection-bundles-enterprise.spec.js │ ├── integration-test │ ├── README.md │ ├── initialize-data.js │ ├── initialize-data.sh │ ├── mock-data │ │ ├── blue-v1.json │ │ ├── blue-v2.json │ │ ├── collection-index-v1.json │ │ ├── collection-index-v2.json │ │ ├── green-v1.json │ │ └── red-v1.json │ ├── mock-remote-host │ │ └── index.js │ ├── update-collection-a.sh │ ├── update-subscription.js │ └── update-subscription.sh │ ├── openapi │ └── validate-open-api.spec.js │ ├── run-mocha-separate-jobs.sh │ ├── scheduler │ └── scheduler.spec.js │ └── shared │ ├── keycloak.js │ ├── login.js │ └── pagination.js ├── bin └── www ├── commitlint.config.cjs ├── docs ├── README.md ├── data-model.md ├── images │ ├── anonymous-login-sequence.png │ ├── oidc-login-sequence.png │ └── user-authentication-sequence.png └── legacy │ ├── authentication.md │ ├── docker.md │ ├── link-by-id.md │ └── user-management.md ├── eslint.config.mjs ├── migrations ├── 20220705205741-move-relationships.js └── sample-migration.js ├── package-lock.json ├── package.json ├── resources ├── postman-exports │ ├── README.md │ ├── attack-workbench-rest-api.postman_collection.json │ └── test-service-authentication.postman_environment.json └── sample-configurations │ ├── collection-manager-apikey.json │ ├── collection-manager-oidc-keycloak.json │ ├── collection-manager-oidc-okta.json │ ├── multiple-apikey-services.json │ ├── navigator-basic-apikey.json │ ├── test-service-basic-apikey.json │ └── test-service-challenge-apikey.json ├── scripts ├── README.md ├── build-Dockerfile.sh ├── clearDatabase.js ├── configureKeycloak.js ├── loadBundle.js └── validateBundle.js └── template.env /.c8rc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "include": ["app/**/*.js"], 4 | "exclude": ["app/tests/**/*.js"] 5 | } 6 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .git 4 | 5 | app/tests 6 | local-only 7 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no-install commitlint --edit -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run format -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.14.0 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 100, 4 | "trailingComma": "all", 5 | "tabWidth": 2 6 | } 7 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "+([0-9])?(.{+([0-9]),x}).x", 4 | "master", 5 | "main", 6 | "next", 7 | "next-major", 8 | { 9 | "name": "beta", 10 | "prerelease": true 11 | }, 12 | { 13 | "name": "alpha", 14 | "prerelease": true 15 | } 16 | ], 17 | "plugins": [ 18 | "@semantic-release/commit-analyzer", 19 | "@semantic-release/release-notes-generator", 20 | [ 21 | "@semantic-release/exec", 22 | { 23 | "prepareCmd": "./scripts/build-Dockerfile.sh ${nextRelease.version} ${nextRelease.type} ${process.env.GITHUB_SHA}" 24 | } 25 | ], 26 | "@semantic-release/github" 27 | ] 28 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": ["/**"], 12 | "program": "${workspaceFolder}/bin/www", 13 | "outputCapture": "std", 14 | "envFile": "${workspaceFolder}/.env" 15 | }, 16 | { 17 | "type": "node", 18 | "request": "launch", 19 | "name": "Run Regression Tests", 20 | "skipFiles": ["/**"], 21 | "runtimeExecutable": "npm", 22 | "args": ["run", "test"], 23 | "console": "integratedTerminal", 24 | "internalConsoleOptions": "neverOpen", 25 | "outputCapture": "std", 26 | "envFile": "${workspaceFolder}/.env" 27 | }, 28 | { 29 | "type": "node", 30 | "request": "launch", 31 | "name": "Run Scheduler Tests", 32 | "skipFiles": ["/**"], 33 | "runtimeExecutable": "npm", 34 | "args": ["run", "test:scheduler"], 35 | "console": "integratedTerminal", 36 | "internalConsoleOptions": "neverOpen", 37 | "outputCapture": "std", 38 | "envFile": "${workspaceFolder}/.env" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22 2 | 3 | # Define build arguments 4 | ARG VERSION=dev 5 | ARG BUILDTIME=unknown 6 | ARG REVISION=unknown 7 | 8 | # Set Docker labels 9 | LABEL org.opencontainers.image.title="ATT&CK Workbench REST API Service" \ 10 | org.opencontainers.image.description="This Docker image contains the REST API service of the ATT&CK Workbench, an application for exploring, creating, annotating, and sharing extensions of the MITRE ATT&CK® knowledge base. The service handles the storage, querying, and editing of ATT&CK objects. The application is built on Node.js and Express.js, and is served by the built-in web server provided by Express.js." \ 11 | org.opencontainers.image.source="https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api" \ 12 | org.opencontainers.image.documentation="https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/README.md" \ 13 | org.opencontainers.image.url="https://ghcr.io/center-for-threat-informed-defense/attack-workbench-rest-api" \ 14 | org.opencontainers.image.vendor="The MITRE Corporation" \ 15 | org.opencontainers.image.licenses="Apache-2.0" \ 16 | org.opencontainers.image.authors="MITRE ATT&CK" \ 17 | org.opencontainers.image.version="${VERSION}" \ 18 | org.opencontainers.image.created="${BUILDTIME}" \ 19 | org.opencontainers.image.revision="${REVISION}" \ 20 | maintainer="MITRE ATT&CK" 21 | 22 | # Create app directory 23 | WORKDIR /usr/src/app 24 | 25 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 26 | # where available (npm@5+) 27 | COPY package*.json ./ 28 | 29 | # Install the app dependencies 30 | RUN npm ci --only=production 31 | 32 | # Copy app source 33 | COPY . . 34 | 35 | # Set version as environment variable for runtime access 36 | ENV APP_VERSION=${VERSION} \ 37 | GIT_COMMIT=${REVISION} \ 38 | BUILD_DATE=${BUILDTIME} 39 | 40 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020-2025 MITRE Engenuity. Approved for public release. Document number CT0020 and public release case number 22-3206. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | This project makes use of ATT&CK® 16 | ATT&CK Terms of Use — https://attack.mitre.org/resources/terms-of-use/ 17 | -------------------------------------------------------------------------------- /app/api/definitions/components/assets.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | asset: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/x-mitre-asset' 13 | 14 | x-mitre-asset: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - name 20 | properties: 21 | # asset specific properties 22 | name: 23 | type: string 24 | example: 'Asset Name' 25 | description: 26 | type: string 27 | example: 'This is an asset' 28 | # ATT&CK custom properties 29 | x_mitre_modified_by_ref: 30 | type: string 31 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 32 | x_mitre_sectors: 33 | type: array 34 | items: 35 | type: string 36 | x_mitre_related_assets: 37 | type: array 38 | items: 39 | $ref: '#/components/schemas/x-mitre-related-asset' 40 | x_mitre_platforms: 41 | type: array 42 | items: 43 | type: string 44 | example: 'Windows' 45 | x_mitre_contributors: 46 | type: array 47 | items: 48 | type: string 49 | x_mitre_deprecated: 50 | type: boolean 51 | example: false 52 | x_mitre_domains: 53 | type: array 54 | items: 55 | type: string 56 | x_mitre_version: 57 | type: string 58 | example: '1.0' 59 | x_mitre_attack_spec_version: 60 | type: string 61 | example: '2.1.0' 62 | 63 | x-mitre-related-asset: 64 | type: object 65 | properties: 66 | name: 67 | type: string 68 | related_asset_sectors: 69 | type: array 70 | items: 71 | type: string 72 | description: 73 | type: string 74 | required: 75 | - name 76 | -------------------------------------------------------------------------------- /app/api/definitions/components/authn.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | service-apikey-challenge-response: 4 | type: object 5 | required: 6 | - api-key 7 | properties: 8 | api-key: 9 | type: string 10 | description: 'API key for this service' 11 | 12 | service-apikey-token-response: 13 | type: object 14 | properties: 15 | token: 16 | type: string 17 | description: 'JWT that must be provided on subsequent requests.' 18 | -------------------------------------------------------------------------------- /app/api/definitions/components/campaigns.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | campaign: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/campaign-stix-object' 13 | 14 | campaign-stix-object: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - name 20 | - first_seen 21 | - last_seen 22 | - x_mitre_first_seen_citation 23 | - x_mitre_last_seen_citation 24 | properties: 25 | # campaign specific properties 26 | name: 27 | type: string 28 | description: 29 | type: string 30 | example: 'This is a campaign' 31 | aliases: 32 | type: array 33 | items: 34 | type: string 35 | first_seen: 36 | type: string 37 | format: date-time 38 | last_seen: 39 | type: string 40 | format: date-time 41 | # ATT&CK custom properties 42 | x_mitre_first_seen_citation: 43 | type: string 44 | x_mitre_last_seen_citation: 45 | type: string 46 | x_mitre_modified_by_ref: 47 | type: string 48 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 49 | x_mitre_contributors: 50 | type: array 51 | items: 52 | type: string 53 | x_mitre_deprecated: 54 | type: boolean 55 | example: false 56 | x_mitre_version: 57 | type: string 58 | example: '1.0' 59 | x_mitre_attack_spec_version: 60 | type: string 61 | example: '2.1.0' 62 | -------------------------------------------------------------------------------- /app/api/definitions/components/collection-bundles.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | collection-bundle: 4 | type: object 5 | required: 6 | - id 7 | - type 8 | - objects 9 | properties: 10 | id: 11 | type: string 12 | example: 'bundle--0cde353c-ea5b-4668-9f68-971946609282' 13 | type: 14 | type: string 15 | pattern: '^(bundle)$' 16 | objects: 17 | type: array 18 | items: 19 | type: object 20 | -------------------------------------------------------------------------------- /app/api/definitions/components/collection-indexes.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | collection-index-wrapper: 4 | type: object 5 | required: 6 | - collection_index 7 | properties: 8 | collection_index: 9 | $ref: '#/components/schemas/collection-index-object' 10 | workspace: 11 | $ref: '#/components/schemas/collection-index-workspace' 12 | 13 | collection-index-object: 14 | type: object 15 | required: 16 | - id 17 | - name 18 | - created 19 | - modified 20 | - collections 21 | properties: 22 | id: 23 | type: string 24 | name: 25 | type: string 26 | description: 27 | type: string 28 | created: 29 | type: string 30 | format: date-time 31 | modified: 32 | type: string 33 | format: date-time 34 | collections: 35 | type: array 36 | items: 37 | $ref: '#/components/schemas/collection-reference' 38 | 39 | collection-reference: 40 | type: object 41 | required: 42 | - id 43 | - name 44 | - created 45 | - versions 46 | properties: 47 | id: 48 | type: string 49 | name: 50 | type: string 51 | description: 52 | type: string 53 | created: 54 | type: string 55 | format: date-time 56 | versions: 57 | type: array 58 | items: 59 | $ref: '#/components/schemas/collection-version' 60 | 61 | collection-version: 62 | type: object 63 | required: 64 | - version 65 | - modified 66 | properties: 67 | version: 68 | type: string 69 | modified: 70 | type: string 71 | format: date-time 72 | url: 73 | type: string 74 | taxii_url: 75 | type: string 76 | release_notes: 77 | type: string 78 | 79 | collection-index-workspace: 80 | type: object 81 | properties: 82 | remote_url: 83 | type: string 84 | update_policy: 85 | type: object 86 | properties: 87 | automatic: 88 | type: boolean 89 | interval: 90 | type: number 91 | last_retrieval: 92 | type: string 93 | format: date-time 94 | subscriptions: 95 | type: array 96 | items: 97 | type: string 98 | example: 'x-mitre-collection--915b6504-bde8-40b5-bfda-0c3ecb46a9b9' 99 | -------------------------------------------------------------------------------- /app/api/definitions/components/data-components.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | data-component: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/x-mitre-data-component' 13 | 14 | x-mitre-data-component: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - name 20 | properties: 21 | # data-component specific properties 22 | name: 23 | type: string 24 | example: 'Data Component Name' 25 | description: 26 | type: string 27 | example: 'This is a data component' 28 | # ATT&CK custom properties 29 | x_mitre_modified_by_ref: 30 | type: string 31 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 32 | x_mitre_data_source_ref: 33 | type: string 34 | example: 'x-mitre-data-source--6da9ab38-437f-4e2f-b24c-f0da3a8ce441' 35 | x_mitre_deprecated: 36 | type: boolean 37 | example: false 38 | x_mitre_domains: 39 | type: array 40 | items: 41 | type: string 42 | x_mitre_version: 43 | type: string 44 | example: '1.0' 45 | x_mitre_attack_spec_version: 46 | type: string 47 | example: '2.1.0' 48 | -------------------------------------------------------------------------------- /app/api/definitions/components/data-sources.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | data-source: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/x-mitre-data-source' 13 | 14 | x-mitre-data-source: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - name 20 | properties: 21 | # data-source specific properties 22 | name: 23 | type: string 24 | example: 'Data Source Name' 25 | description: 26 | type: string 27 | example: 'This is a data source' 28 | # ATT&CK custom properties 29 | x_mitre_modified_by_ref: 30 | type: string 31 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 32 | x_mitre_platforms: 33 | type: array 34 | items: 35 | type: string 36 | example: 'Windows' 37 | x_mitre_contributors: 38 | type: array 39 | items: 40 | type: string 41 | x_mitre_deprecated: 42 | type: boolean 43 | example: false 44 | x_mitre_domains: 45 | type: array 46 | items: 47 | type: string 48 | x_mitre_version: 49 | type: string 50 | example: '1.0' 51 | x_mitre_attack_spec_version: 52 | type: string 53 | example: '2.1.0' 54 | x_mitre_collection_layers: 55 | type: array 56 | items: 57 | type: string 58 | example: 'host' 59 | -------------------------------------------------------------------------------- /app/api/definitions/components/groups.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | group: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/intrusion-set' 13 | 14 | intrusion-set: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - name 20 | properties: 21 | # intrusion-set specific properties 22 | name: 23 | type: string 24 | example: 'APT29' 25 | description: 26 | type: string 27 | example: 'This is an intrusion set (group)' 28 | # ATT&CK custom properties 29 | aliases: 30 | type: array 31 | items: 32 | type: string 33 | x_mitre_modified_by_ref: 34 | type: string 35 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 36 | x_mitre_contributors: 37 | type: array 38 | items: 39 | type: string 40 | x_mitre_deprecated: 41 | type: boolean 42 | example: false 43 | x_mitre_domains: 44 | type: array 45 | items: 46 | type: string 47 | x_mitre_version: 48 | type: string 49 | example: '1.0' 50 | x_mitre_attack_spec_version: 51 | type: string 52 | example: '2.1.0' 53 | -------------------------------------------------------------------------------- /app/api/definitions/components/identities.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | identity: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/identity-stix-object' 13 | 14 | identity-stix-object: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - name 20 | properties: 21 | # identity specific properties 22 | name: 23 | type: string 24 | example: 'The MITRE Corporation' 25 | description: 26 | type: string 27 | example: 'This is an identity' 28 | roles: 29 | type: array 30 | items: 31 | type: string 32 | identity_class: 33 | type: string 34 | example: 'organization' 35 | sectors: 36 | type: array 37 | items: 38 | type: string 39 | contact_information: 40 | type: string 41 | # ATT&CK custom properties 42 | x_mitre_modified_by_ref: 43 | type: string 44 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 45 | x_mitre_deprecated: 46 | type: boolean 47 | example: false 48 | x_mitre_version: 49 | type: string 50 | example: '1.0' 51 | x_mitre_attack_spec_version: 52 | type: string 53 | example: '2.1.0' 54 | -------------------------------------------------------------------------------- /app/api/definitions/components/marking-definitions.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | marking-definition: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/marking-definition-stix-object' 13 | 14 | marking-definition-stix-object: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common-marking-definition' 17 | - type: object 18 | properties: 19 | # marking-definition specific properties 20 | name: 21 | type: string 22 | definition_type: 23 | type: string 24 | example: 'statement' 25 | definition: 26 | $ref: '#/components/schemas/marking-object-definition' 27 | # ATT&CK custom properties 28 | x_mitre_deprecated: 29 | type: boolean 30 | example: false 31 | x_mitre_attack_spec_version: 32 | type: string 33 | example: '2.1.0' 34 | 35 | marking-object-definition: 36 | type: object 37 | properties: 38 | statement: 39 | type: string 40 | -------------------------------------------------------------------------------- /app/api/definitions/components/matrices.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | matrix: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/x-mitre-matrix' 13 | 14 | x-mitre-matrix: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - name 20 | properties: 21 | # x-mitre-matrix specific properties 22 | name: 23 | type: string 24 | example: 'Mobile ATT&CK' 25 | description: 26 | type: string 27 | example: 'This is a matrix' 28 | # ATT&CK custom properties 29 | tactic_refs: 30 | type: array 31 | items: 32 | type: string 33 | x_mitre_modified_by_ref: 34 | type: string 35 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 36 | x_mitre_deprecated: 37 | type: boolean 38 | example: false 39 | x_mitre_domains: 40 | type: array 41 | items: 42 | type: string 43 | x_mitre_version: 44 | type: string 45 | example: '1.0' 46 | x_mitre_attack_spec_version: 47 | type: string 48 | example: '2.1.0' 49 | -------------------------------------------------------------------------------- /app/api/definitions/components/mitigations.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | mitigation: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/course-of-action' 13 | 14 | course-of-action: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - name 20 | properties: 21 | # mitigation-specific properties 22 | name: 23 | type: string 24 | example: 'Compiled HTML File Mitigation' 25 | description: 26 | type: string 27 | example: 'This is a course of action (mitigation).' 28 | # ATT&CK custom properties 29 | x_mitre_modified_by_ref: 30 | type: string 31 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 32 | x_mitre_deprecated: 33 | type: boolean 34 | example: false 35 | x_mitre_domains: 36 | type: array 37 | items: 38 | type: string 39 | x_mitre_version: 40 | type: string 41 | example: '1.0' 42 | x_mitre_attack_spec_version: 43 | type: string 44 | example: '2.1.0' 45 | -------------------------------------------------------------------------------- /app/api/definitions/components/notes.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | note: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/stix-note' 13 | 14 | stix-note: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - content 20 | - object_refs 21 | properties: 22 | # note specific properties 23 | abstract: 24 | type: string 25 | content: 26 | type: string 27 | authors: 28 | type: array 29 | items: 30 | type: string 31 | object_refs: 32 | type: array 33 | items: 34 | type: string 35 | # ATT&CK custom properties 36 | x_mitre_modified_by_ref: 37 | type: string 38 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 39 | x_mitre_deprecated: 40 | type: boolean 41 | example: false 42 | x_mitre_attack_spec_version: 43 | type: string 44 | example: '2.1.0' 45 | -------------------------------------------------------------------------------- /app/api/definitions/components/references.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | reference: 4 | type: object 5 | required: 6 | - source_name 7 | - description 8 | properties: 9 | source_name: 10 | type: string 11 | description: 12 | type: string 13 | url: 14 | type: string 15 | -------------------------------------------------------------------------------- /app/api/definitions/components/relationships.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | relationship: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/stix-relationship' 13 | 14 | stix-relationship: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - relationship_type 20 | - source_ref 21 | - target_ref 22 | properties: 23 | # relationship specific properties 24 | description: 25 | type: string 26 | example: 'This is a relationship' 27 | relationship_type: 28 | type: string 29 | example: 'uses' 30 | source_ref: 31 | type: string 32 | target_ref: 33 | type: string 34 | start_time: 35 | type: string 36 | format: date-time 37 | stop_time: 38 | type: string 39 | format: date-time 40 | # ATT&CK custom properties 41 | x_mitre_modified_by_ref: 42 | type: string 43 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 44 | x_mitre_deprecated: 45 | type: boolean 46 | example: false 47 | x_mitre_domains: 48 | type: array 49 | items: 50 | type: string 51 | x_mitre_version: 52 | type: string 53 | example: '1.0' 54 | x_mitre_attack_spec_version: 55 | type: string 56 | example: '2.1.0' 57 | -------------------------------------------------------------------------------- /app/api/definitions/components/sessions.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | user-session: 4 | type: object 5 | properties: 6 | email: 7 | type: string 8 | name: 9 | type: string 10 | status: 11 | type: string 12 | example: 'active' 13 | role: 14 | type: string 15 | example: 'visitor' 16 | identity: 17 | $ref: 'identities.yml#/components/schemas/identity' 18 | registered: 19 | type: boolean 20 | -------------------------------------------------------------------------------- /app/api/definitions/components/software.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | software: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/software-type' 13 | 14 | software-type: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - name 20 | properties: 21 | # software specific properties 22 | name: 23 | type: string 24 | example: 'Net' 25 | description: 26 | type: string 27 | example: 'This is a software' 28 | is_family: 29 | type: boolean 30 | # ATT&CK custom properties 31 | x_mitre_modified_by_ref: 32 | type: string 33 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 34 | x_mitre_aliases: 35 | type: array 36 | items: 37 | type: string 38 | x_mitre_contributors: 39 | type: array 40 | items: 41 | type: string 42 | x_mitre_deprecated: 43 | type: boolean 44 | example: false 45 | x_mitre_domains: 46 | type: array 47 | items: 48 | type: string 49 | x_mitre_version: 50 | type: string 51 | example: '1.0' 52 | x_mitre_attack_spec_version: 53 | type: string 54 | example: '2.1.0' 55 | x_mitre_platforms: 56 | type: array 57 | items: 58 | type: string 59 | example: 'Windows' 60 | -------------------------------------------------------------------------------- /app/api/definitions/components/stix-bundles.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | stix-bundle: 4 | type: object 5 | required: 6 | - id 7 | - type 8 | - spec_version 9 | - objects 10 | properties: 11 | id: 12 | type: string 13 | example: 'bundle--0cde353c-ea5b-4668-9f68-971946609282' 14 | type: 15 | type: string 16 | pattern: '^(bundle)$' 17 | spec_version: 18 | type: string 19 | example: '2.1' 20 | objects: 21 | type: array 22 | items: 23 | type: object 24 | -------------------------------------------------------------------------------- /app/api/definitions/components/system-configuration.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | system-version: 4 | type: object 5 | properties: 6 | version: 7 | type: string 8 | description: Version of the REST API software 9 | attackSpecVersion: 10 | type: string 11 | description: ATT&CK spec version of the REST API software 12 | 13 | allowed-values: 14 | type: object 15 | properties: 16 | objectType: 17 | type: string 18 | example: 'technique' 19 | properties: 20 | $ref: '#/components/schemas/allowedValuesForProperty' 21 | 22 | allowedValuesForProperty: 23 | type: array 24 | items: 25 | type: object 26 | properties: 27 | propertyName: 28 | type: string 29 | example: 'x_mitre_platforms' 30 | domains: 31 | $ref: '#/components/schemas/allowedValuesForDomain' 32 | 33 | allowedValuesForDomain: 34 | type: array 35 | items: 36 | type: object 37 | properties: 38 | domainName: 39 | type: string 40 | example: 'enterprise-attack' 41 | allowedValues: 42 | type: array 43 | items: 44 | type: string 45 | example: 'Linux' 46 | 47 | organization-identity: 48 | type: object 49 | properties: 50 | id: 51 | type: string 52 | example: 'identity--76abfbed-a92f-4e2a-953e-dc83f90ecddc' 53 | 54 | authn-mechanism: 55 | type: object 56 | properties: 57 | authnType: 58 | type: string 59 | example: 'oidc' 60 | 61 | default-marking-definition-ids: 62 | type: array 63 | items: 64 | type: string 65 | example: 'marking-definition--a0262641-9222-4e26-b5f5-f44c59fb3230' 66 | 67 | organization-namespace: 68 | type: object 69 | properties: 70 | range_start: 71 | type: number 72 | example: 4000 73 | prefix: 74 | type: string 75 | example: 'MYORG' 76 | -------------------------------------------------------------------------------- /app/api/definitions/components/tactics.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | tactic: 4 | type: object 5 | required: 6 | - workspace 7 | - stix 8 | properties: 9 | workspace: 10 | $ref: 'workspace.yml#/components/schemas/workspace' 11 | stix: 12 | $ref: '#/components/schemas/x-mitre-tactic' 13 | 14 | x-mitre-tactic: 15 | allOf: 16 | - $ref: 'stix-common.yml#/components/schemas/stix-common' 17 | - type: object 18 | required: 19 | - name 20 | properties: 21 | # x-mitre-tactic specific properties 22 | name: 23 | type: string 24 | example: 'Collection' 25 | description: 26 | type: string 27 | example: 'This is a tactic.' 28 | # ATT&CK custom properties 29 | x_mitre_modified_by_ref: 30 | type: string 31 | example: 'identity--6444f546-6900-4456-b3b1-015c88d70dab' 32 | x_mitre_contributors: 33 | type: array 34 | items: 35 | type: string 36 | x_mitre_deprecated: 37 | type: boolean 38 | example: false 39 | x_mitre_domains: 40 | type: array 41 | items: 42 | type: string 43 | x_mitre_version: 44 | type: string 45 | example: '1.0' 46 | x_mitre_attack_spec_version: 47 | type: string 48 | example: '2.1.0' 49 | -------------------------------------------------------------------------------- /app/api/definitions/components/teams.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | team: 4 | type: object 5 | required: 6 | - id 7 | - name 8 | properties: 9 | id: 10 | type: string 11 | example: 'team--6444f546-6900-4456-b3b1-015c88d70dab' 12 | name: 13 | type: string 14 | example: 'Team Name' 15 | description: 16 | type: string 17 | example: 'Team Description' 18 | userIDs: 19 | type: array 20 | items: 21 | type: string 22 | created: 23 | type: string 24 | format: date-time 25 | modified: 26 | type: string 27 | format: date-time 28 | create-team-request: 29 | type: object 30 | required: 31 | - name 32 | properties: 33 | name: 34 | type: string 35 | example: 'Team Name' 36 | description: 37 | type: string 38 | example: 'Team Description' 39 | userIDs: 40 | type: array 41 | items: 42 | type: string 43 | -------------------------------------------------------------------------------- /app/api/definitions/components/user-accounts.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | user-account: 4 | type: object 5 | required: 6 | - username 7 | - status 8 | properties: 9 | id: 10 | type: string 11 | email: 12 | type: string 13 | username: 14 | type: string 15 | displayName: 16 | type: string 17 | status: 18 | type: string 19 | enum: ['pending', 'active', 'inactive'] 20 | example: 'active' 21 | role: 22 | type: string 23 | enum: ['visitor', 'editor', 'admin'] 24 | example: 'editor' 25 | created: 26 | type: string 27 | format: date-time 28 | modified: 29 | type: string 30 | format: date-time 31 | -------------------------------------------------------------------------------- /app/api/definitions/components/workspace.yml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | workspace: 4 | type: object 5 | properties: 6 | # STIX common properties 7 | workflow: 8 | type: object 9 | properties: 10 | state: 11 | type: string 12 | enum: ['work-in-progress', 'awaiting-review', 'reviewed', 'static'] 13 | attackId: 14 | type: string 15 | example: 'T9999' 16 | collections: 17 | type: array 18 | items: 19 | $ref: '#/components/schemas/collection_reference' 20 | 21 | collection_reference: 22 | type: object 23 | properties: 24 | collection_ref: 25 | type: string 26 | collection_modified: 27 | type: string 28 | format: date-time 29 | required: 30 | - collection_ref 31 | - collection_modified 32 | -------------------------------------------------------------------------------- /app/api/definitions/paths/health-paths.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | /api/health/ping: 3 | get: 4 | summary: 'Test whether the REST API is responding to requests' 5 | operationId: 'health-ping' 6 | description: | 7 | This endpoint tests whether the REST API is responding to requests. 8 | It doesn't test any other aspect of system health. 9 | tags: 10 | - 'Health Check' 11 | responses: 12 | '204': 13 | description: 'Indicates that the REST API is responding to requests' 14 | 15 | /api/health/status: 16 | get: 17 | summary: 'Returns a summary of the system status' 18 | operationId: 'health-status' 19 | description: | 20 | This endpoint returns a summary of the system status, including system uptime and database connection state. 21 | tags: 22 | - 'Health Check' 23 | responses: 24 | '200': 25 | description: 'Summary of the system status' 26 | -------------------------------------------------------------------------------- /app/api/definitions/paths/session-paths.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | /api/session: 3 | get: 4 | summary: 'Get the current user session object for a logged in user' 5 | operationId: 'session-get-current' 6 | description: | 7 | This endpoint retrieves the current user session object for a logged in user. 8 | If the user is not logged in this endpoint returns a status 401 Not authorized. 9 | tags: 10 | - 'Session Management' 11 | responses: 12 | '200': 13 | description: 'A user session object.' 14 | content: 15 | application/json: 16 | schema: 17 | $ref: '../components/sessions.yml#/components/schemas/user-session' 18 | -------------------------------------------------------------------------------- /app/controllers/attack-objects-controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const attackObjectsService = require('../services/attack-objects-service'); 4 | const logger = require('../lib/logger'); 5 | 6 | exports.retrieveAll = async function (req, res) { 7 | const options = { 8 | attackId: req.query.attackId, 9 | offset: req.query.offset || 0, 10 | limit: req.query.limit || 0, 11 | state: req.query.state, 12 | includeRevoked: req.query.includeRevoked, 13 | includeDeprecated: req.query.includeDeprecated, 14 | search: req.query.search, 15 | lastUpdatedBy: req.query.lastUpdatedBy, 16 | includePagination: req.query.includePagination, 17 | versions: req.query.versions, 18 | }; 19 | 20 | try { 21 | const results = await attackObjectsService.retrieveAll(options); 22 | 23 | if (options.includePagination) { 24 | logger.debug( 25 | `Success: Retrieved ${results.data.length} of ${results.pagination.total} total ATT&CK object(s)`, 26 | ); 27 | } else { 28 | logger.debug(`Success: Retrieved ${results.length} ATT&CK object(s)`); 29 | } 30 | 31 | return res.status(200).send(results); 32 | } catch (err) { 33 | logger.error('Failed with error: ' + err); 34 | return res.status(500).send('Unable to get ATT&CK objects. Server error.'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /app/controllers/authn-anonymous-controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const logger = require('../lib/logger'); 4 | 5 | exports.login = function (req, res) { 6 | if (req.user) { 7 | logger.info(`Success: User logged in with uuid: ${req.user.anonymousUuid}`); 8 | return res.status(200).send('User logged in'); 9 | } else { 10 | logger.warn('Unable to log user in, failed with error: req.user not found'); 11 | return res.status(401).send('Not authorized'); 12 | } 13 | }; 14 | 15 | exports.logout = function (req, res) { 16 | try { 17 | const anonymousUuid = req.user.anonymousUuid; 18 | req.logout(function (err) { 19 | if (err) { 20 | logger.error('Unable to log out anonymous user, failed with error: ' + err); 21 | return res.status(500).send('Unable to log out anonymous user. Server error.'); 22 | } else { 23 | logger.info(`Success: User logged out with uuid: ${anonymousUuid}`); 24 | return res.status(200).send('User logged out'); 25 | } 26 | }); 27 | } catch (err) { 28 | logger.error('Unable to log out anonymous user, failed with error: ' + err); 29 | return res.status(500).send('Unable to log out anonymous user. Server error.'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /app/controllers/authn-oidc-controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const logger = require('../lib/logger'); 4 | 5 | exports.login = function (req, res, next) { 6 | // Save the destination and call next() to trigger the redirect to the identity provider 7 | req.session.oidcDestination = req.query.destination; 8 | return next(); 9 | }; 10 | 11 | exports.identityProviderCallback = function (req, res) { 12 | logger.debug('Success: OIDC user logged in.'); 13 | return res.redirect(req.session.oidcDestination); 14 | }; 15 | 16 | exports.logout = function (req, res) { 17 | try { 18 | const email = req.user?.email; 19 | req.logout(function (err) { 20 | if (err) { 21 | logger.error('Unable to log out user, failed with error: ' + err); 22 | return res.status(500).send('Unable to log out user. Server error.'); 23 | } else { 24 | logger.info(`Success: User logged out with email: ${email}`); 25 | return res.status(200).send('User logged out'); 26 | } 27 | }); 28 | } catch (err) { 29 | logger.error('Unable to log out user, failed with error: ' + err); 30 | return res.status(500).send('Unable to log out user. Server error.'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /app/controllers/health-controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // const mongoose = require('mongoose'); 4 | const logger = require('../lib/logger'); 5 | 6 | exports.getPing = function (req, res) { 7 | return res.status(204).send(); 8 | }; 9 | 10 | exports.getStatus = function (req, res) { 11 | try { 12 | const status = { 13 | // TBD: check database connection without waiting 30 seconds for timeout when not connected 14 | // dbState: mongoose.STATES[mongoose.connection.readyState], 15 | uptime: process.uptime(), 16 | }; 17 | return res.status(200).send(status); 18 | } catch (err) { 19 | logger.error('Unable to retrieve system status, failed with error: ' + err); 20 | return res.status(500).send('Unable to retrieve system status. Server error.'); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /app/controllers/recent-activity-controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const recentActivityService = require('../services/recent-activity-service'); 4 | const logger = require('../lib/logger'); 5 | 6 | exports.retrieveAll = async function (req, res) { 7 | const options = { 8 | offset: req.query.offset || 0, 9 | limit: req.query.limit || 0, 10 | includeRevoked: req.query.includeRevoked, 11 | includeDeprecated: req.query.includeDeprecated, 12 | lastUpdatedBy: req.query.lastUpdatedBy, 13 | includePagination: req.query.includePagination, 14 | }; 15 | 16 | try { 17 | const results = await recentActivityService.retrieveAll(options); 18 | 19 | if (options.includePagination) { 20 | logger.debug( 21 | `Success: Retrieved ${results.data.length} of ${results.pagination.total} total ATT&CK object(s)`, 22 | ); 23 | } else { 24 | logger.debug(`Success: Retrieved ${results.length} ATT&CK object(s)`); 25 | } 26 | 27 | return res.status(200).send(results); 28 | } catch (err) { 29 | logger.error('Failed with error: ' + err); 30 | return res.status(500).send('Unable to get ATT&CK objects. Server error.'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /app/controllers/session-controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //const sessionService = require('../services/session-service'); 4 | const logger = require('../lib/logger'); 5 | 6 | exports.retrieveCurrentSession = function (req, res) { 7 | if (req.user) { 8 | logger.debug('Success: Retrieved current user session.'); 9 | return res.status(200).send(req.user); 10 | } else { 11 | logger.warn('Unable to retrieve current user session, failed with error: req.user not found'); 12 | return res.status(401).send('Not authorized'); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /app/controllers/stix-bundles-controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stixBundlesService = require('../services/stix-bundles-service'); 4 | const logger = require('../lib/logger'); 5 | 6 | const validStixVersions = ['2.0', '2.1']; 7 | 8 | exports.exportBundle = async function (req, res) { 9 | if (!req.query.domain) { 10 | return res.status(400).send('domain is required'); 11 | } 12 | 13 | if (!validStixVersions.includes(req.query.stixVersion)) { 14 | return res.status(400).send('invalid STIX version'); 15 | } 16 | 17 | const options = { 18 | domain: req.query.domain, 19 | state: req.query.state, 20 | includeRevoked: req.query.includeRevoked, 21 | includeDeprecated: req.query.includeDeprecated, 22 | stixVersion: req.query.stixVersion, 23 | includeMissingAttackId: req.query.includeMissingAttackId, 24 | includeNotes: req.query.includeNotes, 25 | }; 26 | 27 | try { 28 | const stixBundle = await stixBundlesService.exportBundle(options); 29 | 30 | return res.status(200).send(stixBundle); 31 | } catch (err) { 32 | logger.error('Unable to export STIX bundle: ' + err); 33 | return res.status(500).send('Unable to export STIX bundle.'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /app/lib/authenticated-request.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const superagent = require('superagent'); 4 | const authenticationService = require('../services/authentication-service'); 5 | 6 | /** 7 | * Send an HTTP GET request to the provided URL, including the appropriate Authorization header 8 | */ 9 | exports.get = async function (url) { 10 | try { 11 | const tokenString = await authenticationService.getAccessToken(); 12 | const authorizationHeader = `Bearer ${tokenString}`; 13 | return await superagent.get(url).set('Authorization', authorizationHeader); 14 | } catch (err) { 15 | if (Object.values(authenticationService.errors).includes(err.message)) { 16 | throw new Error(`Authentication Error, ${err.message}`); 17 | } else { 18 | throw err; 19 | } 20 | } 21 | }; 22 | 23 | /** 24 | * Send an HTTP PUT request to the provided URL, including the appropriate Authorization header 25 | */ 26 | exports.put = async function (url, data) { 27 | try { 28 | const tokenString = await authenticationService.getAccessToken(); 29 | const authorizationHeader = `Bearer ${tokenString}`; 30 | return await superagent.put(url).set('Authorization', authorizationHeader).send(data); 31 | } catch (err) { 32 | if (Object.values(authenticationService.errors).includes(err.message)) { 33 | throw new Error(`Authentication Error, ${err.message}`); 34 | } else { 35 | throw err; 36 | } 37 | } 38 | }; 39 | 40 | /** 41 | * Send an HTTP POST request to the provided URL, including the appropriate Authorization header 42 | */ 43 | exports.post = async function (url, data) { 44 | try { 45 | const tokenString = await authenticationService.getAccessToken(); 46 | const authorizationHeader = `Bearer ${tokenString}`; 47 | return await superagent.post(url).set('Authorization', authorizationHeader).send(data); 48 | } catch (err) { 49 | if (Object.values(authenticationService.errors).includes(err.message)) { 50 | throw new Error(`Authentication Error, ${err.message}`); 51 | } else { 52 | throw err; 53 | } 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /app/lib/authn-anonymous.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AnonymousUuidStrategy = require('passport-anonym-uuid'); 4 | 5 | const systemConfigurationService = require('../services/system-configuration-service'); 6 | 7 | let strategyName; 8 | exports.strategyName = function () { 9 | return strategyName; 10 | }; 11 | 12 | /** 13 | * This function takes the user session object and returns the value (the userSessionKey) that will be 14 | * stored in the express session for this user 15 | */ 16 | exports.serializeUser = function (userSession, done) { 17 | if (userSession.strategy === 'anonymId') { 18 | const userSessionKey = { 19 | strategy: 'anonymId', 20 | sessionId: userSession.anonymousUuid, 21 | }; 22 | 23 | done(null, userSessionKey); 24 | } else { 25 | // Try the next serializer 26 | done('pass'); 27 | } 28 | }; 29 | 30 | /** 31 | * This function takes the userSessionKey (the value stored in the express session for this user) and 32 | * returns the user session object 33 | */ 34 | exports.deserializeUser = function (userSessionKey, done) { 35 | if (userSessionKey.strategy === 'anonymId') { 36 | makeUserSession(userSessionKey.sessionId) 37 | .then((userSession) => done(null, userSession)) 38 | .catch((err) => done(err)); 39 | } else { 40 | // Try the next deserializer 41 | done('pass'); 42 | } 43 | }; 44 | 45 | exports.getStrategy = function () { 46 | const strategy = new AnonymousUuidStrategy(verifyCallback); 47 | strategyName = strategy.name; 48 | 49 | return strategy; 50 | }; 51 | 52 | /** 53 | * This function is called by the strategy after the user has authenticated using the anonymous strategy 54 | * It creates and returns the user session for this user 55 | */ 56 | function verifyCallback(req, uuid, done) { 57 | // The anonymous strategy creates a new uuid for each login 58 | makeUserSession(uuid) 59 | .then((userSession) => done(null, userSession)) 60 | .catch((err) => done(err)); 61 | } 62 | 63 | async function makeUserSession(uuid) { 64 | const anonymousUserAccount = await systemConfigurationService.retrieveAnonymousUserAccount(); 65 | 66 | const userAccountData = (({ email, name, status, role }) => ({ email, name, status, role }))( 67 | anonymousUserAccount, 68 | ); 69 | const userSession = { 70 | strategy: 'anonymId', 71 | userAccountId: anonymousUserAccount.id, 72 | ...userAccountData, 73 | registered: true, 74 | anonymousUuid: uuid, 75 | }; 76 | 77 | return userSession; 78 | } 79 | -------------------------------------------------------------------------------- /app/lib/authn-middleware.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('../config/config'); 4 | const authnBearer = require('../lib/authn-bearer'); 5 | const authnBasic = require('../lib/authn-basic'); 6 | 7 | /** 8 | * This middleware function verifies that a request is authenticated. 9 | * 10 | * The bearer strategy requires the Bearer token to be validated for every request. This middleware function 11 | * checks for the Authorization header, and if present, calls the authenticate() function for the Bearer 12 | * strategy (which cause the token to be validated). 13 | * 14 | * Other strategies authenticate the user through dedicated endpoints and set a session cookie in the browser. 15 | * Those strategies rely on the existence of the session cookie and the corresponding server session object 16 | * when authenticating subsequent requests. 17 | */ 18 | const bearerScheme = 'bearer'; 19 | const basicScheme = 'basic'; 20 | exports.authenticate = function (req, res, next) { 21 | const authzHeader = req.get('Authorization'); 22 | const authzScheme = getScheme(authzHeader); 23 | if ( 24 | (config.serviceAuthn.oidcClientCredentials.enable || 25 | config.serviceAuthn.challengeApikey.enable) && 26 | authzHeader && 27 | authzScheme === bearerScheme 28 | ) { 29 | // Authorization header found 30 | // Authenticate the service using the Bearer token 31 | authnBearer.authenticate(req, res, next); 32 | } else if (config.serviceAuthn.basicApikey.enable && authzHeader && authzScheme === basicScheme) { 33 | // Authorization header found 34 | // Authenticate the service using Basic Authentication with apikey 35 | authnBasic.authenticate(req, res, next); 36 | } else if (req.isAuthenticated()) { 37 | // User has been authenticated using a non-Bearer strategy 38 | next(); 39 | } else { 40 | return res.status(401).send('Not authorized'); 41 | } 42 | }; 43 | 44 | function getScheme(authorizationHeader) { 45 | if (authorizationHeader) { 46 | return authorizationHeader.split(' ')[0].toLowerCase(); 47 | } else { 48 | return null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/lib/database-connection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.initializeConnection = async function (options) { 4 | const logger = require('./logger'); 5 | const config = require('../config/config'); 6 | 7 | const databaseUrl = options?.databaseUrl || config.database.url; 8 | 9 | if (!databaseUrl) { 10 | throw new Error( 11 | 'The URL for the MongoDB database was not set in the DATABASE_URL environment variable.', 12 | ); 13 | } 14 | 15 | const mongoose = require('mongoose'); 16 | 17 | // Set `strictQuery` to `true` to omit unknown fields in queries. 18 | mongoose.set('strictQuery', true); 19 | 20 | // Configure mongoose to use ES6 promises 21 | mongoose.Promise = global.Promise; 22 | 23 | // Bootstrap db connection 24 | logger.info('Mongoose attempting to connect to ' + databaseUrl); 25 | await mongoose.connect(databaseUrl); 26 | 27 | logger.info('Mongoose connected to ' + databaseUrl); 28 | }; 29 | -------------------------------------------------------------------------------- /app/lib/database-in-memory.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { MongoMemoryServer } = require('mongodb-memory-server'); 3 | const logger = require('./logger'); 4 | 5 | let mongod; 6 | 7 | exports.initializeConnection = async function () { 8 | if (!mongod) { 9 | mongod = await MongoMemoryServer.create(); 10 | } 11 | 12 | const uri = mongod.getUri(); 13 | 14 | // Set `strictQuery` to `true` to omit unknown fields in queries. 15 | mongoose.set('strictQuery', true); 16 | 17 | // Configure mongoose to use ES6 promises 18 | mongoose.Promise = global.Promise; 19 | 20 | // Bootstrap db connection 21 | logger.info('Mongoose attempting to connect to in memory database at ' + uri); 22 | try { 23 | await mongoose.connect(uri); 24 | } catch (error) { 25 | handleError(error); 26 | } 27 | logger.info('Mongoose connected to ' + uri); 28 | }; 29 | 30 | exports.closeConnection = async function () { 31 | if (mongod) { 32 | await mongoose.connection.dropDatabase(); 33 | await mongoose.connection.close(); 34 | await mongod.stop(); 35 | 36 | mongod = null; 37 | } 38 | }; 39 | 40 | exports.clearDatabase = async function () { 41 | const collections = mongoose.connection.collections; 42 | 43 | for (const key in collections) { 44 | const collection = collections[key]; 45 | await collection.deleteMany(); 46 | } 47 | }; 48 | 49 | function handleError(error) { 50 | logger.warn('Mongoose connection error: ' + error); 51 | logger.warn('Database (mongoose) connection is required. Terminating app.'); 52 | 53 | // Terminate the app 54 | process.exit(1); 55 | } 56 | -------------------------------------------------------------------------------- /app/lib/default-static-marking-definitions/tlp.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "marking-definition", 4 | "spec_version": "2.1", 5 | "id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9", 6 | "created": "2017-01-20T00:00:00.000Z", 7 | "definition_type": "tlp", 8 | "name": "TLP:WHITE", 9 | "definition": { 10 | "tlp": "white" 11 | } 12 | }, 13 | { 14 | "type": "marking-definition", 15 | "spec_version": "2.1", 16 | "id": "marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da", 17 | "created": "2017-01-20T00:00:00.000Z", 18 | "definition_type": "tlp", 19 | "name": "TLP:GREEN", 20 | "definition": { 21 | "tlp": "green" 22 | } 23 | }, 24 | { 25 | "type": "marking-definition", 26 | "spec_version": "2.1", 27 | "id": "marking-definition--f88d31f6-486f-44da-b317-01333bde0b82", 28 | "created": "2017-01-20T00:00:00.000Z", 29 | "definition_type": "tlp", 30 | "name": "TLP:AMBER", 31 | "definition": { 32 | "tlp": "amber" 33 | } 34 | }, 35 | { 36 | "type": "marking-definition", 37 | "spec_version": "2.1", 38 | "id": "marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed", 39 | "created": "2017-01-20T00:00:00.000Z", 40 | "definition_type": "tlp", 41 | "name": "TLP:RED", 42 | "definition": { 43 | "tlp": "red" 44 | } 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /app/lib/error-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const logger = require('./logger'); 4 | 5 | exports.bodyParser = function (err, req, res, next) { 6 | if (err.name === 'SyntaxError') { 7 | logger.warn('Unable to parse body, syntax error: ' + err.type); 8 | res.status(400).send('Syntax error.'); 9 | } else { 10 | next(err); 11 | } 12 | }; 13 | 14 | exports.requestValidation = function (err, req, res, next) { 15 | if (err.status && err.message) { 16 | logger.warn('Request failed validation'); 17 | logger.info(JSON.stringify(err)); 18 | res.status(err.status).send(err.message); 19 | } else { 20 | next(err); 21 | } 22 | }; 23 | 24 | // eslint-disable-next-line no-unused-vars 25 | exports.catchAll = function (err, req, res, next) { 26 | logger.error('catch all: ' + err); 27 | res.status(500).send('Server error.'); 28 | }; 29 | -------------------------------------------------------------------------------- /app/lib/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | const config = require('../config/config'); 5 | 6 | // function formatId(info) { 7 | // if (info.level.toUpperCase() === 'HTTP') { 8 | // return ''; 9 | // } 10 | // else if (info.id) { 11 | // return `[${ info.id }] `; 12 | // } 13 | // else { 14 | // return '[ 000000000000 ] '; 15 | // } 16 | // } 17 | 18 | const consoleFormat = winston.format.combine( 19 | winston.format.timestamp(), 20 | winston.format.printf( 21 | (info) => `${info.timestamp} [${info.level.toUpperCase()}] ${info.message}`, 22 | ), 23 | // winston.format.printf(info => `${ info.timestamp } [${ info.level.toUpperCase() }] ${ formatId(info) }${ info.message }`) 24 | ); 25 | 26 | const logLevels = { 27 | error: 0, 28 | warn: 1, 29 | http: 2, 30 | info: 3, 31 | verbose: 4, 32 | debug: 5, 33 | }; 34 | 35 | const logger = winston.createLogger({ 36 | format: consoleFormat, 37 | transports: [new winston.transports.Console({ level: config.logging.logLevel })], 38 | levels: logLevels, 39 | }); 40 | 41 | logger.stream = { 42 | // eslint-disable-next-line no-unused-vars 43 | write: function (message, encoding) { 44 | // TODO determine if 'encoding' argument can be omitted 45 | // Write to the log. Remove the last character to avoid double 'new line' characters. 46 | logger.http(message.slice(0, -1)); 47 | }, 48 | }; 49 | 50 | module.exports = logger; 51 | -------------------------------------------------------------------------------- /app/lib/migration/migrate-database.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const migrateMongo = require('migrate-mongo'); 4 | const config = require('../../config/config'); 5 | const logger = require('../logger'); 6 | 7 | exports.migrateDatabase = async function () { 8 | global.options = { 9 | file: './app/lib/migration/migration-config.js', 10 | }; 11 | 12 | const { db, client } = await migrateMongo.database.connect(); 13 | const migrationStatus = await migrateMongo.status(db); 14 | 15 | const actionsPending = Boolean(migrationStatus.find((elem) => elem.appliedAt === 'PENDING')); 16 | if (actionsPending) { 17 | if (config.database.migration.enable) { 18 | logger.info('Starting database migration...'); 19 | const appliedActions = await migrateMongo.up(db, client); 20 | for (const action of appliedActions) { 21 | logger.info(`Applied migration action: ${action}`); 22 | } 23 | } else { 24 | await client.close(); 25 | throw new Error( 26 | 'One or more database migration actions are pending, but database migrations are disabled', 27 | ); 28 | } 29 | } else { 30 | logger.info('No pending database migration actions found'); 31 | } 32 | 33 | await client.close(); 34 | }; 35 | -------------------------------------------------------------------------------- /app/lib/migration/migration-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('../../config/config'); 4 | 5 | module.exports = { 6 | mongodb: { 7 | url: config.database.url, 8 | 9 | options: { 10 | useNewUrlParser: true, 11 | }, 12 | }, 13 | 14 | // The migrations dir, can be an relative or absolute path. Only edit this when really necessary. 15 | migrationsDir: 'migrations', 16 | 17 | // The mongodb collection where the applied changes are stored. Only edit this when really necessary. 18 | changelogCollectionName: 'changelog', 19 | 20 | // The file extension to create migrations and search for in migration dir 21 | migrationFileExtension: '.js', 22 | 23 | // Enable the algorithm to create a checksum of the file contents and use that in the comparison to determine 24 | // if the file should be run. Requires that scripts are coded to be run multiple times. 25 | useFileHash: false, 26 | }; 27 | -------------------------------------------------------------------------------- /app/lib/model-names.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Enumeration of possible model names. 5 | * @readonly 6 | * @enum {string} 7 | */ 8 | exports.ModelName = { 9 | Asset: 'Asset', 10 | Campaign: 'Campaign', 11 | Collection: 'Collection', 12 | DataComponent: 'Data-Component', 13 | DataSource: 'Data-Source', 14 | Group: 'Intrusion-Set', 15 | Identity: 'IdentityModel', 16 | MarkingDefinition: 'MarkingDefinitionModel', 17 | Matrix: 'MatrixModel', 18 | Mitigation: 'Course-of-Action', 19 | Note: 'NoteModel', 20 | Relationship: 'Relationship', 21 | Software: 'Software', 22 | Tactic: 'Tactic', 23 | Technique: 'Technique', 24 | }; 25 | -------------------------------------------------------------------------------- /app/lib/regex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const logger = require('../lib/logger'); 4 | 5 | exports.sanitizeRegex = function (expression) { 6 | // Compile the expression. If it's valid, return the expression. Otherwise, return an empty string. 7 | try { 8 | // Escapes all regex characters so they are treated like literal characters rather than special regex characters 9 | expression = expression.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 10 | 11 | new RegExp(expression); 12 | return expression; 13 | } catch (err) { 14 | logger.warn(err); 15 | return ''; 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /app/lib/request-parameter-helper.js: -------------------------------------------------------------------------------- 1 | const lastUpdatedByQueryHelper = function (lastUpdatedByOption) { 2 | if (Array.isArray(lastUpdatedByOption)) { 3 | return { $in: lastUpdatedByOption }; 4 | } else { 5 | return lastUpdatedByOption; 6 | } 7 | }; 8 | 9 | module.exports = { 10 | lastUpdatedByQueryHelper, 11 | }; 12 | -------------------------------------------------------------------------------- /app/lib/requestId.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { customAlphabet } = require('nanoid'); 4 | const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 5 | const idGenerator = customAlphabet(alphabet, 12); 6 | 7 | module.exports = function (req, res, next) { 8 | req.id = idGenerator(); 9 | next(); 10 | }; 11 | -------------------------------------------------------------------------------- /app/lib/types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | Asset: 'x-mitre-asset', 5 | Campaign: 'campaign', 6 | Collection: 'x-mitre-collection', 7 | Group: 'intrusion-set', 8 | Mitigation: 'course-of-action', 9 | Tool: 'tool', 10 | Tactic: 'x-mitre-tactic', 11 | Malware: 'malware', 12 | Matrix: 'x-mitre-matrix', 13 | Relationship: 'relationship', 14 | MarkingDefinition: 'marking-definition', 15 | Identity: 'identity', 16 | Note: 'note', 17 | DataSource: 'x-mitre-data-source', 18 | DataComponent: 'x-mitre-data-component', 19 | Technique: 'attack-pattern', 20 | }; 21 | -------------------------------------------------------------------------------- /app/models/asset-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const relatedAsset = { 9 | name: { type: String, required: true }, 10 | related_asset_sectors: [String], 11 | description: String, 12 | }; 13 | const relatedAssetSchema = new mongoose.Schema(relatedAsset, { _id: false }); 14 | 15 | const stixAsset = { 16 | // STIX asset specific properties 17 | modified: { type: Date, required: true }, 18 | name: { type: String, required: true }, 19 | description: String, 20 | 21 | // ATT&CK custom stix properties 22 | x_mitre_sectors: [String], 23 | x_mitre_related_assets: [relatedAssetSchema], 24 | x_mitre_modified_by_ref: String, 25 | x_mitre_platforms: [String], 26 | x_mitre_deprecated: Boolean, 27 | x_mitre_domains: [String], 28 | x_mitre_version: String, 29 | x_mitre_attack_spec_version: String, 30 | x_mitre_contributors: [String], 31 | }; 32 | 33 | // Create the definition 34 | const assetDefinition = { 35 | stix: { 36 | ...stixCoreDefinitions.commonRequiredSDO, 37 | ...stixCoreDefinitions.commonOptionalSDO, 38 | ...stixAsset, 39 | }, 40 | }; 41 | 42 | // Create the schema 43 | const assetSchema = new mongoose.Schema(assetDefinition); 44 | 45 | // Create the model 46 | const AssetModel = AttackObject.discriminator(ModelName.Asset, assetSchema); 47 | 48 | module.exports = AssetModel; 49 | -------------------------------------------------------------------------------- /app/models/attack-object-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const workspaceDefinitions = require('./subschemas/workspace'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | 7 | const config = require('../config/config'); 8 | 9 | // Create the definition 10 | const attackObjectDefinition = { 11 | workspace: { 12 | ...workspaceDefinitions.common, 13 | }, 14 | stix: { 15 | ...stixCoreDefinitions.commonRequiredSDO, 16 | ...stixCoreDefinitions.commonOptionalSDO, 17 | }, 18 | }; 19 | 20 | // Create the schema 21 | const options = { 22 | collection: 'attackObjects', 23 | }; 24 | const attackObjectSchema = new mongoose.Schema(attackObjectDefinition, options); 25 | 26 | //Save the ATT&CK ID in a more easily queried location 27 | attackObjectSchema.pre('save', function (next) { 28 | if (this.stix.external_references) { 29 | const mitreAttackReference = this.stix.external_references.find((externalReference) => 30 | config.attackSourceNames.includes(externalReference.source_name), 31 | ); 32 | if (mitreAttackReference && mitreAttackReference.external_id) { 33 | this.workspace.attack_id = mitreAttackReference.external_id; 34 | } 35 | } 36 | return next(); 37 | }); 38 | 39 | // Add an index on stix.id and stix.modified 40 | // This improves the efficiency of queries and enforces uniqueness on this combination of properties 41 | attackObjectSchema.index({ 'stix.id': 1, 'stix.modified': -1 }, { unique: true }); 42 | 43 | // Create the model 44 | const attackObjectModel = mongoose.model('AttackObject', attackObjectSchema); 45 | 46 | module.exports = attackObjectModel; 47 | -------------------------------------------------------------------------------- /app/models/campaign-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const stixCampaign = { 9 | // STIX campaign specific properties 10 | modified: { type: Date, required: true }, 11 | name: { type: String, required: true }, 12 | description: String, 13 | aliases: [String], 14 | first_seen: { type: Date, required: true }, 15 | last_seen: { type: Date, required: true }, 16 | 17 | // ATT&CK custom stix properties 18 | x_mitre_first_seen_citation: { type: String, required: true }, 19 | x_mitre_last_seen_citation: { type: String, required: true }, 20 | x_mitre_modified_by_ref: String, 21 | x_mitre_deprecated: Boolean, 22 | x_mitre_version: String, 23 | x_mitre_attack_spec_version: String, 24 | x_mitre_contributors: [String], 25 | }; 26 | 27 | // Create the definition 28 | const campaignDefinition = { 29 | stix: { 30 | ...stixCoreDefinitions.commonRequiredSDO, 31 | ...stixCoreDefinitions.commonOptionalSDO, 32 | ...stixCampaign, 33 | }, 34 | }; 35 | 36 | // Create the schema 37 | const campaignSchema = new mongoose.Schema(campaignDefinition); 38 | 39 | // Create the model 40 | const CampaignModel = AttackObject.discriminator(ModelName.Campaign, campaignSchema); 41 | 42 | module.exports = CampaignModel; 43 | -------------------------------------------------------------------------------- /app/models/collection-index-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | // Create the definition 6 | const collectionVersionDefinition = { 7 | version: { type: String, required: true }, 8 | modified: { type: Date, required: true }, 9 | url: { type: String }, 10 | taxii_url: { type: String }, 11 | release_notes: { type: String }, 12 | }; 13 | const collectionVersionSchema = new mongoose.Schema(collectionVersionDefinition, { _id: false }); 14 | 15 | const collectionReferenceDefinition = { 16 | id: { type: String, required: true }, 17 | name: { type: String, required: true }, 18 | description: { type: String }, 19 | created: { type: Date, required: true }, 20 | versions: [collectionVersionSchema], 21 | }; 22 | const collectionReferenceSchema = new mongoose.Schema(collectionReferenceDefinition, { 23 | _id: false, 24 | }); 25 | 26 | // This is the collection index that was retrieved 27 | const collectionIndexObjectDefinition = { 28 | id: { type: String, required: true }, 29 | name: { type: String, required: true }, 30 | description: { type: String }, 31 | created: { type: Date, required: true }, 32 | modified: { type: Date, required: true }, 33 | collections: [collectionReferenceSchema], 34 | }; 35 | 36 | // This is the collection index with its workspace data 37 | const collectionIndexWrapperDefinition = { 38 | collection_index: { 39 | ...collectionIndexObjectDefinition, 40 | }, 41 | workspace: { 42 | remote_url: { type: String }, 43 | update_policy: { 44 | automatic: { type: Boolean }, 45 | interval: { type: Number }, 46 | last_retrieval: { type: Date }, 47 | subscriptions: [String], 48 | }, 49 | }, 50 | }; 51 | 52 | // Create the schema 53 | const options = { 54 | collection: 'collectionIndexes', 55 | }; 56 | const collectionIndexSchema = new mongoose.Schema(collectionIndexWrapperDefinition, options); 57 | 58 | // Add an index on id 59 | // This improves the efficiency of queries and enforces uniqueness on this property 60 | collectionIndexSchema.index({ 'collection_index.id': 1 }, { unique: true }); 61 | 62 | // Create the model 63 | const CollectionIndexModel = mongoose.model('CollectionIndexModel', collectionIndexSchema); 64 | 65 | module.exports = CollectionIndexModel; 66 | -------------------------------------------------------------------------------- /app/models/collection-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const workspaceDefinitions = require('./subschemas/workspace'); 6 | const stixCoreDefinitions = require('./subschemas/stix-core'); 7 | const { ModelName } = require('../lib/model-names'); 8 | 9 | const xMitreContent = { 10 | object_ref: { type: String, required: true }, 11 | object_modified: { type: Date, required: true }, 12 | }; 13 | const xMitreContentSchema = new mongoose.Schema(xMitreContent, { _id: false }); 14 | 15 | const xMitreCollection = { 16 | modified: { type: Date, required: true }, 17 | name: { type: String, required: true }, 18 | description: String, 19 | 20 | x_mitre_modified_by_ref: String, 21 | x_mitre_contents: [xMitreContentSchema], 22 | x_mitre_deprecated: Boolean, 23 | x_mitre_domains: [String], 24 | x_mitre_version: String, 25 | x_mitre_attack_spec_version: String, 26 | }; 27 | 28 | // Create the definition 29 | const collectionDefinition = { 30 | workspace: { 31 | ...workspaceDefinitions.collection, 32 | }, 33 | stix: { 34 | ...stixCoreDefinitions.commonRequiredSDO, 35 | ...stixCoreDefinitions.commonOptionalSDO, 36 | ...xMitreCollection, 37 | }, 38 | }; 39 | 40 | // Create the schema 41 | const collectionSchema = new mongoose.Schema(collectionDefinition); 42 | 43 | // Create the model 44 | const CollectionModel = AttackObject.discriminator(ModelName.Collection, collectionSchema); 45 | 46 | module.exports = CollectionModel; 47 | -------------------------------------------------------------------------------- /app/models/data-component-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const stixDataComponent = { 9 | // STIX x-mitre-data-component specific properties 10 | modified: { type: Date, required: true }, 11 | name: { type: String, required: true }, 12 | description: String, 13 | 14 | // ATT&CK custom stix properties 15 | x_mitre_data_source_ref: String, 16 | x_mitre_modified_by_ref: String, 17 | x_mitre_deprecated: Boolean, 18 | x_mitre_domains: [String], 19 | x_mitre_version: String, 20 | x_mitre_attack_spec_version: String, 21 | }; 22 | 23 | // Create the definition 24 | const dataComponentDefinition = { 25 | stix: { 26 | ...stixCoreDefinitions.commonRequiredSDO, 27 | ...stixCoreDefinitions.commonOptionalSDO, 28 | ...stixDataComponent, 29 | }, 30 | }; 31 | 32 | // Create the schema 33 | const dataComponentSchema = new mongoose.Schema(dataComponentDefinition); 34 | 35 | // Create the model 36 | const DataComponentModel = AttackObject.discriminator(ModelName.DataComponent, dataComponentSchema); 37 | 38 | module.exports = DataComponentModel; 39 | -------------------------------------------------------------------------------- /app/models/data-source-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const stixDataSource = { 9 | // STIX x-mitre-data-source specific properties 10 | modified: { type: Date, required: true }, 11 | name: { type: String, required: true }, 12 | description: String, 13 | 14 | // ATT&CK custom stix properties 15 | x_mitre_modified_by_ref: String, 16 | x_mitre_platforms: [String], 17 | x_mitre_deprecated: Boolean, 18 | x_mitre_domains: [String], 19 | x_mitre_version: String, 20 | x_mitre_attack_spec_version: String, 21 | x_mitre_contributors: [String], 22 | x_mitre_collection_layers: [String], 23 | }; 24 | 25 | // Create the definition 26 | const dataSourceDefinition = { 27 | stix: { 28 | ...stixCoreDefinitions.commonRequiredSDO, 29 | ...stixCoreDefinitions.commonOptionalSDO, 30 | ...stixDataSource, 31 | }, 32 | }; 33 | 34 | // Create the schema 35 | const dataSourceSchema = new mongoose.Schema(dataSourceDefinition); 36 | 37 | // Create the model 38 | const DataSourceModel = AttackObject.discriminator(ModelName.DataSource, dataSourceSchema); 39 | 40 | module.exports = DataSourceModel; 41 | -------------------------------------------------------------------------------- /app/models/group-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const stixIntrusionSet = { 9 | // STIX intrusion-set specific properties 10 | modified: { type: Date, required: true }, 11 | name: { type: String, required: true }, 12 | description: String, 13 | 14 | // ATT&CK custom stix properties 15 | aliases: [String], 16 | x_mitre_modified_by_ref: String, 17 | x_mitre_deprecated: Boolean, 18 | x_mitre_domains: [String], // TBD drop this property 19 | x_mitre_version: String, 20 | x_mitre_attack_spec_version: String, 21 | x_mitre_contributors: [String], 22 | }; 23 | 24 | // Create the definition 25 | const groupDefinition = { 26 | stix: { 27 | ...stixCoreDefinitions.commonRequiredSDO, 28 | ...stixCoreDefinitions.commonOptionalSDO, 29 | ...stixIntrusionSet, 30 | }, 31 | }; 32 | 33 | // Create the schema 34 | const groupSchema = new mongoose.Schema(groupDefinition); 35 | 36 | // Create the model 37 | const GroupModel = AttackObject.discriminator(ModelName.Group, groupSchema); 38 | 39 | module.exports = GroupModel; 40 | -------------------------------------------------------------------------------- /app/models/identity-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const identityProperties = { 9 | // identity specific properties 10 | modified: { type: Date, required: true }, 11 | name: { type: String, required: true }, 12 | description: String, 13 | roles: [String], 14 | identity_class: String, 15 | sectors: [String], 16 | contact_information: String, 17 | 18 | // ATT&CK custom stix properties 19 | x_mitre_modified_by_ref: String, 20 | x_mitre_deprecated: Boolean, 21 | x_mitre_version: String, 22 | x_mitre_attack_spec_version: String, 23 | }; 24 | 25 | // Create the definition 26 | const identityDefinition = { 27 | stix: { 28 | ...stixCoreDefinitions.commonRequiredSDO, 29 | ...stixCoreDefinitions.commonOptionalSDO, 30 | ...identityProperties, 31 | }, 32 | }; 33 | 34 | // Create the schema 35 | const identitySchema = new mongoose.Schema(identityDefinition); 36 | 37 | // Create the model 38 | const IdentityModel = AttackObject.discriminator(ModelName.Identity, identitySchema); 39 | 40 | module.exports = IdentityModel; 41 | -------------------------------------------------------------------------------- /app/models/marking-definition-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const markingObject = { 9 | statement: String, 10 | tlp: String, 11 | }; 12 | 13 | // TBD: Marking Definition should not have modified or revoked properties. 14 | 15 | const markingDefinitionProperties = { 16 | // marking definition specific properties 17 | name: String, 18 | definition_type: String, 19 | definition: markingObject, 20 | 21 | // ATT&CK custom stix properties 22 | x_mitre_deprecated: Boolean, 23 | x_mitre_attack_spec_version: String, 24 | }; 25 | 26 | // Create the definition 27 | const markingDefinitionDefinition = { 28 | stix: { 29 | ...stixCoreDefinitions.commonRequiredSDO, 30 | ...stixCoreDefinitions.commonOptionalSDO, 31 | ...markingDefinitionProperties, 32 | }, 33 | }; 34 | 35 | // Create the schema 36 | const markingDefinitionSchema = new mongoose.Schema(markingDefinitionDefinition); 37 | 38 | // Create the model 39 | const MarkingDefinitionModel = AttackObject.discriminator( 40 | ModelName.MarkingDefinition, 41 | markingDefinitionSchema, 42 | ); 43 | 44 | module.exports = MarkingDefinitionModel; 45 | -------------------------------------------------------------------------------- /app/models/matrix-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const matrixProperties = { 9 | // x-mitre-matrix specific properties 10 | modified: { type: Date, required: true }, 11 | name: { type: String, required: true }, 12 | description: String, 13 | 14 | // ATT&CK custom stix properties 15 | tactic_refs: [String], 16 | x_mitre_modified_by_ref: String, 17 | x_mitre_deprecated: Boolean, 18 | x_mitre_domains: [String], // TBD drop this property 19 | x_mitre_version: String, 20 | x_mitre_attack_spec_version: String, 21 | }; 22 | 23 | // Create the definition 24 | const matrixDefinition = { 25 | stix: { 26 | ...stixCoreDefinitions.commonRequiredSDO, 27 | ...stixCoreDefinitions.commonOptionalSDO, 28 | ...matrixProperties, 29 | }, 30 | }; 31 | 32 | // Create the schema 33 | const matrixSchema = new mongoose.Schema(matrixDefinition); 34 | 35 | // Create the model 36 | const MatrixModel = AttackObject.discriminator(ModelName.Matrix, matrixSchema); 37 | 38 | module.exports = MatrixModel; 39 | -------------------------------------------------------------------------------- /app/models/mitigation-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const stixCourseOfAction = { 9 | // STIX course-of-action specific properties 10 | modified: { type: Date, required: true }, 11 | name: { type: String, required: true }, 12 | description: String, 13 | labels: [String], 14 | 15 | // ATT&CK custom stix properties 16 | x_mitre_modified_by_ref: String, 17 | x_mitre_deprecated: Boolean, 18 | x_mitre_domains: [String], 19 | x_mitre_version: String, 20 | x_mitre_attack_spec_version: String, 21 | }; 22 | 23 | // Create the definition 24 | const mitigationDefinition = { 25 | stix: { 26 | ...stixCoreDefinitions.commonRequiredSDO, 27 | ...stixCoreDefinitions.commonOptionalSDO, 28 | ...stixCourseOfAction, 29 | }, 30 | }; 31 | 32 | // Create the schema 33 | const mitigationSchema = new mongoose.Schema(mitigationDefinition); 34 | 35 | // Create the model 36 | const MitigationModel = AttackObject.discriminator(ModelName.Mitigation, mitigationSchema); 37 | 38 | module.exports = MitigationModel; 39 | -------------------------------------------------------------------------------- /app/models/note-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const noteProperties = { 9 | // note specific properties 10 | modified: { type: Date, required: true }, 11 | abstract: String, 12 | content: { type: String, required: true }, 13 | authors: [String], 14 | object_refs: { type: [String], required: true }, 15 | 16 | // ATT&CK custom stix properties 17 | x_mitre_modified_by_ref: String, 18 | x_mitre_deprecated: Boolean, 19 | x_mitre_attack_spec_version: String, 20 | }; 21 | 22 | // Create the definition 23 | const noteDefinition = { 24 | stix: { 25 | ...stixCoreDefinitions.commonRequiredSDO, 26 | ...stixCoreDefinitions.commonOptionalSDO, 27 | ...noteProperties, 28 | }, 29 | }; 30 | 31 | // Create the schema 32 | const noteSchema = new mongoose.Schema(noteDefinition); 33 | 34 | // Create the model 35 | const NoteModel = AttackObject.discriminator(ModelName.Note, noteSchema); 36 | 37 | module.exports = NoteModel; 38 | -------------------------------------------------------------------------------- /app/models/reference-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | // Create the definition 6 | const referenceDefinition = { 7 | source_name: { type: String, required: true }, 8 | description: { type: String, required: true }, 9 | url: String, 10 | }; 11 | 12 | // Create the schema 13 | const referenceSchema = new mongoose.Schema(referenceDefinition, { bufferCommands: false }); 14 | 15 | // The source_name must be unique 16 | referenceSchema.index({ source_name: 1 }, { unique: true }); 17 | 18 | // Create a text index to allow for text-based queries 19 | referenceSchema.index({ source_name: 'text', description: 'text', url: 'text' }); 20 | 21 | // Create the model 22 | const ReferenceModel = mongoose.model('Reference', referenceSchema); 23 | 24 | module.exports = ReferenceModel; 25 | -------------------------------------------------------------------------------- /app/models/relationship-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const workspaceDefinitions = require('./subschemas/workspace'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const relationshipProperties = { 9 | // relationship specific properties 10 | modified: { type: Date, required: true }, 11 | name: String, 12 | description: String, 13 | relationship_type: { type: String, required: true }, 14 | source_ref: { type: String, required: true }, 15 | target_ref: { type: String, required: true }, 16 | start_time: Date, 17 | stop_time: Date, 18 | 19 | // ATT&CK custom stix properties 20 | x_mitre_modified_by_ref: String, 21 | x_mitre_deprecated: Boolean, 22 | x_mitre_version: String, 23 | x_mitre_attack_spec_version: String, 24 | }; 25 | 26 | // Create the definition 27 | const relationshipDefinition = { 28 | workspace: { 29 | ...workspaceDefinitions.common, 30 | }, 31 | stix: { 32 | ...stixCoreDefinitions.commonRequiredSDO, 33 | ...stixCoreDefinitions.commonOptionalSDO, 34 | ...relationshipProperties, 35 | }, 36 | }; 37 | 38 | // Create the schema 39 | const relationshipSchema = new mongoose.Schema(relationshipDefinition); 40 | 41 | relationshipSchema.index({ 'stix.id': 1, 'stix.modified': -1 }, { unique: true }); 42 | 43 | // Create the model 44 | const RelationshipModel = mongoose.model(ModelName.Relationship, relationshipSchema); 45 | 46 | module.exports = RelationshipModel; 47 | -------------------------------------------------------------------------------- /app/models/software-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const stixMalware = { 9 | // STIX malware and tool specific properties 10 | modified: { type: Date, required: true }, 11 | name: { type: String, required: true }, 12 | description: String, 13 | is_family: Boolean, 14 | labels: [String], 15 | 16 | // ATT&CK custom stix properties 17 | x_mitre_modified_by_ref: String, 18 | x_mitre_platforms: [String], 19 | x_mitre_deprecated: Boolean, 20 | x_mitre_domains: [String], 21 | x_mitre_version: String, 22 | x_mitre_attack_spec_version: String, 23 | x_mitre_contributors: [String], 24 | x_mitre_aliases: [String], 25 | }; 26 | 27 | // Create the definition 28 | const softwareDefinition = { 29 | stix: { 30 | ...stixCoreDefinitions.commonRequiredSDO, 31 | ...stixCoreDefinitions.commonOptionalSDO, 32 | ...stixMalware, 33 | }, 34 | }; 35 | 36 | // Create the schema 37 | const softwareSchema = new mongoose.Schema(softwareDefinition); 38 | 39 | // Create the model 40 | const SoftwareModel = AttackObject.discriminator(ModelName.Software, softwareSchema); 41 | 42 | module.exports = SoftwareModel; 43 | -------------------------------------------------------------------------------- /app/models/subschemas/attack-pattern.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stixCore = require('./stix-core'); 4 | 5 | module.exports.attackPattern = { 6 | // STIX attack-pattern specific properties 7 | modified: { type: Date, required: true }, 8 | name: { type: String, required: true }, 9 | description: String, 10 | kill_chain_phases: [stixCore.killChainPhaseSchema], 11 | 12 | // ATT&CK custom STIX properties 13 | x_mitre_attack_spec_version: String, 14 | x_mitre_contributors: [String], 15 | x_mitre_deprecated: Boolean, 16 | x_mitre_detection: String, 17 | x_mitre_domains: [String], 18 | x_mitre_is_subtechnique: Boolean, 19 | x_mitre_modified_by_ref: String, 20 | x_mitre_platforms: [String], 21 | x_mitre_version: String, 22 | }; 23 | 24 | // Domain specific properties 25 | module.exports.attackPatternEnterpriseDomain = { 26 | x_mitre_data_sources: { type: [String], default: undefined }, 27 | x_mitre_defense_bypassed: { type: [String], default: undefined }, 28 | x_mitre_effective_permissions: { type: [String], default: undefined }, 29 | x_mitre_impact_type: { type: [String], default: undefined }, 30 | x_mitre_network_requirements: Boolean, 31 | x_mitre_permissions_required: { type: [String], default: undefined }, 32 | x_mitre_remote_support: Boolean, 33 | x_mitre_system_requirements: { type: [String], default: undefined }, 34 | }; 35 | 36 | module.exports.attackPatternMobileDomain = { 37 | x_mitre_tactic_type: { type: [String], default: undefined }, 38 | }; 39 | 40 | module.exports.attackPatternICSDomain = { 41 | x_mitre_data_sources: { type: [String], default: undefined }, 42 | }; 43 | -------------------------------------------------------------------------------- /app/models/subschemas/stix-core.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | const externalReference = { 6 | source_name: { type: String, required: true }, 7 | description: { type: String }, 8 | url: { type: String }, 9 | external_id: { type: String }, 10 | }; 11 | const externalReferenceSchema = new mongoose.Schema(externalReference, { _id: false }); 12 | 13 | const killChainPhase = { 14 | kill_chain_name: { type: String, required: true }, 15 | phase_name: { type: String, required: true }, 16 | }; 17 | module.exports.killChainPhaseSchema = new mongoose.Schema(killChainPhase, { _id: false }); 18 | 19 | module.exports.commonRequiredSDO = { 20 | type: { 21 | type: String, 22 | enum: [ 23 | 'attack-pattern', 24 | 'campaign', 25 | 'course-of-action', 26 | 'identity', 27 | 'intrusion-set', 28 | 'malware', 29 | 'marking-definition', 30 | 'note', 31 | 'relationship', 32 | 'tool', 33 | 'x-mitre-asset', 34 | 'x-mitre-collection', 35 | 'x-mitre-data-source', 36 | 'x-mitre-data-component', 37 | 'x-mitre-matrix', 38 | 'x-mitre-tactic', 39 | ], 40 | }, 41 | spec_version: { type: String, required: true }, 42 | id: { type: String, required: true }, 43 | created: { type: Date, required: true }, 44 | }; 45 | 46 | module.exports.commonOptionalSDO = { 47 | created_by_ref: { type: String }, 48 | revoked: { type: Boolean }, 49 | external_references: [externalReferenceSchema], 50 | object_marking_refs: [String], 51 | }; 52 | -------------------------------------------------------------------------------- /app/models/subschemas/workspace.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | const collectionVersion = { 6 | collection_ref: { type: String, required: true }, 7 | collection_modified: { type: Date, required: true }, 8 | }; 9 | const collectionVersionSchema = new mongoose.Schema(collectionVersion, { _id: false }); 10 | 11 | /** 12 | * Workspace property definition for most object types 13 | */ 14 | module.exports.common = { 15 | workflow: { 16 | state: { 17 | type: String, 18 | enum: ['work-in-progress', 'awaiting-review', 'reviewed', 'static'], 19 | }, 20 | created_by_user_account: String, 21 | }, 22 | attack_id: String, 23 | collections: [collectionVersionSchema], 24 | }; 25 | 26 | // x-mitre-collection workspace structure 27 | 28 | const exportData = { 29 | export_timestamp: Date, 30 | bundle_id: String, 31 | }; 32 | const exportDataSchema = new mongoose.Schema(exportData, { _id: false }); 33 | 34 | const importError = { 35 | object_ref: { type: String, required: true }, 36 | object_modified: { type: Date }, 37 | error_type: { type: String, required: true }, 38 | error_message: { type: String }, 39 | }; 40 | const importErrorSchema = new mongoose.Schema(importError, { _id: false }); 41 | 42 | /** 43 | * Workspace property definition for collection objects 44 | */ 45 | const importCategories = { 46 | additions: [String], 47 | changes: [String], 48 | minor_changes: [String], 49 | revocations: [String], 50 | deprecations: [String], 51 | supersedes_user_edits: [String], 52 | supersedes_collection_changes: [String], 53 | duplicates: [String], 54 | out_of_date: [String], 55 | errors: [importErrorSchema], 56 | }; 57 | 58 | const importReferences = { 59 | additions: [String], 60 | changes: [String], 61 | }; 62 | 63 | const reimportData = { 64 | imported: Date, 65 | import_categories: importCategories, 66 | import_references: importReferences, 67 | }; 68 | 69 | module.exports.collection = { 70 | imported: Date, 71 | exported: [exportDataSchema], 72 | import_categories: importCategories, 73 | import_references: importReferences, 74 | reimports: [reimportData], 75 | workflow: { 76 | state: { 77 | type: String, 78 | enum: ['work-in-progress', 'awaiting-review', 'reviewed'], 79 | }, 80 | created_by_user_account: String, 81 | release: Boolean, 82 | }, 83 | }; 84 | -------------------------------------------------------------------------------- /app/models/system-configuration-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | // Create the definition 6 | const systemConfigurationDefinition = { 7 | organization_identity_ref: { type: String, required: true }, 8 | anonymous_user_account_id: String, 9 | default_marking_definitions: [String], 10 | organization_namespace: { 11 | range_start: { type: Number, default: null }, 12 | prefix: { type: String, default: null }, 13 | }, 14 | }; 15 | 16 | // Create the schema 17 | const systemConfigurationSchema = new mongoose.Schema(systemConfigurationDefinition, { 18 | bufferCommands: false, 19 | }); 20 | 21 | // Create the model 22 | const SystemConfigurationModel = mongoose.model('SystemConfiguration', systemConfigurationSchema); 23 | 24 | module.exports = SystemConfigurationModel; 25 | -------------------------------------------------------------------------------- /app/models/tactic-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const stixCoreDefinitions = require('./subschemas/stix-core'); 6 | const { ModelName } = require('../lib/model-names'); 7 | 8 | const stixTactic = { 9 | // STIX x-mitre-tactic specific properties 10 | modified: { type: Date, required: true }, 11 | name: { type: String, required: true }, 12 | description: String, 13 | 14 | // ATT&CK custom stix properties 15 | x_mitre_modified_by_ref: String, 16 | x_mitre_deprecated: Boolean, 17 | x_mitre_domains: [String], 18 | x_mitre_version: String, 19 | x_mitre_attack_spec_version: String, 20 | x_mitre_contributors: [String], 21 | x_mitre_shortname: String, 22 | }; 23 | 24 | // Create the definition 25 | const tacticDefinition = { 26 | stix: { 27 | ...stixCoreDefinitions.commonRequiredSDO, 28 | ...stixCoreDefinitions.commonOptionalSDO, 29 | ...stixTactic, 30 | }, 31 | }; 32 | 33 | // Create the schema 34 | const tacticSchema = new mongoose.Schema(tacticDefinition); 35 | 36 | // Create the model 37 | const TacticModel = AttackObject.discriminator(ModelName.Tactic, tacticSchema); 38 | 39 | module.exports = TacticModel; 40 | -------------------------------------------------------------------------------- /app/models/team-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | // Create the definition 6 | const teamDefinition = { 7 | id: { 8 | type: String, 9 | index: { 10 | unique: true, 11 | }, 12 | }, 13 | name: { type: String, required: true, unique: true }, 14 | description: { type: String }, 15 | userIDs: [String], 16 | created: { type: Date, required: true }, 17 | modified: { type: Date, required: true }, 18 | }; 19 | 20 | // Create the schema 21 | const teamSchema = new mongoose.Schema(teamDefinition, { bufferCommands: false }); 22 | 23 | // Create the model 24 | const TeamModel = mongoose.model('Team', teamSchema); 25 | 26 | module.exports = TeamModel; 27 | -------------------------------------------------------------------------------- /app/models/technique-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const AttackObject = require('./attack-object-model'); 5 | const attackPatternDefinitions = require('./subschemas/attack-pattern'); 6 | const stixCoreDefinitions = require('./subschemas/stix-core'); 7 | const { ModelName } = require('../lib/model-names'); 8 | 9 | // Create the definition 10 | const techniqueDefinition = { 11 | stix: { 12 | ...stixCoreDefinitions.commonRequiredSDO, 13 | ...stixCoreDefinitions.commonOptionalSDO, 14 | ...attackPatternDefinitions.attackPattern, 15 | }, 16 | }; 17 | // Use Object.assign() to add properties in case there are duplicates 18 | Object.assign(techniqueDefinition.stix, attackPatternDefinitions.attackPatternEnterpriseDomain); 19 | Object.assign(techniqueDefinition.stix, attackPatternDefinitions.attackPatternMobileDomain); 20 | Object.assign(techniqueDefinition.stix, attackPatternDefinitions.attackPatternICSDomain); 21 | 22 | // Create the schema 23 | const techniqueSchema = new mongoose.Schema(techniqueDefinition); 24 | 25 | // Create the model 26 | const TechniqueModel = AttackObject.discriminator(ModelName.Technique, techniqueSchema); 27 | 28 | module.exports = TechniqueModel; 29 | -------------------------------------------------------------------------------- /app/models/user-account-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | // Create the definition 6 | const userAccountDefinition = { 7 | id: { type: String, required: true }, 8 | email: { 9 | type: String, 10 | index: { 11 | unique: true, 12 | partialFilterExpression: { email: { $type: 'string' } }, 13 | }, 14 | }, 15 | username: { type: String, required: true }, 16 | displayName: { type: String }, 17 | status: { type: String, required: true }, 18 | role: { type: String }, 19 | created: { type: Date, required: true }, 20 | modified: { type: Date, required: true }, 21 | }; 22 | 23 | // Create the schema 24 | const userAccountSchema = new mongoose.Schema(userAccountDefinition, { bufferCommands: false }); 25 | 26 | // Create the model 27 | const UserAccountModel = mongoose.model('UserAccount', userAccountSchema); 28 | 29 | module.exports = UserAccountModel; 30 | -------------------------------------------------------------------------------- /app/repository/campaigns-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const Campaign = require('../models/campaign-model'); 5 | 6 | class CampaignRepository extends BaseRepository {} 7 | 8 | module.exports = new CampaignRepository(Campaign); 9 | -------------------------------------------------------------------------------- /app/repository/collection-indexes-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const CollectionIndex = require('../models/collection-index-model'); 5 | const { DatabaseError, DuplicateIdError } = require('../exceptions'); 6 | 7 | class CollectionIndexesRepository extends BaseRepository { 8 | async retrieveAll(options) { 9 | try { 10 | return await this.model.find().skip(options.offset).limit(options.limit).lean().exec(); 11 | } catch (err) { 12 | throw new DatabaseError(err); 13 | } 14 | } 15 | 16 | async retrieveById(id) { 17 | try { 18 | return await this.model.findOne({ 'collection_index.id': id }).exec(); 19 | } catch (err) { 20 | throw new DatabaseError(err); 21 | } 22 | } 23 | 24 | async save(data) { 25 | try { 26 | const document = new this.model(data); 27 | return await document.save(); 28 | } catch (err) { 29 | if (err.name === 'MongoServerError' && err.code === 11000) { 30 | throw new DuplicateIdError({ 31 | details: `Document with id '${data.id}' already exists.`, 32 | }); 33 | } 34 | throw new DatabaseError(err); 35 | } 36 | } 37 | 38 | async findOneAndDelete(id) { 39 | try { 40 | return await this.model.findOneAndDelete({ 'collection_index.id': id }).exec(); 41 | } catch (err) { 42 | throw new DatabaseError(err); 43 | } 44 | } 45 | } 46 | 47 | module.exports = new CollectionIndexesRepository(CollectionIndex); 48 | -------------------------------------------------------------------------------- /app/repository/data-components-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const DataComponent = require('../models/data-component-model'); 5 | 6 | class DataComponentsRepository extends BaseRepository {} 7 | 8 | module.exports = new DataComponentsRepository(DataComponent); 9 | -------------------------------------------------------------------------------- /app/repository/data-sources-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const DataSource = require('../models/data-source-model'); 5 | 6 | class DataSourcesRepository extends BaseRepository {} 7 | 8 | module.exports = new DataSourcesRepository(DataSource); 9 | -------------------------------------------------------------------------------- /app/repository/groups-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const Group = require('../models/group-model'); 5 | 6 | class GroupsRepository extends BaseRepository {} 7 | 8 | module.exports = new GroupsRepository(Group); 9 | -------------------------------------------------------------------------------- /app/repository/identities-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const Identity = require('../models/identity-model'); 5 | 6 | class IdentitiesRepository extends BaseRepository {} 7 | 8 | module.exports = new IdentitiesRepository(Identity); 9 | -------------------------------------------------------------------------------- /app/repository/marking-definitions-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const MarkingDefinition = require('../models/marking-definition-model'); 5 | const { DatabaseError } = require('../exceptions'); 6 | 7 | class MarkingDefinitionsRepository extends BaseRepository { 8 | async deleteOneById(stixId) { 9 | try { 10 | return await this.model.findOneAndDelete({ 'stix.id': stixId }).exec(); 11 | } catch (err) { 12 | throw new DatabaseError(err); 13 | } 14 | } 15 | } 16 | 17 | module.exports = new MarkingDefinitionsRepository(MarkingDefinition); 18 | -------------------------------------------------------------------------------- /app/repository/mitigations-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const Mitigation = require('../models/mitigation-model'); 5 | 6 | class MitigationsRepository extends BaseRepository {} 7 | 8 | module.exports = new MitigationsRepository(Mitigation); 9 | -------------------------------------------------------------------------------- /app/repository/software-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const Software = require('../models/software-model'); 5 | 6 | class SoftwareRepository extends BaseRepository {} 7 | 8 | module.exports = new SoftwareRepository(Software); 9 | -------------------------------------------------------------------------------- /app/repository/system-configurations-repository.js: -------------------------------------------------------------------------------- 1 | const SystemConfiguration = require('../models/system-configuration-model'); 2 | const { DatabaseError } = require('../exceptions'); 3 | 4 | class SystemConfigurationsRepository { 5 | constructor(model) { 6 | this.model = model; 7 | } 8 | 9 | createNewDocument(data) { 10 | return new this.model(data); 11 | } 12 | 13 | static async saveDocument(document) { 14 | try { 15 | return await document.save(); 16 | } catch (err) { 17 | throw new DatabaseError(err); 18 | } 19 | } 20 | 21 | async retrieveOne(options) { 22 | options = options ?? {}; 23 | if (options.lean) { 24 | return await this.model.findOne().lean(); 25 | } else { 26 | return await this.model.findOne(); 27 | } 28 | } 29 | } 30 | 31 | module.exports = new SystemConfigurationsRepository(SystemConfiguration); 32 | -------------------------------------------------------------------------------- /app/repository/tactics-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const Tactic = require('../models/tactic-model'); 5 | 6 | class TacticsRepository extends BaseRepository {} 7 | 8 | module.exports = new TacticsRepository(Tactic); 9 | -------------------------------------------------------------------------------- /app/repository/techniques-repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseRepository = require('./_base.repository'); 4 | const Technique = require('../models/technique-model'); 5 | 6 | class TechniqueRepository extends BaseRepository {} 7 | 8 | module.exports = new TechniqueRepository(Technique); 9 | -------------------------------------------------------------------------------- /app/routes/assets-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const assetsController = require('../controllers/assets-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/assets') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | assetsController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), assetsController.create); 19 | 20 | router 21 | .route('/assets/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | assetsController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.admin), assetsController.deleteById); 28 | 29 | router 30 | .route('/assets/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | assetsController.retrieveVersionById, 35 | ) 36 | .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), assetsController.updateFull) 37 | .delete(authn.authenticate, authz.requireRole(authz.admin), assetsController.deleteVersionById); 38 | 39 | module.exports = router; 40 | -------------------------------------------------------------------------------- /app/routes/attack-objects-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const attackObjectsController = require('../controllers/attack-objects-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/attack-objects') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | attackObjectsController.retrieveAll, 17 | ); 18 | 19 | module.exports = router; 20 | -------------------------------------------------------------------------------- /app/routes/authn-anonymous-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const passport = require('passport'); 5 | 6 | const authnConfig = require('../lib/authn-configuration'); 7 | const authnAnonymous = require('../lib/authn-anonymous'); 8 | const authnAnonymousController = require('../controllers/authn-anonymous-controller'); 9 | 10 | const router = express.Router(); 11 | 12 | router 13 | .route('/authn/anonymous/login') 14 | .get( 15 | authnConfig.isUserAuthenticationMechanismEnabled('anonymous'), 16 | passport.authenticate(authnAnonymous.strategyName()), 17 | authnAnonymousController.login, 18 | ); 19 | 20 | router 21 | .route('/authn/anonymous/logout') 22 | .get( 23 | authnConfig.isUserAuthenticationMechanismEnabled('anonymous'), 24 | authnAnonymousController.logout, 25 | ); 26 | 27 | module.exports = router; 28 | -------------------------------------------------------------------------------- /app/routes/authn-oidc-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const passport = require('passport'); 5 | 6 | const authnConfig = require('../lib/authn-configuration'); 7 | const authnOidc = require('../lib/authn-oidc'); 8 | const authnOidcController = require('../controllers/authn-oidc-controller'); 9 | 10 | const router = express.Router(); 11 | router 12 | .route('/authn/oidc/login') 13 | .get( 14 | authnConfig.isUserAuthenticationMechanismEnabled('oidc'), 15 | authnOidcController.login, 16 | passport.authenticate(authnOidc.strategyName()), 17 | ); 18 | 19 | router 20 | .route('/authn/oidc/callback') 21 | .get( 22 | authnConfig.isUserAuthenticationMechanismEnabled('oidc'), 23 | passport.authenticate(authnOidc.strategyName(), { keepSessionInfo: true }), 24 | authnOidcController.identityProviderCallback, 25 | ); 26 | 27 | router 28 | .route('/authn/oidc/logout') 29 | .get(authnConfig.isUserAuthenticationMechanismEnabled('oidc'), authnOidcController.logout); 30 | 31 | module.exports = router; 32 | -------------------------------------------------------------------------------- /app/routes/authn-service-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const authnServiceController = require('../controllers/authn-service-controller'); 6 | const authnConfig = require('../lib/authn-configuration'); 7 | 8 | const router = express.Router(); 9 | 10 | router 11 | .route('/authn/service/apikey-challenge') 12 | .get( 13 | authnConfig.isServiceAuthenticationMechanismEnabled('challenge-apikey'), 14 | authnServiceController.apikeyGetChallenge, 15 | ); 16 | 17 | router 18 | .route('/authn/service/apikey-token') 19 | .get( 20 | authnConfig.isServiceAuthenticationMechanismEnabled('challenge-apikey'), 21 | authnServiceController.apikeyGetToken, 22 | ); 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /app/routes/campaigns-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const campaignsController = require('../controllers/campaigns-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/campaigns') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | campaignsController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), campaignsController.create); 19 | 20 | router 21 | .route('/campaigns/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | campaignsController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.admin), campaignsController.deleteById); 28 | 29 | router 30 | .route('/campaigns/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | campaignsController.retrieveVersionById, 35 | ) 36 | .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), campaignsController.updateFull) 37 | .delete( 38 | authn.authenticate, 39 | authz.requireRole(authz.admin), 40 | campaignsController.deleteVersionById, 41 | ); 42 | 43 | module.exports = router; 44 | -------------------------------------------------------------------------------- /app/routes/collection-bundles-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const collectionBundlesController = require('../controllers/collection-bundles-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/collection-bundles') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | collectionBundlesController.exportBundle, 17 | ) 18 | .post( 19 | authn.authenticate, 20 | authz.requireRole(authz.editorOrHigher, [authz.serviceRoles.collectionManager]), 21 | collectionBundlesController.importBundle, 22 | ); 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /app/routes/collection-indexes-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const collectionIndexesController = require('../controllers/collection-indexes-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/collection-indexes') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, [ 16 | authz.serviceRoles.readOnly, 17 | authz.serviceRoles.collectionManager, 18 | ]), 19 | collectionIndexesController.retrieveAll, 20 | ) 21 | .post( 22 | authn.authenticate, 23 | authz.requireRole(authz.editorOrHigher), 24 | collectionIndexesController.create, 25 | ); 26 | 27 | router 28 | .route('/collection-indexes/:id') 29 | .get( 30 | authn.authenticate, 31 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 32 | collectionIndexesController.retrieveById, 33 | ) 34 | .put( 35 | authn.authenticate, 36 | authz.requireRole(authz.editorOrHigher, [authz.serviceRoles.collectionManager]), 37 | collectionIndexesController.updateFull, 38 | ) 39 | .delete(authn.authenticate, authz.requireRole(authz.admin), collectionIndexesController.delete); 40 | 41 | router 42 | .route('/collection-indexes/:id/refresh') 43 | .post( 44 | authn.authenticate, 45 | authz.requireRole(authz.editorOrHigher), 46 | collectionIndexesController.refresh, 47 | ); 48 | 49 | module.exports = router; 50 | -------------------------------------------------------------------------------- /app/routes/collections-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const collectionsController = require('../controllers/collections-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/collections') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | collectionsController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), collectionsController.create); 19 | 20 | router 21 | .route('/collections/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, [ 25 | authz.serviceRoles.readOnly, 26 | authz.serviceRoles.collectionManager, 27 | ]), 28 | collectionsController.retrieveById, 29 | ) 30 | .delete(authn.authenticate, authz.requireRole(authz.admin), collectionsController.delete); 31 | 32 | router 33 | .route('/collections/:stixId/modified/:modified') 34 | .get( 35 | authn.authenticate, 36 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 37 | collectionsController.retrieveVersionById, 38 | ) 39 | .delete( 40 | authn.authenticate, 41 | authz.requireRole(authz.admin), 42 | collectionsController.deleteVersionById, 43 | ); 44 | 45 | module.exports = router; 46 | -------------------------------------------------------------------------------- /app/routes/data-components-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const dataComponentsController = require('../controllers/data-components-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/data-components') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | dataComponentsController.retrieveAll, 17 | ) 18 | .post( 19 | authn.authenticate, 20 | authz.requireRole(authz.editorOrHigher), 21 | dataComponentsController.create, 22 | ); 23 | 24 | router 25 | .route('/data-components/:stixId') 26 | .get( 27 | authn.authenticate, 28 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 29 | dataComponentsController.retrieveById, 30 | ) 31 | .delete(authn.authenticate, authz.requireRole(authz.admin), dataComponentsController.deleteById); 32 | 33 | router 34 | .route('/data-components/:stixId/modified/:modified') 35 | .get( 36 | authn.authenticate, 37 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 38 | dataComponentsController.retrieveVersionById, 39 | ) 40 | .put( 41 | authn.authenticate, 42 | authz.requireRole(authz.editorOrHigher), 43 | dataComponentsController.updateFull, 44 | ) 45 | .delete( 46 | authn.authenticate, 47 | authz.requireRole(authz.admin), 48 | dataComponentsController.deleteVersionById, 49 | ); 50 | 51 | module.exports = router; 52 | -------------------------------------------------------------------------------- /app/routes/data-sources-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const dataSourcesController = require('../controllers/data-sources-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/data-sources') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | dataSourcesController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), dataSourcesController.create); 19 | 20 | router 21 | .route('/data-sources/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | dataSourcesController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.admin), dataSourcesController.deleteById); 28 | 29 | router 30 | .route('/data-sources/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | dataSourcesController.retrieveVersionById, 35 | ) 36 | .put( 37 | authn.authenticate, 38 | authz.requireRole(authz.editorOrHigher), 39 | dataSourcesController.updateFull, 40 | ) 41 | .delete( 42 | authn.authenticate, 43 | authz.requireRole(authz.admin), 44 | dataSourcesController.deleteVersionById, 45 | ); 46 | 47 | module.exports = router; 48 | -------------------------------------------------------------------------------- /app/routes/groups-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const groupsController = require('../controllers/groups-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/groups') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | groupsController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), groupsController.create); 19 | 20 | router 21 | .route('/groups/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | groupsController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.admin), groupsController.deleteById); 28 | 29 | router 30 | .route('/groups/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | groupsController.retrieveVersionById, 35 | ) 36 | .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), groupsController.updateFull) 37 | .delete(authn.authenticate, authz.requireRole(authz.admin), groupsController.deleteVersionById); 38 | 39 | module.exports = router; 40 | -------------------------------------------------------------------------------- /app/routes/health-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const healthController = require('../controllers/health-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router.route('/health/ping').get( 12 | // No authentication or authorization required 13 | healthController.getPing, 14 | ); 15 | 16 | router 17 | .route('/health/status') 18 | .get( 19 | authn.authenticate, 20 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 21 | healthController.getStatus, 22 | ); 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /app/routes/identities-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const identitiesController = require('../controllers/identities-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/identities') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | identitiesController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), identitiesController.create); 19 | 20 | router 21 | .route('/identities/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | identitiesController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.admin), identitiesController.deleteById); 28 | 29 | router 30 | .route('/identities/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | identitiesController.retrieveVersionById, 35 | ) 36 | .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), identitiesController.updateFull) 37 | .delete( 38 | authn.authenticate, 39 | authz.requireRole(authz.admin), 40 | identitiesController.deleteVersionById, 41 | ); 42 | 43 | module.exports = router; 44 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const bodyParser = require('body-parser'); 5 | const OpenApiValidator = require('express-openapi-validator'); 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | 9 | const errorHandler = require('../lib/error-handler'); 10 | const config = require('../config/config'); 11 | const authnConfiguration = require('../lib/authn-configuration'); 12 | 13 | const router = express.Router(); 14 | 15 | // Parse the request body 16 | router.use('/api', bodyParser.json({ limit: '50mb' })); 17 | router.use('/api', bodyParser.urlencoded({ limit: '1mb', extended: true })); 18 | 19 | // Setup request validation 20 | router.use( 21 | OpenApiValidator.middleware({ 22 | apiSpec: config.openApi.specPath, 23 | validateRequests: true, 24 | validateResponses: false, 25 | }), 26 | ); 27 | 28 | // Setup passport middleware 29 | router.use('/api', authnConfiguration.passportMiddleware()); 30 | 31 | // Set up the endpoint routes 32 | // All files in this directory that end in '-routes.js' will be added as endpoint routes 33 | fs.readdirSync(path.join(__dirname, '.')).forEach(function (filename) { 34 | if (filename.endsWith('-routes.js')) { 35 | const moduleName = path.basename(filename, '.js'); 36 | const module = require('./' + moduleName); 37 | router.use('/api', module); 38 | } 39 | }); 40 | 41 | // Handle errors that haven't otherwise been caught 42 | router.use(errorHandler.bodyParser); 43 | router.use(errorHandler.requestValidation); 44 | router.use(errorHandler.catchAll); 45 | 46 | module.exports = router; 47 | -------------------------------------------------------------------------------- /app/routes/marking-definitions-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const markingDefinitionsController = require('../controllers/marking-definitions-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/marking-definitions') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | markingDefinitionsController.retrieveAll, 17 | ) 18 | .post( 19 | authn.authenticate, 20 | authz.requireRole(authz.editorOrHigher), 21 | markingDefinitionsController.create, 22 | ); 23 | 24 | router 25 | .route('/marking-definitions/:stixId') 26 | .get( 27 | authn.authenticate, 28 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 29 | markingDefinitionsController.retrieveById, 30 | ) 31 | .put( 32 | authn.authenticate, 33 | authz.requireRole(authz.editorOrHigher), 34 | markingDefinitionsController.updateFull, 35 | ) 36 | .delete(authn.authenticate, authz.requireRole(authz.admin), markingDefinitionsController.delete); 37 | 38 | module.exports = router; 39 | -------------------------------------------------------------------------------- /app/routes/matrices-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const matricesController = require('../controllers/matrices-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/matrices') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | matricesController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), matricesController.create); 19 | 20 | router 21 | .route('/matrices/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | matricesController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.admin), matricesController.deleteById); 28 | 29 | router 30 | .route('/matrices/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | matricesController.retrieveVersionById, 35 | ) 36 | .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), matricesController.updateFull) 37 | .delete(authn.authenticate, authz.requireRole(authz.admin), matricesController.deleteVersionById); 38 | 39 | router 40 | .route('/matrices/:stixId/modified/:modified/techniques') 41 | .get( 42 | authn.authenticate, 43 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 44 | matricesController.retrieveTechniquesForMatrix, 45 | ); 46 | 47 | module.exports = router; 48 | -------------------------------------------------------------------------------- /app/routes/mitigations-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const mitigationsController = require('../controllers/mitigations-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/mitigations') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | mitigationsController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), mitigationsController.create); 19 | 20 | router 21 | .route('/mitigations/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | mitigationsController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.admin), mitigationsController.deleteById); 28 | 29 | router 30 | .route('/mitigations/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | mitigationsController.retrieveVersionById, 35 | ) 36 | .put( 37 | authn.authenticate, 38 | authz.requireRole(authz.editorOrHigher), 39 | mitigationsController.updateFull, 40 | ) 41 | .delete( 42 | authn.authenticate, 43 | authz.requireRole(authz.admin), 44 | mitigationsController.deleteVersionById, 45 | ); 46 | 47 | module.exports = router; 48 | -------------------------------------------------------------------------------- /app/routes/notes-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const notesController = require('../controllers/notes-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/notes') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | notesController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), notesController.create); 19 | 20 | router 21 | .route('/notes/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | notesController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.editorOrHigher), notesController.deleteById); 28 | 29 | router 30 | .route('/notes/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | notesController.retrieveVersionById, 35 | ) 36 | .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), notesController.updateVersion) 37 | .delete( 38 | authn.authenticate, 39 | authz.requireRole(authz.editorOrHigher), 40 | notesController.deleteVersionById, 41 | ); 42 | 43 | module.exports = router; 44 | -------------------------------------------------------------------------------- /app/routes/recent-activity-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const recentActivityController = require('../controllers/recent-activity-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/recent-activity') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | recentActivityController.retrieveAll, 17 | ); 18 | 19 | module.exports = router; 20 | -------------------------------------------------------------------------------- /app/routes/references-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const referencesController = require('../controllers/references-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/references') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | referencesController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), referencesController.create) 19 | .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), referencesController.update) 20 | .delete( 21 | authn.authenticate, 22 | authz.requireRole(authz.editorOrHigher), 23 | referencesController.deleteBySourceName, 24 | ); 25 | 26 | module.exports = router; 27 | -------------------------------------------------------------------------------- /app/routes/relationships-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const relationshipsController = require('../controllers/relationships-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/relationships') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | relationshipsController.retrieveAll, 17 | ) 18 | .post( 19 | authn.authenticate, 20 | authz.requireRole(authz.editorOrHigher), 21 | relationshipsController.create, 22 | ); 23 | 24 | router 25 | .route('/relationships/:stixId') 26 | .get( 27 | authn.authenticate, 28 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 29 | relationshipsController.retrieveById, 30 | ) 31 | .delete(authn.authenticate, authz.requireRole(authz.admin), relationshipsController.deleteById); 32 | 33 | router 34 | .route('/relationships/:stixId/modified/:modified') 35 | .get( 36 | authn.authenticate, 37 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 38 | relationshipsController.retrieveVersionById, 39 | ) 40 | .put( 41 | authn.authenticate, 42 | authz.requireRole(authz.editorOrHigher), 43 | relationshipsController.updateFull, 44 | ) 45 | .delete( 46 | authn.authenticate, 47 | authz.requireRole(authz.admin), 48 | relationshipsController.deleteVersionById, 49 | ); 50 | 51 | module.exports = router; 52 | -------------------------------------------------------------------------------- /app/routes/session-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const sessionController = require('../controllers/session-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | 8 | const router = express.Router(); 9 | 10 | router.route('/session').get(authn.authenticate, sessionController.retrieveCurrentSession); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /app/routes/software-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const softwareController = require('../controllers/software-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/software') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | softwareController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), softwareController.create); 19 | 20 | router 21 | .route('/software/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | softwareController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.admin), softwareController.deleteById); 28 | 29 | router 30 | .route('/software/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | softwareController.retrieveVersionById, 35 | ) 36 | .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), softwareController.updateFull) 37 | .delete(authn.authenticate, authz.requireRole(authz.admin), softwareController.deleteVersionById); 38 | 39 | module.exports = router; 40 | -------------------------------------------------------------------------------- /app/routes/stix-bundles-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const stixBundlesController = require('../controllers/stix-bundles-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/stix-bundles') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, [ 16 | authz.serviceRoles.readOnly, 17 | authz.serviceRoles.stixExport, 18 | ]), 19 | stixBundlesController.exportBundle, 20 | ); 21 | 22 | module.exports = router; 23 | -------------------------------------------------------------------------------- /app/routes/system-configuration-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const systemConfigurationController = require('../controllers/system-configuration-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/config/system-version') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | systemConfigurationController.retrieveSystemVersion, 17 | ); 18 | 19 | router 20 | .route('/config/allowed-values') 21 | .get( 22 | authn.authenticate, 23 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 24 | systemConfigurationController.retrieveAllowedValues, 25 | ); 26 | 27 | router 28 | .route('/config/organization-identity') 29 | .get( 30 | authn.authenticate, 31 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 32 | systemConfigurationController.retrieveOrganizationIdentity, 33 | ) 34 | .post( 35 | authn.authenticate, 36 | authz.requireRole(authz.admin), 37 | systemConfigurationController.setOrganizationIdentity, 38 | ); 39 | 40 | router.route('/config/authn').get( 41 | // No authentication or authorization required 42 | systemConfigurationController.retrieveAuthenticationConfig, 43 | ); 44 | 45 | router 46 | .route('/config/default-marking-definitions') 47 | .get( 48 | authn.authenticate, 49 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 50 | systemConfigurationController.retrieveDefaultMarkingDefinitions, 51 | ) 52 | .post( 53 | authn.authenticate, 54 | authz.requireRole(authz.admin), 55 | systemConfigurationController.setDefaultMarkingDefinitions, 56 | ); 57 | 58 | router 59 | .route('/config/organization-namespace') 60 | .get( 61 | authn.authenticate, 62 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 63 | systemConfigurationController.retrieveOrganizationNamespace, 64 | ) 65 | .post( 66 | authn.authenticate, 67 | authz.requireRole(authz.admin), 68 | systemConfigurationController.setOrganizationNamespace, 69 | ); 70 | 71 | module.exports = router; 72 | -------------------------------------------------------------------------------- /app/routes/tactics-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const tacticsController = require('../controllers/tactics-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/tactics') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | tacticsController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), tacticsController.create); 19 | 20 | router 21 | .route('/tactics/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | tacticsController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.admin), tacticsController.deleteById); 28 | 29 | router 30 | .route('/tactics/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | tacticsController.retrieveVersionById, 35 | ) 36 | .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), tacticsController.updateFull) 37 | .delete(authn.authenticate, authz.requireRole(authz.admin), tacticsController.deleteVersionById); 38 | 39 | router 40 | .route('/tactics/:stixId/modified/:modified/techniques') 41 | .get( 42 | authn.authenticate, 43 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 44 | tacticsController.retrieveTechniquesForTactic, 45 | ); 46 | 47 | module.exports = router; 48 | -------------------------------------------------------------------------------- /app/routes/teams-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const teamsController = require('../controllers/teams-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/teams') 13 | .get(authn.authenticate, authz.requireRole(authz.admin), teamsController.retrieveAll) 14 | .post(authn.authenticate, authz.requireRole(authz.admin), teamsController.create); 15 | 16 | router 17 | .route('/teams/:id') 18 | .get(authn.authenticate, authz.requireRole(authz.admin), teamsController.retrieveById) 19 | .put(authn.authenticate, authz.requireRole(authz.admin), teamsController.updateFull) 20 | .delete(authn.authenticate, authz.requireRole(authz.admin), teamsController.delete); 21 | 22 | router 23 | .route('/teams/:id/users') 24 | .get(authn.authenticate, authz.requireRole(authz.admin), teamsController.retrieveAllUsers); 25 | 26 | module.exports = router; 27 | -------------------------------------------------------------------------------- /app/routes/techniques-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const techniquesController = require('../controllers/techniques-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/techniques') 13 | .get( 14 | authn.authenticate, 15 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 16 | techniquesController.retrieveAll, 17 | ) 18 | .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), techniquesController.create); 19 | 20 | router 21 | .route('/techniques/:stixId') 22 | .get( 23 | authn.authenticate, 24 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 25 | techniquesController.retrieveById, 26 | ) 27 | .delete(authn.authenticate, authz.requireRole(authz.admin), techniquesController.deleteById); 28 | 29 | router 30 | .route('/techniques/:stixId/modified/:modified') 31 | .get( 32 | authn.authenticate, 33 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 34 | techniquesController.retrieveVersionById, 35 | ) 36 | .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), techniquesController.updateFull) 37 | .delete( 38 | authn.authenticate, 39 | authz.requireRole(authz.admin), 40 | techniquesController.deleteVersionById, 41 | ); 42 | 43 | router 44 | .route('/techniques/:stixId/modified/:modified/tactics') 45 | .get( 46 | authn.authenticate, 47 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 48 | techniquesController.retrieveTacticsForTechnique, 49 | ); 50 | 51 | module.exports = router; 52 | -------------------------------------------------------------------------------- /app/routes/user-accounts-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const userAccountsController = require('../controllers/user-accounts-controller'); 6 | const authn = require('../lib/authn-middleware'); 7 | const authz = require('../lib/authz-middleware'); 8 | 9 | const router = express.Router(); 10 | 11 | router 12 | .route('/user-accounts') 13 | .get(authn.authenticate, authz.requireRole(authz.admin), userAccountsController.retrieveAll) 14 | .post(authn.authenticate, authz.requireRole(authz.admin), userAccountsController.create); 15 | 16 | router 17 | .route('/user-accounts/:id') 18 | .get( 19 | authn.authenticate, 20 | authz.requireRole(authz.editorOrHigher), 21 | userAccountsController.retrieveById, 22 | ) 23 | .put(authn.authenticate, authz.requireRole(authz.admin), userAccountsController.updateFull) 24 | .delete(authn.authenticate, authz.requireRole(authz.admin), userAccountsController.delete); 25 | 26 | router 27 | .route('/user-accounts/:id/teams') 28 | .get( 29 | authn.authenticate, 30 | authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), 31 | userAccountsController.retrieveTeamsByUserId, 32 | ); 33 | 34 | router.route('/user-accounts/register').post( 35 | // authn and authz handled in controller 36 | userAccountsController.register, 37 | ); 38 | 39 | module.exports = router; 40 | -------------------------------------------------------------------------------- /app/services/_abstract.service.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | // Note there is a bug in eslint where single line comment will not work ^ 3 | 'use strict'; 4 | 5 | const { NotImplementedError } = require('../exceptions'); 6 | 7 | class AbstractService { 8 | retrieveAll(options) { 9 | throw new NotImplementedError(this.constructor.name, 'retrieveAll'); 10 | } 11 | 12 | retrieveById(stixId, options) { 13 | throw new NotImplementedError(this.constructor.name, 'retrieveById'); 14 | } 15 | 16 | retrieveVersionById(stixId, modified) { 17 | throw new NotImplementedError(this.constructor.name, 'retrieveVersionById'); 18 | } 19 | 20 | create(data, options) { 21 | throw new NotImplementedError(this.constructor.name, 'create'); 22 | } 23 | 24 | // ... other abstract methods ... 25 | } 26 | 27 | module.exports = AbstractService; 28 | -------------------------------------------------------------------------------- /app/services/assets-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assetsRepository = require('../repository/assets-repository'); 4 | const BaseService = require('./_base.service'); 5 | const { Asset: AssetType } = require('../lib/types'); 6 | 7 | class AssetsService extends BaseService {} 8 | 9 | module.exports = new AssetsService(AssetType, assetsRepository); 10 | -------------------------------------------------------------------------------- /app/services/campaigns-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const campaignsRepository = require('../repository/campaigns-repository'); 4 | const BaseService = require('./_base.service'); 5 | const { Campaign: CampaignType } = require('../lib/types'); 6 | 7 | class CampaignService extends BaseService {} 8 | 9 | module.exports = new CampaignService(CampaignType, campaignsRepository); 10 | -------------------------------------------------------------------------------- /app/services/collection-bundles-service/bundle-helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.forceImportParameters = { 4 | attackSpecVersionViolations: 'attack-spec-version-violations', 5 | duplicateCollection: 'duplicate-collection', 6 | }; 7 | 8 | module.exports.errors = { 9 | duplicateCollection: 'Duplicate collection', 10 | notFound: 'Collection not found', 11 | attackSpecVersionViolation: 'ATT&CK Spec version violation', 12 | }; 13 | 14 | module.exports.validationErrors = { 15 | duplicateObjectInBundle: 'Duplicate object in bundle', 16 | invalidAttackSpecVersion: 'Invalid ATT&CK Spec version', 17 | }; 18 | 19 | module.exports.importErrors = { 20 | duplicateCollection: 'Duplicate collection object', 21 | retrievalError: 'Retrieval error', 22 | unknownObjectType: 'Unknown object type', 23 | notInContents: 'Not in contents', // object in bundle but not in x_mitre_contents 24 | missingObject: 'Missing object', // object in x_mitre_contents but not in bundle 25 | saveError: 'Save error', 26 | attackSpecVersionViolation: 'ATT&CK Spec version violation', 27 | }; 28 | 29 | module.exports.defaultAttackSpecVersion = '2.0.0'; 30 | 31 | /** 32 | * Creates a unique key for a STIX object based on its ID and modified/created date 33 | * @param {string} stixId - The STIX object ID 34 | * @param {string} modified - The modified/created timestamp 35 | * @returns {string} A unique key combining the ID and timestamp 36 | */ 37 | module.exports.makeKey = function (stixId, modified) { 38 | return stixId + '/' + modified; 39 | }; 40 | 41 | /** 42 | * Creates a unique key from a STIX object, handling special case for marking definitions 43 | * @param {Object} stixObject - The STIX object 44 | * @returns {string} A unique key for the object 45 | */ 46 | module.exports.makeKeyFromObject = function (stixObject) { 47 | if (stixObject.type === 'marking-definition') { 48 | return exports.makeKey(stixObject.id, stixObject.created); 49 | } else { 50 | return exports.makeKey(stixObject.id, stixObject.modified); 51 | } 52 | }; 53 | 54 | /** 55 | * Convert the date to seconds past the epoch 56 | * @param {Date|string} date - Date to convert 57 | * @returns {number} Epoch time in milliseconds 58 | */ 59 | module.exports.toEpoch = function (date) { 60 | if (date instanceof Date) { 61 | return date.getTime(); 62 | } else { 63 | return Date.parse(date); 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /app/services/collection-bundles-service/index.js: -------------------------------------------------------------------------------- 1 | // Default export 2 | module.exports = { 3 | importBundle: require('./import-bundle'), 4 | validateBundle: require('./validate-bundle'), 5 | exportBundle: require('./export-bundle'), 6 | errors: require('./bundle-helpers').errors, 7 | forceImportParameters: require('./bundle-helpers').forceImportParameters, 8 | }; 9 | -------------------------------------------------------------------------------- /app/services/collection-indexes-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const CollectionIndexesRepository = require('../repository/collection-indexes-repository'); 4 | const BaseService = require('./_base.service'); 5 | const { MissingParameterError, DatabaseError } = require('../exceptions'); 6 | const config = require('../config/config'); 7 | 8 | class CollectionIndexesService extends BaseService { 9 | async retrieveAll(options) { 10 | return await this.repository.retrieveAll(options); 11 | } 12 | 13 | async retrieveById(id) { 14 | return await this.repository.retrieveById(id); 15 | } 16 | 17 | async create(data) { 18 | if ( 19 | data.workspace.update_policy && 20 | data.workspace.update_policy.automatic && 21 | !data.workspace.update_policy.interval 22 | ) { 23 | data.workspace.update_policy.interval = config.collectionIndex.defaultInterval; 24 | } 25 | 26 | return await this.repository.save(data); 27 | } 28 | 29 | async updateFull(id, data) { 30 | if (!id) { 31 | throw new MissingParameterError('collection_index.id'); 32 | } 33 | 34 | const collectionIndex = await this.repository.retrieveById(id); 35 | 36 | if (!collectionIndex) return null; 37 | 38 | const newCollectionIndex = await this.repository.updateAndSave(collectionIndex, data); 39 | 40 | if (newCollectionIndex === collectionIndex) { 41 | return newCollectionIndex; 42 | } else { 43 | throw new DatabaseError({ 44 | details: 'Document could not be saved', 45 | collectionIndex, // Pass along the document that could not be saved 46 | }); 47 | } 48 | } 49 | 50 | async delete(id) { 51 | if (!id) { 52 | throw new MissingParameterError('collection_index.id'); 53 | } 54 | 55 | return await this.repository.findOneAndDelete(id); 56 | } 57 | } 58 | 59 | module.exports = new CollectionIndexesService(null, CollectionIndexesRepository); 60 | -------------------------------------------------------------------------------- /app/services/data-components-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dataComponentsRepository = require('../repository/data-components-repository.js'); 4 | const BaseService = require('./_base.service'); 5 | const { DataComponent: DataComponentType } = require('../lib/types.js'); 6 | 7 | class DataComponentsService extends BaseService {} 8 | 9 | module.exports = new DataComponentsService(DataComponentType, dataComponentsRepository); 10 | -------------------------------------------------------------------------------- /app/services/groups-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseService = require('./_base.service'); 4 | const groupsRepository = require('../repository/groups-repository'); 5 | const { Group: GroupType } = require('../lib/types'); 6 | 7 | class GroupsService extends BaseService {} 8 | 9 | module.exports = new GroupsService(GroupType, groupsRepository); 10 | -------------------------------------------------------------------------------- /app/services/identities-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const uuid = require('uuid'); 4 | const config = require('../config/config'); 5 | const identitiesRepository = require('../repository/identities-repository'); 6 | const BaseService = require('./_base.service'); 7 | const { InvalidTypeError } = require('../exceptions'); 8 | const { Identity: IdentityType } = require('../lib/types'); 9 | 10 | class IdentitiesService extends BaseService { 11 | /** 12 | * @public 13 | * CRUD Operation: Create 14 | * 15 | * Creates a new identity object 16 | * 17 | * Override of base class create() because: 18 | * 1. Does not set created_by_ref or x_mitre_modified_by_ref 19 | * 2. Does not check for existing identity object 20 | */ 21 | async create(data, options) { 22 | if (data?.stix?.type !== IdentityType) { 23 | throw new InvalidTypeError(); 24 | } 25 | 26 | options = options || {}; 27 | if (!options.import) { 28 | // Set the ATT&CK Spec Version 29 | data.stix.x_mitre_attack_spec_version = 30 | data.stix.x_mitre_attack_spec_version ?? config.app.attackSpecVersion; 31 | 32 | // Record the user account that created the object 33 | if (options.userAccountId) { 34 | data.workspace.workflow.created_by_user_account = options.userAccountId; 35 | } 36 | 37 | // Set the default marking definitions 38 | await this.setDefaultMarkingDefinitionsForObject(data); 39 | 40 | // Assign a new STIX id if not already provided 41 | data.stix.id = data.stix.id || `identity--${uuid.v4()}`; 42 | } 43 | 44 | // Save the document in the database 45 | return await this.repository.save(data); 46 | } 47 | } 48 | 49 | //Default export 50 | module.exports.IdentitiesService = IdentitiesService; 51 | 52 | // Export an instance of the service 53 | module.exports = new IdentitiesService(IdentityType, identitiesRepository); 54 | -------------------------------------------------------------------------------- /app/services/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //** import repositories */ 4 | const attackObjectsRepository = require('../repository/attack-objects-repository'); 5 | const identitiesRepository = require('../repository/identities-repository'); 6 | const relationshipsRepository = require('../repository/relationships-repository'); 7 | 8 | //** imports services */ 9 | const AttackObjectsService = require('./attack-objects-service'); 10 | const { IdentitiesService } = require('./identities-service'); 11 | const RelationshipsService = require('./relationships-service'); 12 | 13 | //** import types */ 14 | const { Identity: IdentityType, Relationship: RelationshipType } = require('../lib/types'); 15 | 16 | // ** initialize services */ 17 | const identitiesService = new IdentitiesService(IdentityType, identitiesRepository); 18 | const relationshipsService = new RelationshipsService(RelationshipType, relationshipsRepository); 19 | const attackObjectsService = new AttackObjectsService( 20 | attackObjectsRepository, 21 | identitiesService, 22 | relationshipsService, 23 | ); 24 | 25 | module.exports = { 26 | identitiesService, 27 | relationshipsService, 28 | attackObjectsService, 29 | }; 30 | -------------------------------------------------------------------------------- /app/services/mitigations-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mitigationsRepository = require('../repository/mitigations-repository'); 4 | const BaseService = require('./_base.service'); 5 | const { Mitigation: MitigationType } = require('../lib/types'); 6 | 7 | class MitigationsService extends BaseService {} 8 | 9 | module.exports = new MitigationsService(MitigationType, mitigationsRepository); 10 | -------------------------------------------------------------------------------- /app/services/recent-activity-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const recentActivityRepository = require('../repository/recent-activity-repository'); 4 | const identitiesService = require('./identities-service'); 5 | 6 | class RecentActivityService { 7 | constructor(repository) { 8 | this.repository = repository; 9 | } 10 | 11 | async retrieveAll(options) { 12 | const documents = await this.repository.retrieveAll(options); 13 | 14 | // Move latest source and target objects to a non-array property, then remove array of source and target objects 15 | for (const document of documents) { 16 | if (Array.isArray(document.source_objects)) { 17 | if (document.source_objects.length === 0) { 18 | document.source_objects = undefined; 19 | } else { 20 | document.source_object = document.source_objects[0]; 21 | document.source_objects = undefined; 22 | } 23 | } 24 | 25 | if (Array.isArray(document.target_objects)) { 26 | if (document.target_objects.length === 0) { 27 | document.target_objects = undefined; 28 | } else { 29 | document.target_object = document.target_objects[0]; 30 | document.target_objects = undefined; 31 | } 32 | } 33 | } 34 | 35 | // Apply pagination 36 | const offset = options.offset ?? 0; 37 | let paginatedDocuments; 38 | if (options.limit > 0) { 39 | paginatedDocuments = documents.slice(offset, offset + options.limit); 40 | } else { 41 | paginatedDocuments = documents.slice(offset); 42 | } 43 | 44 | // Add identities 45 | await identitiesService.addCreatedByAndModifiedByIdentitiesToAll(paginatedDocuments); 46 | 47 | // Prepare the return value 48 | if (options.includePagination) { 49 | const returnValue = { 50 | pagination: { 51 | total: documents.length, 52 | offset: options.offset, 53 | limit: options.limit, 54 | }, 55 | data: paginatedDocuments, 56 | }; 57 | return returnValue; 58 | } else { 59 | return paginatedDocuments; 60 | } 61 | } 62 | } 63 | 64 | module.exports = new RecentActivityService(recentActivityRepository); 65 | -------------------------------------------------------------------------------- /app/services/references-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseService = require('./_base.service'); 4 | const referencesRepository = require('../repository/references-repository'); 5 | const { MissingParameterError } = require('../exceptions'); 6 | 7 | class ReferencesService { 8 | constructor(repository) { 9 | this.repository = repository; 10 | } 11 | 12 | async retrieveAll(options) { 13 | const results = await this.repository.retrieveAll(options); 14 | const paginatedResults = BaseService.paginate(options, results); 15 | 16 | return paginatedResults; 17 | } 18 | 19 | async create(data) { 20 | return await this.repository.save(data); 21 | } 22 | 23 | async update(data) { 24 | // Note: source_name is used as the key and cannot be updated 25 | if (!data.source_name) { 26 | throw new MissingParameterError('data.source_name'); 27 | } 28 | 29 | return await this.repository.updateAndSave(data); 30 | } 31 | 32 | async deleteBySourceName(sourceName) { 33 | if (!sourceName) { 34 | throw new MissingParameterError('source_name'); 35 | } 36 | 37 | return await this.repository.findOneAndDelete(sourceName); 38 | } 39 | } 40 | 41 | module.exports = new ReferencesService(referencesRepository); 42 | -------------------------------------------------------------------------------- /app/tests/api/attack-objects/attack-objects-pagination.spec.js: -------------------------------------------------------------------------------- 1 | const techniquesService = require('../../../services/techniques-service'); 2 | const PaginationTests = require('../../shared/pagination'); 3 | 4 | // modified and created properties will be set before calling REST API 5 | // stix.id property will be created by REST API 6 | const initialObjectData = { 7 | workspace: { 8 | workflow: { 9 | state: 'work-in-progress', 10 | }, 11 | }, 12 | stix: { 13 | spec_version: '2.1', 14 | type: 'attack-pattern', 15 | description: 'This is a technique.', 16 | external_references: [{ source_name: 'source-1', external_id: 's1' }], 17 | object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], 18 | created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', 19 | kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], 20 | x_mitre_data_sources: ['data-source-1', 'data-source-2'], 21 | x_mitre_detection: 'detection text', 22 | x_mitre_is_subtechnique: false, 23 | x_mitre_impact_type: ['impact-1'], 24 | x_mitre_platforms: ['platform-1', 'platform-2'], 25 | }, 26 | }; 27 | 28 | // Use the techniques service for creating objects, but the attack-objects API for retrieving them 29 | // Include the state so that the placeholder organization identity isn't retrieved (which would throw off the numbers) 30 | const options = { 31 | prefix: 'attack-pattern', 32 | baseUrl: '/api/attack-objects', 33 | label: 'Attack Objects', 34 | state: 'work-in-progress', 35 | }; 36 | const paginationTests = new PaginationTests(techniquesService, initialObjectData, options); 37 | paginationTests.executeTests(); 38 | -------------------------------------------------------------------------------- /app/tests/api/data-components/data-components-pagination.spec.js: -------------------------------------------------------------------------------- 1 | const dataComponentsService = require('../../../services/data-components-service'); 2 | const PaginationTests = require('../../shared/pagination'); 3 | 4 | // modified and created properties will be set before calling REST API 5 | // stix.id property will be created by REST API 6 | const initialObjectData = { 7 | workspace: { 8 | workflow: { 9 | state: 'work-in-progress', 10 | }, 11 | }, 12 | stix: { 13 | spec_version: '2.1', 14 | type: 'x-mitre-data-component', 15 | description: 'This is a data component.', 16 | external_references: [{ source_name: 'source-1', external_id: 's1' }], 17 | object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], 18 | created_by_ref: 'identity--6444f546-6900-4456-b3b1-015c88d70dab', 19 | }, 20 | }; 21 | 22 | const options = { 23 | prefix: 'x-mitre-data-component', 24 | baseUrl: '/api/data-components', 25 | label: 'Data Components', 26 | }; 27 | const paginationTests = new PaginationTests(dataComponentsService, initialObjectData, options); 28 | paginationTests.executeTests(); 29 | -------------------------------------------------------------------------------- /app/tests/api/data-sources/data-sources-pagination.spec.js: -------------------------------------------------------------------------------- 1 | const dataSourcesService = require('../../../services/data-sources-service'); 2 | const PaginationTests = require('../../shared/pagination'); 3 | 4 | // modified and created properties will be set before calling REST API 5 | // stix.id property will be created by REST API 6 | const initialObjectData = { 7 | workspace: { 8 | workflow: { 9 | state: 'work-in-progress', 10 | }, 11 | }, 12 | stix: { 13 | spec_version: '2.1', 14 | type: 'x-mitre-data-source', 15 | description: 'This is a data source.', 16 | external_references: [{ source_name: 'source-1', external_id: 's1' }], 17 | object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], 18 | created_by_ref: 'identity--6444f546-6900-4456-b3b1-015c88d70dab', 19 | }, 20 | }; 21 | 22 | const options = { 23 | prefix: 'x-mitre-data-source', 24 | baseUrl: '/api/data-sources', 25 | label: 'Data Sources', 26 | }; 27 | const paginationTests = new PaginationTests(dataSourcesService, initialObjectData, options); 28 | paginationTests.executeTests(); 29 | -------------------------------------------------------------------------------- /app/tests/api/groups/groups-pagination.spec.js: -------------------------------------------------------------------------------- 1 | const groupsService = require('../../../services/groups-service'); 2 | const PaginationTests = require('../../shared/pagination'); 3 | 4 | // modified and created properties will be set before calling REST API 5 | // stix.id property will be created by REST API 6 | const initialObjectData = { 7 | workspace: { 8 | workflow: { 9 | state: 'work-in-progress', 10 | }, 11 | }, 12 | stix: { 13 | spec_version: '2.1', 14 | type: 'intrusion-set', 15 | description: 'This is a group. Blue.', 16 | external_references: [{ source_name: 'source-1', external_id: 's1' }], 17 | object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], 18 | created_by_ref: 'identity--6444f546-6900-4456-b3b1-015c88d70dab', 19 | }, 20 | }; 21 | 22 | const options = { 23 | prefix: 'intrustion-set', 24 | baseUrl: '/api/groups', 25 | label: 'Groups', 26 | }; 27 | const paginationTests = new PaginationTests(groupsService, initialObjectData, options); 28 | paginationTests.executeTests(); 29 | -------------------------------------------------------------------------------- /app/tests/api/groups/groups.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspace": { 3 | "workflow": {} 4 | }, 5 | "stix": { 6 | "spec_version": "2.1", 7 | "type": "intrusion-set", 8 | "description": "This is a group.", 9 | "external_references": [], 10 | "object_marking_refs": ["marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"], 11 | "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", 12 | "x_mitre_version": "1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/tests/api/mitigations/mitigations-pagination.spec.js: -------------------------------------------------------------------------------- 1 | const mitigationsService = require('../../../services/mitigations-service'); 2 | const PaginationTests = require('../../shared/pagination'); 3 | 4 | // modified and created properties will be set before calling REST API 5 | // stix.id property will be created by REST API 6 | const initialObjectData = { 7 | workspace: { 8 | workflow: { 9 | state: 'work-in-progress', 10 | }, 11 | }, 12 | stix: { 13 | spec_version: '2.1', 14 | type: 'course-of-action', 15 | description: 'This is a mitigation.', 16 | external_references: [{ source_name: 'source-1', external_id: 's1' }], 17 | object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], 18 | created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', 19 | x_mitre_version: '1.1', 20 | }, 21 | }; 22 | 23 | const options = { 24 | prefix: 'course-of-action', 25 | baseUrl: '/api/mitigations', 26 | label: 'Mitigations', 27 | }; 28 | const paginationTests = new PaginationTests(mitigationsService, initialObjectData, options); 29 | paginationTests.executeTests(); 30 | -------------------------------------------------------------------------------- /app/tests/api/relationships/relationships-pagination.spec.js: -------------------------------------------------------------------------------- 1 | const relationshipsService = require('../../../services/relationships-service'); 2 | const PaginationTests = require('../../shared/pagination'); 3 | 4 | // modified and created properties will be set before calling REST API 5 | // stix.id property will be created by REST API 6 | const sourceRef1 = 'malware--67e6d66b-1b82-4699-b47a-e2efb6268d14'; 7 | const targetRef1 = 'attack-pattern--7b211ac6-c815-4189-93a9-ab415deca926'; 8 | const initialObjectData = { 9 | workspace: { 10 | workflow: { 11 | state: 'work-in-progress', 12 | }, 13 | }, 14 | stix: { 15 | spec_version: '2.1', 16 | type: 'relationship', 17 | description: 'This is a relationship.', 18 | source_ref: sourceRef1, 19 | relationship_type: 'uses', 20 | target_ref: targetRef1, 21 | external_references: [{ source_name: 'source-1', external_id: 's1' }], 22 | object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], 23 | created_by_ref: 'identity--6444f546-6900-4456-b3b1-015c88d70dab', 24 | }, 25 | }; 26 | 27 | const options = { 28 | prefix: 'relationship', 29 | baseUrl: '/api/relationships', 30 | label: 'Relationships', 31 | }; 32 | const paginationTests = new PaginationTests(relationshipsService, initialObjectData, options); 33 | paginationTests.executeTests(); 34 | -------------------------------------------------------------------------------- /app/tests/api/session/session.spec.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | //const { expect } = require('expect'); 3 | 4 | const database = require('../../../lib/database-in-memory'); 5 | const databaseConfiguration = require('../../../lib/database-configuration'); 6 | 7 | const logger = require('../../../lib/logger'); 8 | logger.level = 'debug'; 9 | 10 | describe('Session API', function () { 11 | let app; 12 | 13 | before(async function () { 14 | // Establish the database connection 15 | // Use an in-memory database that we spin up for the test 16 | await database.initializeConnection(); 17 | 18 | // Check for a valid database configuration 19 | await databaseConfiguration.checkSystemConfiguration(); 20 | 21 | // Initialize the express app 22 | app = await require('../../../index').initializeApp(); 23 | }); 24 | 25 | // it('GET /api/session', function (done) { 26 | // request(app) 27 | // .get('/api/session') 28 | // .set('Accept', 'application/json') 29 | // .expect(200) 30 | // .expect('Content-Type', /json/) 31 | // .end(function(err, res) { 32 | // if (err) { 33 | // done(err); 34 | // } 35 | // else { 36 | // // We expect to get the current session 37 | // const session = res.body; 38 | // expect(session).toBeDefined(); 39 | // 40 | // done(); 41 | // } 42 | // }); 43 | // }); 44 | 45 | // Temporary change: /api/session returns 401 if the user is not logged in. 46 | // This will be fixed with a general purpose solution for logging in when 47 | // running tests, but is changed to expect the 401 for now. 48 | it('GET /api/session', async function () { 49 | await request(app).get('/api/session').set('Accept', 'application/json').expect(401); 50 | }); 51 | 52 | after(async function () { 53 | await database.closeConnection(); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /app/tests/api/software/software-pagination.spec.js: -------------------------------------------------------------------------------- 1 | const softwareService = require('../../../services/software-service'); 2 | const PaginationTests = require('../../shared/pagination'); 3 | 4 | // modified and created properties will be set before calling REST API 5 | // stix.id property will be created by REST API 6 | const initialObjectData = { 7 | workspace: { 8 | workflow: { 9 | state: 'work-in-progress', 10 | }, 11 | }, 12 | stix: { 13 | spec_version: '2.1', 14 | type: 'malware', 15 | description: 'This is a malware type of software.', 16 | is_family: true, 17 | external_references: [{ source_name: 'source-1', external_id: 's1' }], 18 | object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], 19 | created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', 20 | x_mitre_version: '1.1', 21 | x_mitre_aliases: ['software-1'], 22 | x_mitre_platforms: ['platform-1'], 23 | x_mitre_contributors: ['contributor-1', 'contributor-2'], 24 | x_mitre_domains: ['mobile-attack'], 25 | }, 26 | }; 27 | 28 | const options = { 29 | prefix: 'software', 30 | baseUrl: '/api/software', 31 | label: 'Software', 32 | }; 33 | const paginationTests = new PaginationTests(softwareService, initialObjectData, options); 34 | paginationTests.executeTests(); 35 | -------------------------------------------------------------------------------- /app/tests/api/teams/teams-invalid.spec.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | 3 | const logger = require('../../../lib/logger'); 4 | logger.level = 'debug'; 5 | 6 | const database = require('../../../lib/database-in-memory'); 7 | const databaseConfiguration = require('../../../lib/database-configuration'); 8 | 9 | const teams = require('./teams.invalid.json'); 10 | 11 | const login = require('../../shared/login'); 12 | 13 | describe('Teams API Test Invalid Data', function () { 14 | let app; 15 | let passportCookie; 16 | 17 | before(async function () { 18 | // Establish the database connection 19 | // Use an in-memory database that we spin up for the test 20 | await database.initializeConnection(); 21 | 22 | // Check for a valid database configuration 23 | await databaseConfiguration.checkSystemConfiguration(); 24 | 25 | // Initialize the express app 26 | app = await require('../../../index').initializeApp(); 27 | 28 | // Log into the app 29 | passportCookie = await login.loginAnonymous(app); 30 | }); 31 | 32 | for (const teamData of teams) { 33 | it(`POST /api/teams does not create a user account with invalid data (${teamData.description})`, async function () { 34 | const body = teamData; 35 | await request(app) 36 | .post('/api/teams') 37 | .send(body) 38 | .set('Accept', 'application/json') 39 | .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) 40 | .expect(400); 41 | }); 42 | } 43 | 44 | after(async function () { 45 | await database.closeConnection(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /app/tests/api/teams/teams.invalid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "no name", 4 | "userIDs": [] 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /app/tests/api/teams/teams.valid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "team1", 4 | "description": "exampleTeam", 5 | "userIDs": [] 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /app/tests/api/techniques/techniques-pagination.spec.js: -------------------------------------------------------------------------------- 1 | const techniquesService = require('../../../services/techniques-service'); 2 | const PaginationTests = require('../../shared/pagination'); 3 | 4 | // modified and created properties will be set before calling REST API 5 | // stix.id property will be created by REST API 6 | const initialObjectData = { 7 | workspace: { 8 | workflow: { 9 | state: 'work-in-progress', 10 | }, 11 | }, 12 | stix: { 13 | spec_version: '2.1', 14 | type: 'attack-pattern', 15 | description: 'This is a technique.', 16 | external_references: [{ source_name: 'source-1', external_id: 's1' }], 17 | object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], 18 | created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', 19 | kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], 20 | x_mitre_data_sources: ['data-source-1', 'data-source-2'], 21 | x_mitre_detection: 'detection text', 22 | x_mitre_is_subtechnique: false, 23 | x_mitre_impact_type: ['impact-1'], 24 | x_mitre_platforms: ['platform-1', 'platform-2'], 25 | }, 26 | }; 27 | 28 | const options = { 29 | prefix: 'attack-pattern', 30 | baseUrl: '/api/techniques', 31 | label: 'Techniques', 32 | }; 33 | const paginationTests = new PaginationTests(techniquesService, initialObjectData, options); 34 | paginationTests.executeTests(); 35 | -------------------------------------------------------------------------------- /app/tests/api/techniques/techniques.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspace": { 3 | "workflow": {} 4 | }, 5 | "stix": { 6 | "spec_version": "2.1", 7 | "type": "attack-pattern", 8 | "description": "This is a technique.", 9 | "external_references": [{ "source_name": "source-1", "external_id": "s1" }], 10 | "object_marking_refs": ["marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"], 11 | "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", 12 | "kill_chain_phases": [{ "kill_chain_name": "kill-chain-name-1", "phase_name": "phase-1" }], 13 | "x_mitre_data_sources": ["data-source-1", "data-source-2"], 14 | "x_mitre_detection": "detection text", 15 | "x_mitre_is_subtechnique": false, 16 | "x_mitre_impact_type": ["impact-1"], 17 | "x_mitre_platforms": ["platform-1", "platform-2"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/tests/api/user-accounts/user-accounts-invalid.spec.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | 3 | const logger = require('../../../lib/logger'); 4 | logger.level = 'debug'; 5 | 6 | const database = require('../../../lib/database-in-memory'); 7 | const databaseConfiguration = require('../../../lib/database-configuration'); 8 | 9 | const userAccounts = require('./user-accounts.invalid.json'); 10 | 11 | const login = require('../../shared/login'); 12 | 13 | describe('User Accounts API Test Invalid Data', function () { 14 | let app; 15 | let passportCookie; 16 | 17 | before(async function () { 18 | // Establish the database connection 19 | // Use an in-memory database that we spin up for the test 20 | await database.initializeConnection(); 21 | 22 | // Check for a valid database configuration 23 | await databaseConfiguration.checkSystemConfiguration(); 24 | 25 | // Initialize the express app 26 | app = await require('../../../index').initializeApp(); 27 | 28 | // Log into the app 29 | passportCookie = await login.loginAnonymous(app); 30 | }); 31 | 32 | for (const userAccountData of userAccounts) { 33 | it(`POST /api/user-accounts does not create a user account with invalid data (${userAccountData.username})`, async function () { 34 | const body = userAccountData; 35 | await request(app) 36 | .post('/api/user-accounts') 37 | .send(body) 38 | .set('Accept', 'application/json') 39 | .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) 40 | .expect(400); 41 | }); 42 | } 43 | 44 | after(async function () { 45 | await database.closeConnection(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /app/tests/api/user-accounts/user-accounts.invalid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "email": "user1@test.com", 4 | "username": "user missing status and role" 5 | }, 6 | { 7 | "email": "user1@test.com", 8 | "displayName": "user missing username", 9 | "status": "pending" 10 | }, 11 | { 12 | "email": "user2@test.com", 13 | "username": "user invalid status", 14 | "status": "abcde", 15 | "role": "editor" 16 | }, 17 | { 18 | "email": "user3@test.com", 19 | "username": "user invalid role", 20 | "status": "active", 21 | "role": "xyzzy" 22 | }, 23 | { 24 | "email": "user4@test.com", 25 | "username": "user inactive cannot have role", 26 | "status": "inactive", 27 | "role": "admin" 28 | }, 29 | { 30 | "email": 5, 31 | "username": "user has number for email", 32 | "status": "active", 33 | "role": "editor" 34 | }, 35 | { 36 | "email": "user6@test.com", 37 | "username": 6, 38 | "status": "active", 39 | "role": "editor" 40 | }, 41 | { 42 | "email": "user7@test.com", 43 | "username": "user has number for status", 44 | "status": 7, 45 | "role": "editor" 46 | }, 47 | { 48 | "email": "user8@test.com", 49 | "username": "user has number for role", 50 | "status": "active", 51 | "role": 8 52 | } 53 | ] 54 | -------------------------------------------------------------------------------- /app/tests/api/user-accounts/user-accounts.valid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "email": "user1@test.com", 4 | "username": "user1@test.com", 5 | "displayName": "User 1", 6 | "status": "active", 7 | "role": "visitor" 8 | }, 9 | { 10 | "email": "user2@test.com", 11 | "username": "user2@test.com", 12 | "displayName": "User 2", 13 | "status": "active", 14 | "role": "editor" 15 | }, 16 | { 17 | "email": "user3@test.com", 18 | "username": "user3@test.com", 19 | "displayName": "User 3", 20 | "status": "active", 21 | "role": "admin" 22 | }, 23 | { 24 | "email": "user4@test.com", 25 | "username": "user4", 26 | "status": "inactive" 27 | }, 28 | { 29 | "email": "user5@test.com", 30 | "username": "user5", 31 | "status": "pending" 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /app/tests/authn/README.md: -------------------------------------------------------------------------------- 1 | ## Authentication Tests 2 | 3 | These tests can be run using an npm script: 4 | 5 | ```shell 6 | npm run test:authn 7 | ``` 8 | 9 | Note that each test spec is run as a separate mocha job. This is because each spec requires a unique run environment. 10 | 11 | ### Keycloak 12 | 13 | These tests require a keycloak server to be running. The server can be started on Docker with the command: 14 | 15 | ```shell 16 | docker run -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin --name keycloak -d quay.io/keycloak/keycloak:26.0.1 start-dev 17 | docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin --name keycloak -d jboss/keycloak 18 | ``` 19 | 20 | This starts the keycloak server and adds an admin user. 21 | -------------------------------------------------------------------------------- /app/tests/authn/basic-apikey-service-account.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceAuthn": { 3 | "basicApikey": { 4 | "enable": true, 5 | "serviceAccounts": [ 6 | { 7 | "name": "apikey-test-service", 8 | "apikey": "xyzzy", 9 | "serviceRole": "stix-export" 10 | } 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/tests/authn/challenge-apikey-service-account.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceAuthn": { 3 | "challengeApikey": { 4 | "enable": true, 5 | "serviceAccounts": [ 6 | { 7 | "name": "apikey-test-service", 8 | "apikey": "xyzzy", 9 | "serviceRole": "read-only" 10 | } 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/tests/authn/oidc-client-credentials-service-account.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceAuthn": { 3 | "oidcClientCredentials": { 4 | "enable": true, 5 | "clients": [ 6 | { 7 | "clientId": "oidc-test-service" 8 | } 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/tests/config/test-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "name": "test-config" 4 | }, 5 | "configurationFiles": { 6 | "staticMarkingDefinitionsPath": "./app/tests/config/test-static-marking-definitions" 7 | }, 8 | "collectionIndex": { 9 | "defaultInterval": 100 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/tests/config/test-static-marking-definitions/empty-definitions.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /app/tests/config/test-static-marking-definitions/static-marking-definitions-1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "marking-definition", 4 | "spec_version": "2.1", 5 | "id": "marking-definition--840f7f6c-c95f-47c0-82f8-43b0d7c6cb95", 6 | "created": "2022-01-01T00:00:00.000Z", 7 | "definition_type": "statement", 8 | "name": "Test Marking Definition 1", 9 | "definition": { 10 | "statement": "This is a test (1)" 11 | } 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /app/tests/config/test-static-marking-definitions/static-marking-definitions-2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "marking-definition", 4 | "spec_version": "2.1", 5 | "id": "marking-definition--20bafdac-191c-4ac4-8c3f-22c8bb9002b1", 6 | "created": "2022-01-01T00:00:00.000Z", 7 | "definition_type": "statement", 8 | "name": "Test Marking Definition 2", 9 | "definition": { 10 | "statement": "This is a test (2)" 11 | } 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /app/tests/integration-test/README.md: -------------------------------------------------------------------------------- 1 | ## Integration Test 2 | 3 | ### 1 Start the Servers 4 | 5 | - MongoDB 6 | - REST API 7 | - Mock Remote Host 8 | - Collection Manager 9 | 10 | ### 2 Clear the Database 11 | 12 | In the attack-workbench-rest-api project, run: 13 | 14 | ```shell 15 | node ./scripts/clearDatabase.js 16 | ``` 17 | 18 | Also, delete the collection index manually. (The script only deletes ATT&CK objects and references.) 19 | 20 | ### 3 Initialize Data 21 | 22 | In this project, run: 23 | 24 | ```shell 25 | bash ./tests/integration-test/initialize-data.sh 26 | ``` 27 | 28 | This script will: 29 | 30 | - Clear the test directories 31 | - Copy the collection index v1 file to the index test directory 32 | - Copy the collection bundle Blue v1 to the bundle test directory 33 | - Run a JavaScript program to read the collection index file and import the collection index into the database 34 | 35 | Because the collection index is initialized with a subscription for the Blue collection, this should cause the Collection Manager to import the collection bundle Blue v1. 36 | 37 | ### 4 Update the Collection Index 38 | 39 | In this project, run: 40 | 41 | ```shell 42 | bash ./tests/integration-test/update-collection-a.sh 43 | ``` 44 | 45 | This script will: 46 | 47 | - Copy collection index v2 to the index test directory, overwriting v1 48 | - Copy the collection bundles Blue v2, Red v1, and Green v1 to the bundle test directory 49 | 50 | Due to the subscription to the Blue collection, this should cause the Collection Manager to import the collection bundle Blue v2. 51 | 52 | ### 5 Add a New Subscription 53 | 54 | In this project, run: 55 | 56 | ```shell 57 | bash ./tests/integration-test/update-subscription.sh 58 | ``` 59 | 60 | This script will: 61 | 62 | - Modify the collection index in the database, adding a subscription to the Green collection 63 | 64 | Due to the added subscription to the Green collection, this should cause the Collection Manager to import the collection bundle Green v1. 65 | -------------------------------------------------------------------------------- /app/tests/integration-test/initialize-data.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const superagent = require('superagent'); 6 | const setCookieParser = require('set-cookie-parser'); 7 | 8 | const passportCookieName = 'connect.sid'; 9 | 10 | let passportCookie; 11 | async function login(url) { 12 | const res = await superagent.get(url); 13 | const cookies = setCookieParser(res); 14 | passportCookie = cookies.find((c) => c.name === passportCookieName); 15 | } 16 | 17 | function post(url, data) { 18 | return superagent 19 | .post(url) 20 | .set('Cookie', `${passportCookieName}=${passportCookie.value}`) 21 | .send(data); 22 | } 23 | 24 | async function initializeData() { 25 | // Read the collection index v1 from the file 26 | const collectionIndexJson = require('./mock-data/collection-index-v1.json'); 27 | 28 | // Create the collection index object, including a subscription to the Blue collection 29 | const collectionIndex = { 30 | collection_index: collectionIndexJson, 31 | workspace: { 32 | remote_url: 'http://localhost/collection-indexes/collection-index.json', 33 | update_policy: { 34 | automatic: true, 35 | interval: 30, 36 | last_retrieval: new Date().toISOString(), 37 | subscriptions: [collectionIndexJson.collections[0].id], 38 | }, 39 | }, 40 | }; 41 | 42 | // Log into the Workbench REST API 43 | const loginUrl = 'http://localhost:3000/api/authn/anonymous/login'; 44 | await login(loginUrl); 45 | 46 | // Import the collection index v1 into the database 47 | const postCollectionIndexesUrl = 'http://localhost:3000/api/collection-indexes'; 48 | await post(postCollectionIndexesUrl, collectionIndex); 49 | } 50 | 51 | initializeData() 52 | .then(() => { 53 | console.log('initializeData() - Terminating normally'); 54 | process.exit(); 55 | }) 56 | .catch((err) => { 57 | console.log('initializeData() - Error: ' + err); 58 | process.exit(1); 59 | }); 60 | -------------------------------------------------------------------------------- /app/tests/integration-test/initialize-data.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Clear the test directories 4 | rm -fv ./tests/integration-test/mock-remote-host/mock-data/collection-indexes/*.json 5 | rm -fv ./tests/integration-test/mock-remote-host/mock-data/collection-bundles/*.json 6 | 7 | # Copy the first set of files to the test directories 8 | cp ./tests/integration-test/mock-data/collection-index-v1.json ./tests/integration-test/mock-remote-host/mock-data/collection-indexes/collection-index.json 9 | cp ./tests/integration-test/mock-data/blue-v1.json ./tests/integration-test/mock-remote-host/mock-data/collection-bundles/ 10 | 11 | cd ./tests/integration-test 12 | env WORKBENCH_AUTHN_APIKEY=ePcAssW9Ad9CUBghWCeW node ./initialize-data.js 13 | -------------------------------------------------------------------------------- /app/tests/integration-test/mock-data/collection-index-v1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "43f56ef6-99a3-455d-9acc-88fce5e9dcd7", 3 | "name": "Test Data", 4 | "description": "Mock data for integration testing the Collection Manager service", 5 | "created": "2020-01-30T01:01:01.111111Z", 6 | "modified": "2020-01-30T01:01:01.111111Z", 7 | "collections": [ 8 | { 9 | "versions": [ 10 | { 11 | "url": "http://localhost/collection-bundles/blue-v1.json", 12 | "version": "1.0.0", 13 | "modified": "2020-01-01T01:01:01.111111Z", 14 | "release_notes": "Version 1" 15 | } 16 | ], 17 | "description": "Collection Blue", 18 | "created": "2020-01-01T01:01:01.111111Z", 19 | "id": "x-mitre-collection--6c402d41-d768-4e13-9caa-8fec689f8680", 20 | "name": "Collection Blue" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /app/tests/integration-test/mock-data/collection-index-v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "43f56ef6-99a3-455d-9acc-88fce5e9dcd7", 3 | "name": "Test Data", 4 | "description": "Mock data for integration testing the Collection Manager service", 5 | "created": "2020-01-30T01:01:01.111111Z", 6 | "modified": "2020-02-30T02:02:02.222222Z", 7 | "collections": [ 8 | { 9 | "versions": [ 10 | { 11 | "url": "http://localhost/collection-bundles/blue-v1.json", 12 | "version": "1.0.0", 13 | "modified": "2020-01-01T01:01:01.111111Z", 14 | "release_notes": "Version 1" 15 | }, 16 | { 17 | "url": "http://localhost/collection-bundles/blue-v2.json", 18 | "version": "2.0.0", 19 | "modified": "2020-02-02T02:02:02.222222Z", 20 | "release_notes": "Version 2" 21 | } 22 | ], 23 | "description": "Collection Blue", 24 | "created": "2020-01-01T01:01:01.111111Z", 25 | "id": "x-mitre-collection--6c402d41-d768-4e13-9caa-8fec689f8680", 26 | "name": "Collection Blue" 27 | }, 28 | { 29 | "versions": [ 30 | { 31 | "url": "http://localhost/collection-bundles/red-v1.json", 32 | "version": "1.0.0", 33 | "modified": "2020-01-11T01:01:01.111111Z", 34 | "release_notes": "Version 1" 35 | } 36 | ], 37 | "description": "Collection Red", 38 | "created": "2020-01-11T01:01:01.111111Z", 39 | "id": "x-mitre-collection--2f78fb25-989e-487e-be91-f0d204223c37", 40 | "name": "Collection Red" 41 | }, 42 | { 43 | "versions": [ 44 | { 45 | "url": "http://localhost/collection-bundles/green-v1.json", 46 | "version": "1.0.0", 47 | "modified": "2020-01-21T01:01:01.111111Z", 48 | "release_notes": "Version 1" 49 | } 50 | ], 51 | "description": "Collection Green", 52 | "created": "2020-01-21T01:01:01.111111Z", 53 | "id": "x-mitre-collection--39fd8b2e-8546-4339-9d05-f53395e23ad0", 54 | "name": "Collection Green" 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /app/tests/integration-test/mock-remote-host/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function initializeApp() { 4 | const express = require('express'); 5 | const app = express(); 6 | 7 | // Set up the static routes 8 | app.use('/collection-indexes', express.static('mock-data/collection-indexes')); 9 | app.use('/collection-bundles', express.static('mock-data/collection-bundles')); 10 | 11 | return app; 12 | } 13 | 14 | async function runMockRemoteHost() { 15 | // Create the express app 16 | const app = initializeApp(); 17 | 18 | const port = process.env.PORT ?? 80; 19 | const server = app.listen(port, function () { 20 | const host = server.address().address; 21 | const port = server.address().port; 22 | 23 | console.info(`Listening at http://${host}:${port}`); 24 | console.info('Mock Remote Host start up complete'); 25 | }); 26 | 27 | // Listen for a ctrl-c 28 | process.on('SIGINT', () => { 29 | console.info('SIGINT received, stopping HTTP server'); 30 | server.close(); 31 | }); 32 | 33 | // Docker terminates a container with a SIGTERM 34 | process.on('SIGTERM', () => { 35 | console.info('SIGTERM received, stopping HTTP server'); 36 | server.close(); 37 | }); 38 | 39 | // Wait for the server to close 40 | const events = require('events'); 41 | await events.once(server, 'close'); 42 | 43 | console.info('Mock Remote Host terminating'); 44 | } 45 | 46 | runMockRemoteHost() 47 | .then(() => { 48 | console.log('runMockRemoteHost() - Terminating normally'); 49 | process.exit(); 50 | }) 51 | .catch((err) => { 52 | console.log('runMockRemoteHost() - Error: ' + err); 53 | process.exit(1); 54 | }); 55 | -------------------------------------------------------------------------------- /app/tests/integration-test/update-collection-a.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pwd 4 | 5 | # Copy the next set of files to the test directories 6 | cp ./tests/integration-test/mock-data/collection-index-v2.json ./tests/integration-test/mock-remote-host/mock-data/collection-indexes/collection-index.json 7 | cp ./tests/integration-test/mock-data/blue-v2.json ./tests/integration-test/mock-remote-host/mock-data/collection-bundles/ 8 | cp ./tests/integration-test/mock-data/red-v1.json ./tests/integration-test/mock-remote-host/mock-data/collection-bundles/ 9 | cp ./tests/integration-test/mock-data/green-v1.json ./tests/integration-test/mock-remote-host/mock-data/collection-bundles/ 10 | -------------------------------------------------------------------------------- /app/tests/integration-test/update-subscription.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const superagent = require('superagent'); 6 | const setCookieParser = require('set-cookie-parser'); 7 | 8 | const passportCookieName = 'connect.sid'; 9 | 10 | let passportCookie; 11 | async function login(url) { 12 | const res = await superagent.get(url); 13 | const cookies = setCookieParser(res); 14 | passportCookie = cookies.find((c) => c.name === passportCookieName); 15 | } 16 | 17 | async function get(url) { 18 | const res = await superagent 19 | .get(url) 20 | .set('Cookie', `${passportCookieName}=${passportCookie.value}`); 21 | 22 | return res.body; 23 | } 24 | 25 | function put(url, data) { 26 | return superagent 27 | .put(url) 28 | .set('Cookie', `${passportCookieName}=${passportCookie.value}`) 29 | .send(data); 30 | } 31 | 32 | async function updateSubscription() { 33 | // Log into the Workbench REST API 34 | const loginUrl = 'http://localhost:3000/api/authn/anonymous/login'; 35 | await login(loginUrl); 36 | 37 | // Get the collection index from the server 38 | const collectionIndexId = '43f56ef6-99a3-455d-9acc-88fce5e9dcd7'; 39 | const getCollectionIndexesUrl = `http://localhost:3000/api/collection-indexes/${collectionIndexId}`; 40 | const collectionIndex = await get(getCollectionIndexesUrl); 41 | 42 | // Add the subscription to the Green collection 43 | const collectionId = collectionIndex.collection_index.collections[2].id; 44 | collectionIndex.workspace.update_policy.subscriptions.push(collectionId); 45 | 46 | // Write the updated collection index to the server 47 | const putCollectionIndexesUrl = `http://localhost:3000/api/collection-indexes/${collectionIndexId}`; 48 | await put(putCollectionIndexesUrl, collectionIndex); 49 | } 50 | 51 | updateSubscription() 52 | .then(() => { 53 | console.log('updateSubscription() - Terminating normally'); 54 | process.exit(); 55 | }) 56 | .catch((err) => { 57 | console.log('updateSubscription() - Error: ' + err); 58 | process.exit(1); 59 | }); 60 | -------------------------------------------------------------------------------- /app/tests/integration-test/update-subscription.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd ./tests/integration-test 4 | env WORKBENCH_AUTHN_APIKEY=ePcAssW9Ad9CUBghWCeW node ./update-subscription.js 5 | -------------------------------------------------------------------------------- /app/tests/openapi/validate-open-api.spec.js: -------------------------------------------------------------------------------- 1 | const OpenAPISchemaValidator = require('openapi-schema-validator').default; 2 | const refParser = require('@apidevtools/json-schema-ref-parser'); 3 | const config = require('../../config/config'); 4 | const { expect } = require('expect'); 5 | 6 | const validator = new OpenAPISchemaValidator({ version: 3 }); 7 | 8 | describe('OpenAPI Spec Validation', function () { 9 | it('The OpenAPI spec should exist', async function () { 10 | const openApiDoc = await refParser.dereference(config.openApi.specPath); 11 | expect(openApiDoc).toBeDefined(); 12 | }); 13 | 14 | it('The OpenAPI spec should be valid', async function () { 15 | const openApiDoc = await refParser.dereference(config.openApi.specPath); 16 | const results = validator.validate(openApiDoc); 17 | 18 | expect(results).toBeDefined(); 19 | expect(results.errors).toBeDefined(); 20 | expect(results.errors.length).toBe(0); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /app/tests/run-mocha-separate-jobs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | find $1 -maxdepth 1 -type f -name '*.spec.js' -exec npx mocha --timeout 10000 {} \; 4 | -------------------------------------------------------------------------------- /app/tests/shared/login.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const request = require('supertest'); 4 | const setCookieParser = require('set-cookie-parser'); 5 | 6 | const passportCookieName = 'connect.sid'; 7 | exports.passportCookieName = passportCookieName; 8 | 9 | exports.loginAnonymous = async function (app) { 10 | const res = await request(app) 11 | .get('/api/authn/anonymous/login') 12 | .set('Accept', 'application/json') 13 | .expect(200); 14 | 15 | // Save the cookie for later tests 16 | const cookies = setCookieParser(res); 17 | const passportCookie = cookies.find((c) => c.name === passportCookieName); 18 | 19 | return passportCookie; 20 | }; 21 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | ignores: [ 4 | // Ignore commits starting with "chore(release):" (semantic-release commits) 5 | (message) => /^chore\(release\):/s.test(message), 6 | ], 7 | rules: { 8 | 'body-max-line-length': [0, 'always'], // Disable body max line length rule 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # ATT&CK Workbench REST API Documentation 2 | 3 | This directory contains supplementary technical documentation for the ATT&CK Workbench REST API. For general usage and contribution guides, please refer to the main documentation in the project root: 4 | 5 | - [USAGE.md](../USAGE.md): Comprehensive usage instructions 6 | - [CONTRIBUTING.md](../CONTRIBUTING.md): Guide for developers 7 | 8 | ## Technical Reference Documentation 9 | 10 | The following documents provide detailed technical information about specific aspects of the REST API: 11 | 12 | - [Data Model](data-model.md): Detailed explanation of the database schema and STIX object structure 13 | 14 | ## Legacy Documentation 15 | 16 | The following documents contain additional information that may be useful for specific scenarios but are not part of the primary documentation: 17 | 18 | - [Authentication Details](legacy/authentication.md): Technical details about authentication mechanisms 19 | - [User Management](legacy/user-management.md): Detailed information about user accounts and permissions 20 | - [Docker Deployment](legacy/docker.md): Legacy instructions for Docker deployment 21 | - [Link-by-ID Mechanism](legacy/link-by-id.md): Technical details about object linking in the data model 22 | 23 | ## API Documentation 24 | 25 | Interactive API documentation is available when running the application in development mode at the `/api-docs` endpoint. 26 | 27 | ## Additional Resources 28 | 29 | - [GitHub Repository](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api) 30 | - [Frontend Repository](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend) 31 | - [Issue Tracker](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues) -------------------------------------------------------------------------------- /docs/images/anonymous-login-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/center-for-threat-informed-defense/attack-workbench-rest-api/5ad98ac1dac9628c0dcea01b59c8402597a6b4b3/docs/images/anonymous-login-sequence.png -------------------------------------------------------------------------------- /docs/images/oidc-login-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/center-for-threat-informed-defense/attack-workbench-rest-api/5ad98ac1dac9628c0dcea01b59c8402597a6b4b3/docs/images/oidc-login-sequence.png -------------------------------------------------------------------------------- /docs/images/user-authentication-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/center-for-threat-informed-defense/attack-workbench-rest-api/5ad98ac1dac9628c0dcea01b59c8402597a6b4b3/docs/images/user-authentication-sequence.png -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | // import tseslint from "typescript-eslint"; // TODO uncomment after TS migration 4 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; 5 | 6 | export default [ 7 | // Base config for all files - adding Node.js globals 8 | { 9 | files: ["**/*.{js,mjs,cjs,ts}"], 10 | languageOptions: { 11 | globals: { 12 | ...globals.browser, 13 | ...globals.node // Add Node.js globals which include 'require' and 'exports' 14 | }, 15 | sourceType: "commonjs" // Set sourceType to commonjs 16 | } 17 | }, 18 | // Specific config for test files 19 | { 20 | files: ["**/*.test.js", "**/*.spec.js", "**/tests/**/*.js"], 21 | languageOptions: { 22 | globals: { 23 | ...globals.browser, 24 | ...globals.node, 25 | ...globals.mocha 26 | }, 27 | sourceType: "commonjs" // Set sourceType to commonjs for tests 28 | } 29 | }, 30 | pluginJs.configs.recommended, 31 | // ...tseslint.configs.recommended, // uncomment after TS migration 32 | eslintPluginPrettierRecommended 33 | ]; -------------------------------------------------------------------------------- /migrations/sample-migration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async up(db, client) { 5 | // TODO write your migration here. 6 | // See https://github.com/seppevs/migrate-mongo/#creating-a-new-migration-script 7 | // Example: 8 | // await db.collection('albums').updateOne({artist: 'The Beatles'}, {$set: {blacklisted: true}}); 9 | }, 10 | 11 | async down(db, client) { 12 | // TODO write the statements to rollback your migration (if possible) 13 | // Example: 14 | // await db.collection('albums').updateOne({artist: 'The Beatles'}, {$set: {blacklisted: false}}); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /resources/postman-exports/README.md: -------------------------------------------------------------------------------- 1 | This directory holds collections and environments exported from the Postman application. 2 | 3 | - The collections contain sample REST API requests that can be used during software development. 4 | - The environments contain variables that can be used with the sample requests to simplify configuring the requests. 5 | 6 | To use a collection, import it into the Postman application. 7 | -------------------------------------------------------------------------------- /resources/postman-exports/test-service-authentication.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "93316f8d-dc07-41ed-9096-814192ad3feb", 3 | "name": "Test Service Authentication", 4 | "values": [ 5 | { 6 | "key": "apikey_service_name", 7 | "value": "apikey-test-service", 8 | "type": "default", 9 | "enabled": true 10 | }, 11 | { 12 | "key": "apikey", 13 | "value": "xyzzy", 14 | "type": "default", 15 | "enabled": true 16 | }, 17 | { 18 | "key": "challenge", 19 | "value": "", 20 | "type": "default", 21 | "enabled": true 22 | }, 23 | { 24 | "key": "challenge_hash", 25 | "value": "", 26 | "type": "default", 27 | "enabled": true 28 | }, 29 | { 30 | "key": "access_token", 31 | "value": "", 32 | "type": "default", 33 | "enabled": true 34 | } 35 | ], 36 | "_postman_variable_scope": "environment", 37 | "_postman_exported_at": "2022-01-21T22:08:11.930Z", 38 | "_postman_exported_using": "Postman/9.9.3" 39 | } 40 | -------------------------------------------------------------------------------- /resources/sample-configurations/collection-manager-apikey.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceAuthn": { 3 | "challengeApikey": { 4 | "enable": true, 5 | "serviceAccounts": [ 6 | { 7 | "name": "collection-manager", 8 | "apikey": "ePcAssW9Ad9CUBghWCeW", 9 | "serviceRole": "collection-manager" 10 | } 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /resources/sample-configurations/collection-manager-oidc-keycloak.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceAuthn": { 3 | "oidcClientCredentials": { 4 | "enable": true, 5 | "clients": [ 6 | { 7 | "clientId": "attack-workbench-collection-manager", 8 | "serviceRole": "collection-manager" 9 | } 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /resources/sample-configurations/collection-manager-oidc-okta.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceAuthn": { 3 | "oidcClientCredentials": { 4 | "enable": true, 5 | "clients": [ 6 | { 7 | "clientId": "0oa3xb9oz3QLY1avc5d7", 8 | "serviceRole": "collection-manager" 9 | } 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /resources/sample-configurations/multiple-apikey-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceAuthn": { 3 | "challengeApikey": { 4 | "enable": true, 5 | "serviceAccounts": [ 6 | { 7 | "name": "collection-manager", 8 | "apikey": "ePcAssW9Ad9CUBghWCeW", 9 | "serviceRole": "collection-manager" 10 | } 11 | ] 12 | }, 13 | "basicApikey": { 14 | "enable": true, 15 | "serviceAccounts": [ 16 | { 17 | "name": "navigator", 18 | "apikey": "sample-navigator-apikey", 19 | "serviceRole": "read-only" 20 | } 21 | ] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /resources/sample-configurations/navigator-basic-apikey.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceAuthn": { 3 | "basicApikey": { 4 | "enable": true, 5 | "serviceAccounts": [ 6 | { 7 | "name": "navigator", 8 | "apikey": "sample-navigator-apikey", 9 | "serviceRole": "read-only" 10 | } 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /resources/sample-configurations/test-service-basic-apikey.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceAuthn": { 3 | "basicApikey": { 4 | "enable": true, 5 | "serviceAccounts": [ 6 | { 7 | "name": "apikey-test-service", 8 | "apikey": "xyzzy", 9 | "serviceRole": "stix-export" 10 | } 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /resources/sample-configurations/test-service-challenge-apikey.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceAuthn": { 3 | "challengeApikey": { 4 | "enable": true, 5 | "serviceAccounts": [ 6 | { 7 | "name": "apikey-test-service", 8 | "apikey": "xyzzy", 9 | "serviceRole": "read-only" 10 | } 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | This directory holds utility scripts that are used for system configuration during software development. 2 | -------------------------------------------------------------------------------- /scripts/build-Dockerfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ##################################################################### 3 | # Docker Build Script for ATT&CK Workbench REST API 4 | # 5 | # Purpose: 6 | # This script builds and pushes Docker images for the ATT&CK Workbench 7 | # REST API. It's designed to be called by semantic-release during 8 | # the GitHub Actions CI/CD pipeline. 9 | # 10 | # Usage: 11 | # ./docker-build.sh 12 | # 13 | # Parameters: 14 | # - version: Semantic version (e.g. 1.2.3 or 1.2.3-alpha.1) 15 | # - release_type: Type of release (e.g. "major", "minor", "patch", "prerelease") 16 | # - git_sha: Git commit SHA of the build 17 | # 18 | # Behavior: 19 | # - For non-prerelease builds (release_type != "prerelease"), 20 | # the image is tagged with both the version and "latest" 21 | # - For prerelease builds (from alpha, beta, etc. branches), 22 | # the image is tagged only with the version (no "latest") 23 | # 24 | # Called by: 25 | # .releaserc file via @semantic-release/exec plugin 26 | ##################################################################### 27 | 28 | set -e 29 | 30 | VERSION=$1 31 | RELEASE_TYPE=$2 32 | REVISION=$3 33 | BUILDTIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ') 34 | IMAGE_NAME="ghcr.io/center-for-threat-informed-defense/attack-workbench-rest-api" 35 | 36 | echo "Building Docker image for version: $VERSION (release type: $RELEASE_TYPE)" 37 | 38 | # Construct the base Docker buildx command with common parameters 39 | BUILD_CMD="docker buildx build --push --platform linux/amd64,linux/arm64" 40 | BUILD_CMD+=" --build-arg VERSION=$VERSION" 41 | BUILD_CMD+=" --build-arg BUILDTIME=$BUILDTIME" 42 | BUILD_CMD+=" --build-arg REVISION=$REVISION" 43 | BUILD_CMD+=" -t $IMAGE_NAME:$VERSION" 44 | 45 | # Add the 'latest' tag only for non-prerelease versions (main/master branches) 46 | if [[ "$RELEASE_TYPE" != "prerelease" ]]; then 47 | echo "Adding latest tag (non-prerelease build)" 48 | BUILD_CMD+=" -t $IMAGE_NAME:latest" 49 | else 50 | echo "Skipping latest tag (prerelease build)" 51 | fi 52 | 53 | # Execute the final constructed Docker build command 54 | $BUILD_CMD . 55 | 56 | # Log completion status (will only run if build is successful due to set -e) 57 | echo "Docker build and push completed successfully" -------------------------------------------------------------------------------- /scripts/clearDatabase.js: -------------------------------------------------------------------------------- 1 | #!/bin/node 2 | 3 | /** 4 | * This script removes all objects from the attackObjects and references collections in the ATT&CK Workbench database. 5 | * It does not remove objects from the useraccounts, systemconfigurations, or collectionIndexes collections. 6 | * 7 | * It requires the database URL to be provided in the DATABASE_URL environment variable. 8 | * 9 | * Usage: 10 | * DATABASE_URL=mongodb://localhost/attack-workspace node ./scripts/clearDatabase.js 11 | * 12 | */ 13 | 14 | 'use strict'; 15 | 16 | const AttackObject = require('../app/models/attack-object-model'); 17 | const Relationship = require('../app/models/relationship-model'); 18 | const Reference = require('../app/models/reference-model'); 19 | 20 | async function clearDatabase() { 21 | // Establish the database connection 22 | console.log('Setting up the database connection'); 23 | await require('../app/lib/database-connection').initializeConnection(); 24 | 25 | let result = await AttackObject.deleteMany(); 26 | console.log(`Deleted ${result.deletedCount} objects from the attackObjects collection.`); 27 | 28 | result = await Relationship.deleteMany(); 29 | console.log(`Deleted ${result.deletedCount} objects from the relationships collection.`); 30 | 31 | result = await Reference.deleteMany(); 32 | console.log(`Deleted ${result.deletedCount} objects from the references collection.`); 33 | } 34 | 35 | clearDatabase() 36 | .then(() => process.exit()) 37 | .catch((err) => { 38 | console.log('clearDatabase() - Error: ' + err); 39 | process.exit(1); 40 | }); 41 | -------------------------------------------------------------------------------- /scripts/validateBundle.js: -------------------------------------------------------------------------------- 1 | #!/bin/node 2 | 3 | /** 4 | * This script validates a collection bundle. 5 | * 6 | * The filename and directory of the collection bundle to validate are currently hardcoded in this script. 7 | * 8 | * Usage: 9 | * node ./scripts/validateBundle.js 10 | * 11 | */ 12 | 13 | 'use strict'; 14 | 15 | const collectionBundleService = require('../app/services/collection-bundles-service'); 16 | const { promises: fs } = require('fs'); 17 | 18 | async function readJson(path) { 19 | const filePath = require.resolve(path); 20 | const data = await fs.readFile(filePath); 21 | return JSON.parse(data); 22 | } 23 | 24 | async function validateBundle() { 25 | const filename = 'ics-attack-10.1.json'; 26 | 27 | const collectionBundlesDirectory = '../app/tests/import/test-files'; 28 | const filePath = collectionBundlesDirectory + '/' + filename; 29 | const bundle = await readJson(filePath); 30 | 31 | const options = {}; 32 | 33 | // Find the x-mitre-collection objects 34 | const collections = bundle.objects.filter((object) => object.type === 'x-mitre-collection'); 35 | 36 | // The bundle must have an x-mitre-collection object 37 | if (collections.length === 0) { 38 | console.warn('Unable to validate collection bundle. Missing x-mitre-collection object.'); 39 | throw new Error('Unable to validate collection bundle. Missing x-mitre-collection object.'); 40 | } else if (collections.length > 1) { 41 | console.warn('Unable to validate collection bundle. More than one x-mitre-collection object.'); 42 | throw new Error( 43 | 'Unable to validate collection bundle. More than one x-mitre-collection object.', 44 | ); 45 | } 46 | 47 | // The collection must have an id. 48 | if (!collections[0].id) { 49 | console.warn('Unable to validate collection bundle. x-mitre-collection missing id'); 50 | throw new Error('Unable to validate collection bundle. x-mitre-collection missing id'); 51 | } 52 | 53 | console.log('Validating bundle...'); 54 | const validationResult = collectionBundleService.validateBundle(bundle, options); 55 | console.log(JSON.stringify(validationResult, null, 2)); 56 | } 57 | 58 | validateBundle() 59 | .then(() => process.exit()) 60 | .catch((err) => { 61 | console.log('validateBundle() - Error: ' + err); 62 | process.exit(1); 63 | }); 64 | -------------------------------------------------------------------------------- /template.env: -------------------------------------------------------------------------------- 1 | AUTHN_MECHANISM=anonymous 2 | DATABASE_URL=mongodb://localhost/attack-workspace 3 | JSON_CONFIG_PATH=/some/path/to/rest-api-service-config.json 4 | PORT=8080 5 | LOG_LEVEL=debug --------------------------------------------------------------------------------