Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic user mgmt #47

Merged
merged 64 commits into from
Aug 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
d162a06
add sfcc-ci user:list to read all AM users, add sfcc-ci user:list --l…
tobiaslohr Jun 7, 2018
ec753d9
add command sfcc-ci user:create for AM based user creation
tobiaslohr Jun 7, 2018
0442d12
updated readme with new user commands
tobiaslohr Jun 7, 2018
f36c19a
added command sfcc-ci role:list to list all roles and role details on…
tobiaslohr Jun 7, 2018
63abd60
user list in sfcc-ci user:list to only support --count starting with …
tobiaslohr Jun 7, 2018
92df4b4
set default user list count to 25
tobiaslohr Jun 7, 2018
add4397
role list retrieval and got details retrieval with more robust error …
tobiaslohr Jun 7, 2018
a23029e
remove user id from user list
tobiaslohr Jun 7, 2018
74223ad
adding options --instance and --role to user:list to search users on …
tobiaslohr Jun 7, 2018
d5eda9c
read AM host from central place
tobiaslohr Oct 16, 2018
21235d8
switch to 18_8 Data APIs to enable searching local users
tobiaslohr Oct 16, 2018
52f34c5
mapped user details to CLI representation
tobiaslohr Oct 16, 2018
e761586
implement role:grant and role:revoke
tobiaslohr Oct 16, 2018
618b143
adjust readme for new commands
tobiaslohr Oct 16, 2018
6077df3
make --instance mandatory for role:list
tobiaslohr Oct 16, 2018
411187f
add support for user search on instance using --instance option
tobiaslohr Oct 17, 2018
6faa85d
adjust help for user:list command
tobiaslohr Oct 17, 2018
cd51eaa
adjust help for user:create command
tobiaslohr Oct 17, 2018
1d8e611
renaming of internal implementation for local user search
tobiaslohr Oct 17, 2018
dca9a1c
apply sorting for instance user search; capture 400 error gracefully
tobiaslohr Oct 17, 2018
86cd252
add support for instance user search by role using --role
tobiaslohr Oct 17, 2018
0acc86b
return json properly in case of error
tobiaslohr Oct 17, 2018
8d80134
add missing function param role for user search by role
tobiaslohr Oct 17, 2018
6229a6a
add support for user creation on an instance
tobiaslohr Oct 17, 2018
8511ef2
adjust readme with ocapi settings required for user search and creation
tobiaslohr Oct 18, 2018
9367886
add support for for instance user deletion with new command user:delete
tobiaslohr Oct 18, 2018
9bc3e26
return proper json for user:list using --instance option
tobiaslohr Oct 18, 2018
7caf79b
add support for --instance for role:grant and role:revoke
tobiaslohr Oct 18, 2018
df4fb29
handle insufficient permission gracefully
tobiaslohr Oct 18, 2018
0163c23
reimplemented role search
tobiaslohr Oct 18, 2018
027cc98
handle --json properly for user search
tobiaslohr Oct 18, 2018
cc12fe3
don't allow providing user password
tobiaslohr Oct 18, 2018
1e7167f
include users and permissions of a role using --verbose flag
tobiaslohr Oct 18, 2018
11b4077
Merge branch 'master' into basic-user-mgmt
tobiaslohr Oct 18, 2018
cba7755
adjust ocapi settings in readme
tobiaslohr Oct 19, 2018
f6cfe5e
handle user creation errors and general errors more gracefully
tobiaslohr Oct 29, 2018
8fe8615
remove debug
tobiaslohr Oct 29, 2018
e1f0f7b
Add capability to user:list to narrow done by role or org+role
tobiaslohr Apr 5, 2019
aca3471
Enhance callback for local user list
tobiaslohr Apr 5, 2019
4eb6a3f
Add updating capability as user:update command
tobiaslohr Apr 5, 2019
8d5373b
Add --purge option to user:delete to force deletion in Account Manager
tobiaslohr Apr 5, 2019
2df43de
User different shortcut -N for --noprompt in user:delete
tobiaslohr Apr 5, 2019
12603cc
Merge master into basic-user-mgmt
tobiaslohr Apr 5, 2019
14fa6bf
Update readme with new commands
tobiaslohr Apr 8, 2019
bbf49d6
Reading size of users from current list, page property is only availa…
tobiaslohr Apr 8, 2019
48abb25
Add capability to update a local user
tobiaslohr Apr 8, 2019
e7c110d
Pretty print roles of users on an instance
tobiaslohr Apr 9, 2019
8f911bc
add support for retrieval of large lists of users
tobiaslohr Jul 15, 2019
7dcbde9
Minor output formatting
tobiaslohr Jul 15, 2019
3ae69e8
Add option --selfsigned to allow connection to hosts using self-signe…
tobiaslohr Jul 15, 2019
b02c9ac
Minor adjust example command for user:update
tobiaslohr Jul 15, 2019
4efff9f
Return assigned organizations per user, implement exact match for org…
tobiaslohr Jul 15, 2019
c863f5b
Add global option -I,--ignorewarnings to ignore warnings
tobiaslohr Jul 15, 2019
cf0dc09
Add command to pull list of orgs available to manage
tobiaslohr Jul 15, 2019
2e5cc40
CLI tests added for org:list command
tobiaslohr Jul 15, 2019
326a601
Merge master into basic-user-mgmt
tobiaslohr Jul 15, 2019
32a06b7
Merge branch 'master' into basic-user-mgmt
tobiaslohr Jul 15, 2019
1682975
#51 adding feature description to readme
tobiaslohr Jul 16, 2019
d924511
Remove array style for more consistent property access
tobiaslohr Jul 17, 2019
eae2c97
Fix reference errors when calling function from org module
tobiaslohr Jul 17, 2019
a20c190
Additional example for querying users
tobiaslohr Jul 22, 2019
0b4bcd0
Add support to return items of large user lists beyond index 200
tobiaslohr Jul 24, 2019
bc7a31e
#61 add support for querying Account Manager roles
tobiaslohr Aug 5, 2019
453da62
Merge branch 'master' into basic-user-mgmt
tobiaslohr Aug 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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