1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | Object.defineProperty(exports, "__esModule", { value: true });
|
9 | exports.User = exports.DefaultUserFields = exports.REQUIRED_FIELDS = void 0;
|
10 | const os_1 = require("os");
|
11 | const kit_1 = require("@salesforce/kit");
|
12 | const ts_types_1 = require("@salesforce/ts-types");
|
13 | const http_api_1 = require("jsforce/lib/http-api");
|
14 | const logger_1 = require("../logger");
|
15 | const messages_1 = require("../messages");
|
16 | const secureBuffer_1 = require("../crypto/secureBuffer");
|
17 | const sfError_1 = require("../sfError");
|
18 | const sfdc_1 = require("../util/sfdc");
|
19 | const connection_1 = require("./connection");
|
20 | const permissionSetAssignment_1 = require("./permissionSetAssignment");
|
21 | const authInfo_1 = require("./authInfo");
|
22 | const rand = (len) => Math.floor(Math.random() * len.length);
|
23 | const CHARACTERS = {
|
24 | LOWER: 'abcdefghijklmnopqrstuvwxyz',
|
25 | UPPER: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
26 | NUMBERS: '1234567890',
|
27 | SYMBOLS: ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '[', ']', '|', '-'],
|
28 | };
|
29 | const PASSWORD_COMPLEXITY = {
|
30 | '0': { LOWER: true },
|
31 | '1': { LOWER: true, NUMBERS: true },
|
32 | '2': { LOWER: true, SYMBOLS: true },
|
33 | '3': { LOWER: true, UPPER: true, NUMBERS: true },
|
34 | '4': { LOWER: true, NUMBERS: true, SYMBOLS: true },
|
35 | '5': { LOWER: true, UPPER: true, NUMBERS: true, SYMBOLS: true },
|
36 | };
|
37 | const scimEndpoint = '/services/scim/v1/Users';
|
38 | const scimHeaders = { 'auto-approve-user': 'true' };
|
39 | messages_1.Messages.importMessagesDirectory(__dirname);
|
40 | const messages = messages_1.Messages.load('@salesforce/core', 'user', [
|
41 | 'invalidHttpResponseCreatingUser',
|
42 | 'userQueryFailed',
|
43 | 'missingId',
|
44 | 'permsetNamesAreRequired',
|
45 | 'missingFields',
|
46 | 'lengthOutOfBound',
|
47 | 'complexityOutOfBound',
|
48 | ]);
|
49 |
|
50 |
|
51 |
|
52 | exports.REQUIRED_FIELDS = {
|
53 | id: 'id',
|
54 | username: 'username',
|
55 | lastName: 'lastName',
|
56 | alias: 'alias',
|
57 | timeZoneSidKey: 'timeZoneSidKey',
|
58 | localeSidKey: 'localeSidKey',
|
59 | emailEncodingKey: 'emailEncodingKey',
|
60 | profileId: 'profileId',
|
61 | languageLocaleKey: 'languageLocaleKey',
|
62 | email: 'email',
|
63 | };
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | async function retrieveUserFields(logger, username) {
|
71 | const connection = await connection_1.Connection.create({
|
72 | authInfo: await authInfo_1.AuthInfo.create({ username }),
|
73 | });
|
74 | if ((0, sfdc_1.matchesAccessToken)(username)) {
|
75 | logger.debug('received an accessToken for the username. Converting...');
|
76 | username = (await connection.identity()).username;
|
77 | logger.debug(`accessToken converted to ${username}`);
|
78 | }
|
79 | else {
|
80 | logger.debug('not a accessToken');
|
81 | }
|
82 | const fromFields = Object.keys(exports.REQUIRED_FIELDS).map(kit_1.upperFirst);
|
83 |
|
84 | const requiredFieldsFromAdminQuery = `SELECT ${fromFields} FROM User WHERE Username='${username}'`;
|
85 | const result = await connection.query(requiredFieldsFromAdminQuery);
|
86 | logger.debug('Successfully retrieved the admin user for this org.');
|
87 | if (result.totalSize === 1) {
|
88 | const results = (0, kit_1.mapKeys)(result.records[0], (value, key) => (0, kit_1.lowerFirst)(key));
|
89 | const fields = {
|
90 | id: (0, ts_types_1.ensureString)(results[exports.REQUIRED_FIELDS.id]),
|
91 | username,
|
92 | alias: (0, ts_types_1.ensureString)(results[exports.REQUIRED_FIELDS.alias]),
|
93 | email: (0, ts_types_1.ensureString)(results[exports.REQUIRED_FIELDS.email]),
|
94 | emailEncodingKey: (0, ts_types_1.ensureString)(results[exports.REQUIRED_FIELDS.emailEncodingKey]),
|
95 | languageLocaleKey: (0, ts_types_1.ensureString)(results[exports.REQUIRED_FIELDS.languageLocaleKey]),
|
96 | localeSidKey: (0, ts_types_1.ensureString)(results[exports.REQUIRED_FIELDS.localeSidKey]),
|
97 | profileId: (0, ts_types_1.ensureString)(results[exports.REQUIRED_FIELDS.profileId]),
|
98 | lastName: (0, ts_types_1.ensureString)(results[exports.REQUIRED_FIELDS.lastName]),
|
99 | timeZoneSidKey: (0, ts_types_1.ensureString)(results[exports.REQUIRED_FIELDS.timeZoneSidKey]),
|
100 | };
|
101 | return fields;
|
102 | }
|
103 | else {
|
104 | throw messages.createError('userQueryFailed', [username]);
|
105 | }
|
106 | }
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 | async function retrieveProfileId(name, connection) {
|
114 | if (!(0, sfdc_1.validateSalesforceId)(name)) {
|
115 | const profileQuery = `SELECT Id FROM Profile WHERE name='${name}'`;
|
116 | const result = await connection.query(profileQuery);
|
117 | if (result.records.length > 0) {
|
118 | return result.records[0].Id;
|
119 | }
|
120 | }
|
121 | return name;
|
122 | }
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 | class DefaultUserFields extends kit_1.AsyncCreatable {
|
139 | |
140 |
|
141 |
|
142 | constructor(options) {
|
143 | super(options);
|
144 | this.options = options || { templateUser: '' };
|
145 | }
|
146 | |
147 |
|
148 |
|
149 | getFields() {
|
150 | return this.userFields;
|
151 | }
|
152 | |
153 |
|
154 |
|
155 | async init() {
|
156 | this.logger = await logger_1.Logger.child('DefaultUserFields');
|
157 | this.userFields = await retrieveUserFields(this.logger, this.options.templateUser);
|
158 | this.userFields.profileId = await retrieveProfileId('Standard User', await connection_1.Connection.create({
|
159 | authInfo: await authInfo_1.AuthInfo.create({ username: this.options.templateUser }),
|
160 | }));
|
161 | this.logger.debug(`Standard User profileId: ${this.userFields.profileId}`);
|
162 | if (this.options.newUserName) {
|
163 | this.userFields.username = this.options.newUserName;
|
164 | }
|
165 | else {
|
166 | this.userFields.username = `${Date.now()}_${this.userFields.username}`;
|
167 | }
|
168 | }
|
169 | }
|
170 | exports.DefaultUserFields = DefaultUserFields;
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | class User extends kit_1.AsyncCreatable {
|
176 | |
177 |
|
178 |
|
179 | constructor(options) {
|
180 | super(options);
|
181 | this.org = options.org;
|
182 | }
|
183 | |
184 |
|
185 |
|
186 | static generatePasswordUtf8(passwordCondition = { length: 13, complexity: 5 }) {
|
187 | if (!PASSWORD_COMPLEXITY[passwordCondition.complexity]) {
|
188 | const msg = messages.getMessage('complexityOutOfBound');
|
189 | throw new sfError_1.SfError(msg, 'complexityOutOfBound');
|
190 | }
|
191 | if (passwordCondition.length < 8 || passwordCondition.length > 1000) {
|
192 | const msg = messages.getMessage('lengthOutOfBound');
|
193 | throw new sfError_1.SfError(msg, 'lengthOutOfBound');
|
194 | }
|
195 | let password = [];
|
196 | ['SYMBOLS', 'NUMBERS', 'UPPER', 'LOWER'].forEach((charSet) => {
|
197 | if (PASSWORD_COMPLEXITY[passwordCondition.complexity][charSet]) {
|
198 | password.push(CHARACTERS[charSet][rand(CHARACTERS[charSet])]);
|
199 | }
|
200 | });
|
201 |
|
202 | password = password.concat(Array(Math.max(passwordCondition.length - password.length, 0))
|
203 | .fill('0')
|
204 | .map(() => CHARACTERS['LOWER'][rand(CHARACTERS['LOWER'])]));
|
205 | password = password.sort(() => Math.random() - 0.5);
|
206 | const secureBuffer = new secureBuffer_1.SecureBuffer();
|
207 | secureBuffer.consume(Buffer.from(password.join(''), 'utf8'));
|
208 | return secureBuffer;
|
209 | }
|
210 | |
211 |
|
212 |
|
213 | async init() {
|
214 | this.logger = await logger_1.Logger.child('User');
|
215 | await this.org.refreshAuth();
|
216 | this.logger.debug('Auth refresh ok');
|
217 | }
|
218 | |
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 | async assignPassword(info, password = User.generatePasswordUtf8()) {
|
226 | this.logger.debug(`Attempting to set password for userId: ${info.getFields().userId} username: ${info.getFields().username}`);
|
227 | const userConnection = await connection_1.Connection.create({ authInfo: info });
|
228 | return new Promise((resolve, reject) => {
|
229 |
|
230 |
|
231 | password.value(async (buffer) => {
|
232 | try {
|
233 | const soap = userConnection.soap;
|
234 | await soap.setPassword((0, ts_types_1.ensureString)(info.getFields().userId), buffer.toString('utf8'));
|
235 | this.logger.debug(`Set password for userId: ${info.getFields().userId}`);
|
236 | resolve();
|
237 | }
|
238 | catch (e) {
|
239 | reject(e);
|
240 | }
|
241 | });
|
242 | });
|
243 | }
|
244 | |
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 | async assignPermissionSets(id, permsetNames) {
|
262 | if (!id) {
|
263 | throw messages.createError('missingId');
|
264 | }
|
265 | if (!permsetNames) {
|
266 | throw messages.createError('permsetNamesAreRequired');
|
267 | }
|
268 | const assignments = await permissionSetAssignment_1.PermissionSetAssignment.init(this.org);
|
269 | await Promise.all(permsetNames.map((permsetName) => assignments.create(id, permsetName)));
|
270 | }
|
271 | |
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 | async createUser(fields) {
|
295 |
|
296 | const refreshTokenSecret = await this.createUserInternal(fields);
|
297 |
|
298 | const authInfo = await authInfo_1.AuthInfo.create({ username: this.org.getUsername() });
|
299 | const adminUserAuthFields = authInfo.getFields(true);
|
300 |
|
301 | const oauthOptions = {
|
302 |
|
303 | loginUrl: adminUserAuthFields.instanceUrl ?? adminUserAuthFields.loginUrl,
|
304 | refreshToken: refreshTokenSecret.buffer.value((buffer) => buffer.toString('utf8')),
|
305 | clientId: adminUserAuthFields.clientId,
|
306 | clientSecret: adminUserAuthFields.clientSecret,
|
307 | privateKey: adminUserAuthFields.privateKey,
|
308 | };
|
309 |
|
310 | const newUserAuthInfo = await authInfo_1.AuthInfo.create({
|
311 | username: fields.username,
|
312 | oauth2Options: oauthOptions,
|
313 | });
|
314 |
|
315 | const newUserAuthFields = newUserAuthInfo.getFields();
|
316 | newUserAuthFields.userId = refreshTokenSecret.userId;
|
317 |
|
318 | await this.describeUserAndSave(newUserAuthInfo);
|
319 |
|
320 | await this.org.addUsername(newUserAuthInfo);
|
321 | return newUserAuthInfo;
|
322 | }
|
323 | |
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 | async retrieve(username) {
|
339 | return retrieveUserFields(this.logger, username);
|
340 | }
|
341 | |
342 |
|
343 |
|
344 |
|
345 |
|
346 | async describeUserAndSave(newUserAuthInfo) {
|
347 | const connection = await connection_1.Connection.create({ authInfo: newUserAuthInfo });
|
348 | this.logger.debug(`Created connection for user: ${newUserAuthInfo.getUsername()}`);
|
349 | const userDescribe = await connection.describe('User');
|
350 | if (userDescribe?.fields) {
|
351 | await newUserAuthInfo.save();
|
352 | return newUserAuthInfo;
|
353 | }
|
354 | else {
|
355 | throw messages.createError('permsetNamesAreRequired');
|
356 | }
|
357 | }
|
358 | |
359 |
|
360 |
|
361 |
|
362 |
|
363 | async createUserInternal(fields) {
|
364 | if (!fields) {
|
365 | throw messages.createError('missingFields');
|
366 | }
|
367 | const conn = this.org.getConnection();
|
368 | const body = JSON.stringify({
|
369 | username: fields.username,
|
370 | emails: [fields.email],
|
371 | name: {
|
372 | familyName: fields.lastName,
|
373 | },
|
374 | nickName: fields.username.substring(0, 40),
|
375 | entitlements: [
|
376 | {
|
377 | value: fields.profileId,
|
378 | },
|
379 | ],
|
380 | });
|
381 | this.logger.debug(`user create request body: ${body}`);
|
382 | const scimUrl = conn.normalizeUrl(scimEndpoint);
|
383 | this.logger.debug(`scimUrl: ${scimUrl}`);
|
384 | const info = {
|
385 | method: 'POST',
|
386 | url: scimUrl,
|
387 | headers: scimHeaders,
|
388 | body,
|
389 | };
|
390 | const response = await this.rawRequest(conn, info);
|
391 | const responseBody = (0, kit_1.parseJsonMap)((0, ts_types_1.ensureString)(response['body']));
|
392 | const statusCode = (0, ts_types_1.asNumber)(response.statusCode);
|
393 | this.logger.debug(`user create response.statusCode: ${response.statusCode}`);
|
394 | if (!(statusCode === 201 || statusCode === 200)) {
|
395 | let message = messages.getMessage('invalidHttpResponseCreatingUser', [statusCode]);
|
396 | if (responseBody) {
|
397 | const errors = (0, ts_types_1.asJsonArray)(responseBody.Errors);
|
398 | if (errors && errors.length > 0) {
|
399 | message = `${message} causes:${os_1.EOL}`;
|
400 | errors.forEach((singleMessage) => {
|
401 | if (!(0, ts_types_1.isJsonMap)(singleMessage))
|
402 | return;
|
403 |
|
404 | message = `${message}${os_1.EOL}${singleMessage.description}`;
|
405 | });
|
406 | }
|
407 | }
|
408 | this.logger.debug(message);
|
409 | throw new sfError_1.SfError(message, 'UserCreateHttpError');
|
410 | }
|
411 | fields.id = (0, ts_types_1.ensureString)(responseBody.id);
|
412 | await this.updateRequiredUserFields(fields);
|
413 | const buffer = new secureBuffer_1.SecureBuffer();
|
414 | const headers = (0, ts_types_1.ensureJsonMap)(response.headers);
|
415 | const autoApproveUser = (0, ts_types_1.ensureString)(headers['auto-approve-user']);
|
416 | buffer.consume(Buffer.from(autoApproveUser));
|
417 | return {
|
418 | buffer,
|
419 | userId: fields.id,
|
420 | };
|
421 | }
|
422 |
|
423 | async rawRequest(conn, options) {
|
424 | return new Promise((resolve, reject) => {
|
425 |
|
426 | const httpApi = new http_api_1.HttpApi(conn, options);
|
427 | httpApi.on('response', (response) => resolve(response));
|
428 | httpApi.request(options).catch(reject);
|
429 | });
|
430 | }
|
431 | |
432 |
|
433 |
|
434 |
|
435 |
|
436 | async updateRequiredUserFields(fields) {
|
437 | const leftOverRequiredFields = (0, kit_1.omit)(fields, [
|
438 | exports.REQUIRED_FIELDS.username,
|
439 | exports.REQUIRED_FIELDS.email,
|
440 | exports.REQUIRED_FIELDS.lastName,
|
441 | exports.REQUIRED_FIELDS.profileId,
|
442 | ]);
|
443 | const object = (0, kit_1.mapKeys)(leftOverRequiredFields, (value, key) => (0, kit_1.upperFirst)(key));
|
444 | await this.org.getConnection().sobject('User').update(object);
|
445 | this.logger.debug(`Successfully Updated additional properties for user: ${fields.username}`);
|
446 | }
|
447 | }
|
448 | exports.User = User;
|
449 |
|
\ | No newline at end of file |