@j-schreiber/sf-cli-security-audit 0.4.1 → 0.6.0
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 +20 -5
- package/lib/commands/org/audit/init.d.ts +2 -0
- package/lib/commands/org/audit/init.js +10 -0
- package/lib/commands/org/audit/init.js.map +1 -1
- package/lib/commands/org/scan/user-perms.d.ts +20 -0
- package/lib/commands/org/scan/user-perms.js +88 -0
- package/lib/commands/org/scan/user-perms.js.map +1 -0
- package/lib/libs/conf-init/auditConfig.d.ts +8 -0
- package/lib/libs/conf-init/auditConfig.js +3 -2
- package/lib/libs/conf-init/auditConfig.js.map +1 -1
- package/lib/libs/conf-init/permissionsClassification.d.ts +3 -2
- package/lib/libs/conf-init/permissionsClassification.js +37 -27
- package/lib/libs/conf-init/permissionsClassification.js.map +1 -1
- package/lib/libs/conf-init/presets/loose.d.ts +6 -0
- package/lib/libs/conf-init/presets/loose.js +35 -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 +28 -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/classification-types.d.ts +1 -1
- package/lib/libs/core/classification-types.js +1 -1
- package/lib/libs/core/classification-types.js.map +1 -1
- package/lib/libs/core/constants.d.ts +1 -0
- package/lib/libs/core/constants.js +4 -0
- package/lib/libs/core/constants.js.map +1 -1
- package/lib/libs/core/file-mgmt/auditConfigFileManager.d.ts +1 -0
- package/lib/libs/core/file-mgmt/auditConfigFileManager.js +49 -4
- package/lib/libs/core/file-mgmt/auditConfigFileManager.js.map +1 -1
- package/lib/libs/core/mdapi/mdapiRetriever.d.ts +12 -68
- package/lib/libs/core/mdapi/mdapiRetriever.js +20 -90
- package/lib/libs/core/mdapi/mdapiRetriever.js.map +1 -1
- 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/utils.d.ts +2 -0
- package/lib/libs/core/utils.js +6 -0
- package/lib/libs/core/utils.js.map +1 -1
- package/lib/libs/policies/profilePolicy.js +21 -28
- package/lib/libs/policies/profilePolicy.js.map +1 -1
- 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/messages/org.audit.init.md +12 -0
- package/messages/org.audit.run.md +12 -0
- package/messages/org.scan.user-perms.md +31 -0
- package/messages/policyclassifications.md +38 -2
- package/oclif.manifest.json +96 -2
- package/package.json +1 -1
- package/lib/libs/conf-init/defaultPolicyClassification.d.ts +0 -2
- package/lib/libs/conf-init/defaultPolicyClassification.js +0 -63
- package/lib/libs/conf-init/defaultPolicyClassification.js.map +0 -1
|
@@ -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"}
|
package/lib/libs/core/utils.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export declare function isEmpty(anything?: unknown): boolean;
|
|
2
2
|
export declare function isNullish(anything: unknown): boolean;
|
|
3
|
+
export declare function capitalize(anyString: string): string;
|
|
4
|
+
export declare function uncapitalize(anyString: string): string;
|
|
3
5
|
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
package/lib/libs/core/utils.js
CHANGED
|
@@ -10,4 +10,10 @@ export function isEmpty(anything) {
|
|
|
10
10
|
export function isNullish(anything) {
|
|
11
11
|
return !(Boolean(anything) && anything !== null);
|
|
12
12
|
}
|
|
13
|
+
export function capitalize(anyString) {
|
|
14
|
+
return `${anyString[0].toUpperCase()}${anyString.slice(1)}`;
|
|
15
|
+
}
|
|
16
|
+
export function uncapitalize(anyString) {
|
|
17
|
+
return `${anyString[0].toLowerCase()}${anyString.slice(1)}`;
|
|
18
|
+
}
|
|
13
19
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/libs/core/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,OAAO,CAAC,QAAkB;IACxC,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,MAAM,CAAC,OAAO,CAAC,QAAS,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAiB;IACzC,OAAO,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,KAAK,IAAI,CAAC,CAAC;AACnD,CAAC"}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/libs/core/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,OAAO,CAAC,QAAkB;IACxC,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,MAAM,CAAC,OAAO,CAAC,QAAS,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAiB;IACzC,OAAO,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,KAAK,IAAI,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9D,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Messages } from '@salesforce/core';
|
|
2
|
-
import
|
|
2
|
+
import MDAPI from '../core/mdapi/mdapiRetriever.js';
|
|
3
3
|
import { RuleRegistries } from '../core/registries/types.js';
|
|
4
4
|
import { ProfilesRiskPreset } from '../core/policy-types.js';
|
|
5
5
|
import Policy, { getTotal } from './policy.js';
|
|
@@ -22,42 +22,35 @@ export default class ProfilePolicy extends Policy {
|
|
|
22
22
|
});
|
|
23
23
|
const successfullyResolved = {};
|
|
24
24
|
const ignoredEntities = {};
|
|
25
|
-
const profileQueryResults = Array();
|
|
26
25
|
const definitiveProfiles = this.config.profiles ?? {};
|
|
26
|
+
const classifiedProfiles = [];
|
|
27
27
|
Object.entries(definitiveProfiles).forEach(([profileName, profileDef]) => {
|
|
28
|
-
if (profileDef.preset
|
|
29
|
-
const qr = Promise.resolve(context.targetOrgConnection.tooling.query(`SELECT Name,Metadata FROM Profile WHERE Name = '${profileName}'`));
|
|
30
|
-
profileQueryResults.push(qr);
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
28
|
+
if (profileDef.preset === ProfilesRiskPreset.UNKNOWN) {
|
|
33
29
|
ignoredEntities[profileName] = {
|
|
34
30
|
name: profileName,
|
|
35
31
|
message: messages.getMessage('preset-unknown', ['Profile']),
|
|
36
32
|
};
|
|
37
33
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
queryResults.forEach((qr) => {
|
|
41
|
-
if (qr.records && qr.records.length > 0) {
|
|
42
|
-
const record = qr.records[0];
|
|
43
|
-
if (isNullish(record.Metadata)) {
|
|
44
|
-
ignoredEntities[record.Name] = {
|
|
45
|
-
name: record.Name,
|
|
46
|
-
message: messages.getMessage('profile-invalid-no-metadata'),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
successfullyResolved[record.Name] = {
|
|
51
|
-
name: record.Name,
|
|
52
|
-
preset: definitiveProfiles[record.Name].preset,
|
|
53
|
-
metadata: record.Metadata,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
34
|
+
else {
|
|
35
|
+
classifiedProfiles.push(profileName);
|
|
56
36
|
}
|
|
57
37
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
38
|
+
const mdapi = new MDAPI(context.targetOrgConnection);
|
|
39
|
+
const resolvedProfiles = await mdapi.resolve('Profile', classifiedProfiles);
|
|
40
|
+
classifiedProfiles.forEach((profileName) => {
|
|
41
|
+
const resolvedProfile = resolvedProfiles[profileName];
|
|
42
|
+
if (!resolvedProfile) {
|
|
43
|
+
ignoredEntities[profileName] = {
|
|
44
|
+
name: profileName,
|
|
45
|
+
message: messages.getMessage('entity-not-found'),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
successfullyResolved[profileName] = {
|
|
50
|
+
name: profileName,
|
|
51
|
+
preset: definitiveProfiles[profileName].preset,
|
|
52
|
+
metadata: resolvedProfile,
|
|
53
|
+
};
|
|
61
54
|
}
|
|
62
55
|
});
|
|
63
56
|
const result = { resolvedEntities: successfullyResolved, ignoredEntities: Object.values(ignoredEntities) };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profilePolicy.js","sourceRoot":"","sources":["../../../src/libs/policies/profilePolicy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"profilePolicy.js","sourceRoot":"","sources":["../../../src/libs/policies/profilePolicy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Connection } from '@salesforce/core';
|
|
2
|
+
export type QuickScanResult = {
|
|
3
|
+
permissions: QuickScanPermissionResult;
|
|
4
|
+
scannedProfiles: string[];
|
|
5
|
+
scannedPermissionSets: string[];
|
|
6
|
+
};
|
|
7
|
+
export type QuickScanPermissionResult = {
|
|
8
|
+
[permissionName: string]: PermissionScanResult;
|
|
9
|
+
};
|
|
10
|
+
export type PermissionScanResult = {
|
|
11
|
+
profiles: string[];
|
|
12
|
+
permissionSets: string[];
|
|
13
|
+
};
|
|
14
|
+
export type QuickScanOptions = {
|
|
15
|
+
targetOrg: Connection;
|
|
16
|
+
permissions: string[];
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/libs/quick-scan/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { QuickScanOptions, QuickScanResult } from './types.js';
|
|
3
|
+
export type ScanStatusEvent = {
|
|
4
|
+
profiles: EntityScanStatus;
|
|
5
|
+
permissionSets: EntityScanStatus;
|
|
6
|
+
users: EntityScanStatus;
|
|
7
|
+
status: 'Pending' | 'In Progress' | 'Completed';
|
|
8
|
+
};
|
|
9
|
+
export type EntityScanStatus = {
|
|
10
|
+
total?: number;
|
|
11
|
+
resolved?: number;
|
|
12
|
+
status?: string;
|
|
13
|
+
};
|
|
14
|
+
export default class UserPermissionScanner extends EventEmitter {
|
|
15
|
+
private status;
|
|
16
|
+
constructor();
|
|
17
|
+
quickScan(opts: QuickScanOptions): Promise<QuickScanResult>;
|
|
18
|
+
private resolveEntities;
|
|
19
|
+
private resolveProfiles;
|
|
20
|
+
private resolvePermissionSets;
|
|
21
|
+
private emitProgress;
|
|
22
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import MDAPI from '../core/mdapi/mdapiRetriever.js';
|
|
3
|
+
import { PERMISSION_SETS_QUERY, PROFILES_QUERY } from '../core/constants.js';
|
|
4
|
+
export default class UserPermissionScanner extends EventEmitter {
|
|
5
|
+
status = {
|
|
6
|
+
profiles: {},
|
|
7
|
+
permissionSets: {},
|
|
8
|
+
users: {},
|
|
9
|
+
status: 'Pending',
|
|
10
|
+
};
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
}
|
|
14
|
+
async quickScan(opts) {
|
|
15
|
+
this.emitProgress({ status: 'Pending' });
|
|
16
|
+
const scannedEntities = await this.resolveEntities(opts.targetOrg);
|
|
17
|
+
const scanResult = {
|
|
18
|
+
permissions: {},
|
|
19
|
+
scannedProfiles: Object.keys(scannedEntities.profiles),
|
|
20
|
+
scannedPermissionSets: Object.keys(scannedEntities.permissionSets),
|
|
21
|
+
};
|
|
22
|
+
opts.permissions.forEach((permName) => {
|
|
23
|
+
const profiles = findGrantingEntities(permName, scannedEntities.profiles);
|
|
24
|
+
const permissionSets = findGrantingEntities(permName, scannedEntities.permissionSets);
|
|
25
|
+
scanResult.permissions[permName] = { permissionSets, profiles };
|
|
26
|
+
});
|
|
27
|
+
this.emitProgress({ status: 'Completed' });
|
|
28
|
+
return scanResult;
|
|
29
|
+
}
|
|
30
|
+
async resolveEntities(targetOrg) {
|
|
31
|
+
const promises = [];
|
|
32
|
+
this.emitProgress({ status: 'In Progress' });
|
|
33
|
+
promises.push(this.resolveProfiles(targetOrg));
|
|
34
|
+
promises.push(this.resolvePermissionSets(targetOrg));
|
|
35
|
+
const resolvedEntities = await Promise.all(promises);
|
|
36
|
+
return {
|
|
37
|
+
profiles: resolvedEntities[0],
|
|
38
|
+
permissionSets: resolvedEntities[1],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async resolveProfiles(targetOrg) {
|
|
42
|
+
const profiles = await targetOrg.query(PROFILES_QUERY);
|
|
43
|
+
this.emitProgress({ profiles: { total: profiles.records.length, resolved: 0 } });
|
|
44
|
+
const mdapi = MDAPI.create(targetOrg);
|
|
45
|
+
const resolved = await mdapi.resolve('Profile', profiles.records.map((permsetRecord) => permsetRecord.Profile.Name));
|
|
46
|
+
this.emitProgress({ profiles: { resolved: Object.keys(resolved).length } });
|
|
47
|
+
return resolved;
|
|
48
|
+
}
|
|
49
|
+
async resolvePermissionSets(targetOrg) {
|
|
50
|
+
const permSets = await targetOrg.query(PERMISSION_SETS_QUERY);
|
|
51
|
+
this.emitProgress({ permissionSets: { total: permSets.records.length, resolved: 0 } });
|
|
52
|
+
const mdapi = MDAPI.create(targetOrg);
|
|
53
|
+
const resolved = await mdapi.resolve('PermissionSet', permSets.records.map((permsetRecord) => permsetRecord.Name));
|
|
54
|
+
this.emitProgress({ permissionSets: { resolved: Object.keys(resolved).length } });
|
|
55
|
+
return resolved;
|
|
56
|
+
}
|
|
57
|
+
emitProgress(update) {
|
|
58
|
+
this.status.profiles = { ...this.status.profiles, ...update.profiles };
|
|
59
|
+
this.status.permissionSets = { ...this.status.permissionSets, ...update.permissionSets };
|
|
60
|
+
this.status.users = { ...this.status.users, ...update.users };
|
|
61
|
+
this.status.status = update.status ?? this.status.status;
|
|
62
|
+
this.emit('progress', structuredClone(this.status));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function findGrantingEntities(permName, resolvedEntities) {
|
|
66
|
+
const entities = new Set();
|
|
67
|
+
Object.entries(resolvedEntities).forEach(([entityName, metadata]) => {
|
|
68
|
+
const userPerms = metadata.userPermissions.map((userPerm) => userPerm.name);
|
|
69
|
+
if (userPerms.includes(permName)) {
|
|
70
|
+
entities.add(entityName);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return Array.from(entities);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=userPermissionScanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userPermissionScanner.js","sourceRoot":"","sources":["../../../src/libs/quick-scan/userPermissionScanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,12 +14,24 @@ Target org to export permissions, profiles, users, etc.
|
|
|
14
14
|
|
|
15
15
|
Directory where the audit config is initialised. If not set, the root directory will be used.
|
|
16
16
|
|
|
17
|
+
# flags.preset.summary
|
|
18
|
+
|
|
19
|
+
Select a preset to initialise permission classifications (risk levels).
|
|
20
|
+
|
|
21
|
+
# flags.preset.description
|
|
22
|
+
|
|
23
|
+
The selected preset is applied before any other default mechanisms (such as template configs). This means, values from a selected template override the preset. Consult the documentation to learn more about the rationale behind the default risk levels. The risk levels interact with the configured preset on profiles and permission sets and essentially control, if a permission is allowed in a certain profile / permission set.
|
|
24
|
+
|
|
17
25
|
# examples
|
|
18
26
|
|
|
19
27
|
- Initialise audit policies at the root directory
|
|
20
28
|
|
|
21
29
|
<%= config.bin %> <%= command.id %> -o MyTargetOrg
|
|
22
30
|
|
|
31
|
+
- Initialise audit config at custom directory with preset
|
|
32
|
+
|
|
33
|
+
<%= config.bin %> <%= command.id %> -o MyTargetOrg -d my_dir -p loose
|
|
34
|
+
|
|
23
35
|
# success.perm-classification-summary
|
|
24
36
|
|
|
25
37
|
Initialised %s permissions at %s.
|
|
@@ -35,3 +35,15 @@ At least one policy is not compliant. Review details below.
|
|
|
35
35
|
# info.report-file-location
|
|
36
36
|
|
|
37
37
|
Full report was written to: %s.
|
|
38
|
+
|
|
39
|
+
# NoAuditConfigFound
|
|
40
|
+
|
|
41
|
+
The target directory %s is empty or no valid audit config was found. A valid audit config must contain at least one policy.
|
|
42
|
+
|
|
43
|
+
# UserPermClassificationRequiredForProfiles
|
|
44
|
+
|
|
45
|
+
The "Profiles" policy requires at least userPermissions to be initialised, but none were found at the target directory.
|
|
46
|
+
|
|
47
|
+
# UserPermClassificationRequiredForPermSets
|
|
48
|
+
|
|
49
|
+
The "Permission Sets" policy requires at least userPermissions to be initialised, but none were found at the target directory.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# summary
|
|
2
|
+
|
|
3
|
+
Performs a quick scan to check permission sets and profiles for user permissions.
|
|
4
|
+
|
|
5
|
+
# description
|
|
6
|
+
|
|
7
|
+
The quick scan does not need an audit config and does not create reports. The target org is scanned "in memory" and simply outputs information, where the searched user permissions
|
|
8
|
+
|
|
9
|
+
# flags.name.summary
|
|
10
|
+
|
|
11
|
+
One or more permissions to be scanned.
|
|
12
|
+
|
|
13
|
+
# flags.name.description
|
|
14
|
+
|
|
15
|
+
You can specify any valid user permission on your org, such as "AuthorApex", "CustomizeApplication" or "ViewSetup". If you are unsure what permissions are available on your org, initialise a new audit config and check the created userPermissions.yml.
|
|
16
|
+
|
|
17
|
+
# flags.target-org.summary
|
|
18
|
+
|
|
19
|
+
The target org to scan.
|
|
20
|
+
|
|
21
|
+
# examples
|
|
22
|
+
|
|
23
|
+
- <%= config.bin %> <%= command.id %>
|
|
24
|
+
|
|
25
|
+
# success.profiles-count
|
|
26
|
+
|
|
27
|
+
Scanned %s profiles.
|
|
28
|
+
|
|
29
|
+
# success.permissionsets-count
|
|
30
|
+
|
|
31
|
+
Scanned %s permission sets.
|
|
@@ -2,9 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
Allows to modify all parts of the app, including security settings.
|
|
4
4
|
|
|
5
|
-
#
|
|
5
|
+
# ModifyMetadata
|
|
6
6
|
|
|
7
|
-
Allows to
|
|
7
|
+
Allows to modify all parts of the app, including security settings.
|
|
8
|
+
|
|
9
|
+
# Packaging2
|
|
10
|
+
|
|
11
|
+
General permissions for 2nd generation packages.
|
|
12
|
+
|
|
13
|
+
# InstallPackaging
|
|
14
|
+
|
|
15
|
+
Install unlocked and managed packages.
|
|
16
|
+
|
|
17
|
+
# Packaging2PromoteVersion
|
|
18
|
+
|
|
19
|
+
Promote 2nd generation packages for distribution and install on production orgs.
|
|
20
|
+
|
|
21
|
+
# Packaging2Delete
|
|
22
|
+
|
|
23
|
+
Delete versions of 2nd generation packages.
|
|
8
24
|
|
|
9
25
|
# ViewSetup
|
|
10
26
|
|
|
@@ -14,6 +30,10 @@ Allows to browse setup and view sensitive configurations.
|
|
|
14
30
|
|
|
15
31
|
Bypass all sharing, making all sharing architecture obsolete.
|
|
16
32
|
|
|
33
|
+
# ModifyAllData
|
|
34
|
+
|
|
35
|
+
Bypass all sharing and layout permissions.
|
|
36
|
+
|
|
17
37
|
# AuthorApex
|
|
18
38
|
|
|
19
39
|
Apex can perform harmful actions, and deployed Apex runs in system mode.
|
|
@@ -33,3 +53,19 @@ Set up and reset the connected MFA for a user.
|
|
|
33
53
|
# CanApproveUninstalledApps
|
|
34
54
|
|
|
35
55
|
Allows to authorize new connected apps and therefore new integrations.
|
|
56
|
+
|
|
57
|
+
# UseAnyApiClient
|
|
58
|
+
|
|
59
|
+
Bypass all security settings and use deprecated login types.
|
|
60
|
+
|
|
61
|
+
# ViewClientSecret
|
|
62
|
+
|
|
63
|
+
Access and export secrets from connected apps.
|
|
64
|
+
|
|
65
|
+
# ExportReport
|
|
66
|
+
|
|
67
|
+
Reports allow to export classified or sensitive data.
|
|
68
|
+
|
|
69
|
+
# ManageRemoteAccess
|
|
70
|
+
|
|
71
|
+
Manage, create, edit, and delete connected applications.
|
package/oclif.manifest.json
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
"args": {},
|
|
6
6
|
"description": "Exports permissions (standard and custom), permission sets, profiles, users, etc from the target org. All classifications are initialised with sane defaults that you can customize later.",
|
|
7
7
|
"examples": [
|
|
8
|
-
"Initialise audit policies at the root directory\n<%= config.bin %> <%= command.id %> -o MyTargetOrg"
|
|
8
|
+
"Initialise audit policies at the root directory\n<%= config.bin %> <%= command.id %> -o MyTargetOrg",
|
|
9
|
+
"Initialise audit config at custom directory with preset\n<%= config.bin %> <%= command.id %> -o MyTargetOrg -d my_dir -p loose"
|
|
9
10
|
],
|
|
10
11
|
"flags": {
|
|
11
12
|
"json": {
|
|
@@ -43,6 +44,21 @@
|
|
|
43
44
|
"multiple": false,
|
|
44
45
|
"type": "option"
|
|
45
46
|
},
|
|
47
|
+
"preset": {
|
|
48
|
+
"char": "p",
|
|
49
|
+
"description": "The selected preset is applied before any other default mechanisms (such as template configs). This means, values from a selected template override the preset. Consult the documentation to learn more about the rationale behind the default risk levels. The risk levels interact with the configured preset on profiles and permission sets and essentially control, if a permission is allowed in a certain profile / permission set.",
|
|
50
|
+
"name": "preset",
|
|
51
|
+
"summary": "Select a preset to initialise permission classifications (risk levels).",
|
|
52
|
+
"default": "strict",
|
|
53
|
+
"hasDynamicHelp": false,
|
|
54
|
+
"multiple": false,
|
|
55
|
+
"options": [
|
|
56
|
+
"strict",
|
|
57
|
+
"loose",
|
|
58
|
+
"none"
|
|
59
|
+
],
|
|
60
|
+
"type": "option"
|
|
61
|
+
},
|
|
46
62
|
"api-version": {
|
|
47
63
|
"description": "Override the api version used for api requests made by this command",
|
|
48
64
|
"name": "api-version",
|
|
@@ -155,7 +171,85 @@
|
|
|
155
171
|
"run:org:audit",
|
|
156
172
|
"run:audit:org"
|
|
157
173
|
]
|
|
174
|
+
},
|
|
175
|
+
"org:scan:user-perms": {
|
|
176
|
+
"aliases": [],
|
|
177
|
+
"args": {},
|
|
178
|
+
"description": "The quick scan does not need an audit config and does not create reports. The target org is scanned \"in memory\" and simply outputs information, where the searched user permissions",
|
|
179
|
+
"examples": [
|
|
180
|
+
"<%= config.bin %> <%= command.id %>"
|
|
181
|
+
],
|
|
182
|
+
"flags": {
|
|
183
|
+
"json": {
|
|
184
|
+
"description": "Format output as json.",
|
|
185
|
+
"helpGroup": "GLOBAL",
|
|
186
|
+
"name": "json",
|
|
187
|
+
"allowNo": false,
|
|
188
|
+
"type": "boolean"
|
|
189
|
+
},
|
|
190
|
+
"flags-dir": {
|
|
191
|
+
"helpGroup": "GLOBAL",
|
|
192
|
+
"name": "flags-dir",
|
|
193
|
+
"summary": "Import flag values from a directory.",
|
|
194
|
+
"hasDynamicHelp": false,
|
|
195
|
+
"multiple": false,
|
|
196
|
+
"type": "option"
|
|
197
|
+
},
|
|
198
|
+
"name": {
|
|
199
|
+
"char": "n",
|
|
200
|
+
"description": "You can specify any valid user permission on your org, such as \"AuthorApex\", \"CustomizeApplication\" or \"ViewSetup\". If you are unsure what permissions are available on your org, initialise a new audit config and check the created userPermissions.yml.",
|
|
201
|
+
"name": "name",
|
|
202
|
+
"required": true,
|
|
203
|
+
"summary": "One or more permissions to be scanned.",
|
|
204
|
+
"hasDynamicHelp": false,
|
|
205
|
+
"multiple": true,
|
|
206
|
+
"type": "option"
|
|
207
|
+
},
|
|
208
|
+
"target-org": {
|
|
209
|
+
"char": "o",
|
|
210
|
+
"name": "target-org",
|
|
211
|
+
"noCacheDefault": true,
|
|
212
|
+
"required": true,
|
|
213
|
+
"summary": "The target org to scan.",
|
|
214
|
+
"hasDynamicHelp": true,
|
|
215
|
+
"multiple": false,
|
|
216
|
+
"type": "option"
|
|
217
|
+
},
|
|
218
|
+
"api-version": {
|
|
219
|
+
"description": "Override the api version used for api requests made by this command",
|
|
220
|
+
"name": "api-version",
|
|
221
|
+
"hasDynamicHelp": false,
|
|
222
|
+
"multiple": false,
|
|
223
|
+
"type": "option"
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
"hasDynamicHelp": true,
|
|
227
|
+
"hiddenAliases": [],
|
|
228
|
+
"id": "org:scan:user-perms",
|
|
229
|
+
"pluginAlias": "@j-schreiber/sf-cli-security-audit",
|
|
230
|
+
"pluginName": "@j-schreiber/sf-cli-security-audit",
|
|
231
|
+
"pluginType": "core",
|
|
232
|
+
"strict": true,
|
|
233
|
+
"summary": "Performs a quick scan to check permission sets and profiles for user permissions.",
|
|
234
|
+
"enableJsonFlag": true,
|
|
235
|
+
"isESM": true,
|
|
236
|
+
"relativePath": [
|
|
237
|
+
"lib",
|
|
238
|
+
"commands",
|
|
239
|
+
"org",
|
|
240
|
+
"scan",
|
|
241
|
+
"user-perms.js"
|
|
242
|
+
],
|
|
243
|
+
"aliasPermutations": [],
|
|
244
|
+
"permutations": [
|
|
245
|
+
"org:scan:user-perms",
|
|
246
|
+
"scan:org:user-perms",
|
|
247
|
+
"scan:user-perms:org",
|
|
248
|
+
"org:user-perms:scan",
|
|
249
|
+
"user-perms:org:scan",
|
|
250
|
+
"user-perms:scan:org"
|
|
251
|
+
]
|
|
158
252
|
}
|
|
159
253
|
},
|
|
160
|
-
"version": "0.
|
|
254
|
+
"version": "0.6.0"
|
|
161
255
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@j-schreiber/sf-cli-security-audit",
|
|
3
3
|
"description": "Salesforce CLI plugin to automate highly configurable security audits",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "https",
|
|
7
7
|
"url": "https://github.com/j-schreiber/js-sf-cli-security-audit"
|