UNPKG

16.6 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright (c) 2018, salesforce.com, inc.
4 * All rights reserved.
5 * SPDX-License-Identifier: BSD-3-Clause
6 * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7 */
8Object.defineProperty(exports, "__esModule", { value: true });
9const kit_1 = require("@salesforce/kit");
10const ts_types_1 = require("@salesforce/ts-types");
11const os_1 = require("os");
12const authInfo_1 = require("./authInfo");
13const connection_1 = require("./connection");
14const logger_1 = require("./logger");
15const messages_1 = require("./messages");
16const permissionSetAssignment_1 = require("./permissionSetAssignment");
17const secureBuffer_1 = require("./secureBuffer");
18const sfdxError_1 = require("./sfdxError");
19const sfdc_1 = require("./util/sfdc");
20const PASSWORD_LENGTH = 10;
21const LOWER = 'abcdefghijklmnopqrstuvwxyz';
22const UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
23const NUMBERS = '1234567890';
24const SYMBOLS = ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '[', ']', '|', '-'];
25const ALL = [LOWER, UPPER, NUMBERS, SYMBOLS.join('')];
26const rand = (len) => Math.floor(Math.random() * len.length);
27const scimEndpoint = '/services/scim/v1/Users';
28const scimHeaders = { 'auto-approve-user': 'true' };
29/**
30 * A Map of Required Salesforce User fields.
31 */
32exports.REQUIRED_FIELDS = {
33 id: 'id',
34 username: 'username',
35 lastName: 'lastName',
36 alias: 'alias',
37 timeZoneSidKey: 'timeZoneSidKey',
38 localeSidKey: 'localeSidKey',
39 emailEncodingKey: 'emailEncodingKey',
40 profileId: 'profileId',
41 languageLocaleKey: 'languageLocaleKey',
42 email: 'email'
43};
44/**
45 * Helper method to lookup UserFields.
46 * @param username The username.
47 */
48async function _retrieveUserFields(username) {
49 const connection = await connection_1.Connection.create({
50 authInfo: await authInfo_1.AuthInfo.create({ username })
51 });
52 const fromFields = Object.keys(exports.REQUIRED_FIELDS).map(kit_1.upperFirst);
53 const requiredFieldsFromAdminQuery = `SELECT ${fromFields} FROM User WHERE Username='${username}'`;
54 const result = await connection.query(requiredFieldsFromAdminQuery);
55 this.logger.debug('Successfully retrieved the admin user for this org.');
56 if (result.totalSize === 1) {
57 const results = kit_1.mapKeys(result.records[0], (value, key) => kit_1.lowerFirst(key));
58 const fields = {
59 id: ts_types_1.ensure(ts_types_1.getString(results, exports.REQUIRED_FIELDS.id)),
60 username,
61 alias: ts_types_1.ensure(ts_types_1.getString(results, exports.REQUIRED_FIELDS.alias)),
62 email: ts_types_1.ensure(ts_types_1.getString(results, exports.REQUIRED_FIELDS.email)),
63 emailEncodingKey: ts_types_1.ensure(ts_types_1.getString(results, exports.REQUIRED_FIELDS.emailEncodingKey)),
64 languageLocaleKey: ts_types_1.ensure(ts_types_1.getString(results, exports.REQUIRED_FIELDS.languageLocaleKey)),
65 localeSidKey: ts_types_1.ensure(ts_types_1.getString(results, exports.REQUIRED_FIELDS.localeSidKey)),
66 profileId: ts_types_1.ensure(ts_types_1.getString(results, exports.REQUIRED_FIELDS.profileId)),
67 lastName: ts_types_1.ensure(ts_types_1.getString(results, exports.REQUIRED_FIELDS.lastName)),
68 timeZoneSidKey: ts_types_1.ensure(ts_types_1.getString(results, exports.REQUIRED_FIELDS.timeZoneSidKey))
69 };
70 return fields;
71 }
72 else {
73 throw sfdxError_1.SfdxError.create('@salesforce/core', 'user', 'userQueryFailed', [username]);
74 }
75}
76/**
77 * Gets the profile id associated with a profile name.
78 * @param name The name of the profile.
79 * @param connection The connection for the query.
80 */
81async function _retrieveProfileId(name, connection) {
82 if (!sfdc_1.sfdc.validateSalesforceId(name)) {
83 const profileQuery = `SELECT Id FROM Profile WHERE name='${name}'`;
84 const result = await connection.query(profileQuery);
85 if (result.records.length > 0) {
86 return result.records[0].Id;
87 }
88 }
89 return name;
90}
91/**
92 * Provides a default set of fields values that can be used to create a user. This is handy for
93 * software development purposes.
94 *
95 * ```
96 * const connection: Connection = await Connection.create({
97 * authInfo: await AuthInfo.create({ username: 'user@example.com' })
98 * });
99 * const org: Org = await Org.create({ connection });
100 * const options: DefaultUserFields.Options = {
101 * templateUser: org.getUsername()
102 * };
103 * const fields = (await DefaultUserFields.create(options)).getFields();
104 * ```
105 */
106class DefaultUserFields extends kit_1.AsyncCreatable {
107 /**
108 * @ignore
109 */
110 constructor(options) {
111 super(options);
112 this.options = options || { templateUser: '' };
113 }
114 /**
115 * Get user fields.
116 */
117 getFields() {
118 return this.userFields;
119 }
120 /**
121 * Initialize asynchronous components.
122 */
123 async init() {
124 this.logger = await logger_1.Logger.child('DefaultUserFields');
125 this.userFields = await _retrieveUserFields.call({ logger: this.logger }, this.options.templateUser);
126 this.userFields.profileId = await _retrieveProfileId('Standard User', await connection_1.Connection.create({
127 authInfo: await authInfo_1.AuthInfo.create({ username: this.options.templateUser })
128 }));
129 this.logger.debug(`Standard User profileId: ${this.userFields.profileId}`);
130 if (this.options.newUserName) {
131 this.userFields.username = this.options.newUserName;
132 }
133 else {
134 this.userFields.username = `${Date.now()}_${this.userFields.username}`;
135 }
136 }
137}
138exports.DefaultUserFields = DefaultUserFields;
139/**
140 * A class for creating a User, generating a password for a user, and assigning a user to one or more permission sets.
141 * See methods for examples.
142 */
143class User extends kit_1.AsyncCreatable {
144 /**
145 * Generate default password for a user. Returns An encrypted buffer containing a utf8 encoded password.
146 */
147 static generatePasswordUtf8() {
148 // Fill an array with random characters from random requirement sets
149 const pass = Array(PASSWORD_LENGTH - ALL.length)
150 .fill(9)
151 .map(() => {
152 const _set = ALL[rand(ALL)];
153 return _set[rand(_set)];
154 });
155 const secureBuffer = new secureBuffer_1.SecureBuffer();
156 secureBuffer.consume(Buffer.from(pass.join(''), 'utf8'));
157 return secureBuffer;
158 }
159 /**
160 * @ignore
161 */
162 constructor(options) {
163 super(options);
164 this.org = options.org;
165 }
166 /**
167 * Initialize a new instance of a user and return it.
168 */
169 async init() {
170 this.logger = await logger_1.Logger.child('User');
171 await this.org.refreshAuth();
172 this.logger.debug('Auth refresh ok');
173 }
174 /**
175 * Assigns a password to a user. For a user to have the ability to assign their own password, the org needs the
176 * following org preference: SelfSetPasswordInApi.
177 * @param info The AuthInfo object for user to assign the password to.
178 * @param password [throwWhenRemoveFails = User.generatePasswordUtf8()] A SecureBuffer containing the new password.
179 */
180 async assignPassword(info, password = User.generatePasswordUtf8()) {
181 this.logger.debug(`Attempting to set password for userId: ${info.getFields().userId} username: ${info.getFields().username}`);
182 const userConnection = await connection_1.Connection.create({ authInfo: info });
183 return new Promise((resolve, reject) => {
184 password.value(async (buffer) => {
185 try {
186 // @ts-ignore TODO: expose `soap` on Connection however appropriate
187 const soap = userConnection.soap;
188 await soap.setPassword(info.getFields().userId, buffer.toString('utf8'));
189 this.logger.debug(`Set password for userId: ${info.getFields().userId}`);
190 resolve();
191 }
192 catch (e) {
193 reject(e);
194 }
195 });
196 });
197 }
198 /**
199 * Methods to assign one or more permission set names to a user.
200 * @param id The Salesforce id of the user to assign the permission set to.
201 * @param permsetNames An array of permission set names.
202 *
203 * ```
204 * const username = 'user@example.com';
205 * const connection: Connection = await Connection.create({
206 * authInfo: await AuthInfo.create({ username })
207 * });
208 * const org = await Org.create({ connection });
209 * const user: User = await User.create({ org });
210 * const fields: UserFields = await user.retrieve(username);
211 * await user.assignPermissionSets(fields.id, ['sfdx', 'approver']);
212 * ```
213 */
214 async assignPermissionSets(id, permsetNames) {
215 if (!id) {
216 throw sfdxError_1.SfdxError.create('@salesforce/core', 'user', 'missingId');
217 }
218 if (!permsetNames) {
219 throw sfdxError_1.SfdxError.create('@salesforce/core', 'user', 'permsetNamesAreRequired');
220 }
221 const assignments = await permissionSetAssignment_1.PermissionSetAssignment.init(this.org);
222 for (const permsetName of permsetNames) {
223 await assignments.create(id, permsetName);
224 }
225 }
226 /**
227 * Method for creating a new User.
228 *
229 * By default scratch orgs only allow creating 2 additional users. Work with Salesforce Customer Service to increase
230 * user limits.
231 *
232 * The Org Preferences required to increase the number of users are:
233 * Standard User Licenses
234 * Salesforce CRM Content User
235 *
236 * @param fields The required fields for creating a user.
237 *
238 * ```
239 * const connection: Connection = await Connection.create({
240 * authInfo: await AuthInfo.create({ username: 'user@example.com' })
241 * });
242 * const org = await Org.create({ connection });
243 *
244 * const defaultUserFields = await DefaultUserFields.create({ templateUser: 'devhub_user@example.com' });
245 * const user: User = await User.create({ org });
246 * const info: AuthInfo = await user.createUser(defaultUserFields.getFields());
247 * ```
248 */
249 async createUser(fields) {
250 // Create a user and get a refresh token
251 const refreshTokenSecret = await this.createUserInternal(fields);
252 // Create the initial auth info
253 const adminUserAuthFields = this.org.getConnection().getAuthInfoFields();
254 // Setup oauth options for the new user
255 const oauthOptions = {
256 loginUrl: adminUserAuthFields.loginUrl,
257 refreshToken: refreshTokenSecret.buffer.value((buffer) => buffer.toString('utf8')),
258 clientId: adminUserAuthFields.clientId,
259 clientSecret: adminUserAuthFields.clientSecret,
260 privateKey: adminUserAuthFields.privateKey
261 };
262 // Create an auth info object for the new user
263 const newUserAuthInfo = await authInfo_1.AuthInfo.create({
264 username: fields.username,
265 oauth2Options: oauthOptions
266 });
267 // Update the auth info object with created user id.
268 const newUserAuthFields = newUserAuthInfo.getFields();
269 newUserAuthFields.userId = refreshTokenSecret.userId;
270 // Make sure we can connect and if so save the auth info.
271 await this.describeUserAndSave(newUserAuthInfo);
272 // Let the org know there is a new user. See $HOME/.sfdx/[orgid].json for the mapping.
273 await this.org.addUsername(newUserAuthInfo);
274 return newUserAuthInfo;
275 }
276 /**
277 * Method to retrieve the UserFields for a user.
278 * @param username The username of the user.
279 *
280 * ```
281 * const username = 'boris@thecat.com';
282 * const connection: Connection = await Connection.create({
283 * authInfo: await AuthInfo.create({ username })
284 * });
285 * const org = await Org.create({ connection });
286 * const user: User = await User.create({ org });
287 * const fields: UserFields = await user.retrieve(username);
288 * ```
289 */
290 async retrieve(username) {
291 return await _retrieveUserFields.call(this, username);
292 }
293 /**
294 * Helper method that verifies the server's User object is available and if so allows persisting the Auth information.
295 * @param newUserAuthInfo The AuthInfo for the new user.
296 */
297 async describeUserAndSave(newUserAuthInfo) {
298 const connection = await connection_1.Connection.create({ authInfo: newUserAuthInfo });
299 this.logger.debug(`Created connection for user: ${newUserAuthInfo.getUsername()}`);
300 const userDescribe = await connection.describe('User');
301 if (userDescribe && userDescribe.fields) {
302 await newUserAuthInfo.save();
303 return newUserAuthInfo;
304 }
305 else {
306 throw sfdxError_1.SfdxError.create('@salesforce/core', 'user', 'problemsDescribingTheUserObject');
307 }
308 }
309 /**
310 * Helper that makes a REST request to create the user, and update additional required fields.
311 * @param fields The configuration the new user should have.
312 */
313 async createUserInternal(fields) {
314 if (!fields) {
315 throw sfdxError_1.SfdxError.create('@salesforce/core', 'user', 'missingFields');
316 }
317 const body = JSON.stringify({
318 username: fields.username,
319 emails: [fields.email],
320 name: {
321 familyName: fields.lastName
322 },
323 nickName: fields.username.substring(0, 40),
324 entitlements: [
325 {
326 value: fields.profileId
327 }
328 ]
329 });
330 this.logger.debug(`user create request body: ${body}`);
331 const scimUrl = this.org.getConnection().normalizeUrl(scimEndpoint);
332 this.logger.debug(`scimUrl: ${scimUrl}`);
333 const info = {
334 method: 'POST',
335 url: scimUrl,
336 headers: scimHeaders,
337 body
338 };
339 const response = await this.org.getConnection().requestRaw(info);
340 const responseBody = kit_1.parseJsonMap(ts_types_1.ensureString(response['body']));
341 const statusCode = ts_types_1.asNumber(response.statusCode);
342 this.logger.debug(`user create response.statusCode: ${response.statusCode}`);
343 if (!(statusCode === 201 || statusCode === 200)) {
344 const messages = messages_1.Messages.loadMessages('@salesforce/core', 'user');
345 let message = messages.getMessage('invalidHttpResponseCreatingUser', [statusCode]);
346 if (responseBody) {
347 const errors = ts_types_1.asJsonArray(responseBody.Errors);
348 if (errors && errors.length > 0) {
349 message = `${message} causes:${os_1.EOL}`;
350 errors.forEach(singleMessage => {
351 if (!ts_types_1.isJsonMap(singleMessage))
352 return;
353 message = `${message}${os_1.EOL}${singleMessage.description}`;
354 });
355 }
356 }
357 this.logger.debug(message);
358 throw new sfdxError_1.SfdxError(message, 'UserCreateHttpError');
359 }
360 fields.id = ts_types_1.ensureString(responseBody.id);
361 await this.updateRequiredUserFields(fields);
362 const buffer = new secureBuffer_1.SecureBuffer();
363 const headers = ts_types_1.ensureJsonMap(response.headers);
364 const autoApproveUser = ts_types_1.ensureString(headers['auto-approve-user']);
365 buffer.consume(Buffer.from(autoApproveUser));
366 return {
367 buffer,
368 userId: fields.id
369 };
370 }
371 /**
372 * Update the remaining required fields for the user.
373 * @param fields The fields for the user.
374 */
375 async updateRequiredUserFields(fields) {
376 const leftOverRequiredFields = kit_1.omit(fields, [
377 exports.REQUIRED_FIELDS.username,
378 exports.REQUIRED_FIELDS.email,
379 exports.REQUIRED_FIELDS.lastName,
380 exports.REQUIRED_FIELDS.profileId
381 ]);
382 const object = kit_1.mapKeys(leftOverRequiredFields, (value, key) => kit_1.upperFirst(key));
383 await this.org
384 .getConnection()
385 .sobject('User')
386 .update(object);
387 this.logger.debug(`Successfully Updated additional properties for user: ${fields.username}`);
388 }
389}
390exports.User = User;
391//# sourceMappingURL=user.js.map
\No newline at end of file