@j-schreiber/sf-cli-security-audit 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/lib/commands/org/audit/init.d.ts +19 -0
- package/lib/commands/org/audit/init.js +72 -0
- package/lib/commands/org/audit/init.js.map +1 -0
- package/lib/commands/org/audit/run.d.ts +23 -0
- package/lib/commands/org/audit/run.js +124 -0
- package/lib/commands/org/audit/run.js.map +1 -0
- package/lib/commands/org/scan/user-perms.d.ts +20 -0
- package/lib/commands/org/scan/user-perms.js +87 -0
- package/lib/commands/org/scan/user-perms.js.map +1 -0
- package/lib/libs/conf-init/auditConfig.d.ts +35 -0
- package/lib/libs/conf-init/auditConfig.js +41 -0
- package/lib/libs/conf-init/auditConfig.js.map +1 -0
- package/lib/libs/conf-init/permissionsClassification.d.ts +17 -0
- package/lib/libs/conf-init/permissionsClassification.js +80 -0
- package/lib/libs/conf-init/permissionsClassification.js.map +1 -0
- package/lib/libs/conf-init/policyConfigs.d.ts +31 -0
- package/lib/libs/conf-init/policyConfigs.js +91 -0
- package/lib/libs/conf-init/policyConfigs.js.map +1 -0
- package/lib/libs/conf-init/presets/loose.d.ts +6 -0
- package/lib/libs/conf-init/presets/loose.js +85 -0
- package/lib/libs/conf-init/presets/loose.js.map +1 -0
- package/lib/libs/conf-init/presets/none.d.ts +30 -0
- package/lib/libs/conf-init/presets/none.js +54 -0
- package/lib/libs/conf-init/presets/none.js.map +1 -0
- package/lib/libs/conf-init/presets/strict.d.ts +4 -0
- package/lib/libs/conf-init/presets/strict.js +79 -0
- package/lib/libs/conf-init/presets/strict.js.map +1 -0
- package/lib/libs/conf-init/presets.d.ts +7 -0
- package/lib/libs/conf-init/presets.js +20 -0
- package/lib/libs/conf-init/presets.js.map +1 -0
- package/lib/libs/core/auditRun.d.ts +36 -0
- package/lib/libs/core/auditRun.js +86 -0
- package/lib/libs/core/auditRun.js.map +1 -0
- package/lib/libs/core/classification-types.d.ts +20 -0
- package/lib/libs/core/classification-types.js +23 -0
- package/lib/libs/core/classification-types.js.map +1 -0
- package/lib/libs/core/constants.d.ts +10 -0
- package/lib/libs/core/constants.js +20 -0
- package/lib/libs/core/constants.js.map +1 -0
- package/lib/libs/core/file-mgmt/auditConfigFileManager.d.ts +48 -0
- package/lib/libs/core/file-mgmt/auditConfigFileManager.js +145 -0
- package/lib/libs/core/file-mgmt/auditConfigFileManager.js.map +1 -0
- package/lib/libs/core/file-mgmt/schema.d.ts +123 -0
- package/lib/libs/core/file-mgmt/schema.js +69 -0
- package/lib/libs/core/file-mgmt/schema.js.map +1 -0
- package/lib/libs/core/mdapi/mdapiRetriever.d.ts +54 -0
- package/lib/libs/core/mdapi/mdapiRetriever.js +123 -0
- package/lib/libs/core/mdapi/mdapiRetriever.js.map +1 -0
- package/lib/libs/core/mdapi/metadataRegistryEntry.d.ts +40 -0
- package/lib/libs/core/mdapi/metadataRegistryEntry.js +46 -0
- package/lib/libs/core/mdapi/metadataRegistryEntry.js.map +1 -0
- package/lib/libs/core/mdapi/namedMetadataToolingQueryable.d.ts +33 -0
- package/lib/libs/core/mdapi/namedMetadataToolingQueryable.js +41 -0
- package/lib/libs/core/mdapi/namedMetadataToolingQueryable.js.map +1 -0
- package/lib/libs/core/mdapi/namedMetadataType.d.ts +20 -0
- package/lib/libs/core/mdapi/namedMetadataType.js +41 -0
- package/lib/libs/core/mdapi/namedMetadataType.js.map +1 -0
- package/lib/libs/core/mdapi/singletonMetadataType.d.ts +21 -0
- package/lib/libs/core/mdapi/singletonMetadataType.js +37 -0
- package/lib/libs/core/mdapi/singletonMetadataType.js.map +1 -0
- package/lib/libs/core/mdapi/usersRepository.d.ts +85 -0
- package/lib/libs/core/mdapi/usersRepository.js +126 -0
- package/lib/libs/core/mdapi/usersRepository.js.map +1 -0
- package/lib/libs/core/policies/connectedAppPolicy.d.ts +10 -0
- package/lib/libs/core/policies/connectedAppPolicy.js +78 -0
- package/lib/libs/core/policies/connectedAppPolicy.js.map +1 -0
- package/lib/libs/core/policies/permissionSetPolicy.d.ts +11 -0
- package/lib/libs/core/policies/permissionSetPolicy.js +62 -0
- package/lib/libs/core/policies/permissionSetPolicy.js.map +1 -0
- package/lib/libs/core/policies/policy.d.ts +31 -0
- package/lib/libs/core/policies/policy.js +100 -0
- package/lib/libs/core/policies/policy.js.map +1 -0
- package/lib/libs/core/policies/profilePolicy.d.ts +11 -0
- package/lib/libs/core/policies/profilePolicy.js +64 -0
- package/lib/libs/core/policies/profilePolicy.js.map +1 -0
- package/lib/libs/core/policies/salesforceStandardTypes.d.ts +58 -0
- package/lib/libs/core/policies/salesforceStandardTypes.js +2 -0
- package/lib/libs/core/policies/salesforceStandardTypes.js.map +1 -0
- package/lib/libs/core/policies/userPolicy.d.ts +11 -0
- package/lib/libs/core/policies/userPolicy.js +60 -0
- package/lib/libs/core/policies/userPolicy.js.map +1 -0
- package/lib/libs/core/policy-types.d.ts +18 -0
- package/lib/libs/core/policy-types.js +28 -0
- package/lib/libs/core/policy-types.js.map +1 -0
- package/lib/libs/core/policyRegistry.d.ts +23 -0
- package/lib/libs/core/policyRegistry.js +38 -0
- package/lib/libs/core/policyRegistry.js.map +1 -0
- package/lib/libs/core/registries/connectedApps.d.ts +13 -0
- package/lib/libs/core/registries/connectedApps.js +13 -0
- package/lib/libs/core/registries/connectedApps.js.map +1 -0
- package/lib/libs/core/registries/helpers/permissionsScanning.d.ts +29 -0
- package/lib/libs/core/registries/helpers/permissionsScanning.js +69 -0
- package/lib/libs/core/registries/helpers/permissionsScanning.js.map +1 -0
- package/lib/libs/core/registries/permissionSets.d.ts +11 -0
- package/lib/libs/core/registries/permissionSets.js +11 -0
- package/lib/libs/core/registries/permissionSets.js.map +1 -0
- package/lib/libs/core/registries/profiles.d.ts +11 -0
- package/lib/libs/core/registries/profiles.js +11 -0
- package/lib/libs/core/registries/profiles.js.map +1 -0
- package/lib/libs/core/registries/ruleRegistry.d.ts +37 -0
- package/lib/libs/core/registries/ruleRegistry.js +48 -0
- package/lib/libs/core/registries/ruleRegistry.js.map +1 -0
- package/lib/libs/core/registries/rules/allUsedAppsUnderManagement.d.ts +7 -0
- package/lib/libs/core/registries/rules/allUsedAppsUnderManagement.js +23 -0
- package/lib/libs/core/registries/rules/allUsedAppsUnderManagement.js.map +1 -0
- package/lib/libs/core/registries/rules/enforcePermissionPresets.d.ts +7 -0
- package/lib/libs/core/registries/rules/enforcePermissionPresets.js +58 -0
- package/lib/libs/core/registries/rules/enforcePermissionPresets.js.map +1 -0
- package/lib/libs/core/registries/rules/enforcePermissionsOnProfileLike.d.ts +7 -0
- package/lib/libs/core/registries/rules/enforcePermissionsOnProfileLike.js +26 -0
- package/lib/libs/core/registries/rules/enforcePermissionsOnProfileLike.js.map +1 -0
- package/lib/libs/core/registries/rules/enforcePermissionsOnUser.d.ts +8 -0
- package/lib/libs/core/registries/rules/enforcePermissionsOnUser.js +42 -0
- package/lib/libs/core/registries/rules/enforcePermissionsOnUser.js.map +1 -0
- package/lib/libs/core/registries/rules/noInactiveUsers.d.ts +9 -0
- package/lib/libs/core/registries/rules/noInactiveUsers.js +44 -0
- package/lib/libs/core/registries/rules/noInactiveUsers.js.map +1 -0
- package/lib/libs/core/registries/rules/noOtherApexApiLogins.d.ts +7 -0
- package/lib/libs/core/registries/rules/noOtherApexApiLogins.js +27 -0
- package/lib/libs/core/registries/rules/noOtherApexApiLogins.js.map +1 -0
- package/lib/libs/core/registries/rules/noUserCanSelfAuthorize.d.ts +7 -0
- package/lib/libs/core/registries/rules/noUserCanSelfAuthorize.js +31 -0
- package/lib/libs/core/registries/rules/noUserCanSelfAuthorize.js.map +1 -0
- package/lib/libs/core/registries/rules/policyRule.d.ts +19 -0
- package/lib/libs/core/registries/rules/policyRule.js +32 -0
- package/lib/libs/core/registries/rules/policyRule.js.map +1 -0
- package/lib/libs/core/registries/types.d.ts +37 -0
- package/lib/libs/core/registries/types.js +11 -0
- package/lib/libs/core/registries/types.js.map +1 -0
- package/lib/libs/core/registries/users.d.ts +10 -0
- package/lib/libs/core/registries/users.js +17 -0
- package/lib/libs/core/registries/users.js.map +1 -0
- package/lib/libs/core/result-types.d.ts +172 -0
- package/lib/libs/core/result-types.js +2 -0
- package/lib/libs/core/result-types.js.map +1 -0
- package/lib/libs/core/utils.d.ts +12 -0
- package/lib/libs/core/utils.js +31 -0
- package/lib/libs/core/utils.js.map +1 -0
- package/lib/libs/quick-scan/types.d.ts +17 -0
- package/lib/libs/quick-scan/types.js +2 -0
- package/lib/libs/quick-scan/types.js.map +1 -0
- package/lib/libs/quick-scan/userPermissionScanner.d.ts +22 -0
- package/lib/libs/quick-scan/userPermissionScanner.js +75 -0
- package/lib/libs/quick-scan/userPermissionScanner.js.map +1 -0
- package/lib/ux/auditRunMultiStage.d.ts +65 -0
- package/lib/ux/auditRunMultiStage.js +120 -0
- package/lib/ux/auditRunMultiStage.js.map +1 -0
- package/oclif.lock +13606 -10113
- package/oclif.manifest.json +253 -2
- package/package.json +13 -41
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
4
|
+
import { RETRIEVE_CACHE } from '../constants.js';
|
|
5
|
+
export default class MetadataRegistryEntry {
|
|
6
|
+
opts;
|
|
7
|
+
parser;
|
|
8
|
+
retrieveType;
|
|
9
|
+
rootNodeName;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
this.opts = opts;
|
|
12
|
+
this.retrieveType = this.opts.retrieveType;
|
|
13
|
+
this.parser = this.opts.parser ?? new XMLParser();
|
|
14
|
+
this.rootNodeName = this.opts.rootNodeName;
|
|
15
|
+
}
|
|
16
|
+
parse(fullFilePath) {
|
|
17
|
+
const fileContent = readFileSync(fullFilePath, 'utf-8');
|
|
18
|
+
const parsedContent = this.parser.parse(fileContent);
|
|
19
|
+
if (this.opts.parsePostProcessor) {
|
|
20
|
+
return this.opts.parsePostProcessor(parsedContent[this.rootNodeName]);
|
|
21
|
+
}
|
|
22
|
+
return parsedContent[this.rootNodeName];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function retrieve(compSet, con) {
|
|
26
|
+
const retrieveRequest = await compSet.retrieve({
|
|
27
|
+
usernameOrConnection: con,
|
|
28
|
+
output: RETRIEVE_CACHE,
|
|
29
|
+
});
|
|
30
|
+
const retrieveResult = await retrieveRequest.pollStatus();
|
|
31
|
+
return retrieveResult;
|
|
32
|
+
}
|
|
33
|
+
export function cleanRetrieveDir(files) {
|
|
34
|
+
const dirNames = new Set();
|
|
35
|
+
files.forEach((file) => {
|
|
36
|
+
if (file.filePath) {
|
|
37
|
+
const dirName = path.dirname(path.normalize(file.filePath));
|
|
38
|
+
const parts = dirName.split(path.sep).filter((dirPart) => dirPart.startsWith('metadataPackage_'));
|
|
39
|
+
parts.forEach((mdPart) => dirNames.add(mdPart));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
dirNames.forEach((dir) => {
|
|
43
|
+
rmSync(path.join(RETRIEVE_CACHE, dir), { recursive: true });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=metadataRegistryEntry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadataRegistryEntry.js","sourceRoot":"","sources":["../../../../src/libs/core/mdapi/metadataRegistryEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA8BjD,MAAM,CAAC,OAAO,OAAgB,qBAAqB;IAKtB;IAJpB,MAAM,CAAY;IAClB,YAAY,CAAS;IACrB,YAAY,CAAM;IAEzB,YAA2B,IAA0C;QAA1C,SAAI,GAAJ,IAAI,CAAsC;QACnE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;QAC3C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAClD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;IAC7C,CAAC;IAEM,KAAK,CAAC,YAAsB;QACjC,MAAM,WAAW,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACxD,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAS,CAAC;QAC7D,IAAI,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAqB,EAAE,GAAe;IACnE,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC;QAC7C,oBAAoB,EAAE,GAAG;QACzB,MAAM,EAAE,cAAc;KACvB,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,MAAM,eAAe,CAAC,UAAU,EAAE,CAAC;IAC1D,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAqB;IACpD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACrB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC5D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC;YAClG,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACvB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Connection } from '@salesforce/core';
|
|
2
|
+
import { NamedMetadataResolver } from './metadataRegistryEntry.js';
|
|
3
|
+
export type NamedMetadataQueryableOpts<Type> = {
|
|
4
|
+
/**
|
|
5
|
+
* Object API name to retrieve. Must be available in tooling API
|
|
6
|
+
*/
|
|
7
|
+
objectName: string;
|
|
8
|
+
/**
|
|
9
|
+
* Unique name field that is used to retrieve the object
|
|
10
|
+
*/
|
|
11
|
+
nameField: string;
|
|
12
|
+
/**
|
|
13
|
+
* Post processor function that sanitises the XML parse result
|
|
14
|
+
*/
|
|
15
|
+
parsePostProcessor?: (parseResult: Type) => Type;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The entry is a typical named metadata that is organized in a dedicated source folder
|
|
19
|
+
* where all entities have the same format. The components are queried from tooling API
|
|
20
|
+
* and organized by their developer name.
|
|
21
|
+
*/
|
|
22
|
+
export default class NamedMetadataQueryable<Type, Key extends keyof Type> implements NamedMetadataResolver<Type[Key]> {
|
|
23
|
+
private opts;
|
|
24
|
+
constructor(opts: NamedMetadataQueryableOpts<Type[Key]>);
|
|
25
|
+
/**
|
|
26
|
+
* Resolves a set of component names by querying "Metadata" property from tooling API
|
|
27
|
+
*
|
|
28
|
+
* @param con
|
|
29
|
+
* @param componentNames
|
|
30
|
+
* @returns
|
|
31
|
+
*/
|
|
32
|
+
resolve(con: Connection, componentNames: string[]): Promise<Record<string, Type[Key]>>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { isNullish } from '../utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* The entry is a typical named metadata that is organized in a dedicated source folder
|
|
4
|
+
* where all entities have the same format. The components are queried from tooling API
|
|
5
|
+
* and organized by their developer name.
|
|
6
|
+
*/
|
|
7
|
+
export default class NamedMetadataQueryable {
|
|
8
|
+
opts;
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
this.opts = opts;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a set of component names by querying "Metadata" property from tooling API
|
|
14
|
+
*
|
|
15
|
+
* @param con
|
|
16
|
+
* @param componentNames
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
async resolve(con, componentNames) {
|
|
20
|
+
const pendingQueries = new Array();
|
|
21
|
+
componentNames.forEach((cname) => {
|
|
22
|
+
const qr = Promise.resolve(con.tooling.query(`SELECT ${this.opts.nameField},Metadata FROM ${this.opts.objectName} WHERE ${this.opts.nameField} = '${cname}'`));
|
|
23
|
+
pendingQueries.push(qr);
|
|
24
|
+
});
|
|
25
|
+
const queryResults = await Promise.all(pendingQueries);
|
|
26
|
+
const resultMap = {};
|
|
27
|
+
queryResults.forEach((qr) => {
|
|
28
|
+
if (qr.totalSize > 0) {
|
|
29
|
+
const record = qr.records[0];
|
|
30
|
+
const identifier = record[this.opts.nameField];
|
|
31
|
+
if (identifier && !isNullish(record.Metadata)) {
|
|
32
|
+
resultMap[identifier] = this.opts.parsePostProcessor
|
|
33
|
+
? this.opts.parsePostProcessor(record.Metadata)
|
|
34
|
+
: record.Metadata;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return resultMap;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=namedMetadataToolingQueryable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"namedMetadataToolingQueryable.js","sourceRoot":"","sources":["../../../../src/libs/core/mdapi/namedMetadataToolingQueryable.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAoBxC;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,sBAAsB;IACd;IAA3B,YAA2B,IAA2C;QAA3C,SAAI,GAAJ,IAAI,CAAuC;IAAG,CAAC;IAC1E;;;;;;OAMG;IACI,KAAK,CAAC,OAAO,CAAC,GAAe,EAAE,cAAwB;QAC5D,MAAM,cAAc,GAAG,IAAI,KAAK,EAAmD,CAAC;QACpF,cAAc,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YAC/B,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CACxB,GAAG,CAAC,OAAO,CAAC,KAAK,CACf,UAAU,IAAI,CAAC,IAAI,CAAC,SAAS,kBAAkB,IAAI,CAAC,IAAI,CAAC,UAAU,UAAU,IAAI,CAAC,IAAI,CAAC,SAAS,OAAO,KAAK,GAAG,CAChH,CACF,CAAC;YACF,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QACH,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACvD,MAAM,SAAS,GAA8B,EAAE,CAAC;QAChD,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;YAC1B,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBAC7B,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAW,CAAC;gBACzD,IAAI,UAAU,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC9C,SAAS,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB;wBAClD,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC;wBAC/C,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;gBACtB,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Connection } from '@salesforce/core';
|
|
2
|
+
import MetadataRegistryEntry, { MetadataRegistryEntryOpts } from './metadataRegistryEntry.js';
|
|
3
|
+
/**
|
|
4
|
+
* The entry is a typical named metadata that is organized in a dedicated source folder
|
|
5
|
+
* where all entities have the same format. The components are retrieved and organized
|
|
6
|
+
* by their developer name.
|
|
7
|
+
*/
|
|
8
|
+
export default class NamedMetadata<Type, Key extends keyof Type> extends MetadataRegistryEntry<Type, Key> {
|
|
9
|
+
constructor(opts: MetadataRegistryEntryOpts<Type, Key>);
|
|
10
|
+
/**
|
|
11
|
+
* Resolves component names, retrieves the metadata and returns
|
|
12
|
+
* as a strongly typed result.
|
|
13
|
+
*
|
|
14
|
+
* @param con
|
|
15
|
+
* @param componentNames
|
|
16
|
+
* @returns
|
|
17
|
+
*/
|
|
18
|
+
resolve(con: Connection, componentNames: string[]): Promise<Record<string, Type[Key]>>;
|
|
19
|
+
private parseSourceFiles;
|
|
20
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ComponentSet } from '@salesforce/source-deploy-retrieve';
|
|
2
|
+
import MetadataRegistryEntry, { cleanRetrieveDir, retrieve, } from './metadataRegistryEntry.js';
|
|
3
|
+
/**
|
|
4
|
+
* The entry is a typical named metadata that is organized in a dedicated source folder
|
|
5
|
+
* where all entities have the same format. The components are retrieved and organized
|
|
6
|
+
* by their developer name.
|
|
7
|
+
*/
|
|
8
|
+
export default class NamedMetadata extends MetadataRegistryEntry {
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
super(opts);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolves component names, retrieves the metadata and returns
|
|
14
|
+
* as a strongly typed result.
|
|
15
|
+
*
|
|
16
|
+
* @param con
|
|
17
|
+
* @param componentNames
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
20
|
+
async resolve(con, componentNames) {
|
|
21
|
+
const cmpSet = new ComponentSet(componentNames.map((cname) => ({ type: this.retrieveType, fullName: cname })));
|
|
22
|
+
const retrieveResult = await retrieve(cmpSet, con);
|
|
23
|
+
const resolvedFiles = this.parseSourceFiles(retrieveResult.components, componentNames);
|
|
24
|
+
cleanRetrieveDir(retrieveResult.getFileResponses());
|
|
25
|
+
return resolvedFiles;
|
|
26
|
+
}
|
|
27
|
+
parseSourceFiles(componentSet, retrievedNames) {
|
|
28
|
+
const cmps = componentSet.getSourceComponents().toArray();
|
|
29
|
+
const result = {};
|
|
30
|
+
cmps.forEach((sourceComponent) => {
|
|
31
|
+
if (sourceComponent.xml && retrievedNames.includes(sourceComponent.name)) {
|
|
32
|
+
// the available method parseXmlSync on source component does not
|
|
33
|
+
// resolve the "rootNodeProblem" from XML. Therefore, we implement
|
|
34
|
+
// our own method to parse and return the "inner xml".
|
|
35
|
+
result[sourceComponent.name] = this.parse(sourceComponent.xml);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=namedMetadataType.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"namedMetadataType.js","sourceRoot":"","sources":["../../../../src/libs/core/mdapi/namedMetadataType.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,qBAAqB,EAAE,EAC5B,gBAAgB,EAEhB,QAAQ,GACT,MAAM,4BAA4B,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,aAA4C,SAAQ,qBAAgC;IACvG,YAAmB,IAA0C;QAC3D,KAAK,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IACD;;;;;;;OAOG;IACI,KAAK,CAAC,OAAO,CAAC,GAAe,EAAE,cAAwB;QAC5D,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAC/G,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACnD,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QACvF,gBAAgB,CAAC,cAAc,CAAC,gBAAgB,EAAE,CAAC,CAAC;QACpD,OAAO,aAAa,CAAC;IACvB,CAAC;IAEO,gBAAgB,CAAC,YAA0B,EAAE,cAAwB;QAC3E,MAAM,IAAI,GAAG,YAAY,CAAC,mBAAmB,EAAE,CAAC,OAAO,EAAE,CAAC;QAC1D,MAAM,MAAM,GAA8B,EAAE,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,CAAC,eAAe,EAAE,EAAE;YAC/B,IAAI,eAAe,CAAC,GAAG,IAAI,cAAc,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzE,iEAAiE;gBACjE,kEAAkE;gBAClE,sDAAsD;gBACtD,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;YACjE,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Connection } from '@salesforce/core';
|
|
2
|
+
import MetadataRegistryEntry, { MetadataRegistryEntryOpts } from './metadataRegistryEntry.js';
|
|
3
|
+
/**
|
|
4
|
+
* The entry is a type that only has one single instance on the org, such as
|
|
5
|
+
* a Setting. The component is typically retrieved by a more generic name and
|
|
6
|
+
* organized & cached by the explicit name.
|
|
7
|
+
*/
|
|
8
|
+
export default class SingletonMetadata<Type, Key extends keyof Type> extends MetadataRegistryEntry<Type, Key> {
|
|
9
|
+
retrieveName: string;
|
|
10
|
+
constructor(opts: MetadataRegistryEntryOpts<Type, Key>);
|
|
11
|
+
/**
|
|
12
|
+
* Resolves component names, retrieves the metadata and returns
|
|
13
|
+
* as a strongly typed result.
|
|
14
|
+
*
|
|
15
|
+
* @param con
|
|
16
|
+
* @param componentNames
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
resolve(con: Connection): Promise<Type[Key]>;
|
|
20
|
+
private parseSourceFile;
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ComponentSet } from '@salesforce/source-deploy-retrieve';
|
|
2
|
+
import MetadataRegistryEntry, { cleanRetrieveDir, retrieve, } from './metadataRegistryEntry.js';
|
|
3
|
+
/**
|
|
4
|
+
* The entry is a type that only has one single instance on the org, such as
|
|
5
|
+
* a Setting. The component is typically retrieved by a more generic name and
|
|
6
|
+
* organized & cached by the explicit name.
|
|
7
|
+
*/
|
|
8
|
+
export default class SingletonMetadata extends MetadataRegistryEntry {
|
|
9
|
+
retrieveName;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
super(opts);
|
|
12
|
+
this.retrieveName = opts.retrieveName ?? String(this.rootNodeName);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolves component names, retrieves the metadata and returns
|
|
16
|
+
* as a strongly typed result.
|
|
17
|
+
*
|
|
18
|
+
* @param con
|
|
19
|
+
* @param componentNames
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
22
|
+
async resolve(con) {
|
|
23
|
+
const cmpSet = new ComponentSet([{ type: this.retrieveType, fullName: this.retrieveName }]);
|
|
24
|
+
const retrieveResult = await retrieve(cmpSet, con);
|
|
25
|
+
const resolvedCmp = this.parseSourceFile(retrieveResult.components);
|
|
26
|
+
cleanRetrieveDir(retrieveResult.getFileResponses());
|
|
27
|
+
return resolvedCmp;
|
|
28
|
+
}
|
|
29
|
+
parseSourceFile(componentSet) {
|
|
30
|
+
const cmps = componentSet.getSourceComponents({ type: this.retrieveType, fullName: this.retrieveName }).toArray();
|
|
31
|
+
if (cmps.length > 0 && cmps[0].xml) {
|
|
32
|
+
return this.parse(cmps[0].xml);
|
|
33
|
+
}
|
|
34
|
+
throw new Error('Failed to resolve settings for: ' + this.retrieveName);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=singletonMetadataType.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singletonMetadataType.js","sourceRoot":"","sources":["../../../../src/libs/core/mdapi/singletonMetadataType.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,qBAAqB,EAAE,EAC5B,gBAAgB,EAEhB,QAAQ,GACT,MAAM,4BAA4B,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAgD,SAAQ,qBAAgC;IACpG,YAAY,CAAS;IAC5B,YAAmB,IAA0C;QAC3D,KAAK,CAAC,IAAI,CAAC,CAAC;QACZ,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACrE,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,OAAO,CAAC,GAAe;QAClC,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;QAC5F,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACnD,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QACpE,gBAAgB,CAAC,cAAc,CAAC,gBAAgB,EAAE,CAAC,CAAC;QACpD,OAAO,WAAW,CAAC;IACrB,CAAC;IAEO,eAAe,CAAC,YAA0B;QAChD,MAAM,IAAI,GAAG,YAAY,CAAC,mBAAmB,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAClH,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;IAC1E,CAAC;CACF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Connection } from '@salesforce/core';
|
|
2
|
+
import { PermissionSet, Profile } from '@jsforce/jsforce-node/lib/api/metadata.js';
|
|
3
|
+
export type User = {
|
|
4
|
+
userId: string;
|
|
5
|
+
username: string;
|
|
6
|
+
profileName: string;
|
|
7
|
+
createdDate: number;
|
|
8
|
+
lastLogin?: number;
|
|
9
|
+
logins?: UserLogins[];
|
|
10
|
+
};
|
|
11
|
+
export type UserPermissions = {
|
|
12
|
+
profileMetadata?: Profile;
|
|
13
|
+
assignedPermissionsets: PermissionSetAssignment[];
|
|
14
|
+
};
|
|
15
|
+
export type UserLogins = {
|
|
16
|
+
loginType: string;
|
|
17
|
+
application: string;
|
|
18
|
+
loginCount: number;
|
|
19
|
+
lastLogin: number;
|
|
20
|
+
};
|
|
21
|
+
export type PermissionSetAssignment = {
|
|
22
|
+
/**
|
|
23
|
+
* Developer name of the permission set
|
|
24
|
+
*/
|
|
25
|
+
permissionSetIdentifier: string;
|
|
26
|
+
/**
|
|
27
|
+
* How user got this permission set assigned
|
|
28
|
+
*/
|
|
29
|
+
permissionSetSource: 'direct' | 'group';
|
|
30
|
+
/**
|
|
31
|
+
* Metadata of the permission set
|
|
32
|
+
*/
|
|
33
|
+
metadata?: PermissionSet;
|
|
34
|
+
/**
|
|
35
|
+
* If permission set is assigned through a group,
|
|
36
|
+
* this is the name of the group.
|
|
37
|
+
*/
|
|
38
|
+
groupName?: string;
|
|
39
|
+
};
|
|
40
|
+
export type ResolveUsersOptions = {
|
|
41
|
+
/**
|
|
42
|
+
* Include aggregated login history
|
|
43
|
+
*/
|
|
44
|
+
withLoginHistory: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* When login history is set, the number of days that is searched
|
|
47
|
+
*/
|
|
48
|
+
loginHistoryDaysToAnalyse?: number;
|
|
49
|
+
};
|
|
50
|
+
export type ResolvePermissionsOptions = {
|
|
51
|
+
/**
|
|
52
|
+
* Resolve permission set and profile metadata
|
|
53
|
+
*/
|
|
54
|
+
withMetadata: boolean;
|
|
55
|
+
};
|
|
56
|
+
export default class UsersRepository {
|
|
57
|
+
private readonly connection;
|
|
58
|
+
private readonly mdapiRepo;
|
|
59
|
+
constructor(connection: Connection);
|
|
60
|
+
/**
|
|
61
|
+
* Resolves all users from the target org of this repository
|
|
62
|
+
*
|
|
63
|
+
* @param opts
|
|
64
|
+
* @returns
|
|
65
|
+
*/
|
|
66
|
+
resolveAllUsers(opts?: ResolveUsersOptions): Promise<Map<string, User>>;
|
|
67
|
+
/**
|
|
68
|
+
* Resolves permission-granting entities (profiles and permission sets)
|
|
69
|
+
* for a list of users.
|
|
70
|
+
*
|
|
71
|
+
* @param userIds Users to be resolved
|
|
72
|
+
* @returns Map of permissions organized by user id
|
|
73
|
+
*/
|
|
74
|
+
resolveUserPermissions(users: User[], opts?: ResolvePermissionsOptions): Promise<Map<string, UserPermissions>>;
|
|
75
|
+
/**
|
|
76
|
+
* Resolves all permission set assignments for the user with metadata of the
|
|
77
|
+
* permission set. If the user has no assignments, an empty list is returned.
|
|
78
|
+
*
|
|
79
|
+
* @param userIds
|
|
80
|
+
* @returns
|
|
81
|
+
*/
|
|
82
|
+
resolvePermissionSetAssignments(userIds: string[], opts?: ResolvePermissionsOptions): Promise<Map<string, PermissionSetAssignment[]>>;
|
|
83
|
+
private resolveLogins;
|
|
84
|
+
private fetchAssignments;
|
|
85
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ACTIVE_USERS_DETAILS_QUERY, buildLoginHistoryQuery, buildPermsetAssignmentsQuery } from '../constants.js';
|
|
2
|
+
import { isNullish } from '../utils.js';
|
|
3
|
+
import MDAPI from './mdapiRetriever.js';
|
|
4
|
+
export default class UsersRepository {
|
|
5
|
+
connection;
|
|
6
|
+
mdapiRepo;
|
|
7
|
+
constructor(connection) {
|
|
8
|
+
this.connection = connection;
|
|
9
|
+
this.mdapiRepo = MDAPI.create(this.connection);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolves all users from the target org of this repository
|
|
13
|
+
*
|
|
14
|
+
* @param opts
|
|
15
|
+
* @returns
|
|
16
|
+
*/
|
|
17
|
+
async resolveAllUsers(opts) {
|
|
18
|
+
const result = new Map();
|
|
19
|
+
const allUsersOnOrg = await this.connection.query(ACTIVE_USERS_DETAILS_QUERY);
|
|
20
|
+
for (const user of allUsersOnOrg.records) {
|
|
21
|
+
const usr = {
|
|
22
|
+
userId: user.Id,
|
|
23
|
+
username: user.Username,
|
|
24
|
+
lastLogin: user.LastLoginDate ? Date.parse(user.LastLoginDate) : undefined,
|
|
25
|
+
createdDate: Date.parse(user.CreatedDate),
|
|
26
|
+
profileName: user.Profile.Name,
|
|
27
|
+
};
|
|
28
|
+
result.set(user.Username, usr);
|
|
29
|
+
}
|
|
30
|
+
if (opts?.withLoginHistory) {
|
|
31
|
+
const userLogins = await this.resolveLogins(opts.loginHistoryDaysToAnalyse);
|
|
32
|
+
for (const user of result.values()) {
|
|
33
|
+
if (userLogins.has(user.userId)) {
|
|
34
|
+
user.logins = userLogins.get(user.userId);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
user.logins = [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolves permission-granting entities (profiles and permission sets)
|
|
45
|
+
* for a list of users.
|
|
46
|
+
*
|
|
47
|
+
* @param userIds Users to be resolved
|
|
48
|
+
* @returns Map of permissions organized by user id
|
|
49
|
+
*/
|
|
50
|
+
async resolveUserPermissions(users, opts) {
|
|
51
|
+
const result = new Map();
|
|
52
|
+
const permsets = await this.resolvePermissionSetAssignments(users.map((usr) => usr.userId), opts);
|
|
53
|
+
const profiles = opts?.withMetadata
|
|
54
|
+
? await this.mdapiRepo.resolve('Profile', uniqueProfileNames(Object.values(users)))
|
|
55
|
+
: {};
|
|
56
|
+
for (const user of users) {
|
|
57
|
+
result.set(user.userId, {
|
|
58
|
+
assignedPermissionsets: permsets.get(user.userId) ?? [],
|
|
59
|
+
profileMetadata: profiles[user.profileName],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolves all permission set assignments for the user with metadata of the
|
|
66
|
+
* permission set. If the user has no assignments, an empty list is returned.
|
|
67
|
+
*
|
|
68
|
+
* @param userIds
|
|
69
|
+
* @returns
|
|
70
|
+
*/
|
|
71
|
+
async resolvePermissionSetAssignments(userIds, opts) {
|
|
72
|
+
const result = new Map();
|
|
73
|
+
const { assignments, permSetNames } = await this.fetchAssignments(userIds);
|
|
74
|
+
const permsets = opts?.withMetadata ? await this.mdapiRepo.resolve('PermissionSet', permSetNames) : {};
|
|
75
|
+
for (const userId of userIds) {
|
|
76
|
+
result.set(userId, assignments.get(userId)
|
|
77
|
+
? assignments.get(userId).map((ass) => ({
|
|
78
|
+
...ass,
|
|
79
|
+
metadata: permsets[ass.permissionSetIdentifier],
|
|
80
|
+
}))
|
|
81
|
+
: []);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
async resolveLogins(daysToAnalyse) {
|
|
86
|
+
const loginHistory = await this.connection.query(buildLoginHistoryQuery(daysToAnalyse));
|
|
87
|
+
const partialUsers = new Map();
|
|
88
|
+
for (const loginHistoryRow of loginHistory.records) {
|
|
89
|
+
if (!partialUsers.has(loginHistoryRow.UserId)) {
|
|
90
|
+
partialUsers.set(loginHistoryRow.UserId, []);
|
|
91
|
+
}
|
|
92
|
+
partialUsers.get(loginHistoryRow.UserId).push({
|
|
93
|
+
loginType: loginHistoryRow.LoginType,
|
|
94
|
+
loginCount: loginHistoryRow.LoginCount,
|
|
95
|
+
application: loginHistoryRow.Application,
|
|
96
|
+
lastLogin: Date.parse(loginHistoryRow.LastLogin),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return partialUsers;
|
|
100
|
+
}
|
|
101
|
+
async fetchAssignments(userIds) {
|
|
102
|
+
const assignments = new Map();
|
|
103
|
+
const uniquePermSets = new Set();
|
|
104
|
+
const rawAssignment = await this.connection.query(buildPermsetAssignmentsQuery(userIds));
|
|
105
|
+
for (const assignment of rawAssignment.records) {
|
|
106
|
+
if (isNullish(assignments.get(assignment.AssigneeId))) {
|
|
107
|
+
assignments.set(assignment.AssigneeId, []);
|
|
108
|
+
}
|
|
109
|
+
assignments.get(assignment.AssigneeId).push({
|
|
110
|
+
permissionSetIdentifier: assignment.PermissionSet.Name,
|
|
111
|
+
permissionSetSource: assignment.PermissionSetGroupId ? 'group' : 'direct',
|
|
112
|
+
groupName: assignment.PermissionSetGroup?.DeveloperName,
|
|
113
|
+
});
|
|
114
|
+
uniquePermSets.add(assignment.PermissionSet.Name);
|
|
115
|
+
}
|
|
116
|
+
return { assignments, permSetNames: Array.from(uniquePermSets) };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function uniqueProfileNames(users) {
|
|
120
|
+
const uniqueProfiles = new Set();
|
|
121
|
+
for (const usr of users) {
|
|
122
|
+
uniqueProfiles.add(usr.profileName);
|
|
123
|
+
}
|
|
124
|
+
return Array.from(uniqueProfiles);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=usersRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usersRepository.js","sourceRoot":"","sources":["../../../../src/libs/core/mdapi/usersRepository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AuditRunConfig, BasePolicyFileContent } from '../file-mgmt/schema.js';
|
|
2
|
+
import { AuditContext } from '../registries/types.js';
|
|
3
|
+
import { ResolvedConnectedApp } from '../registries/connectedApps.js';
|
|
4
|
+
import Policy, { ResolveEntityResult } from './policy.js';
|
|
5
|
+
export default class ConnectedAppPolicy extends Policy<ResolvedConnectedApp> {
|
|
6
|
+
config: BasePolicyFileContent;
|
|
7
|
+
auditConfig: AuditRunConfig;
|
|
8
|
+
constructor(config: BasePolicyFileContent, auditConfig: AuditRunConfig, registry?: import("../registries/connectedApps.js").default);
|
|
9
|
+
protected resolveEntities(context: AuditContext): Promise<ResolveEntityResult<ResolvedConnectedApp>>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { CONNECTED_APPS_QUERY, OAUTH_TOKEN_QUERY } from '../constants.js';
|
|
2
|
+
import { ConnectedAppsRegistry } from '../registries/connectedApps.js';
|
|
3
|
+
import MDAPI from '../mdapi/mdapiRetriever.js';
|
|
4
|
+
import Policy, { getTotal } from './policy.js';
|
|
5
|
+
export default class ConnectedAppPolicy extends Policy {
|
|
6
|
+
config;
|
|
7
|
+
auditConfig;
|
|
8
|
+
constructor(config, auditConfig, registry = ConnectedAppsRegistry) {
|
|
9
|
+
super(config, auditConfig, registry);
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.auditConfig = auditConfig;
|
|
12
|
+
}
|
|
13
|
+
// eslint-disable-next-line class-methods-use-this
|
|
14
|
+
async resolveEntities(context) {
|
|
15
|
+
const successfullyResolved = {};
|
|
16
|
+
const ignoredEntities = {};
|
|
17
|
+
const metadataApi = new MDAPI(context.targetOrgConnection);
|
|
18
|
+
this.emit('entityresolve', {
|
|
19
|
+
total: 0,
|
|
20
|
+
resolved: 0,
|
|
21
|
+
});
|
|
22
|
+
const installedApps = await context.targetOrgConnection.query(CONNECTED_APPS_QUERY);
|
|
23
|
+
this.emit('entityresolve', {
|
|
24
|
+
total: installedApps.totalSize,
|
|
25
|
+
resolved: 0,
|
|
26
|
+
});
|
|
27
|
+
installedApps.records.forEach((installedApp) => {
|
|
28
|
+
successfullyResolved[installedApp.Name] = {
|
|
29
|
+
name: installedApp.Name,
|
|
30
|
+
origin: 'Installed',
|
|
31
|
+
onlyAdminApprovedUsersAllowed: installedApp.OptionsAllowAdminApprovedUsersOnly,
|
|
32
|
+
overrideByApiSecurityAccess: false,
|
|
33
|
+
useCount: 0,
|
|
34
|
+
users: [],
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
const usersOAuthToken = await context.targetOrgConnection.query(OAUTH_TOKEN_QUERY);
|
|
38
|
+
usersOAuthToken.records.forEach((token) => {
|
|
39
|
+
if (successfullyResolved[token.AppName] === undefined) {
|
|
40
|
+
successfullyResolved[token.AppName] = {
|
|
41
|
+
name: token.AppName,
|
|
42
|
+
origin: 'OauthToken',
|
|
43
|
+
onlyAdminApprovedUsersAllowed: false,
|
|
44
|
+
overrideByApiSecurityAccess: false,
|
|
45
|
+
useCount: token.UseCount,
|
|
46
|
+
users: [token.User.Username],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
successfullyResolved[token.AppName].useCount += token.UseCount;
|
|
51
|
+
if (!successfullyResolved[token.AppName].users.includes(token.User.Username)) {
|
|
52
|
+
successfullyResolved[token.AppName].users.push(token.User.Username);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
this.emit('entityresolve', {
|
|
57
|
+
total: Object.keys(successfullyResolved).length,
|
|
58
|
+
resolved: 0,
|
|
59
|
+
});
|
|
60
|
+
let overrideByApiSecurityAccess = false;
|
|
61
|
+
const apiSecurityAccessSetting = await metadataApi.resolveSingleton('ConnectedAppSettings');
|
|
62
|
+
if (apiSecurityAccessSetting && apiSecurityAccessSetting.enableAdminApprovedAppsOnly) {
|
|
63
|
+
overrideByApiSecurityAccess = true;
|
|
64
|
+
}
|
|
65
|
+
Object.values(successfullyResolved).forEach((conApp) => {
|
|
66
|
+
// eslint-disable-next-line no-param-reassign
|
|
67
|
+
conApp.overrideByApiSecurityAccess = overrideByApiSecurityAccess;
|
|
68
|
+
});
|
|
69
|
+
const result = { resolvedEntities: successfullyResolved, ignoredEntities: Object.values(ignoredEntities) };
|
|
70
|
+
this.emit('entityresolve', {
|
|
71
|
+
total: getTotal(result),
|
|
72
|
+
resolved: getTotal(result),
|
|
73
|
+
});
|
|
74
|
+
// also query from tooling, to get additional information info
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=connectedAppPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectedAppPolicy.js","sourceRoot":"","sources":["../../../../src/libs/core/policies/connectedAppPolicy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AuditRunConfig, PermSetsPolicyFileContent } from '../file-mgmt/schema.js';
|
|
2
|
+
import { AuditContext } from '../registries/types.js';
|
|
3
|
+
import { ResolvedPermissionSet } from '../registries/permissionSets.js';
|
|
4
|
+
import Policy, { ResolveEntityResult } from './policy.js';
|
|
5
|
+
export default class PermissionSetPolicy extends Policy<ResolvedPermissionSet> {
|
|
6
|
+
config: PermSetsPolicyFileContent;
|
|
7
|
+
auditContext: AuditRunConfig;
|
|
8
|
+
private totalEntities;
|
|
9
|
+
constructor(config: PermSetsPolicyFileContent, auditContext: AuditRunConfig, registry?: import("../registries/permissionSets.js").default);
|
|
10
|
+
protected resolveEntities(context: AuditContext): Promise<ResolveEntityResult<ResolvedPermissionSet>>;
|
|
11
|
+
}
|