@salesforce/core 3.30.14 → 3.31.7
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/LICENSE.txt +11 -11
- package/README.md +222 -227
- package/lib/config/aliasesConfig.d.ts +12 -12
- package/lib/config/aliasesConfig.js +27 -27
- package/lib/config/authInfoConfig.d.ts +19 -19
- package/lib/config/authInfoConfig.js +34 -34
- package/lib/config/config.d.ts +311 -311
- package/lib/config/config.js +574 -574
- package/lib/config/configAggregator.d.ts +232 -232
- package/lib/config/configAggregator.js +379 -379
- package/lib/config/configFile.d.ts +199 -199
- package/lib/config/configFile.js +340 -340
- package/lib/config/configGroup.d.ts +141 -141
- package/lib/config/configGroup.js +224 -224
- package/lib/config/configStore.d.ts +241 -241
- package/lib/config/configStore.js +352 -352
- package/lib/config/envVars.d.ts +101 -101
- package/lib/config/envVars.js +456 -456
- package/lib/config/orgUsersConfig.d.ts +31 -31
- package/lib/config/orgUsersConfig.js +41 -41
- package/lib/config/sandboxOrgConfig.d.ts +37 -37
- package/lib/config/sandboxOrgConfig.js +50 -50
- package/lib/config/sandboxProcessCache.d.ts +16 -16
- package/lib/config/sandboxProcessCache.js +37 -37
- package/lib/config/tokensConfig.d.ts +10 -10
- package/lib/config/tokensConfig.js +28 -28
- package/lib/config/ttlConfig.d.ts +34 -34
- package/lib/config/ttlConfig.js +54 -54
- package/lib/crypto/crypto.d.ts +54 -54
- package/lib/crypto/crypto.js +220 -220
- package/lib/crypto/keyChain.d.ts +8 -8
- package/lib/crypto/keyChain.js +61 -61
- package/lib/crypto/keyChainImpl.d.ts +116 -116
- package/lib/crypto/keyChainImpl.js +486 -486
- package/lib/crypto/secureBuffer.d.ts +46 -46
- package/lib/crypto/secureBuffer.js +82 -82
- package/lib/deviceOauthService.d.ts +71 -71
- package/lib/deviceOauthService.js +191 -191
- package/lib/exported.d.ts +38 -38
- package/lib/exported.js +118 -118
- package/lib/global.d.ts +70 -70
- package/lib/global.js +109 -109
- package/lib/lifecycleEvents.d.ts +93 -93
- package/lib/lifecycleEvents.js +188 -188
- package/lib/logger.d.ts +381 -381
- package/lib/logger.js +734 -734
- package/lib/messages.d.ts +291 -291
- package/lib/messages.js +543 -543
- package/lib/org/authInfo.d.ts +344 -344
- package/lib/org/authInfo.js +892 -892
- package/lib/org/authRemover.d.ts +88 -88
- package/lib/org/authRemover.js +182 -182
- package/lib/org/connection.d.ts +197 -197
- package/lib/org/connection.js +395 -395
- package/lib/org/index.d.ts +6 -6
- package/lib/org/index.js +28 -28
- package/lib/org/org.d.ts +558 -558
- package/lib/org/org.js +1267 -1267
- package/lib/org/orgConfigProperties.d.ts +69 -69
- package/lib/org/orgConfigProperties.js +136 -136
- package/lib/org/permissionSetAssignment.d.ts +35 -35
- package/lib/org/permissionSetAssignment.js +125 -125
- package/lib/org/scratchOrgCache.d.ts +20 -20
- package/lib/org/scratchOrgCache.js +32 -32
- package/lib/org/scratchOrgCreate.d.ts +54 -54
- package/lib/org/scratchOrgCreate.js +216 -216
- package/lib/org/scratchOrgErrorCodes.d.ts +10 -10
- package/lib/org/scratchOrgErrorCodes.js +88 -88
- package/lib/org/scratchOrgFeatureDeprecation.d.ts +26 -26
- package/lib/org/scratchOrgFeatureDeprecation.js +109 -109
- package/lib/org/scratchOrgInfoApi.d.ts +68 -68
- package/lib/org/scratchOrgInfoApi.js +413 -413
- package/lib/org/scratchOrgInfoGenerator.d.ts +64 -64
- package/lib/org/scratchOrgInfoGenerator.js +241 -241
- package/lib/org/scratchOrgLifecycleEvents.d.ts +10 -10
- package/lib/org/scratchOrgLifecycleEvents.js +40 -40
- package/lib/org/scratchOrgSettingsGenerator.d.ts +78 -78
- package/lib/org/scratchOrgSettingsGenerator.js +276 -276
- package/lib/org/scratchOrgTypes.d.ts +43 -43
- package/lib/org/scratchOrgTypes.js +8 -8
- package/lib/org/user.d.ts +187 -187
- package/lib/org/user.js +448 -448
- package/lib/schema/printer.d.ts +79 -79
- package/lib/schema/printer.js +260 -260
- package/lib/schema/validator.d.ts +70 -70
- package/lib/schema/validator.js +169 -169
- package/lib/sfError.d.ts +73 -73
- package/lib/sfError.js +136 -136
- package/lib/sfProject.d.ts +357 -357
- package/lib/sfProject.js +671 -671
- package/lib/stateAggregator/accessors/aliasAccessor.d.ts +98 -98
- package/lib/stateAggregator/accessors/aliasAccessor.js +145 -145
- package/lib/stateAggregator/accessors/orgAccessor.d.ts +101 -101
- package/lib/stateAggregator/accessors/orgAccessor.js +240 -240
- package/lib/stateAggregator/accessors/sandboxAccessor.d.ts +8 -8
- package/lib/stateAggregator/accessors/sandboxAccessor.js +27 -27
- package/lib/stateAggregator/accessors/tokenAccessor.d.ts +63 -63
- package/lib/stateAggregator/accessors/tokenAccessor.js +79 -79
- package/lib/stateAggregator/index.d.ts +4 -4
- package/lib/stateAggregator/index.js +26 -26
- package/lib/stateAggregator/stateAggregator.d.ts +25 -25
- package/lib/stateAggregator/stateAggregator.js +45 -45
- package/lib/status/myDomainResolver.d.ts +66 -66
- package/lib/status/myDomainResolver.js +124 -124
- package/lib/status/pollingClient.d.ts +85 -85
- package/lib/status/pollingClient.js +115 -115
- package/lib/status/streamingClient.d.ts +244 -244
- package/lib/status/streamingClient.js +436 -436
- package/lib/status/types.d.ts +89 -89
- package/lib/status/types.js +17 -17
- package/lib/testSetup.d.ts +553 -530
- package/lib/testSetup.js +871 -727
- package/lib/util/cache.d.ts +11 -11
- package/lib/util/cache.js +69 -69
- package/lib/util/checkLightningDomain.d.ts +1 -1
- package/lib/util/checkLightningDomain.js +28 -28
- package/lib/util/directoryWriter.d.ts +12 -12
- package/lib/util/directoryWriter.js +53 -53
- package/lib/util/getJwtAudienceUrl.d.ts +4 -4
- package/lib/util/getJwtAudienceUrl.js +18 -18
- package/lib/util/internal.d.ts +58 -58
- package/lib/util/internal.js +118 -118
- package/lib/util/jsonXmlTools.d.ts +14 -14
- package/lib/util/jsonXmlTools.js +38 -38
- package/lib/util/mapKeys.d.ts +14 -14
- package/lib/util/mapKeys.js +51 -51
- package/lib/util/sfdc.d.ts +52 -52
- package/lib/util/sfdc.js +85 -85
- package/lib/util/sfdcUrl.d.ts +72 -72
- package/lib/util/sfdcUrl.js +215 -215
- package/lib/util/structuredWriter.d.ts +9 -9
- package/lib/util/structuredWriter.js +2 -2
- package/lib/util/zipWriter.d.ts +16 -16
- package/lib/util/zipWriter.js +67 -67
- package/lib/webOAuthServer.d.ts +156 -156
- package/lib/webOAuthServer.js +388 -388
- package/messages/auth.md +37 -37
- package/messages/config.md +156 -156
- package/messages/connection.md +30 -30
- package/messages/core.json +20 -20
- package/messages/core.md +67 -67
- package/messages/encryption.md +85 -85
- package/messages/envVars.md +303 -303
- package/messages/org.md +63 -63
- package/messages/permissionSetAssignment.md +31 -31
- package/messages/scratchOrgCreate.md +23 -23
- package/messages/scratchOrgErrorCodes.md +115 -115
- package/messages/scratchOrgFeatureDeprecation.md +11 -11
- package/messages/scratchOrgInfoApi.md +15 -15
- package/messages/scratchOrgInfoGenerator.md +23 -23
- package/messages/streaming.md +23 -23
- package/messages/user.md +35 -35
- package/package.json +97 -97
|
@@ -1,487 +1,487 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
* Copyright (c) 2020, salesforce.com, inc.
|
|
4
|
-
* All rights reserved.
|
|
5
|
-
* Licensed under the BSD 3-Clause license.
|
|
6
|
-
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
|
-
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.keyChainImpl = exports.GenericWindowsKeychainAccess = exports.GenericUnixKeychainAccess = exports.GenericKeychainAccess = exports.KeychainAccess = void 0;
|
|
10
|
-
const childProcess = require("child_process");
|
|
11
|
-
const nodeFs = require("fs");
|
|
12
|
-
const fs = require("fs");
|
|
13
|
-
const os = require("os");
|
|
14
|
-
const os_1 = require("os");
|
|
15
|
-
const path = require("path");
|
|
16
|
-
const ts_types_1 = require("@salesforce/ts-types");
|
|
17
|
-
const kit_1 = require("@salesforce/kit");
|
|
18
|
-
const global_1 = require("../global");
|
|
19
|
-
const messages_1 = require("../messages");
|
|
20
|
-
messages_1.Messages.importMessagesDirectory(__dirname);
|
|
21
|
-
const messages = messages_1.Messages.load('@salesforce/core', 'encryption', [
|
|
22
|
-
'missingCredentialProgramError',
|
|
23
|
-
'credentialProgramAccessError',
|
|
24
|
-
'keyChainServiceRequiredError',
|
|
25
|
-
'keyChainAccountRequiredError',
|
|
26
|
-
'passwordRetryError',
|
|
27
|
-
'passwordRequiredError',
|
|
28
|
-
'passwordNotFoundError',
|
|
29
|
-
'setCredentialError',
|
|
30
|
-
'keyChainUserCanceledError',
|
|
31
|
-
'genericKeychainServiceError',
|
|
32
|
-
'genericKeychainInvalidPermsError',
|
|
33
|
-
]);
|
|
34
|
-
const GET_PASSWORD_RETRY_COUNT = 3;
|
|
35
|
-
/**
|
|
36
|
-
* Helper to reduce an array of cli args down to a presentable string for logging.
|
|
37
|
-
*
|
|
38
|
-
* @param optionsArray CLI command args.
|
|
39
|
-
*/
|
|
40
|
-
const optionsToString = (optionsArray) => optionsArray.join(' ');
|
|
41
|
-
/**
|
|
42
|
-
* Helper to determine if a program is executable. Returns `true` if the program is executable for the user. For
|
|
43
|
-
* Windows true is always returned.
|
|
44
|
-
*
|
|
45
|
-
* @param mode Stats mode.
|
|
46
|
-
* @param gid Unix group id.
|
|
47
|
-
* @param uid Unix user id.
|
|
48
|
-
*/
|
|
49
|
-
const isExe = (mode, gid, uid) => {
|
|
50
|
-
if (process.platform === 'win32') {
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
return Boolean(mode & parseInt('0001', 8) ||
|
|
54
|
-
(mode & parseInt('0010', 8) && process.getgid && gid === process.getgid()) ||
|
|
55
|
-
(mode & parseInt('0100', 8) && process.getuid && uid === process.getuid()));
|
|
56
|
-
};
|
|
57
|
-
/**
|
|
58
|
-
* Private helper to validate that a program exists on the file system and is executable.
|
|
59
|
-
*
|
|
60
|
-
* **Throws** *{@link SfError}{ name: 'MissingCredentialProgramError' }* When the OS credential program isn't found.
|
|
61
|
-
*
|
|
62
|
-
* **Throws** *{@link SfError}{ name: 'CredentialProgramAccessError' }* When the OS credential program isn't accessible.
|
|
63
|
-
*
|
|
64
|
-
* @param programPath The absolute path of the program.
|
|
65
|
-
* @param fsIfc The file system interface.
|
|
66
|
-
* @param isExeIfc Executable validation function.
|
|
67
|
-
*/
|
|
68
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
69
|
-
const _validateProgram = async (programPath, fsIfc, isExeIfc
|
|
70
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
71
|
-
) => {
|
|
72
|
-
let noPermission;
|
|
73
|
-
try {
|
|
74
|
-
const stats = fsIfc.statSync(programPath);
|
|
75
|
-
noPermission = !isExeIfc(stats.mode, stats.gid, stats.uid);
|
|
76
|
-
}
|
|
77
|
-
catch (e) {
|
|
78
|
-
throw messages.createError('missingCredentialProgramError', [programPath]);
|
|
79
|
-
}
|
|
80
|
-
if (noPermission) {
|
|
81
|
-
throw messages.createError('credentialProgramAccessError', [programPath]);
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
/**
|
|
85
|
-
* @private
|
|
86
|
-
*/
|
|
87
|
-
class KeychainAccess {
|
|
88
|
-
/**
|
|
89
|
-
* Abstract prototype for general cross platform keychain interaction.
|
|
90
|
-
*
|
|
91
|
-
* @param osImpl The platform impl for (linux, darwin, windows).
|
|
92
|
-
* @param fsIfc The file system interface.
|
|
93
|
-
*/
|
|
94
|
-
constructor(osImpl, fsIfc) {
|
|
95
|
-
this.osImpl = osImpl;
|
|
96
|
-
this.fsIfc = fsIfc;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Validates the os level program is executable.
|
|
100
|
-
*/
|
|
101
|
-
async validateProgram() {
|
|
102
|
-
await _validateProgram(this.osImpl.getProgram(), this.fsIfc, isExe);
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Returns a password using the native program for credential management.
|
|
106
|
-
*
|
|
107
|
-
* @param opts Options for the credential lookup.
|
|
108
|
-
* @param fn Callback function (err, password).
|
|
109
|
-
* @param retryCount Used internally to track the number of retries for getting a password out of the keychain.
|
|
110
|
-
*/
|
|
111
|
-
async getPassword(opts, fn, retryCount = 0) {
|
|
112
|
-
if (opts.service == null) {
|
|
113
|
-
fn(messages.createError('keyChainServiceRequiredError'));
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
if (opts.account == null) {
|
|
117
|
-
fn(messages.createError('keyChainAccountRequiredError'));
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
await this.validateProgram();
|
|
121
|
-
const credManager = this.osImpl.getCommandFunc(opts, childProcess.spawn);
|
|
122
|
-
let stdout = '';
|
|
123
|
-
let stderr = '';
|
|
124
|
-
if (credManager.stdout) {
|
|
125
|
-
credManager.stdout.on('data', (data) => {
|
|
126
|
-
stdout += data;
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
if (credManager.stderr) {
|
|
130
|
-
credManager.stderr.on('data', (data) => {
|
|
131
|
-
stderr += data;
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
135
|
-
credManager.on('close', async (code) => {
|
|
136
|
-
try {
|
|
137
|
-
return await this.osImpl.onGetCommandClose(code, stdout, stderr, opts, fn);
|
|
138
|
-
}
|
|
139
|
-
catch (e) {
|
|
140
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
141
|
-
// @ts-ignore
|
|
142
|
-
if (e.retry) {
|
|
143
|
-
if (retryCount >= GET_PASSWORD_RETRY_COUNT) {
|
|
144
|
-
throw messages.createError('passwordRetryError', [GET_PASSWORD_RETRY_COUNT]);
|
|
145
|
-
}
|
|
146
|
-
return this.getPassword(opts, fn, retryCount + 1);
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// if retry
|
|
150
|
-
throw e;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
if (credManager.stdin) {
|
|
155
|
-
credManager.stdin.end();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Sets a password using the native program for credential management.
|
|
160
|
-
*
|
|
161
|
-
* @param opts Options for the credential lookup.
|
|
162
|
-
* @param fn Callback function (err, ConfigContents).
|
|
163
|
-
*/
|
|
164
|
-
async setPassword(opts, fn) {
|
|
165
|
-
if (opts.service == null) {
|
|
166
|
-
fn(messages.createError('keyChainServiceRequiredError'));
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (opts.account == null) {
|
|
170
|
-
fn(messages.createError('keyChainAccountRequiredError'));
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (opts.password == null) {
|
|
174
|
-
fn(messages.createError('passwordRequiredError'));
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
await _validateProgram(this.osImpl.getProgram(), this.fsIfc, isExe);
|
|
178
|
-
const credManager = this.osImpl.setCommandFunc(opts, childProcess.spawn);
|
|
179
|
-
let stdout = '';
|
|
180
|
-
let stderr = '';
|
|
181
|
-
if (credManager.stdout) {
|
|
182
|
-
credManager.stdout.on('data', (data) => {
|
|
183
|
-
stdout += data;
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
if (credManager.stderr) {
|
|
187
|
-
credManager.stderr.on('data', (data) => {
|
|
188
|
-
stderr += data;
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
credManager.on('close',
|
|
192
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
193
|
-
async (code) => this.osImpl.onSetCommandClose(code, stdout, stderr, opts, fn));
|
|
194
|
-
if (credManager.stdin) {
|
|
195
|
-
credManager.stdin.end();
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
exports.KeychainAccess = KeychainAccess;
|
|
200
|
-
/**
|
|
201
|
-
* Linux implementation.
|
|
202
|
-
*
|
|
203
|
-
* Uses libsecret.
|
|
204
|
-
*/
|
|
205
|
-
const linuxImpl = {
|
|
206
|
-
getProgram() {
|
|
207
|
-
return process.env.SFDX_SECRET_TOOL_PATH ?? path.join(path.sep, 'usr', 'bin', 'secret-tool');
|
|
208
|
-
},
|
|
209
|
-
getProgramOptions(opts) {
|
|
210
|
-
return ['lookup', 'user', opts.account, 'domain', opts.service];
|
|
211
|
-
},
|
|
212
|
-
getCommandFunc(opts, fn) {
|
|
213
|
-
return fn(linuxImpl.getProgram(), linuxImpl.getProgramOptions(opts));
|
|
214
|
-
},
|
|
215
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
216
|
-
async onGetCommandClose(code, stdout, stderr, opts, fn) {
|
|
217
|
-
if (code === 1) {
|
|
218
|
-
const command = `${linuxImpl.getProgram()} ${optionsToString(linuxImpl.getProgramOptions(opts))}`;
|
|
219
|
-
const error = messages.createError('passwordNotFoundError', [], [command]);
|
|
220
|
-
// This is a workaround for linux.
|
|
221
|
-
// Calling secret-tool too fast can cause it to return an unexpected error. (below)
|
|
222
|
-
if (stderr?.includes('invalid or unencryptable secret')) {
|
|
223
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
224
|
-
// @ts-ignore TODO: make an error subclass with this field
|
|
225
|
-
error.retry = true;
|
|
226
|
-
// Throwing here allows us to perform a retry in KeychainAccess
|
|
227
|
-
throw error;
|
|
228
|
-
}
|
|
229
|
-
// All other issues we will report back to the handler.
|
|
230
|
-
fn(error);
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
fn(null, stdout.trim());
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
setProgramOptions(opts) {
|
|
237
|
-
return ['store', "--label='salesforce.com'", 'user', opts.account, 'domain', opts.service];
|
|
238
|
-
},
|
|
239
|
-
setCommandFunc(opts, fn) {
|
|
240
|
-
const secretTool = fn(linuxImpl.getProgram(), linuxImpl.setProgramOptions(opts));
|
|
241
|
-
if (secretTool.stdin) {
|
|
242
|
-
secretTool.stdin.write(`${opts.password}\n`);
|
|
243
|
-
}
|
|
244
|
-
return secretTool;
|
|
245
|
-
},
|
|
246
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
247
|
-
async onSetCommandClose(code, stdout, stderr, opts, fn) {
|
|
248
|
-
if (code !== 0) {
|
|
249
|
-
const command = `${linuxImpl.getProgram()} ${optionsToString(linuxImpl.setProgramOptions(opts))}`;
|
|
250
|
-
fn(messages.createError('setCredentialError', [`${stdout} - ${stderr}`], [os.userInfo().username, command]));
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
fn(null);
|
|
254
|
-
}
|
|
255
|
-
},
|
|
256
|
-
};
|
|
257
|
-
/**
|
|
258
|
-
* OSX implementation.
|
|
259
|
-
*
|
|
260
|
-
* /usr/bin/security is a cli front end for OSX keychain.
|
|
261
|
-
*/
|
|
262
|
-
const darwinImpl = {
|
|
263
|
-
getProgram() {
|
|
264
|
-
return path.join(path.sep, 'usr', 'bin', 'security');
|
|
265
|
-
},
|
|
266
|
-
getProgramOptions(opts) {
|
|
267
|
-
return ['find-generic-password', '-a', opts.account, '-s', opts.service, '-g'];
|
|
268
|
-
},
|
|
269
|
-
getCommandFunc(opts, fn) {
|
|
270
|
-
return fn(darwinImpl.getProgram(), darwinImpl.getProgramOptions(opts));
|
|
271
|
-
},
|
|
272
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
273
|
-
async onGetCommandClose(code, stdout, stderr, opts, fn) {
|
|
274
|
-
let err;
|
|
275
|
-
if (code !== 0) {
|
|
276
|
-
switch (code) {
|
|
277
|
-
case 128: {
|
|
278
|
-
err = messages.createError('keyChainUserCanceledError');
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
default: {
|
|
282
|
-
const command = `${darwinImpl.getProgram()} ${optionsToString(darwinImpl.getProgramOptions(opts))}`;
|
|
283
|
-
err = messages.createError('passwordNotFoundError', [`${stdout} - ${stderr}`], [command]);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
fn(err);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
// For better or worse, the last line (containing the actual password) is actually written to stderr instead of
|
|
290
|
-
// stdout. Reference: http://blog.macromates.com/2006/keychain-access-from-shell/
|
|
291
|
-
if (stderr.includes('password')) {
|
|
292
|
-
const match = RegExp(/"(.*)"/).exec(stderr);
|
|
293
|
-
if (!match || !match[1]) {
|
|
294
|
-
fn(messages.createError('passwordNotFoundError', [`${stdout} - ${stderr}`]));
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
fn(null, match[1]);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
const command = `${darwinImpl.getProgram()} ${optionsToString(darwinImpl.getProgramOptions(opts))}`;
|
|
302
|
-
fn(messages.createError('passwordNotFoundError', [`${stdout} - ${stderr}`], [command]));
|
|
303
|
-
}
|
|
304
|
-
},
|
|
305
|
-
setProgramOptions(opts) {
|
|
306
|
-
const result = ['add-generic-password', '-a', opts.account, '-s', opts.service];
|
|
307
|
-
if (opts.password) {
|
|
308
|
-
result.push('-w', opts.password);
|
|
309
|
-
}
|
|
310
|
-
return result;
|
|
311
|
-
},
|
|
312
|
-
setCommandFunc(opts, fn) {
|
|
313
|
-
return fn(darwinImpl.getProgram(), darwinImpl.setProgramOptions(opts));
|
|
314
|
-
},
|
|
315
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
316
|
-
async onSetCommandClose(code, stdout, stderr, opts, fn) {
|
|
317
|
-
if (code !== 0) {
|
|
318
|
-
const command = `${darwinImpl.getProgram()} ${optionsToString(darwinImpl.setProgramOptions(opts))}`;
|
|
319
|
-
fn(messages.createError('setCredentialError', [`${stdout} - ${stderr}`], [os.userInfo().username, command]));
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
fn(null);
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
};
|
|
326
|
-
const getSecretFile = () => path.join(global_1.Global.DIR, 'key.json');
|
|
327
|
-
var SecretField;
|
|
328
|
-
(function (SecretField) {
|
|
329
|
-
SecretField["SERVICE"] = "service";
|
|
330
|
-
SecretField["ACCOUNT"] = "account";
|
|
331
|
-
SecretField["KEY"] = "key";
|
|
332
|
-
})(SecretField || (SecretField = {}));
|
|
333
|
-
async function writeFile(opts, fn) {
|
|
334
|
-
try {
|
|
335
|
-
const contents = {
|
|
336
|
-
[SecretField.ACCOUNT]: opts.account,
|
|
337
|
-
[SecretField.KEY]: opts.password,
|
|
338
|
-
[SecretField.SERVICE]: opts.service,
|
|
339
|
-
};
|
|
340
|
-
const secretFile = getSecretFile();
|
|
341
|
-
await fs.promises.mkdir(path.dirname(secretFile), { recursive: true });
|
|
342
|
-
await fs.promises.writeFile(secretFile, JSON.stringify(contents, null, 4), { mode: '600' });
|
|
343
|
-
fn(null, contents);
|
|
344
|
-
}
|
|
345
|
-
catch (err) {
|
|
346
|
-
fn(err);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
async function readFile() {
|
|
350
|
-
// The file and access is validated before this method is called
|
|
351
|
-
const fileContents = (0, kit_1.parseJsonMap)(await fs.promises.readFile(getSecretFile(), 'utf8'));
|
|
352
|
-
return {
|
|
353
|
-
account: (0, ts_types_1.ensureString)(fileContents[SecretField.ACCOUNT]),
|
|
354
|
-
password: (0, ts_types_1.asString)(fileContents[SecretField.KEY]),
|
|
355
|
-
service: (0, ts_types_1.ensureString)(fileContents[SecretField.SERVICE]),
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
// istanbul ignore next - getPassword/setPassword is always mocked out
|
|
359
|
-
/**
|
|
360
|
-
* @@ignore
|
|
361
|
-
*/
|
|
362
|
-
class GenericKeychainAccess {
|
|
363
|
-
async getPassword(opts, fn) {
|
|
364
|
-
// validate the file in .sfdx
|
|
365
|
-
await this.isValidFileAccess(async (fileAccessError) => {
|
|
366
|
-
// the file checks out.
|
|
367
|
-
if (fileAccessError == null) {
|
|
368
|
-
// read it's contents
|
|
369
|
-
try {
|
|
370
|
-
const { service, account, password } = await readFile();
|
|
371
|
-
// validate service name and account just because
|
|
372
|
-
if (opts.service === service && opts.account === account) {
|
|
373
|
-
fn(null, password);
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
376
|
-
// if the service and account names don't match then maybe someone or something is editing
|
|
377
|
-
// that file. #donotallow
|
|
378
|
-
fn(messages.createError('genericKeychainServiceError', [getSecretFile()]));
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
catch (readJsonErr) {
|
|
382
|
-
fn(readJsonErr);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
else if (fileAccessError.code === 'ENOENT') {
|
|
386
|
-
fn(messages.createError('passwordNotFoundError'));
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
fn(fileAccessError);
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
async setPassword(opts, fn) {
|
|
394
|
-
// validate the file in .sfdx
|
|
395
|
-
await this.isValidFileAccess(async (fileAccessError) => {
|
|
396
|
-
// if there is a validation error
|
|
397
|
-
if (fileAccessError != null) {
|
|
398
|
-
// file not found
|
|
399
|
-
if (fileAccessError.code === 'ENOENT') {
|
|
400
|
-
// create the file
|
|
401
|
-
await writeFile.call(this, opts, fn);
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
fn(fileAccessError);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
// the existing file validated. we can write the updated key
|
|
409
|
-
await writeFile.call(this, opts, fn);
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
// eslint-disable-next-line class-methods-use-this
|
|
414
|
-
async isValidFileAccess(cb) {
|
|
415
|
-
try {
|
|
416
|
-
const root = (0, os_1.homedir)();
|
|
417
|
-
await fs.promises.access(path.join(root, global_1.Global.SFDX_STATE_FOLDER), fs.constants.R_OK | fs.constants.X_OK | fs.constants.W_OK);
|
|
418
|
-
await cb(null);
|
|
419
|
-
}
|
|
420
|
-
catch (err) {
|
|
421
|
-
await cb(err);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
exports.GenericKeychainAccess = GenericKeychainAccess;
|
|
426
|
-
/**
|
|
427
|
-
* @ignore
|
|
428
|
-
*/
|
|
429
|
-
// istanbul ignore next - getPassword/setPassword is always mocked out
|
|
430
|
-
class GenericUnixKeychainAccess extends GenericKeychainAccess {
|
|
431
|
-
async isValidFileAccess(cb) {
|
|
432
|
-
await super.isValidFileAccess(async (err) => {
|
|
433
|
-
if (err != null) {
|
|
434
|
-
await cb(err);
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
const secretFile = getSecretFile();
|
|
438
|
-
const stats = await fs.promises.stat(secretFile);
|
|
439
|
-
const octalModeStr = (stats.mode & 0o777).toString(8);
|
|
440
|
-
const EXPECTED_OCTAL_PERM_VALUE = '600';
|
|
441
|
-
if (octalModeStr === EXPECTED_OCTAL_PERM_VALUE) {
|
|
442
|
-
await cb(null);
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
446
|
-
cb(messages.createError('genericKeychainInvalidPermsError', [secretFile], [secretFile, EXPECTED_OCTAL_PERM_VALUE]));
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
exports.GenericUnixKeychainAccess = GenericUnixKeychainAccess;
|
|
453
|
-
/**
|
|
454
|
-
* @ignore
|
|
455
|
-
*/
|
|
456
|
-
class GenericWindowsKeychainAccess extends GenericKeychainAccess {
|
|
457
|
-
async isValidFileAccess(cb) {
|
|
458
|
-
await super.isValidFileAccess(async (err) => {
|
|
459
|
-
if (err != null) {
|
|
460
|
-
await cb(err);
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
try {
|
|
464
|
-
await fs.promises.access(getSecretFile(), fs.constants.R_OK | fs.constants.W_OK);
|
|
465
|
-
await cb(null);
|
|
466
|
-
}
|
|
467
|
-
catch (e) {
|
|
468
|
-
await cb(e);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
exports.GenericWindowsKeychainAccess = GenericWindowsKeychainAccess;
|
|
475
|
-
/**
|
|
476
|
-
* @ignore
|
|
477
|
-
*/
|
|
478
|
-
exports.keyChainImpl = {
|
|
479
|
-
// eslint-disable-next-line camelcase
|
|
480
|
-
generic_unix: new GenericUnixKeychainAccess(),
|
|
481
|
-
// eslint-disable-next-line camelcase
|
|
482
|
-
generic_windows: new GenericWindowsKeychainAccess(),
|
|
483
|
-
darwin: new KeychainAccess(darwinImpl, nodeFs),
|
|
484
|
-
linux: new KeychainAccess(linuxImpl, nodeFs),
|
|
485
|
-
validateProgram: _validateProgram,
|
|
486
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2020, salesforce.com, inc.
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
* Licensed under the BSD 3-Clause license.
|
|
6
|
+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.keyChainImpl = exports.GenericWindowsKeychainAccess = exports.GenericUnixKeychainAccess = exports.GenericKeychainAccess = exports.KeychainAccess = void 0;
|
|
10
|
+
const childProcess = require("child_process");
|
|
11
|
+
const nodeFs = require("fs");
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const os = require("os");
|
|
14
|
+
const os_1 = require("os");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const ts_types_1 = require("@salesforce/ts-types");
|
|
17
|
+
const kit_1 = require("@salesforce/kit");
|
|
18
|
+
const global_1 = require("../global");
|
|
19
|
+
const messages_1 = require("../messages");
|
|
20
|
+
messages_1.Messages.importMessagesDirectory(__dirname);
|
|
21
|
+
const messages = messages_1.Messages.load('@salesforce/core', 'encryption', [
|
|
22
|
+
'missingCredentialProgramError',
|
|
23
|
+
'credentialProgramAccessError',
|
|
24
|
+
'keyChainServiceRequiredError',
|
|
25
|
+
'keyChainAccountRequiredError',
|
|
26
|
+
'passwordRetryError',
|
|
27
|
+
'passwordRequiredError',
|
|
28
|
+
'passwordNotFoundError',
|
|
29
|
+
'setCredentialError',
|
|
30
|
+
'keyChainUserCanceledError',
|
|
31
|
+
'genericKeychainServiceError',
|
|
32
|
+
'genericKeychainInvalidPermsError',
|
|
33
|
+
]);
|
|
34
|
+
const GET_PASSWORD_RETRY_COUNT = 3;
|
|
35
|
+
/**
|
|
36
|
+
* Helper to reduce an array of cli args down to a presentable string for logging.
|
|
37
|
+
*
|
|
38
|
+
* @param optionsArray CLI command args.
|
|
39
|
+
*/
|
|
40
|
+
const optionsToString = (optionsArray) => optionsArray.join(' ');
|
|
41
|
+
/**
|
|
42
|
+
* Helper to determine if a program is executable. Returns `true` if the program is executable for the user. For
|
|
43
|
+
* Windows true is always returned.
|
|
44
|
+
*
|
|
45
|
+
* @param mode Stats mode.
|
|
46
|
+
* @param gid Unix group id.
|
|
47
|
+
* @param uid Unix user id.
|
|
48
|
+
*/
|
|
49
|
+
const isExe = (mode, gid, uid) => {
|
|
50
|
+
if (process.platform === 'win32') {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return Boolean(mode & parseInt('0001', 8) ||
|
|
54
|
+
(mode & parseInt('0010', 8) && process.getgid && gid === process.getgid()) ||
|
|
55
|
+
(mode & parseInt('0100', 8) && process.getuid && uid === process.getuid()));
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Private helper to validate that a program exists on the file system and is executable.
|
|
59
|
+
*
|
|
60
|
+
* **Throws** *{@link SfError}{ name: 'MissingCredentialProgramError' }* When the OS credential program isn't found.
|
|
61
|
+
*
|
|
62
|
+
* **Throws** *{@link SfError}{ name: 'CredentialProgramAccessError' }* When the OS credential program isn't accessible.
|
|
63
|
+
*
|
|
64
|
+
* @param programPath The absolute path of the program.
|
|
65
|
+
* @param fsIfc The file system interface.
|
|
66
|
+
* @param isExeIfc Executable validation function.
|
|
67
|
+
*/
|
|
68
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
69
|
+
const _validateProgram = async (programPath, fsIfc, isExeIfc
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
71
|
+
) => {
|
|
72
|
+
let noPermission;
|
|
73
|
+
try {
|
|
74
|
+
const stats = fsIfc.statSync(programPath);
|
|
75
|
+
noPermission = !isExeIfc(stats.mode, stats.gid, stats.uid);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
throw messages.createError('missingCredentialProgramError', [programPath]);
|
|
79
|
+
}
|
|
80
|
+
if (noPermission) {
|
|
81
|
+
throw messages.createError('credentialProgramAccessError', [programPath]);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
class KeychainAccess {
|
|
88
|
+
/**
|
|
89
|
+
* Abstract prototype for general cross platform keychain interaction.
|
|
90
|
+
*
|
|
91
|
+
* @param osImpl The platform impl for (linux, darwin, windows).
|
|
92
|
+
* @param fsIfc The file system interface.
|
|
93
|
+
*/
|
|
94
|
+
constructor(osImpl, fsIfc) {
|
|
95
|
+
this.osImpl = osImpl;
|
|
96
|
+
this.fsIfc = fsIfc;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Validates the os level program is executable.
|
|
100
|
+
*/
|
|
101
|
+
async validateProgram() {
|
|
102
|
+
await _validateProgram(this.osImpl.getProgram(), this.fsIfc, isExe);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Returns a password using the native program for credential management.
|
|
106
|
+
*
|
|
107
|
+
* @param opts Options for the credential lookup.
|
|
108
|
+
* @param fn Callback function (err, password).
|
|
109
|
+
* @param retryCount Used internally to track the number of retries for getting a password out of the keychain.
|
|
110
|
+
*/
|
|
111
|
+
async getPassword(opts, fn, retryCount = 0) {
|
|
112
|
+
if (opts.service == null) {
|
|
113
|
+
fn(messages.createError('keyChainServiceRequiredError'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (opts.account == null) {
|
|
117
|
+
fn(messages.createError('keyChainAccountRequiredError'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
await this.validateProgram();
|
|
121
|
+
const credManager = this.osImpl.getCommandFunc(opts, childProcess.spawn);
|
|
122
|
+
let stdout = '';
|
|
123
|
+
let stderr = '';
|
|
124
|
+
if (credManager.stdout) {
|
|
125
|
+
credManager.stdout.on('data', (data) => {
|
|
126
|
+
stdout += data;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (credManager.stderr) {
|
|
130
|
+
credManager.stderr.on('data', (data) => {
|
|
131
|
+
stderr += data;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
135
|
+
credManager.on('close', async (code) => {
|
|
136
|
+
try {
|
|
137
|
+
return await this.osImpl.onGetCommandClose(code, stdout, stderr, opts, fn);
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
141
|
+
// @ts-ignore
|
|
142
|
+
if (e.retry) {
|
|
143
|
+
if (retryCount >= GET_PASSWORD_RETRY_COUNT) {
|
|
144
|
+
throw messages.createError('passwordRetryError', [GET_PASSWORD_RETRY_COUNT]);
|
|
145
|
+
}
|
|
146
|
+
return this.getPassword(opts, fn, retryCount + 1);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// if retry
|
|
150
|
+
throw e;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
if (credManager.stdin) {
|
|
155
|
+
credManager.stdin.end();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Sets a password using the native program for credential management.
|
|
160
|
+
*
|
|
161
|
+
* @param opts Options for the credential lookup.
|
|
162
|
+
* @param fn Callback function (err, ConfigContents).
|
|
163
|
+
*/
|
|
164
|
+
async setPassword(opts, fn) {
|
|
165
|
+
if (opts.service == null) {
|
|
166
|
+
fn(messages.createError('keyChainServiceRequiredError'));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (opts.account == null) {
|
|
170
|
+
fn(messages.createError('keyChainAccountRequiredError'));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (opts.password == null) {
|
|
174
|
+
fn(messages.createError('passwordRequiredError'));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
await _validateProgram(this.osImpl.getProgram(), this.fsIfc, isExe);
|
|
178
|
+
const credManager = this.osImpl.setCommandFunc(opts, childProcess.spawn);
|
|
179
|
+
let stdout = '';
|
|
180
|
+
let stderr = '';
|
|
181
|
+
if (credManager.stdout) {
|
|
182
|
+
credManager.stdout.on('data', (data) => {
|
|
183
|
+
stdout += data;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (credManager.stderr) {
|
|
187
|
+
credManager.stderr.on('data', (data) => {
|
|
188
|
+
stderr += data;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
credManager.on('close',
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
193
|
+
async (code) => this.osImpl.onSetCommandClose(code, stdout, stderr, opts, fn));
|
|
194
|
+
if (credManager.stdin) {
|
|
195
|
+
credManager.stdin.end();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
exports.KeychainAccess = KeychainAccess;
|
|
200
|
+
/**
|
|
201
|
+
* Linux implementation.
|
|
202
|
+
*
|
|
203
|
+
* Uses libsecret.
|
|
204
|
+
*/
|
|
205
|
+
const linuxImpl = {
|
|
206
|
+
getProgram() {
|
|
207
|
+
return process.env.SFDX_SECRET_TOOL_PATH ?? path.join(path.sep, 'usr', 'bin', 'secret-tool');
|
|
208
|
+
},
|
|
209
|
+
getProgramOptions(opts) {
|
|
210
|
+
return ['lookup', 'user', opts.account, 'domain', opts.service];
|
|
211
|
+
},
|
|
212
|
+
getCommandFunc(opts, fn) {
|
|
213
|
+
return fn(linuxImpl.getProgram(), linuxImpl.getProgramOptions(opts));
|
|
214
|
+
},
|
|
215
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
216
|
+
async onGetCommandClose(code, stdout, stderr, opts, fn) {
|
|
217
|
+
if (code === 1) {
|
|
218
|
+
const command = `${linuxImpl.getProgram()} ${optionsToString(linuxImpl.getProgramOptions(opts))}`;
|
|
219
|
+
const error = messages.createError('passwordNotFoundError', [], [command]);
|
|
220
|
+
// This is a workaround for linux.
|
|
221
|
+
// Calling secret-tool too fast can cause it to return an unexpected error. (below)
|
|
222
|
+
if (stderr?.includes('invalid or unencryptable secret')) {
|
|
223
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
224
|
+
// @ts-ignore TODO: make an error subclass with this field
|
|
225
|
+
error.retry = true;
|
|
226
|
+
// Throwing here allows us to perform a retry in KeychainAccess
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
// All other issues we will report back to the handler.
|
|
230
|
+
fn(error);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
fn(null, stdout.trim());
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
setProgramOptions(opts) {
|
|
237
|
+
return ['store', "--label='salesforce.com'", 'user', opts.account, 'domain', opts.service];
|
|
238
|
+
},
|
|
239
|
+
setCommandFunc(opts, fn) {
|
|
240
|
+
const secretTool = fn(linuxImpl.getProgram(), linuxImpl.setProgramOptions(opts));
|
|
241
|
+
if (secretTool.stdin) {
|
|
242
|
+
secretTool.stdin.write(`${opts.password}\n`);
|
|
243
|
+
}
|
|
244
|
+
return secretTool;
|
|
245
|
+
},
|
|
246
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
247
|
+
async onSetCommandClose(code, stdout, stderr, opts, fn) {
|
|
248
|
+
if (code !== 0) {
|
|
249
|
+
const command = `${linuxImpl.getProgram()} ${optionsToString(linuxImpl.setProgramOptions(opts))}`;
|
|
250
|
+
fn(messages.createError('setCredentialError', [`${stdout} - ${stderr}`], [os.userInfo().username, command]));
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
fn(null);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* OSX implementation.
|
|
259
|
+
*
|
|
260
|
+
* /usr/bin/security is a cli front end for OSX keychain.
|
|
261
|
+
*/
|
|
262
|
+
const darwinImpl = {
|
|
263
|
+
getProgram() {
|
|
264
|
+
return path.join(path.sep, 'usr', 'bin', 'security');
|
|
265
|
+
},
|
|
266
|
+
getProgramOptions(opts) {
|
|
267
|
+
return ['find-generic-password', '-a', opts.account, '-s', opts.service, '-g'];
|
|
268
|
+
},
|
|
269
|
+
getCommandFunc(opts, fn) {
|
|
270
|
+
return fn(darwinImpl.getProgram(), darwinImpl.getProgramOptions(opts));
|
|
271
|
+
},
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
273
|
+
async onGetCommandClose(code, stdout, stderr, opts, fn) {
|
|
274
|
+
let err;
|
|
275
|
+
if (code !== 0) {
|
|
276
|
+
switch (code) {
|
|
277
|
+
case 128: {
|
|
278
|
+
err = messages.createError('keyChainUserCanceledError');
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
default: {
|
|
282
|
+
const command = `${darwinImpl.getProgram()} ${optionsToString(darwinImpl.getProgramOptions(opts))}`;
|
|
283
|
+
err = messages.createError('passwordNotFoundError', [`${stdout} - ${stderr}`], [command]);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
fn(err);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
// For better or worse, the last line (containing the actual password) is actually written to stderr instead of
|
|
290
|
+
// stdout. Reference: http://blog.macromates.com/2006/keychain-access-from-shell/
|
|
291
|
+
if (stderr.includes('password')) {
|
|
292
|
+
const match = RegExp(/"(.*)"/).exec(stderr);
|
|
293
|
+
if (!match || !match[1]) {
|
|
294
|
+
fn(messages.createError('passwordNotFoundError', [`${stdout} - ${stderr}`]));
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
fn(null, match[1]);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
const command = `${darwinImpl.getProgram()} ${optionsToString(darwinImpl.getProgramOptions(opts))}`;
|
|
302
|
+
fn(messages.createError('passwordNotFoundError', [`${stdout} - ${stderr}`], [command]));
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
setProgramOptions(opts) {
|
|
306
|
+
const result = ['add-generic-password', '-a', opts.account, '-s', opts.service];
|
|
307
|
+
if (opts.password) {
|
|
308
|
+
result.push('-w', opts.password);
|
|
309
|
+
}
|
|
310
|
+
return result;
|
|
311
|
+
},
|
|
312
|
+
setCommandFunc(opts, fn) {
|
|
313
|
+
return fn(darwinImpl.getProgram(), darwinImpl.setProgramOptions(opts));
|
|
314
|
+
},
|
|
315
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
316
|
+
async onSetCommandClose(code, stdout, stderr, opts, fn) {
|
|
317
|
+
if (code !== 0) {
|
|
318
|
+
const command = `${darwinImpl.getProgram()} ${optionsToString(darwinImpl.setProgramOptions(opts))}`;
|
|
319
|
+
fn(messages.createError('setCredentialError', [`${stdout} - ${stderr}`], [os.userInfo().username, command]));
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
fn(null);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
const getSecretFile = () => path.join(global_1.Global.DIR, 'key.json');
|
|
327
|
+
var SecretField;
|
|
328
|
+
(function (SecretField) {
|
|
329
|
+
SecretField["SERVICE"] = "service";
|
|
330
|
+
SecretField["ACCOUNT"] = "account";
|
|
331
|
+
SecretField["KEY"] = "key";
|
|
332
|
+
})(SecretField || (SecretField = {}));
|
|
333
|
+
async function writeFile(opts, fn) {
|
|
334
|
+
try {
|
|
335
|
+
const contents = {
|
|
336
|
+
[SecretField.ACCOUNT]: opts.account,
|
|
337
|
+
[SecretField.KEY]: opts.password,
|
|
338
|
+
[SecretField.SERVICE]: opts.service,
|
|
339
|
+
};
|
|
340
|
+
const secretFile = getSecretFile();
|
|
341
|
+
await fs.promises.mkdir(path.dirname(secretFile), { recursive: true });
|
|
342
|
+
await fs.promises.writeFile(secretFile, JSON.stringify(contents, null, 4), { mode: '600' });
|
|
343
|
+
fn(null, contents);
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
fn(err);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async function readFile() {
|
|
350
|
+
// The file and access is validated before this method is called
|
|
351
|
+
const fileContents = (0, kit_1.parseJsonMap)(await fs.promises.readFile(getSecretFile(), 'utf8'));
|
|
352
|
+
return {
|
|
353
|
+
account: (0, ts_types_1.ensureString)(fileContents[SecretField.ACCOUNT]),
|
|
354
|
+
password: (0, ts_types_1.asString)(fileContents[SecretField.KEY]),
|
|
355
|
+
service: (0, ts_types_1.ensureString)(fileContents[SecretField.SERVICE]),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
// istanbul ignore next - getPassword/setPassword is always mocked out
|
|
359
|
+
/**
|
|
360
|
+
* @@ignore
|
|
361
|
+
*/
|
|
362
|
+
class GenericKeychainAccess {
|
|
363
|
+
async getPassword(opts, fn) {
|
|
364
|
+
// validate the file in .sfdx
|
|
365
|
+
await this.isValidFileAccess(async (fileAccessError) => {
|
|
366
|
+
// the file checks out.
|
|
367
|
+
if (fileAccessError == null) {
|
|
368
|
+
// read it's contents
|
|
369
|
+
try {
|
|
370
|
+
const { service, account, password } = await readFile();
|
|
371
|
+
// validate service name and account just because
|
|
372
|
+
if (opts.service === service && opts.account === account) {
|
|
373
|
+
fn(null, password);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// if the service and account names don't match then maybe someone or something is editing
|
|
377
|
+
// that file. #donotallow
|
|
378
|
+
fn(messages.createError('genericKeychainServiceError', [getSecretFile()]));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch (readJsonErr) {
|
|
382
|
+
fn(readJsonErr);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else if (fileAccessError.code === 'ENOENT') {
|
|
386
|
+
fn(messages.createError('passwordNotFoundError'));
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
fn(fileAccessError);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
async setPassword(opts, fn) {
|
|
394
|
+
// validate the file in .sfdx
|
|
395
|
+
await this.isValidFileAccess(async (fileAccessError) => {
|
|
396
|
+
// if there is a validation error
|
|
397
|
+
if (fileAccessError != null) {
|
|
398
|
+
// file not found
|
|
399
|
+
if (fileAccessError.code === 'ENOENT') {
|
|
400
|
+
// create the file
|
|
401
|
+
await writeFile.call(this, opts, fn);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
fn(fileAccessError);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
// the existing file validated. we can write the updated key
|
|
409
|
+
await writeFile.call(this, opts, fn);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
// eslint-disable-next-line class-methods-use-this
|
|
414
|
+
async isValidFileAccess(cb) {
|
|
415
|
+
try {
|
|
416
|
+
const root = (0, os_1.homedir)();
|
|
417
|
+
await fs.promises.access(path.join(root, global_1.Global.SFDX_STATE_FOLDER), fs.constants.R_OK | fs.constants.X_OK | fs.constants.W_OK);
|
|
418
|
+
await cb(null);
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
await cb(err);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
exports.GenericKeychainAccess = GenericKeychainAccess;
|
|
426
|
+
/**
|
|
427
|
+
* @ignore
|
|
428
|
+
*/
|
|
429
|
+
// istanbul ignore next - getPassword/setPassword is always mocked out
|
|
430
|
+
class GenericUnixKeychainAccess extends GenericKeychainAccess {
|
|
431
|
+
async isValidFileAccess(cb) {
|
|
432
|
+
await super.isValidFileAccess(async (err) => {
|
|
433
|
+
if (err != null) {
|
|
434
|
+
await cb(err);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
const secretFile = getSecretFile();
|
|
438
|
+
const stats = await fs.promises.stat(secretFile);
|
|
439
|
+
const octalModeStr = (stats.mode & 0o777).toString(8);
|
|
440
|
+
const EXPECTED_OCTAL_PERM_VALUE = '600';
|
|
441
|
+
if (octalModeStr === EXPECTED_OCTAL_PERM_VALUE) {
|
|
442
|
+
await cb(null);
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
446
|
+
cb(messages.createError('genericKeychainInvalidPermsError', [secretFile], [secretFile, EXPECTED_OCTAL_PERM_VALUE]));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
exports.GenericUnixKeychainAccess = GenericUnixKeychainAccess;
|
|
453
|
+
/**
|
|
454
|
+
* @ignore
|
|
455
|
+
*/
|
|
456
|
+
class GenericWindowsKeychainAccess extends GenericKeychainAccess {
|
|
457
|
+
async isValidFileAccess(cb) {
|
|
458
|
+
await super.isValidFileAccess(async (err) => {
|
|
459
|
+
if (err != null) {
|
|
460
|
+
await cb(err);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
try {
|
|
464
|
+
await fs.promises.access(getSecretFile(), fs.constants.R_OK | fs.constants.W_OK);
|
|
465
|
+
await cb(null);
|
|
466
|
+
}
|
|
467
|
+
catch (e) {
|
|
468
|
+
await cb(e);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
exports.GenericWindowsKeychainAccess = GenericWindowsKeychainAccess;
|
|
475
|
+
/**
|
|
476
|
+
* @ignore
|
|
477
|
+
*/
|
|
478
|
+
exports.keyChainImpl = {
|
|
479
|
+
// eslint-disable-next-line camelcase
|
|
480
|
+
generic_unix: new GenericUnixKeychainAccess(),
|
|
481
|
+
// eslint-disable-next-line camelcase
|
|
482
|
+
generic_windows: new GenericWindowsKeychainAccess(),
|
|
483
|
+
darwin: new KeychainAccess(darwinImpl, nodeFs),
|
|
484
|
+
linux: new KeychainAccess(linuxImpl, nodeFs),
|
|
485
|
+
validateProgram: _validateProgram,
|
|
486
|
+
};
|
|
487
487
|
//# sourceMappingURL=keyChainImpl.js.map
|