You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1169 lines
54 KiB

'use strict';
require(['jquery',
'underscore',
'splunkjs/mvc',
'splunkjs/mvc/utils',
'splunkjs/mvc/tokenutils',
'splunkjs/mvc/messages',
'splunkjs/mvc/searchmanager',
'splunkjs/mvc/multidropdownview',
'splunkjs/mvc/dropdownview',
'/static/app/rest-storage-passwords-manager/Modal.js',
'splunkjs/mvc/simpleform/input/dropdown',
'splunkjs/mvc/simplexml/ready!'],
function ($,
_,
mvc,
utils,
TokenUtils,
Messages,
SearchManager,
MultiDropdownView,
DropdownView,
Modal,
Dropdown) {
function showPassword(row) {
var dfd = $.Deferred();
var splunkJsComponent = mvc.Components.getInstance("passwordSearch");
if(splunkJsComponent) {
splunkJsComponent.dispose();
}
var passwordSearch = new SearchManager({
"id": "passwordSearch",
"cancelOnUnload": true,
"status_buckets": 0,
"earliest_time": "-24h@h",
"latest_time": "now",
"sample_ratio": 1,
"search": "| rest /servicesNS/-/-/storage/passwords \
| search title=" + row.realm + ":" + row.username + ": \
| table clear_password",
"app": utils.getCurrentApp(),
"auto_cancel": 90,
"preview": true,
"tokenDependencies": {
},
"runWhenTimeIsUndefined": false
}, {tokens: true, tokenNamespace: "submitted"});
var mainSearch = mvc.Components.getInstance("passwordSearch");
var myResults = mainSearch.data('results', { output_mode:'json', count:0 });
mainSearch.on('search:done', function(properties) {
if(properties.content.resultCount == 0) {
return renderModal("password-not-found",
"Not Found",
"<div class=\"alert alert-warning\"><i class=\"icon-alert\"></i>No password found. Verify <b>list_storage_passwords</b> capability role is enabled.</div>",
"Close")
}
});
myResults.on("data", function() {
var data = myResults.data().results;
dfd.resolve(renderModal("show-password",
"Password",
"<h3>" + data[0].clear_password + "</h3>",
"Close"));
});
return dfd.promise();
}
function anonCallback(callback=function(){}, callbackArgs=null) {
if(callbackArgs) {
callback.apply(this, callbackArgs);
} else {
callback();
}
}
function genericPromise() {
var dfd = $.Deferred();
dfd.resolve();
return dfd.promise();
}
// Wrapper to execute multiple searches in order and resolve when they've all finished
function all(array){
var deferred = $.Deferred();
var fulfilled = 0, length = array.length;
var results = [];
if (length === 0) {
deferred.resolve(results);
} else {
_.each(array, function(promise, i){
$.when(promise()).then(function(value) {
results[i] = value;
fulfilled++;
if(fulfilled === length){
deferred.resolve(results);
}
});
});
}
return deferred.promise();
};
// Helper to figure out if the create form is open
function isFormOpen() {
var formOpen = window.sessionStorage.getItem("formOpen");
if(_.isNull(formOpen) || _.isUndefined(formOpen) || formOpen === "false") {
return false;
} else {
return true;
}
}
// Callback to refresh window and hide create-user
function refreshWindow() {
setTimeout(function () {
location.reload()
$('#create-user').show();
}, 500);
}
function renderModal(id, title, body, buttonText, callback=function(){}, callbackArgs=null) {
// Create the modal
var myModal = new Modal(id, {
title: title,
backdrop: 'static',
keyboard: false,
destroyOnHide: true,
type: 'wide'
});
// Add content
myModal.body.append($(body));
// Add cancel button for update/delete action
if(id == "user-delete-confirm" || id == "update-user-form") {
myModal.footer.append($('<cancel>').attr({
type: 'button',
'data-dismiss': 'modal'
})
.addClass('btn btn-secondary').text("Cancel")).on('click', function(){});
}
// Add footer
myModal.footer.append($('<button>').attr({
type: 'button',
'data-dismiss': 'modal'
})
.addClass('btn btn-primary').text(buttonText).on('click', function () {
anonCallback(callback, callbackArgs);
}))
// Launch it!
myModal.show();
}
// Clear click listener and register with new callback
function clearOnClickAndRegister(el, callback, callbackArgs=null) {
// Register click callback
if(_.isUndefined($._data($(el).get(0), "events"))) {
$(el).on('click', function (event) {
event.preventDefault();
anonCallback(callback, callbackArgs);
});
return;
}
// Unregister click callback
if(_.isObject($._data($(el).get(0), "events")) && _.has($._data($(el).get(0), "events"), "click")) {
$(el).off('click');
$(el).on('click', function () {
anonCallback(callback, callbackArgs);
});
}
}
// Return selected rows from bootstrap-table
function getIdSelections() {
return $.map($('#rest-password-table').bootstrapTable('getSelections'), function (row) {
return row
});
}
// Run search to populate and call create table
function populateTable() {
// Initialize
window.sessionStorage.setItem("formOpen", "false");
var contextMenuDiv = '#context-menu';
var passwordTableDiv = '#password-table';
var search1 = new SearchManager({
"id": "search1",
"cancelOnUnload": true,
"status_buckets": 0,
"earliest_time": "-24h@h",
"latest_time": "now",
"sample_ratio": 1,
"search": "| rest /servicesNS/-/-/configs/conf-passwords \
| rex field=title \"credential:(?<realm>.*?):(?<username>.*?):\" \
| eval uri_base=\"/en-US/splunkd/__raw\" | rex field=id \"(?<rest_uri>/servicesNS.*?$)\" | eval rest_uri = uri_base + rest_uri \
| fields username, eai:userName, password, realm, eai:acl.app, eai:acl.owner, eai:acl.perms.read, eai:acl.perms.write, eai:acl.sharing, realm, rest_uri \
| rename eai:userName as user, eai:acl.app as app, eai:acl.owner as owner, eai:acl.perms.read as acl_read, eai:acl.perms.write as acl_write, eai:acl.sharing as acl_sharing \
| table username, password, realm, app, owner, acl_read, acl_write, acl_sharing, rest_uri \
| fillnull value=\"\"",
"app": utils.getCurrentApp(),
"auto_cancel": 90,
"preview": true,
"tokenDependencies": {
},
"runWhenTimeIsUndefined": false
}, {tokens: true, tokenNamespace: "submitted"});
var mainSearch = splunkjs.mvc.Components.getInstance("search1");
var myResults = mainSearch.data('results', { output_mode:'json', count:0 });
mainSearch.on('search:progress', function(properties) {
Messages.render("waiting", $(passwordTableDiv));
});
mainSearch.on('search:done', function(properties) {
document.getElementById("password-table").innerHTML = "";
if(properties.content.resultCount == 0) {
var noData = null;
createTable(passwordTableDiv, contextMenuDiv, noData);
}
});
myResults.on("data", function() {
var data = myResults.data().results;
createTable(passwordTableDiv, contextMenuDiv, data);
});
}
// Render credential table and wire up context menu
function createTable(tableDiv, contextMenuDiv, data) {
var html = '<div id="open-close-button"> \
<p><button id="main-create" class="btn btn-primary" data-toggle="collapse" href="#create-update-form">Create</button></p> \
</div> \
<div id="create-update-form" class="collapse multi-collapse"> \
<div id="createCredential"> \
<form id="createCredential"> \
<div class="form-group"> \
<label for="username">Username</label> \
<input type="username" class="form-control" id="createUsername" placeholder="Enter username"> \
</div> \
<div class="form-group"> \
<label for="password">Password</label> \
<input type="password" class="form-control" id="createPassword" placeholder="Password"> \
</div> \
<div> \
<label for="confirmPassword">Confirm Password</label> \
<input type="password" class="form-control" id="createConfirmPassword" placeholder="Confirm Password"> \
</div> \
<div class="form-group"> \
<label for="realm">Realm</label> \
<input type="realm" class="form-control" id="createRealm" placeholder="Realm"> \
<br></br>\
</div> \
<div class="form-group"> \
<label for="owner" id="owner">Owner</label> \
<div id="owner-dropdown"></div> \
</div> \
<div class="form-group"> \
<label for="readUsers" id="read-users">Read Users</label> \
<div id="read-user-multi"></div> \
</div> \
<div class="form-group"> \
<label for="writeUsers" id="write-users">Write Users</label> \
<div id="write-user-multi"></div> \
</div> \
<div class="form-group" id="app-scope"> \
<label for="appScope">App Scope</label> \
<div id="app-scope-dropdown"></div> \
</div> \
<div class="form-group"> \
<label for="sharing" id="sharing">Sharing</label> \
<div id="sharing-dropdown"></div> \
</div> \
<div id="create-credential-submit"> \
<button id="create-submit" class="btn btn-primary">Create</button> \
</div> \
</form> \
</div> \
</div>';
var tdHtml = "";
var contextMenu = '<ul id="example1-context-menu" class="dropdown-menu"> \
<li data-item="update"><a>Update</a></li> \
<li data-item="delete"><a>Delete</a></li> \
</ul>';
var header = ' <div> \
<div id="toolbar"> \
<button id="remove" type="button" class="btn icon-x btn-danger" disabled> Delete</button> \
</div> \
<table id="rest-password-table" \
class="table table-striped table-hover" \
data-toolbar="#toolbar" \
data-detail-view="true" \
data-sort-name="username" \
data-show-pagination-switch="true" \
data-id-field="id" \
data-pagination="true" \
data-sortable="true" \
data-page-size="10" \
data-page-list="[10,20,50,ALL]" \
data-id-field="id" \
data-toggle="table" \
data-smart-display="true" \
data-search="true" \
data-checkbox-header="true" \
data-show-footer="false" \
data-select-item-name="button-select" \
data-click-to-select="false"> \
<thead> \
<tr> \
<th data-field="state" data-checkbox="true"></th> \
<th data-field="id" data-visible="false" data-align="center"><div><h3>ID</h3></th> \
<th data-field="username" data-sortable="true" data-align="center"><div><h3>Username</h3></th> \
<th data-field="password" data-events="operateEvents" data-align="center"><div><h3>Password</h3></div></th> \
<th data-field="realm" data-sortable="true" data-align="center"><div><h3><h3>Realm</h3></div></th> \
<th data-field="app" data-sortable="true" data-align="center"><div><h3>App</h3></div></th> \
<th data-field="clear_password" data-visible="false" data-align="center"><div><h3>Clear Password</h3></div></th> \
<th data-field="owner" data-sortable="true" data-align="center"><div><h3>Owner</h3></div></th> \
<th data-field="acl_read" data-sortable="true" data-align="center"><div><h3>Read</h3></div></th> \
<th data-field="acl_write" data-sortable="true" data-align="center"><div><h3>Write</h3></div></th> \
<th data-field="acl_sharing" data-sortable="true" data-align="center"><div><h3>Sharing</h3></div></th> \
<th data-field="rest_uri" data-visible="false" data-align="center"><div><h3>REST URI</h3></div></th> \
</tr> \
</thead> \
<tbody>';
html += header;
_.each(data, function(row, i) {
tdHtml += '<tr class="striped">\
<td class="bs-checkbox"></td>\
<td>' + i + '</td>\
<td>' + row.username + '</td>\
<td>\
<a class="show" href="javascript:void(0)" title="Show Password">\
<li class="icon-visible"></li>\
</a>\
</td>\
<td>' + row.realm + '</td>\
<td>' + row.app + '</td>\
<td>' + row.clear_password + '</td>\
<td>' + row.owner + '</td>\
<td>' + row.acl_read + '</td>\
<td>' + row.acl_write + '</td>\
<td>' + row.acl_sharing + '</td>\
<td>' + row.rest_uri + '</td>\
</tr>';
});
tdHtml += "</tbody></table></div>";
html += tdHtml;
// Render table
$(tableDiv).append(html);
// Rendeer context menu div
$(contextMenuDiv).append(contextMenu);
// Register click listener on create button
$('#main-create').on('click', function () {
if(!isFormOpen()) {
// Change text to close
$('#main-create').text("Close");
window.sessionStorage.setItem("formOpen", "true");
// Clear form values
$('input[id=createUsername]').val("");
$('input[id=createRealm]').val("");
} else {
$('#main-create').text("Create");
window.sessionStorage.setItem("formOpen", "false");
}
// Rnder the create user form
anonCallback(renderCreateUserForm, ["",""])
});
// Current row index in table
var curIndex = null;
// Create bootstrap-table
$('#rest-password-table').bootstrapTable({
contextMenu: '#example1-context-menu',
onContextMenuItem: function(row, $el){
// Actions for right click on row
if($el.data("item") == "update"){
$('#rest-password-table').bootstrapTable('expandRow', curIndex);
} else if($el.data("item") == "delete"){
deleteMultiCredential([row]);
}
},
onContextMenuRow: function(row, $el){
// Set the current index when context menu triggered
curIndex = $el.data().index;
},
onExpandRow: function(index, row, $detail) {
// Add a new table row below current element
$detail.html('<table></table>').find('table').append('<tr><td><div id="' + row.username + '"></div></td></tr>');
// Logic to collapse previous row when new row expanded
$('#rest-password-table').find('.detail-view').each(function () {
if (!$(this).is($detail.parent())) {
$(this).prev().find('.detail-icon').click()
}
})
// Render the update form
renderUpdateUserInTable(row);
}
});
// Toggle remove button on or off depending whether rows are checked
$('#rest-password-table').on('check.bs.table uncheck.bs.table ' +
'check-all.bs.table uncheck-all.bs.table', function () {
$('#remove').prop('disabled', !$('#rest-password-table').bootstrapTable('getSelections').length);
});
// Wire remove button to delete credentials
$('#remove').click(function () {
var rows = getIdSelections();
deleteMultiCredential(rows);
$('#remove').prop('disabled', true);
});
}
// Delete credentials
function deleteMultiCredential(rows) {
// Delete a single credential
var deleteCred = function (row) {
var dfd = $.Deferred();
var deleteUrl = "/en-US/splunkd/__raw/servicesNS/" + row.owner + "/" + row.app + "/storage/passwords/" + row.realm + ":" + row.username +":";
var message = [];
var payload = {"body": undefined,
"responseCode": undefined}
var aclUrl = row.rest_uri + "/acl";
var aclData = {"perms.read": row.acl_read,
"perms.write": row.acl_write,
"sharing": row.acl_sharing == "user" ? "app":row.acl_sharing,
"owner": row.owner}
$.ajax({
type: "POST",
url: aclUrl,
data: aclData,
error: function(xhr, textStatus, error) {
payload.body = "<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to delete user <b> " + row.username + "</b> - " + xhr.responseText + "</div>";
payload.responseCode = xhr.status;
message.push(payload);
dfd.resolve(message);
}
})
.then(function() {
$.ajax({
type: "DELETE",
url: deleteUrl,
success: function(data, textStatus, xhr) {
payload.body = "<div><i class=\"icon-check-circle\"></i> Successfully deleted credential - <b>" + row.realm + ":" + row.username + "</b></div>";
payload.responseCode = xhr.status;
message.push(payload);
dfd.resolve(message);
},
error: function(xhr, textStatus, error) {
payload.body = "<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to delete user <b> " + row.username + "</b> - " + xhr.responseText + "</div>";
payload.responseCode = xhr.status;
message.push(payload);
dfd.resolve(message);
}
})
})
return dfd.promise();
}
var removeUsers = function () {
// promise array
var promises = [];
_.each(rows, function(row, i) {
// Push each row to be deleted onto promises array
promises.push(function() {
return deleteCred(row);
});
});
// Execute deletes and display message
$.when(all(promises)).then(function(messages) {
var status = "";
var failCount = 0;
var deleteCount = messages.length;
var prefix = "Credential";
var modalMessage = $.map(messages, function(i) {
var payload = i[0];
if(parseInt(payload.responseCode) != 200) {
failCount += 1;
}
return payload.body;
})
if(failCount == deleteCount) {
status = prefix + " Delete Failed";
} else if (failCount > 0 && failCount < deleteCount) {
status = prefix + " Delete Partially Succeeded"
} else {
status = prefix + " Deleted";
}
renderModal("user-delete",
status,
modalMessage.join("\n"),
"Close",
refreshWindow)
});
}
// Get the usernames from all rows
var users = $.map(rows, function(row) {
return row.username;
})
// Render delete confirmation modal and regester delete callback action
var deleteUser = renderModal("user-delete-confirm",
"Confirm Delete Action",
"<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>You're about to remove the users <b>" + users.join(', ') + "</b> - Press ok to continue</div>",
"Ok",
removeUsers,
[rows]);
}
/**
* SplunkJS Input object and methods
* @param {object} config Config object holding component specific definitions
*/
function splunkJSInput(config) {
var config = this.config = config;
// Form to render for updating user
var htmlForm = '<div id="' + this.config.username + '" class="collapse multi-collapse"> \
<div id="createCredential"> \
<form id="createCredential"> \
<div class="form-group"> \
<label for="username">Username</label> \
<input type="username" class="form-control" id="createUsername" placeholder="Enter username"> \
</div> \
<div class="form-group"> \
<label for="password">Password</label> \
<input type="password" class="form-control" id="createPassword" placeholder="Password"> \
</div> \
<div> \
<label for="confirmPassword">Confirm Password</label> \
<input type="password" class="form-control" id="createConfirmPassword" placeholder="Confirm Password"> \
</div> \
<div class="form-group"> \
<label for="realm">Realm</label> \
<input type="realm" class="form-control" id="createRealm" placeholder="Realm"> \
<br></br>\
</div> \
<div class="form-group"> \
<label for="owner" id="owner">Owner</label> \
<div id="owner-dropdown"></div> \
</div> \
<div class="form-group"> \
<label for="readUsers" id="read-users">Read Users</label> \
<div id="read-user-multi"></div> \
</div> \
<div class="form-group"> \
<label for="writeUsers" id="write-users">Write Users</label> \
<div id="write-user-multi"></div> \
</div> \
<div class="form-group" id="app-scope"> \
<label for="appScope">App Scope</label> \
<div id="app-scope-dropdown"></div> \
</div> \
<div class="form-group"> \
<label for="sharing" id="sharing">Sharing</label> \
<div id="sharing-dropdown"></div> \
</div> \
<div id="create-credential-submit"> \
<button id="create-submit" class="btn btn-primary">Submit</button> \
</div> \
</form> \
</div> \
</div>'
// Save context
var that = this;
// Remove component and add div back
this.remove = function() {
var el = "#" + this.config.parentEl;
var splunkJsComponent = mvc.Components.get(this.config.id);
if(splunkJsComponent) {
splunkJsComponent.remove();
$(el).append('<div id="' + this.config.el + '"></div>');
}
}
// Remove component from inline update form. Don't add the div back since it's dynamic
this.updateRemove = function() {
var el = "#" + this.config.parentEl;
var splunkJsComponent = mvc.Components.get(this.config.id);
if(splunkJsComponent) {
splunkJsComponent.remove();
}
}
// Render the component in the form
this.renderComponent = function () {
// Remove component if it exists
that.remove();
var el = "#" + this.config.el;
// Get search manager
var splunkJsComponentSearch = mvc.Components.get(this.config.id + "-search");
// Check to make sure div is there before rendering
if ($(el).length) {
var choices = _.has(this.config, "choices") ? this.config.choices:[];
// Create search manager if it doesn't exist
if(!splunkJsComponentSearch) {
this.config.searchInstance = new SearchManager({
id: this.config.id + "-search",
search: this.config.searchString
});
}
if(this.config.type == "dropdown") {
this.config.instance = new DropdownView({
id: this.config.id,
managerid: _.isUndefined(this.config.searchString) ? null:this.config.id + "-search",
choices: choices,
labelField: "label",
valueField: "value",
default: _.has(this.config, "default") ? this.config.default:null,
el: $(el)
}).render();
} else {
this.config.instance = new MultiDropdownView({
id: this.config.id,
choices: choices,
managerid: _.isUndefined(this.config.searchString) ? null:this.config.id + "-search",
labelField: "label",
valueField: "value",
width: 350,
default: _.has(this.config, "default") ? this.config.default:null,
el: $(el)
}).render();
}
} else {
setTimeout(function() {
that.renderComponent();
}, 100);
}
}
// Get values from bootstrap table
this.getVals = function() {
return this.config.instance.val();
}
}
// Used to render create form
function renderCreateUserForm(cUsername = false, cRealm = false) {
var createUser = function createUser() {
var aclData = {};
var formVals = {};
_.each(arguments[2], function(component, i) {
var aclKey = component.config.aclKey;
aclData[aclKey] = _.isArray(component.getVals()) ? component.getVals().join():component.getVals();
formVals[aclKey] = _.isArray(component.config.default) ? component.config.default.join():component.config.default;
});
var username = $('input[id=createUsername]').val();
var password = $('input[id=createPassword]').val();
var confirmPassword = $('input[id=createConfirmPassword]').val();
var realm = $('input[id=createRealm]').val();
if(username == "") {
return renderModal("missing-username",
"Missing Username",
"<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Please enter a username</div>",
"Close")
}
if(password == "") {
return renderModal("missing-password",
"Missing Password",
"<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Please enter a password</div>",
"Close")
}
if(username && !password) {
return renderModal("missing-password",
"Missing Password",
"<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Please enter a password</div>",
"Close");
}
// Create object to POST for user creation
var createData = {"name": username,
"password": password,
"realm": realm};
if(password != confirmPassword) {
return renderModal("password-mismatch",
"Password Mismatch",
"<div class=\"alert alert-warning\"><i class=\"icon-alert\"></i>Passwords do not match</div>",
"Close");
} else {
var currentUser = Splunk.util.getConfigValue("USERNAME");
var createUrl = "/en-US/splunkd/__raw/servicesNS/" + currentUser + "/" + aclData.app + "/storage/passwords";
var aclUrl = "/en-US/splunkd/__raw/servicesNS/" + currentUser + "/" + aclData.app + "/configs/conf-passwords/credential%3A" + realm + "%3A" + username + "%3A/acl";
// Success message for final modal display
var message = [];
$.ajax({
type: "POST",
url: createUrl,
data: createData,
success: function(data, textStatus, xhr) {
message.push("<div><i class=\"icon-check-circle\"></i> Successfully created user <b>" + realm + ":" + username + "</b></div>");
},
error: function(xhr, textStatus, error) {
message.push("<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to create user <b>" + username + ":" + realm + "</b> - " + xhr.responseText + "</div>");
}
})
.then(function() {
// App not a valid key for updating Splunk ACL's, remove it before posting
delete aclData.app;
// Need to set sharing to app level before applying ACL's otherwise read/write perms don't apply
// ACL's will get re-applied back to user sharing context in next chain ACL apply
if(aclData.sharing == "user") {
var aclDataCopy = _.clone(aclData);
aclDataCopy.sharing = "app";
return $.ajax({
type: "POST",
url: aclUrl,
data: aclDataCopy,
error: function(xhr, textStatus, error) {
message.push("<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to apply ACL - " + xhr.responseText + "</div>");
}
})
}
})
.then(function () {
return $.ajax({
type: "POST",
url: aclUrl,
data: aclData,
success: function(data, textStatus, xhr) {
message.push("<div><i class=\"icon-check-circle\"></i> Successfully applied ACL's</div>")
},
error: function(xhr, textStatus, error) {
message.push("<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to apply ACL - " + xhr.responseText + "</div>");
}
})
})
.done(function () {
renderModal("user-create",
"User Created",
message.join('\n'),
"Close",
refreshWindow)
})
.fail(function() {
renderModal("user-create-fail",
"User Create Failed",
message.join('\n'),
"Close",
refreshWindow)
});
}
}
var inputs = [new splunkJSInput({"id": "app-scope-dropdown",
"searchString": "| rest /servicesNS/-/-/apps/local | search disabled=0 | rename title as value | table label, value",
"el": "app-scope-dropdown",
"type": "dropdown",
"default": utils.getCurrentApp(),
"aclKey": "app",
"parentEl": "app-scope"}),
new splunkJSInput({"id": "read-user-multi",
"searchString": "| rest /servicesNS/-/-/authorization/roles | eval label=title | rename title as value | fields label, value | append [| rest /servicesNS/-/-/authentication/users | eval label=title | rename title as value | fields label, value] | dedup label",
"el": "read-user-multi",
"type": "multi-dropdown",
"default": "*",
"choices": [{"label":"*", "value":"*"}],
"aclKey": "perms.read",
"parentEl": "read-users"}),
new splunkJSInput({"id": "write-user-multi",
"searchString": "| rest /servicesNS/-/-/authorization/roles | eval label=title | rename title as value | fields label, value | append [| rest /servicesNS/-/-/authentication/users | eval label=title | rename title as value | fields label, value] | dedup label",
"el": "write-user-multi",
"type": "multi-dropdown",
"parentEl": "write-users",
"aclKey": "perms.write",
"choices": [{"label":"*", "value":"*"}],
"default": ["admin","power"]}),
new splunkJSInput({"id": "sharing-dropdown",
"choices": [{"label":"global", "value": "global"},
{"label":"app", "value": "app"},
{"label":"user", "value": "user"}],
"el": "sharing-dropdown",
"type": "dropdown",
"parentEl": "sharing",
"aclKey": "sharing",
"default": "app"}),
new splunkJSInput({"id": "owner-dropdown",
"searchString": "| rest /servicesNS/-/-/authentication/users | eval label=title | rename title as value | table label, value",
"el": "owner-dropdown",
"type": "dropdown",
"aclKey": "owner",
"default": Splunk.util.getConfigValue("USERNAME"),
"parentEl": "owner"})];
// Render components
_.each(inputs, function(input, i) {
input.renderComponent();
});
// Register createUser callback for button
clearOnClickAndRegister('#create-submit', createUser, [cUsername, cRealm, inputs]);
setTimeout(function () {
if(cUsername != "" || cRealm != "") {
$('input[id=createUsername]').val(cUsername);
$('input[id=createRealm]').val(cRealm);
}
}, 300);
}
// Render form under row in bootstrap-table
function renderUpdateUserInTable(row) {
var updateUser = function updateUser () {
var formVals = {};
var aclData = {};
// Process SplunkJS form components
_.each(arguments[0], function(component, i) {
var aclKey = component.config.aclKey;
// Save submitted values
aclData[aclKey] = _.isArray(component.getVals()) ? component.getVals().join():component.getVals();
// Save initial form values
formVals[aclKey] = _.isArray(component.config.default) ? component.config.default.join():component.config.default;
});
// Grab input values
var username = $('input[id=updateUsername]').val();
var password = $('input[id=updatePassword]').val();
var confirmPassword = $('input[id=updateConfirmPassword]').val();
var realm = $('input[id=updateRealm]').val();
// Save app info since we delete when posting
var aclApp = aclData.app;
var app = formVals.app;
// App is not a valid field when posting to /acl. Remove from object.
delete aclData.app;
// Initialze to apply ACL's
var applyAcl = true;
// Make sure both password fields are set
if(!password && confirmPassword) {
return renderModal("password-mismatch",
"Password Error",
"<div class=\"alert alert-warning\"><i class=\"icon-alert\"></i>Please set password and confirm password fields</div>",
"Close");
}
// No change when form data and submitted values are the same and password is blank
if(JSON.stringify(formVals) === JSON.stringify(aclData) && !password) {
return renderModal("no-change",
"No Change Detected",
"<div class=\"alert alert-info\"><i class=\"icon-alert\"></i>Nothing to see here</div>",
"Close")
}
// // If ACL's haven't changed, don't apply
// if(JSON.stringify(formVals) === JSON.stringify(aclData)) {
// applyAcl = false;
// }
// Add realm to formVals for refrence in REST url's
formVals.realm = arguments[1].realm;
if(password != confirmPassword) {
renderModal("password-mismatch",
"Password Mismatch",
"<div class=\"alert alert-warning\"><i class=\"icon-alert\"></i>Passwords do not match</div>",
"Close");
} else {
var aclUrl = row.rest_uri + "/acl";
// Success message for final modal display
var message = [];
// Copy acl data to new object
var aclDataCopy = _.clone(aclData);
// Set sharing to app for consistent behavior through app and password changes
// When sharing is set to app, eai:userName is always 'nobody' and this gives a predictable
// URI to change password and move configs between apps.
// We'll set sharing back to the form value after password change and movement between apps.
aclDataCopy.sharing = "app";
// Apply ACL's
$.ajax({
type: "POST",
url: aclUrl,
data: aclDataCopy,
error: function(xhr, textStatus, error) {
message.push("<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to update password for user <b>" + username + "</b> - " + xhr.responseText + "</div>");
}
})
.then (function() {
// Change the password
if(password) {
var passwordUrl = "/en-US/splunkd/__raw/servicesNS/nobody/" + formVals.app + "/storage/passwords/" + formVals.realm + ":" + username + ":";
return $.ajax({
type: "POST",
url: passwordUrl,
data: {"password": password},
success: function(data, textStatus, xhr) {
message.push("<div><i class=\"icon-check-circle\"></i> Successfully updated password for credential - <b>" + formVals.realm + ":" + username + "</b></div>");
},
error: function(xhr, textStatus, error) {
message.push("<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to update password for user <b>" + username + "</b> - " + xhr.responseText + "</div>");
}
})
}
})
.then(function() {
// Move app context
if(formVals.app != aclApp) {
var moveUrl = "/en-US/splunkd/__raw/servicesNS/nobody/" + formVals.app + "/configs/conf-passwords/credential%3A" + formVals.realm + "%3A" + username + "%3A/move";
return $.ajax({
type: "POST",
url: moveUrl,
data: {"app": aclApp, "user": "nobody"},
success: function(data, textStatus, xhr) {
message.push("<div><i class=\"icon-check-circle\"></i> Successfully moved credential from <b>" + formVals.app + "</b> to <b>" + aclApp + "</b></div>");
},
error: function(xhr, textStatus, error) {
message.push("<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to move credential from <b>" + formVals.app + "</b> to <b>" + aclApp + "</b> - " + xhr.responseText + "</div>");
}
})
}
})
.then(function() {
// Apply ACL's back to submitted values
aclUrl = "/en-US/splunkd/__raw/servicesNS/nobody/" + aclApp + "/configs/conf-passwords/credential%3A" + formVals.realm + "%3A" + username + "%3A/acl";
return $.ajax({
type: "POST",
//url: newAclUrl,
url: aclUrl,
data: aclData,
success: function(data, textStatus, xhr) {
message.push("<div><i class=\"icon-check-circle\"></i> Successfully applied ACL's</div>")
},
error: function(xhr, textStatus, error) {
message.push("<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to apply ACL - " + xhr.responseText + "</div>");
}
})
})
.done(function() {
renderModal("user-updated",
"User Updated",
message.join('\n'),
"Close",
refreshWindow)
})
.fail(function() {
renderModal("user-update-failed",
"User Update Failed",
message.join('\n'),
"Close",
refreshWindow)
});
}
}
//var divId = "#" + row.username;
var divId = "div[id='" + row.username + "']";
var htmlForm = '<form id="' + row.username + '-update-form"> \
<div class="form-group"> \
<label for="username">Username</label> \
<input type="username" class="form-control" id="updateUsername" placeholder="Enter username"> \
</div> \
<div class="form-group"> \
<label for="password">Password</label> \
<input type="password" class="form-control" id="updatePassword" placeholder="Password"> \
</div> \
<div> \
<label for="confirmPassword">Confirm Password</label> \
<input type="password" class="form-control" id="updateConfirmPassword" placeholder="Confirm Password"> \
</div> \
<div class="form-group"> \
<label for="realm">Realm</label> \
<input type="realm" class="form-control" id="updateRealm" placeholder="Realm" disabled> \
<br></br>\
</div> \
<div class="form-group"> \
<label for="owner" id="owner-inline">Owner</label> \
<div id="owner-dropdown-inline"></div> \
</div> \
<div class="form-group"> \
<label for="readUsers" id="read-users-inline">Read Users</label> \
<div id="read-user-multi-inline"></div> \
</div> \
<div class="form-group"> \
<label for="writeUsers" id="write-users-inline">Write Users</label> \
<div id="write-user-multi-inline"></div> \
</div> \
<div class="form-group" id="app-scope-inline"> \
<label for="appScope">App Scope</label> \
<div id="app-scope-dropdown-inline"></div> \
</div> \
<div class="form-group"> \
<label for="sharing" id="sharing-inline">Sharing</label> \
<div id="sharing-dropdown-inline"></div> \
</div> \
<div id="update-credential-inline-submit"> \
<button id="update-submit-inline" class="btn btn-primary">Update</button> \
</div> \
</form>'
$(divId).append(htmlForm);
// Set form username and realm
$('input[id=updateUsername]').val(row.username);
$('input[id=updateRealm]').val(row.realm);
var inputs = [new splunkJSInput({"id": "app-scope-dropdown-inline",
"searchString": "| rest /servicesNS/-/-/apps/local | search disabled=0 | rename title as value | table label, value",
"el": "app-scope-dropdown-inline",
"type": "dropdown",
"default": [row.app],
"aclKey": "app",
"parentEl": "app-scope-inline"}),
new splunkJSInput({"id": "read-user-multi-inline",
"searchString": "| rest /servicesNS/-/-/authorization/roles | eval label=title | rename title as value | fields label, value | append [| rest /servicesNS/-/-/authentication/users | eval label=title | rename title as value | fields label, value] | dedup label",
"el": "read-user-multi-inline",
"type": "multi-dropdown",
"default": row.acl_read.split(','),
"aclKey": "perms.read",
"choices": [{"label":"*", "value":"*"}],
"parentEl": "read-users-inline"}),
new splunkJSInput({"id": "write-user-multi-inline",
"searchString": "| rest /servicesNS/-/-/authorization/roles | eval label=title | rename title as value | fields label, value | append [| rest /servicesNS/-/-/authentication/users | eval label=title | rename title as value | fields label, value] | dedup label",
"el": "write-user-multi-inline",
"type": "multi-dropdown",
"parentEl": "write-users-inline",
"aclKey": "perms.write",
"choices": [{"label":"*", "value":"*"}],
"default": row.acl_write.split(',')}),
new splunkJSInput({"id": "sharing-dropdown-inline",
"choices": [{"label":"global", "value": "global"},
{"label":"app", "value": "app"},
{"label":"user", "value": "user"}],
"el": "sharing-dropdown-inline",
"type": "dropdown",
"parentEl": "sharing-inline",
"aclKey": "sharing",
"default": [row.acl_sharing]}),
new splunkJSInput({"id": "owner-dropdown-inline",
"searchString": "| rest /servicesNS/-/-/authentication/users | eval label=title | rename title as value | table label, value",
"el": "owner-dropdown-inline",
"type": "dropdown",
"default": [row.owner],
"aclKey": "owner",
"parentEl": "owner-inline"})];
// Render component
_.each(inputs, function(input, i) {
input.renderComponent();
});
// Register updateUser callback for button
clearOnClickAndRegister('#update-submit-inline', updateUser, [inputs, row]);
}
// listener used to show password when clicking the eye icon in the table
window.operateEvents = {
'click .show': function (e, value, row, index) {
var aclData = {"perms.read": row.acl_read,
"perms.write": row.acl_write,
"sharing": row.acl_sharing,
"owner": row.owner}
// Change sharing to app, show password, change back to user
if(row.acl_sharing == "user") {
//var aclUrl = "/en-US/splunkd/__raw/servicesNS/" + row.owner + "/" + row.app + "/configs/conf-passwords/credential%3A" + row.realm + "%3A" + row.username + "%3A/acl";
var aclUrl = row.rest_uri + "/acl";
var aclDataCopy = _.clone(aclData);
aclDataCopy.sharing = "app";
$.ajax({
type: "POST",
url: aclUrl,
data: aclDataCopy,
error: function(xhr, textStatus, error) {
return renderModal("fail-password-view",
"Cannot View Password",
"<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to view password - " + xhr.responseText + "</div>",
"Close");
}
})
.then(function() {
return showPassword(row);
})
.done(function() {
$.ajax({
type: "POST",
url: aclUrl,
data: aclData,
error: function(xhr, textStatus, error) {
return renderModal("fail-password-view",
"Cannot View Password",
"<div class=\"alert alert-error\"><i class=\"icon-alert\"></i>Failed to view password - " + xhr.responseText + "</div>",
"Close");
}
})
})
} else {
showPassword(row);
}
}
};
// Kick it all off
populateTable();
});