Skip to content
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
48 changes: 32 additions & 16 deletions src/app/classes/stix/asset.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { StixObject } from './stix-object';
import { logger } from '../../utils/logger';
import { Observable } from 'rxjs';
import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service';
import { logger } from '../../utils/logger';
import { ValidationData } from '../serializable';
import { StixObject } from './stix-object';

export class Asset extends StixObject {
public name = '';
Expand Down Expand Up @@ -40,21 +40,37 @@ export class Asset extends StixObject {
rep.stix.modified = keepModified;
}
rep.stix.name = this.name.trim();
rep.stix.x_mitre_domains = this.domains;
rep.stix.x_mitre_sectors = this.sectors;
rep.stix.x_mitre_related_assets = this.relatedAssets.map(
(asset: RelatedAsset) => {
return {
name: asset.name.trim(),
related_asset_sectors: asset.related_asset_sectors
? asset.related_asset_sectors
: [],
description: asset.description ? asset.description : '',
};
}

// Conditionally serialize array fields - only include if non-empty
this.serializeArrayField(rep.stix, 'x_mitre_domains', this.domains);
this.serializeArrayField(rep.stix, 'x_mitre_sectors', this.sectors);
this.serializeArrayField(
rep.stix,
'x_mitre_related_assets',
this.relatedAssets,
assets =>
assets.map((asset: RelatedAsset) => {
const relatedAsset: any = {
name: asset.name.trim(),
description: asset.description || '',
};
// Only include related_asset_sectors if it's a non-empty array
if (
Array.isArray(asset.related_asset_sectors) &&
asset.related_asset_sectors.length > 0
) {
relatedAsset.related_asset_sectors = asset.related_asset_sectors;
}
return relatedAsset;
})
);
this.serializeArrayField(rep.stix, 'x_mitre_platforms', this.platforms);
this.serializeArrayField(
rep.stix,
'x_mitre_contributors',
this.contributors,
contributors => contributors.map(x => x.trim())
);
rep.stix.x_mitre_platforms = this.platforms;
rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim());

return rep;
}
Expand Down
50 changes: 43 additions & 7 deletions src/app/classes/stix/stix-object.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { VersionNumber } from '../version-number';
import { ExternalReferences } from '../external-references';
import { v4 as uuid } from 'uuid';
import { Serializable, ValidationData } from '../serializable';
import { forkJoin, Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import {
Paginated,
RestApiConnectorService,
} from 'src/app/services/connectors/rest-api/rest-api-connector.service';
import { forkJoin, Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { logger } from '../../utils/logger';
import {
AttackTypeToRoute,
StixTypeToAttackType,
} from 'src/app/utils/type-mappings';
import { StixType } from 'src/app/utils/types';
import { v4 as uuid } from 'uuid';
import { logger } from '../../utils/logger';
import { ExternalReferences } from '../external-references';
import { Serializable, ValidationData } from '../serializable';
import { VersionNumber } from '../version-number';

export type workflowStates =
| 'work-in-progress'
Expand Down Expand Up @@ -765,6 +765,42 @@ export abstract class StixObject extends Serializable {
return true;
};

/**
* Conditionally serializes an array field to the target object only if it contains at least one element.
* This ensures that empty arrays are never included in the serialized STIX object sent to the backend,
* which is critical because the REST API does not accept empty arrays in POST/PUT requests.
*
* Use this method in serialize() implementations to prevent empty array fields from being set.
*
* @param target - The target object (typically rep.stix) to set the field on
* @param key - The field name in the STIX object (e.g., 'x_mitre_platforms')
* @param value - The array value to conditionally serialize
* @param transform - Optional transformation function to apply to the array before setting (e.g., mapping, trimming)
*
* @example
* // Simple usage - only set if array is non-empty
* this.serializeArrayField(rep.stix, 'x_mitre_platforms', this.platforms);
*
* @example
* // With transformation - trim strings before serializing
* this.serializeArrayField(
* rep.stix,
* 'x_mitre_contributors',
* this.contributors,
* (contributors) => contributors.map(x => x.trim())
* );
*/
protected serializeArrayField<T, R = T>(
target: any,
key: string,
value: T[],
transform?: (val: T[]) => R[]
): void {
if (Array.isArray(value) && value.length > 0) {
target[key] = transform ? transform(value) : value;
}
}

/**
* Save the current state of the STIX object in the database. Update the current object from the response
* @param restAPIService [RestApiConnectorService] the service to perform the POST/PUT through
Expand Down