Skip to content

Commit

Permalink
Merge pull request #47 from SalesforceCommerceCloud/basic-user-mgmt
Browse files Browse the repository at this point in the history
Basic user mgmt
  • Loading branch information
tobiaslohr authored Aug 16, 2019
2 parents 1324ff4 + 453da62 commit f58c1fa
Show file tree
Hide file tree
Showing 9 changed files with 2,574 additions and 2 deletions.
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The focus of the tool is to streamline and easy the communication with Commerce
* Code deployment and code version management
* System job execution and monitoring (site import)
* Custom job execution and monitoring
* Exploring Account Manager orgs and management of users and roles
* JavaScript API

# How do I get set up? #
Expand Down Expand Up @@ -88,6 +89,48 @@ Use the following snippet as your client's permission set, replace `my_client_id
"methods": ["get"],
"read_attributes": "(**)",
"write_attributes": "(**)"
},
{
"resource_id":"/role_search",
"methods":["post"],
"read_attributes":"(**)",
"write_attributes":"(**)"
},
{
"resource_id":"/roles/*",
"methods":["get"],
"read_attributes":"(**)",
"write_attributes":"(**)"
},
{
"resource_id":"/roles/*/user_search",
"methods":["post"],
"read_attributes":"(**)",
"write_attributes":"(**)"
},
{
"resource_id":"/roles/*/users/*",
"methods":["put","delete"],
"read_attributes":"(**)",
"write_attributes":"(**)"
},
{
"resource_id":"/user_search",
"methods":["post"],
"read_attributes":"(**)",
"write_attributes":"(**)"
},
{
"resource_id":"/users",
"methods":["get"],
"read_attributes":"(**)",
"write_attributes":"(**)"
},
{
"resource_id":"/users/*",
"methods":["put","get","patch","delete"],
"read_attributes":"(**)",
"write_attributes":"(**)"
}
]
}
Expand Down Expand Up @@ -226,6 +269,14 @@ Use `sfcc-ci --help` to get started and see the list of commands available:
code:activate [options] <version> Activate the custom code version on a Commerce Cloud instance
job:run [options] <job_id> [job_parameters...] Starts a job execution on a Commerce Cloud instance
job:status [options] <job_id> <job_execution_id> Get the status of a job execution on a Commerce Cloud instance
org:list [options] List all orgs eligible to manage
role:list [options] List roles
role:grant [options] Grant a role to a user
role:revoke [options] Revoke a role from a user
user:list [options] List users eligible to manage
user:create [options] Create a new user
user:update [options] Update a user
user:delete [options] Delete a user

Environment:

Expand Down
22 changes: 22 additions & 0 deletions bin/test-cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -788,3 +788,25 @@ else
echo -e "\t> FAILED"
exit 1
fi

###############################################################################
###### Testing ´sfcc-ci org:list´
###############################################################################

echo "Testing command ´sfcc-ci org:list´ without option:"
node ./cli.js org:list
if [ $? -eq 0 ]; then
echo -e "\t> OK"
else
echo -e "\t> FAILED"
exit 1
fi

echo "Testing command ´sfcc-ci org:list --org <org>´ with invalid org (expected to fail):"
node ./cli.js org:list --org does_not_exist
if [ $? -eq 1 ]; then
echo -e "\t> OK"
else
echo -e "\t> FAILED"
exit 1
fi
401 changes: 401 additions & 0 deletions cli.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ function getOauth2RedirectHTML() {

module.exports.auth = auth;
module.exports.renew = renew;
module.exports.getAMHost = getAMHost;
module.exports.getToken = getToken;
module.exports.getAccessToken = getAccessToken;
module.exports.getRefreshToken = getRefreshToken;
Expand Down
5 changes: 4 additions & 1 deletion lib/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ module.exports.info = function() {
// warn in yellow
module.exports.warn = function() {
arguments[0] = warn('Warning:', arguments[0]);
console.warn.apply(null, arguments);
// log only, if not explicitly ignored
if (!process.env.SFCC_IGNORE_WARNINGS) {
console.warn.apply(null, arguments);
}
}

// error in red
Expand Down
2 changes: 1 addition & 1 deletion lib/ocapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function getOptions(host, path, token, method) {
}

// allow self-signed certificates, if needed (only supported for configuration via dw.json)
if ( dwjson['self-signed'] ) {
if ( dwjson['self-signed'] || process.env.SFCC_ALLOW_SELF_SIGNED ) {
opts['strictSSL'] = false;

console.warn('Allow self-signed certificates. Be caucious as this may expose secure information to an ' +
Expand Down
201 changes: 201 additions & 0 deletions lib/org.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
var request = require('request');
var util = require('util');

var auth = require('./auth');
var console = require('./log');

const API_BASE = '/dw/rest/v1';
const ORG_ALLOWED_READ_PROPERTIES = [ 'id', 'name', 'realms', 'twoFARoles' ];

/**
* Helper to capture most-common responses due to errors which occur across resources. In case a well-known issue
* was identified, the function returns an Error object holding detailed information about the error. A callback
* function can be passed optionally, the error and the response are passed as parameters to the callback function.
*
* @param {Object} err
* @param {Object} response
* @param {Function} callback
* @return {Error} the error or null
*/
function captureCommonErrors(err, response, callback) {
var error = null;
if (err && !response) {
error = new Error('The operation could not be performed properly. ' + ( process.env.DEBUG ? err : '' ));
} else if (response.statusCode === 401) {
error = new Error('Authentication invalid. Please (re-)authenticate by running ' +
'´sfcc-ci auth:login´ or ´sfcc-ci client:auth´');
}
// just return the error, in case no callback is passed
if (!callback) {
return error;
}
callback(error, response);
}

/**
* Contructs the http request options and ensure shared request headers across requests, such as authentication.
*
* @param {String} path
* @param {String} token
* @param {String} method
* @return {Object} the request options
*/
function getOptions(path, token, method) {
var opts = {
uri: 'https://' + auth.getAMHost() + path,
auth: {
bearer: ( token ? token : null )
},
strictSSL: false,
method: method,
json: true
};
return opts;
}

/**
* Retrieves detals of an org
*
* @param {String} org the name of the org
* @param {Function} callback the callback to execute, the error and the org are available as arguments to the callback function
*/
function getOrg(org, callback) {
// build the request options
var options = getOptions(API_BASE + '/organizations/search/findByName?startsWith=' + org + '&ignoreCase=false',
auth.getToken(), 'GET');

// do the request
request(options, function (err, res, body) {
var errback = captureCommonErrors(err, res);
if ( errback ) {
callback(errback, []);
return;
} else if ( err ) {
callback(new Error(util.format('Getting org failed: %s', err)), []);
return;
} else if ( res.statusCode >= 400 ) {
callback(new Error(util.format('Getting org failed: %s', res.statusCode)));
return;
} else if ( body.content.length === 0 ) {
callback(new Error(util.format('Unknown org %s', org)));
return;
} else if ( body.content.length > 1 ) {
// attempt to find an exact match
var filtered = body.content.filter(function(cand) {
// check on filter criterias
return ( cand.name === org );
});
if ( filtered.length === 1 ) {
callback(undefined, filterOrg(filtered[0]));
return;
}
// report ambiguousness
callback(new Error(util.format('Org %s is ambiguous', org)));
return;
}
// do the callback with the body
callback(undefined, filterOrg(body.content[0]));
});
}

/**
* Filters properties of the passed org and returns a reduced object containing only
* an allowed list of properties.
*
* @param {Object} org the original org object
* @return {Object} the filtered org
*/
function filterOrg(org) {
for (var prop in org) {
if (org.hasOwnProperty(prop) && ORG_ALLOWED_READ_PROPERTIES.indexOf(prop) === -1) {
// delete the property if not allowed to read
delete org[prop];
}
}
return org;
}

/**
* Retrieves all orgs and returns them as list.
*
* @param {Function} callback the callback to execute, the error and the list of orgs are available as arguments to the callback function
*/
function getOrgs(callback) {

// build the request options
var options = getOptions(API_BASE + '/organizations', auth.getToken(), 'GET');

// do the request
request(options, function (err, res, body) {
var errback = captureCommonErrors(err, res);
if ( errback ) {
callback(errback, []);
return;
} else if ( err ) {
callback(new Error(util.format('Searching orgs failed: %s', err)), []);
return;
} else if ( res.statusCode >= 400 ) {
callback(new Error(util.format('Searching orgs failed: %s', res.statusCode)));
return;
}
callback(undefined, body.content);
});
}

module.exports.getOrg = getOrg;
module.exports.cli = {
/**
* Lists all org eligible to manage
*
* @param {String} orgId the org id or null, if all orgs should be retrieved
* @param {Boolean} asJson optional flag to force output in json, false by default
* @param {String} sortBy optional field to sort the list of users by
*/
list : function(orgId, asJson, sortBy) {
// get details of a single org if org was passed
if ( typeof(orgId) !== 'undefined' && orgId !== null ) {
getOrg(orgId, function(err, org) {
if (err) {
console.error(err.message);
return;
}
if (asJson) {
console.json(org);
return;
}

console.prettyPrint(org);
});
return;
}
// get all orgs
getOrgs(function(err, list) {
if (err) {
console.error(err.message);
return;
}

if (sortBy) {
list = require('./json').sort(list, sortBy);
}

if (asJson) {
console.json(list);
return;
}

if (list.length === 0) {
console.info('No orgs found');
return;
}

// table fields
var data = [['id', 'name','realms','twoFARoles']];
for (var i of list) {
data.push([i.id, i.name, i.realms.length, ( i.twoFARoles.length > 0 )]);
}

console.table(data);
});
}
};
Loading

0 comments on commit f58c1fa

Please sign in to comment.