Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Merging develop back in to project-orion #354

Merged
merged 7 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ jobs:
name: static-checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18.x'
- run: npm install
- run: npm run lint
- run: npm run snyk
- run: npm run coverage:cobertura
- name: Upload Coverage to CodeCov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_SECRET }}
files: coverage/cobertura-coverage.xml
Expand Down
2 changes: 1 addition & 1 deletion NOTICE.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2020-2023 MITRE Engenuity. Approved for public release. Document number CT0020 and public release case number 22-3206.
Copyright 2020-2024 MITRE Engenuity. Approved for public release. Document number CT0020 and public release case number 22-3206.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ The workflow is defined in `.github/workflows/ci-workflow.yml`

## Notice

Copyright 2020-2023 MITRE Engenuity. Approved for public release. Document number CT0020
Copyright 2020-2024 MITRE Engenuity. Approved for public release. Document number CT0020

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Expand Down
9 changes: 9 additions & 0 deletions app/api/definitions/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ tags:
description: 'Operations for authenticating with the REST API'
- name: 'User Accounts'
description: 'Operations on user accounts'
- name: 'Health Check'
description: 'Operations on system status'

paths:
# ATT&CK Objects
Expand Down Expand Up @@ -315,3 +317,10 @@ paths:
# Recent Activity
/api/recent-activity:
$ref: 'paths/recent-activity-paths.yml#/paths/~1api~1recent-activity'

# Health Checks
/api/health/ping:
$ref: 'paths/health-paths.yml#/paths/~1api~1health~1ping'

/api/health/status:
$ref: 'paths/health-paths.yml#/paths/~1api~1health~1status'
2 changes: 1 addition & 1 deletion app/api/definitions/paths/assets-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ paths:
- name: search
in: query
description: |
Only return objects where the provided search text occurs in the `name` or `description`.
Only return objects where the provided search text occurs in the `attack_id`, `name`, or `description`.
The search is case-insensitive.
schema:
type: string
Expand Down
2 changes: 1 addition & 1 deletion app/api/definitions/paths/attack-objects-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ paths:
- name: search
in: query
description: |
Only return ATT&CK objects where the provided search text occurs in the `name` or `description`.
Only return ATT&CK objects where the provided search text occurs in the `attack_id`, `name`, or `description`.
The search is case-insensitive.
schema:
type: string
Expand Down
6 changes: 6 additions & 0 deletions app/api/definitions/paths/authn-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ paths:
required: true
schema:
type: string
- name: iss
in: query
description: |
iss provided by the identity server.
schema:
type: string
- name: state
in: query
description: |
Expand Down
2 changes: 1 addition & 1 deletion app/api/definitions/paths/campaigns-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ paths:
- name: search
in: query
description: |
Only return objects where the provided search text occurs in the `name` or `description`.
Only return objects where the provided search text occurs in the `attack_id`, `name`, or `description`.
The search is case-insensitive.
schema:
type: string
Expand Down
2 changes: 1 addition & 1 deletion app/api/definitions/paths/data-sources-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ paths:
- name: search
in: query
description: |
Only return objects where the provided search text occurs in the `name` or `description`.
Only return objects where the provided search text occurs in the `attack_id`, `name`, or `description`.
The search is case-insensitive.
schema:
type: string
Expand Down
2 changes: 1 addition & 1 deletion app/api/definitions/paths/groups-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ paths:
- name: search
in: query
description: |
Only return objects where the provided search text occurs in the `name` or `description`.
Only return objects where the provided search text occurs in the `attack_id`, `name`, or `description`.
The search is case-insensitive.
schema:
type: string
Expand Down
25 changes: 25 additions & 0 deletions app/api/definitions/paths/health-paths.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
paths:
/api/health/ping:
get:
summary: 'Test whether the REST API is responding to requests'
operationId: 'health-ping'
description: |
This endpoint tests whether the REST API is responding to requests.
It doesn't test any other aspect of system health.
tags:
- 'Health Check'
responses:
'204':
description: 'Indicates that the REST API is responding to requests'

/api/health/status:
get:
summary: 'Returns a summary of the system status'
operationId: 'health-status'
description: |
This endpoint returns a summary of the system status, including system uptime and database connection state.
tags:
- 'Health Check'
responses:
'200':
description: 'Summary of the system status'
2 changes: 1 addition & 1 deletion app/api/definitions/paths/mitigations-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ paths:
- name: search
in: query
description: |
Only return objects where the provided search text occurs in the `name` or `description`.
Only return objects where the provided search text occurs in the `attack_id`, `name`, or `description`.
The search is case-insensitive.
schema:
type: string
Expand Down
2 changes: 1 addition & 1 deletion app/api/definitions/paths/software-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ paths:
- name: search
in: query
description: |
Only return objects where the provided search text occurs in the `name` or `description`.
Only return objects where the provided search text occurs in the `attack_id`, `name`, or `description`.
The search is case-insensitive.
schema:
type: string
Expand Down
2 changes: 1 addition & 1 deletion app/api/definitions/paths/tactics-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ paths:
- name: search
in: query
description: |
Only return objects where the provided search text occurs in the `name` or `description`.
Only return objects where the provided search text occurs in the `attack_id`, `name`, or `description`.
The search is case-insensitive.
schema:
type: string
Expand Down
2 changes: 1 addition & 1 deletion app/api/definitions/paths/techniques-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ paths:
- name: search
in: query
description: |
Only return objects where the provided search text occurs in the `name` or `description`.
Only return objects where the provided search text occurs in the `attack_id`, `name`, or `description`.
The search is case-insensitive.
schema:
type: string
Expand Down
24 changes: 10 additions & 14 deletions app/config/allowed-values.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
"macOS",
"Windows",
"Network",
"Office 365",
"Azure AD",
"SaaS",
"IaaS",
"Google Workspace",
"Containers"
"Containers",
"Identity Provider",
"Office Suite"
]
},
{
Expand Down Expand Up @@ -159,13 +158,11 @@
"macOS",
"Windows",
"Network",
"AWS",
"GCP",
"Azure",
"Office 365",
"Azure AD",
"IaaS",
"SaaS",
"Containers"
"Containers",
"Identity Provider",
"Office Suite"
]
},
{
Expand Down Expand Up @@ -233,14 +230,13 @@
"allowedValues": [
"Containers",
"Windows",
"Azure AD",
"Linux",
"macOS",
"IaaS",
"SaaS",
"Office 365",
"Google Workspace",
"Network"
"Network",
"Identity Provider",
"Office Suite"
]
},
{
Expand Down
23 changes: 23 additions & 0 deletions app/controllers/health-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

// const mongoose = require('mongoose');
const logger = require('../lib/logger');

exports.getPing = function(req, res) {
return res.status(204).send();
};

exports.getStatus = function(req, res) {
try {
const status = {
// TBD: check database connection without waiting 30 seconds for timeout when not connected
// dbState: mongoose.STATES[mongoose.connection.readyState],
uptime: process.uptime()
};
return res.status(200).send(status);
}
catch(err) {
logger.error("Unable to retrieve system status, failed with error: " + err);
return res.status(500).send("Unable to retrieve system status. Server error.");
}
};
3 changes: 2 additions & 1 deletion app/lib/authn-bearer.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ function verifyClientCredentialsToken(token, decodedHeader, done) {
// Make sure the client is allowed to access the REST API
// Okta returns the client id in payload.cid
// Keycloak returns the client id in payload.clientId
clientId = payload.cid || payload.clientId;
// Newer versions of keycloak appear to return the client id in payload.client_id
clientId = payload.cid || payload.clientId || payload.client_id;
const clients = config.serviceAuthn.oidcClientCredentials.clients;
const client = clients.find(c => c.clientId === clientId);
if (!client) {
Expand Down
90 changes: 89 additions & 1 deletion app/repository/assets-repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,95 @@

const BaseRepository = require('./_base.repository');
const Asset = require('../models/asset-model');
const regexValidator = require('../lib/regex');
const { lastUpdatedByQueryHelper } = require('../lib/request-parameter-helper');
const { DatabaseError } = require('../exceptions');

class AssetsRepository extends BaseRepository { }
class AssetsRepository extends BaseRepository {

async retrieveAll(options) {
try {
// Build the query
const query = {};
if (!options.includeRevoked) {
query['stix.revoked'] = { $in: [null, false] };
}
if (!options.includeDeprecated) {
query['stix.x_mitre_deprecated'] = { $in: [null, false] };
}
if (typeof options.state !== 'undefined') {
if (Array.isArray(options.state)) {
query['workspace.workflow.state'] = { $in: options.state };
}
else {
query['workspace.workflow.state'] = options.state;
}
}
if (typeof options.domain !== 'undefined') {
if (Array.isArray(options.domain)) {
query['stix.x_mitre_domains'] = { $in: options.domain };
}
else {
query['stix.x_mitre_domains'] = options.domain;
}
}
if (typeof options.platform !== 'undefined') {
if (Array.isArray(options.platform)) {
query['stix.x_mitre_platforms'] = { $in: options.platform };
}
else {
query['stix.x_mitre_platforms'] = options.platform;
}
}
if (typeof options.lastUpdatedBy !== 'undefined') {
query['workspace.workflow.created_by_user_account'] = lastUpdatedByQueryHelper(options.lastUpdatedBy);
}

// Build the aggregation
// - Group the documents by stix.id, sorted by stix.modified
// - Use the last document in each group (according to the value of stix.modified)
// - Then apply query, skip and limit options
const aggregation = [
{ $sort: { 'stix.id': 1, 'stix.modified': 1 } },
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason that AssetsRepository has its own retrieveAll function is because of this line. It pulls the last modified document vs. the first modified document in BaseRepository.
Could I convert it to an option instead to avoid so much duplication of code?

{ $group: { _id: '$stix.id', document: { $last: '$$ROOT' }}},
{ $replaceRoot: { newRoot: '$document' }},
{ $sort: { 'stix.id': 1 }},
{ $match: query }
];

if (typeof options.search !== 'undefined') {
options.search = regexValidator.sanitizeRegex(options.search);
const match = { $match: { $or: [
{ 'stix.name': { '$regex': options.search, '$options': 'i' }},
{ 'stix.description': { '$regex': options.search, '$options': 'i' }},
{ 'workspace.attack_id': { '$regex': options.search, '$options': 'i' }}
]}};
aggregation.push(match);
}

const facet = {
$facet: {
totalCount: [ { $count: 'totalCount' }],
documents: [ ]
}
};
if (options.offset) {
facet.$facet.documents.push({ $skip: options.offset });
}
else {
facet.$facet.documents.push({ $skip: 0 });
}
if (options.limit) {
facet.$facet.documents.push({ $limit: options.limit });
}
aggregation.push(facet);

// Retrieve the documents
return await this.model.aggregate(aggregation).exec();
} catch (err) {
throw new DatabaseError(err);
}
}
}

module.exports = new AssetsRepository(Asset);
Loading
Loading