Skip to content
This repository has been archived by the owner on Sep 3, 2024. It is now read-only.

Commit

Permalink
Merge pull request #5 from JupiterOne/INT-9065-legacy-applications
Browse files Browse the repository at this point in the history
Int 9065 - Adding legacy applications and local users
  • Loading branch information
adam-in-ict authored Sep 7, 2023
2 parents fcf7b0a + 0be1ffd commit 62ccc30
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 4 deletions.
2 changes: 2 additions & 0 deletions docs/jupiterone.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ The following entities are created:
| Application | `microsoft_configuration_manager_application` | `Application` |
| Device | `microsoft_configuration_manager_device` | `Device` |
| Device Collection | `microsoft_configuration_manager_device_collection` | `Group` |
| Local User | `microsoft_configuration_manager_local_user` | `User` |

### Relationships

Expand All @@ -110,6 +111,7 @@ The following relationships are created:
| --------------------------------------------------- | --------------------- | --------------------------------------------- |
| `microsoft_configuration_manager_account` | **HAS** | `microsoft_configuration_manager_device` |
| `microsoft_configuration_manager_device_collection` | **HAS** | `microsoft_configuration_manager_device` |
| `microsoft_configuration_manager_device` | **HAS** | `microsoft_configuration_manager_local_user` |
| `microsoft_configuration_manager_device` | **INSTALLED** | `microsoft_configuration_manager_application` |

<!--
Expand Down
22 changes: 22 additions & 0 deletions src/configuration-manager/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
buildCollectionSubscriptionQuery,
buildDeviceCollectionQuery,
buildDeviceListQuery,
buildLegacyApplicationList,
buildLocalUserList,
} from './queries';

type ResourceIteratee<T> = (each: T) => Promise<void> | void;
Expand Down Expand Up @@ -85,6 +87,26 @@ class MicrosoftConfigurationManagerClient {
}
}

async listLegacyApplications<T>(iteratee: ResourceIteratee<T>) {
const result = await this.wrapWithRequestFailedHandler(() =>
this.connection.query(buildLegacyApplicationList(this.dbName)),
);

for (const record of result.recordset) {
await iteratee(record);
}
}

async listLocalUsers<T>(iteratee: ResourceIteratee<T>) {
const result = await this.wrapWithRequestFailedHandler(() =>
this.connection.query(buildLocalUserList(this.dbName)),
);

for (const record of result.recordset) {
await iteratee(record);
}
}

async close() {
if (this.connection.connected) {
await this.connection.close();
Expand Down
79 changes: 79 additions & 0 deletions src/configuration-manager/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,82 @@ function buildCollectionSubscriptionQuery(dbName: string, tableName: string) {
FROM [${dbName}].[dbo].[${tableName}]`;
}

function buildLegacyApplicationList(dbName: string) {
validateDbName(dbName);

return `SELECT [ResourceID]
,[GroupID]
,[RevisionID]
,[AgentID]
,[TimeStamp]
,[DisplayName0]
,[InstallDate0]
,[ProdID0]
,[Publisher0]
,[Version0]
FROM [${dbName}].[dbo].[v_GS_ADD_REMOVE_PROGRAMS]`;
}

// TODO this gives us some information we need, but unsure how we
// will relate to the last user that used it.
function buildApplicationLastUsedList(dbName: string) {
validateDbName(dbName);

return `SELECT TOP (1000) [ResourceID]
,[GroupID]
,[RevisionID]
,[AgentID]
,[TimeStamp]
,[AdditionalProductCodes0]
,[CompanyName0]
,[ExplorerFileName0]
,[FileDescription0]
,[FilePropertiesHash0]
,[FileSize0]
,[FileVersion0]
,[FolderPath0]
,[LastUsedTime0]
,[LastUserName0]
,[LaunchCount0]
,[msiDisplayName0]
,[msiPublisher0]
,[msiVersion0]
,[OriginalFileName0]
,[ProductCode0]
,[ProductLanguage0]
,[ProductName0]
,[ProductVersion0]
,[SoftwarePropertiesHash0]
FROM [${dbName}].[dbo].[v_GS_CCM_RECENTLY_USED_APPS]`;
}

function buildLocalUserList(dbName: string) {
validateDbName(dbName);

return `SELECT TOP (1000) [ResourceID]
,[GroupID]
,[RevisionID]
,[AgentID]
,[TimeStamp]
,[HealthStatus0]
,[LastAttemptedProfileDownload0]
,[LastAttemptedProfileUploadTi0]
,[LastBackgroundRegistryUpload0]
,[LastDownloadTime0]
,[LastUploadTime0]
,[LastUseTime0]
,[Loaded0]
,[LocalPath0]
,[RefCount0]
,[RoamingConfigured0]
,[RoamingPath0]
,[RoamingPreference0]
,[SID0]
,[Special0]
,[Status0]
FROM [${dbName}].[dbo].[v_GS_USER_PROFILE]`;
}

function validateDbName(dbName: string) {
if (!/^[\w]+$/.test(dbName)) {
throw new Error(`Invalid database name: ${dbName}`);
Expand All @@ -300,5 +376,8 @@ export {
buildApplicationDeviceTargetingList,
buildDeviceCollectionQuery,
buildCollectionSubscriptionQuery,
buildLegacyApplicationList,
buildApplicationLastUsedList,
buildLocalUserList,
validateDbName,
};
20 changes: 20 additions & 0 deletions src/steps/applications/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ export function createApplicationEntity(application: any): Entity {
});
}

export function createLegacyApplicationEntity(application: any): Entity {
const id = application.ProdID0?.toString();

return createIntegrationEntity({
entityData: {
source: application,
assign: {
_key: createApplicationKey(id),
_type: Entities.APPLICATION._type,
_class: Entities.APPLICATION._class,
id: id,
name: application.DisplayName0,
displayName: application.DisplayName0,
softwareVersion: application.Version0,
manufacturer: application.Publisher0,
},
},
});
}

export function createDeviceApplicationRelationship(
device: Entity,
application: Entity,
Expand Down
42 changes: 40 additions & 2 deletions src/steps/applications/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
createApplicationEntity,
createApplicationKey,
createDeviceApplicationRelationship,
createLegacyApplicationEntity,
} from './converters';
import { createDeviceKey } from '../devices/converters';

Expand All @@ -22,6 +23,14 @@ export const fetchApplicationsSteps: IntegrationStep<IntegrationConfig>[] = [
dependsOn: [],
executionHandler: fetchApplications,
},
{
id: Steps.FETCH_LEGACY_APPLICATIONS,
name: 'Fetch-Legacy-Applications',
entities: [Entities.APPLICATION],
relationships: [Relationships.DEVICE_INSTALLED_APPLICATION],
dependsOn: [Steps.FETCH_DEVICES],
executionHandler: fetchLegacyApplications,
},
{
id: Steps.BUILD_APPLICATION_RELATIONSHIPS,
name: 'Build-Application-Relationships',
Expand All @@ -32,6 +41,35 @@ export const fetchApplicationsSteps: IntegrationStep<IntegrationConfig>[] = [
},
];

export async function fetchLegacyApplications({
instance: { config },
jobState,
logger,
}: IntegrationStepExecutionContext<IntegrationConfig>) {
const client = await createMicrosoftConfigurationManagerClient(
config,
logger,
);

await client.listLegacyApplications(async (application: any) => {
const applicationEntity = await jobState.addEntity(
createLegacyApplicationEntity(application),
);
const deviceID = createDeviceKey(application.ResourceID?.toString());
const deviceEntity = await jobState.findEntity(deviceID);
if (applicationEntity && deviceEntity) {
await jobState.addRelationship(
createDeviceApplicationRelationship(deviceEntity, applicationEntity),
);
} else {
logger.info(
{ deviceID },
`Unable to create relationship between legacy application and device.`,
);
}
});
}

export async function fetchApplications({
instance: { config },
jobState,
Expand All @@ -42,8 +80,8 @@ export async function fetchApplications({
logger,
);

await client.listApplications(async (device) => {
await jobState.addEntity(createApplicationEntity(device));
await client.listApplications(async (application) => {
await jobState.addEntity(createApplicationEntity(application));
});
}

Expand Down
20 changes: 18 additions & 2 deletions src/steps/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const Steps: Record<
| 'FETCH_APPLICATIONS'
| 'FETCH_COLLECTIONS'
| 'FETCH_DEVICES'
| 'FETCH_LEGACY_APPLICATIONS'
| 'FETCH_LOCAL_USERS'
| 'BUILD_APPLICATION_RELATIONSHIPS'
| 'BUILD_COLLECTION_RELATIONSHIPS',
string
Expand All @@ -17,12 +19,14 @@ export const Steps: Record<
FETCH_APPLICATIONS: 'fetch-applications',
FETCH_COLLECTIONS: 'fetch-collections',
FETCH_DEVICES: 'fetch-devices',
FETCH_LEGACY_APPLICATIONS: 'fetch-legacy-applications',
FETCH_LOCAL_USERS: 'fetch-local-users',
BUILD_APPLICATION_RELATIONSHIPS: 'build-application-relationships',
BUILD_COLLECTION_RELATIONSHIPS: 'build-collection-relationships',
};

export const Entities: Record<
'ACCOUNT' | 'APPLICATION' | 'COLLECTION' | 'DEVICE',
'ACCOUNT' | 'APPLICATION' | 'COLLECTION' | 'DEVICE' | 'LOCAL_USER',
StepEntityMetadata
> = {
ACCOUNT: {
Expand All @@ -45,12 +49,18 @@ export const Entities: Record<
_type: 'microsoft_configuration_manager_device',
_class: ['Device'],
},
LOCAL_USER: {
resourceName: 'Local User',
_type: 'microsoft_configuration_manager_local_user',
_class: ['User'],
},
};

export const Relationships: Record<
| 'ACCOUNT_HAS_DEVICE'
| 'COLLECTION_HAS_DEVICE'
| 'DEVICE_INSTALLED_APPLICATION',
| 'DEVICE_INSTALLED_APPLICATION'
| 'DEVICE_HAS_LOCAL_USER',
StepRelationshipMetadata
> = {
ACCOUNT_HAS_DEVICE: {
Expand All @@ -71,4 +81,10 @@ export const Relationships: Record<
_class: RelationshipClass.INSTALLED,
targetType: Entities.APPLICATION._type,
},
DEVICE_HAS_LOCAL_USER: {
_type: 'microsoft_configuration_manager_device_has_local_user',
sourceType: Entities.DEVICE._type,
_class: RelationshipClass.HAS,
targetType: Entities.LOCAL_USER._type,
},
};
2 changes: 2 additions & 0 deletions src/steps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { fetchAccountSteps } from './account';
import { fetchApplicationsSteps } from './applications';
import { fetchCollectionsSteps } from './collections';
import { fetchDevicesSteps } from './devices';
import { fetchLocalUserSteps } from './localUsers';

const integrationSteps = [
...fetchAccountSteps,
...fetchApplicationsSteps,
...fetchCollectionsSteps,
...fetchDevicesSteps,
...fetchLocalUserSteps,
];

export { integrationSteps };
44 changes: 44 additions & 0 deletions src/steps/localUsers/converters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
createDirectRelationship,
createIntegrationEntity,
Entity,
parseTimePropertyValue,
Relationship,
RelationshipClass,
} from '@jupiterone/integration-sdk-core';

import { Entities } from '../constants';

export function createLocalUserKey(id: string) {
return `${Entities.LOCAL_USER._type}:${id}`;
}

export function createLocalUserEntity(user: any): Entity {
const id = user.SID0?.toString();
return createIntegrationEntity({
entityData: {
source: user,
assign: {
_key: createLocalUserKey(id),
_type: Entities.LOCAL_USER._type,
_class: Entities.LOCAL_USER._class,
id: id,
name: id,
displayName: id,
localPath: user.LocalPath0,
createdOn: parseTimePropertyValue(user.TimeStamp),
},
},
});
}

export function createDeviceLocalUserRelationship(
device: Entity,
user: Entity,
): Relationship {
return createDirectRelationship({
_class: RelationshipClass.HAS,
from: device,
to: user,
});
}
29 changes: 29 additions & 0 deletions src/steps/localUsers/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { executeStepWithDependencies } from '@jupiterone/integration-sdk-testing';
import { buildStepTestConfigForStep } from '../../../test/config';
import { Steps } from '../constants';
import sql from 'mssql';
import { deviceRecords, localUserRecords } from '../../../test/mockData';

test('fetch-local-users', async () => {
jest.mock('mssql');
sql.connect = jest
.fn()
.mockResolvedValueOnce({
query: jest.fn().mockResolvedValue({
recordset: deviceRecords,
recordsets: [0, 0],
}),
close: jest.fn(),
})
.mockReturnValueOnce({
query: jest.fn().mockResolvedValue({
recordset: localUserRecords,
recordsets: [0, 0],
}),
close: jest.fn(),
});

const stepConfig = buildStepTestConfigForStep(Steps.FETCH_LOCAL_USERS);
const stepResult = await executeStepWithDependencies(stepConfig);
expect(stepResult).toMatchStepMetadata(stepConfig);
});
Loading

0 comments on commit 62ccc30

Please sign in to comment.