@*******************************************************************************************************// Users.cshtml - Gbtc//// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.//// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See// the NOTICE file distributed with this work for additional information regarding copyright ownership.// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this// file except in compliance with the License. You may obtain a copy of the License at://// http://opensource.org/licenses/MIT//// Unless agreed to in writing, the subject software distributed under the License is distributed on an// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the// License for the specific language governing permissions and limitations.//// Code Modification History:// ----------------------------------------------------------------------------------------------------// 03/03/2016 - J. Ritchie Carroll// Generated original version of source code.////******************************************************************************************************// To use in an ASP.NET destination project, include a Users.cshtml view with following content://// @using GSF.Web// @section StyleSheets{@Html.Raw(ViewBag.StyleSheetsSection?.ToString())}// @{ViewBag.HelpTarget = Url.Action("Help", "Main");}// @Html.RenderResource("GSF.Web.Security.Views.Users.cshtml")// @section Scripts{@Html.Raw(ViewBag.ScriptsSection?.ToString())}////******************************************************************************************************// To use in a self-hosted web project, include a Users.cshtml view with following content://// @using GSF.Web.Model// @inherits ExtendedTemplateBase<AppModel>// @section StyleSheets{@Html.Raw(ViewBag.StyleSheetsSection.ToString())}// @{Layout = "Layout.cshtml";}// @Html.RenderResource("GSF.Web.Security.Views.Users.cshtml")// @section Scripts{@Html.Raw(ViewBag.ScriptsSection.ToString())}////*****************************************************************************************************@@using GSF.Configuration@using GSF.Data.Model@using GSF.Security@using GSF.Security.Model@using GSF.Web.Model@using GSF.Web.Security@inherits ExtendedTemplateBase@section StyleSheets{ <style> .account-feedback-wrapper { position: relative; display: table; } .account-feedback-message { position: absolute; vertical-align: middle; display: table-cell; left: 10px; width: 425px; display: none; } .account-feedback-icon { font-size: large; vertical-align: middle; margin-top: -5px; } </style>}@{ // Layout will depend on rendering environment, so do not specify one DataContext dataContext = ViewBag.DataContext; ViewBag.Title = "Users"; ViewBag.ShowSearchFilter = true; // Define column headers, use null for field name to make column non-sortable ViewBag.HeaderColumns = new[] { // { "Field", "Label", "Classes" } new[] { "FirstName", "First Name", "text-center valign-middle" }, new[] {"LastName", "Last Name", "text-center valign-middle"}, new[] { null, "User Name", "text-center valign-middle"}, new[] { "UseADAuthentication", "Is AD User", "text-center" }, new[] { "CreatedOn", "Date Added", "text-center" }, new[] { null, "User Roles", "text-center valign-middle" } }; if (ViewBag.HelpTarget == null) { ViewBag.HelpTarget = "Help.cshtml"; } // Make sure default settings exist ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[SecurityProviderBase.DefaultSettingsCategory]; settings.Add("ApplicationName", SecurityProviderBase.DefaultApplicationName, "Name of the application being secured as defined in the backend security data store."); settings.Add("PasswordRequirementsRegex", AdoSecurityProvider.DefaultPasswordRequirementsRegex, "Regular expression used to validate new passwords for database users."); settings.Add("PasswordRequirementsError", AdoSecurityProvider.DefaultPasswordRequirementsError, "Error message to be displayed when new database user password fails regular expression test."); // Get configured settings string applicationName = settings["ApplicationName"].ValueAs(SecurityProviderBase.DefaultApplicationName); string passwordRequirementsRegex = settings["PasswordRequirementsRegex"].ValueAs(AdoSecurityProvider.DefaultPasswordRequirementsRegex); string passwordRequirementsError = settings["PasswordRequirementsError"].ValueAs(AdoSecurityProvider.DefaultPasswordRequirementsError); ViewBag.BodyRows = BodyRows().ToString(); ViewBag.AddNewEditDialog = AddNewEditDialog(dataContext, passwordRequirementsRegex, passwordRequirementsError).ToString(); ViewBag.AddNewEditTitle = "User";}@helper BodyRows(){ <td width="20%" class="text-center valign-middle" nowrap><button type="button" class="btn btn-link" data-bind="text: FirstName"></button></td> <td width="20%" class="text-center valign-middle" nowrap><button type="button" class="btn btn-link" data-bind="text: LastName"></button></td> <td width="25%" class="text-center valign-middle" nowrap><button type="button" class="btn btn-link" data-bind="text: AccountName(), attr: {title: Name}, click: $parent.viewPageRecord"></button></td> <td width="10%" class="text-center valign-middle"><input type="checkbox" id="checkEnabled" data-bind="checked: UseADAuthentication" disabled></td> <td width="10%" class="text-center valign-middle" data-bind="text: CreatedOn.formatDate(DateFormat)"></td> <td width="10%" class="text-center valign-middle"><button type="button" class="btn btn-default btn-sm" data-bind="click: openRolesEditor.bind($data), enable: $parent.canEdit() && $parent.dataHubIsConnected()">Roles...</button></td> <td width="5%" class="text-center valign-middle" nowrap> <button type="button" class="btn btn-xs" data-bind="click: $parent.editPageRecord, enable: $parent.canEdit() && $parent.dataHubIsConnected()"><span class="glyphicon glyphicon-pencil"></span></button> <button type="button" class="btn btn-xs" data-bind="click: $parent.removePageRecord, enable: $parent.canDelete() && $parent.dataHubIsConnected()"><span class="glyphicon glyphicon-remove"></span></button> </td>}@helper AccountValidationFeedback(){ <div class="pull-right account-feedback-wrapper"> <span id="resolvingAccount" class="account-feedback-message"><span class="glyphicon glyphicon-refresh glyphicon-spin account-feedback-icon"></span> <em class="small">Resolving account details...</em></span> <span id="accountValid" class="account-feedback-message"><span class="glyphicon glyphicon-ok-circle account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'green'}"></span> <em class="small">Resolved account name</em></span> <span id="accountInvalid" class="account-feedback-message"><span class="glyphicon glyphicon-remove-circle account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'red'}"></span> <em class="small">Cannot resolve account name</em></span> <span id="accountUnknown" class="account-feedback-message"><span class="glyphicon glyphicon-warning-sign account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'orange'}"></span> <em class="small">Valid account name is not a user or Active Directory access is limited</em></span> </div>}@helper AddNewEditDialog(DataContext dataContext, string passwordRequirementsRegex, string passwordRequirementsError){ <div class="col-md-12"> @Raw(dataContext.AddInputField<UserAccount>("Name", fieldLabel: "Name" + AccountValidationFeedback().ToString().Trim(), customDataBinding: "attr: {'acct-valid': validateAccount($data), 'autocomplete': 'off'}", initialFocus: true)) <div class="panel panel-default" data-bind="css: {'panel-readonly': $root.recordMode()===RecordMode.View, 'panel-primary': UseDBAuthentication}"> <div class="panel-heading"> @* Adding actual UseADAuthentication field check-box, but hidden, to pick-up any model based field initializers *@ <div style="display: none"> @Raw(dataContext.AddCheckBoxField<UserAccount>("UseADAuthentication")) </div> @* Adding radio buttons bound to virtual UseDBAuthentication that models negated state of UseADAuthentication field for simplified dependency field initialization *@ <div class="row"> <div class="col-md-8"> <div class="row"> <div class="col-md-4"> <label class="radio-inline"><input type="radio" name="radioUseDBAuthentication" data-bind="checkedValue: false, checked: UseDBAuthentication, attr: {'disabled': ($root.recordMode()===RecordMode.View ? true : undefined)}"> Active Directory User</label> </div> <div class="col-md-8"> <label class="radio-inline"><input type="radio" name="radioUseDBAuthentication" data-bind="checkedValue: true, checked: UseDBAuthentication, attr: {'disabled': ($root.recordMode()===RecordMode.View ? true : undefined)}"> Database User</label> </div> </div> </div> <div class="col-md-4"> </div> </div> </div> <div class="panel-body"> <div class="row"> <div class="col-md-6"> <div data-bind="visible: UseDBAuthentication"> @Raw(dataContext.AddInputField<UserAccount>("Password", "password", dependencyFieldName: "UseDBAuthentication")) </div> @Raw(dataContext.AddInputField<UserAccount>("FirstName")) @Raw(dataContext.AddInputField<UserAccount>("LastName")) </div> <div class="col-md-6"> <div data-bind="visible: UseDBAuthentication"> @Raw(dataContext.AddInputField<UserAccount>("Phone", dependencyFieldName: "UseDBAuthentication")) </div> @Raw(dataContext.AddInputField<UserAccount>("Email")) <div data-bind="visible: UseDBAuthentication"> @Raw(dataContext.AddDateField<UserAccount>("ChangePasswordOn", dependencyFieldName: "UseDBAuthentication")) @{ dataContext.AddPageDefinedFieldInitialization("Password", "\"\"", /* RegEx Pattern: */ passwordRequirementsRegex, /* Error Message: */ passwordRequirementsError); dataContext.AddPageDefinedFieldInitialization("ChangePasswordOn", "(new Date()).addDays(90)"); } </div> </div> </div> </div> </div> <div class="pull-right"> @Raw(dataContext.AddCheckBoxField<UserAccount>("LockedOut")) </div> </div>}@Html.RenderResource("GSF.Web.Model.Views.PagedViewModel.cshtml")<div id="editRolesDialog" class="modal fade" role="dialog"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal">×</button> <h4 class="modal-title">Edit Roles for <span id="currentUserName"></span>:</h4> </div> <div class="modal-body"> <div class="modal-body auto-height text-center"> <form role="form"> <input type="hidden" id="currentUserID" /> <div class="center-block"> <div id="loadingRoles">Loading role assignments <span class="glyphicon glyphicon-refresh glyphicon-spin"></span><hr class="full-break" /></div> <table class="table-condensed table-center"> @foreach (ApplicationRole role in dataContext.Table<ApplicationRole>().QueryRecords("Name", new RecordRestriction("NodeID={0}", SecurityHub.DefaultNodeID))) { <tr> <td class="text-left valign-middle"> <div class="checkbox" style="margin: 0"> <label for="@role.ID"><input type="checkbox" id="@role.ID"> @role.Description (@role.Name)</label> </div> </td> </tr> } </table> <br /> <div class="text-center"> @* ReSharper disable once Html.IdNotResolved *@ <a href="@ViewBag.HelpTarget#Roles" target="_blank">@applicationName Role Definitions</a> </div> </div> </form> </div> </div> <div class="modal-footer"> <button type="submit" class="btn btn-primary" data-dismiss="modal" id="saveRolesButton" data-bind="enable: canEdit()">Save</button> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> </div> </div> </div></div>@section Scripts { <script> "use strict"; @Raw(dataContext.RenderViewModelConfiguration<UserAccount, SecurityHub>(ViewBag)) var AccountState = { Invalid: 0, Valid: 1, Unknown: 2, Resolving: 3 } function lookupAccountName(record) { if (viewModel.dataHubIsConnected()) { securityHub.sidToAccountName(record.Name).done(function(accountName) { record.AccountName(accountName); }); } } function validateAccount(observableRecord) { showAccountNameValidityFeedback(AccountState.Resolving); if (observableRecord.UseDBAuthentication == null || observableRecord.UseDBAuthentication()) { hideAccountNameValidityFeedback(); } else { if (viewModel.dataHubIsConnected()) { const accountName = observableRecord.Name(); if (isEmpty(accountName)) { showAccountNameValidityFeedback(AccountState.Invalid); } else { securityHub.userNameToSID(accountName).done(function(sid) { if (accountName !== sid && accountName.countOccurrences("\\") < 2) { securityHub.isUserSID(sid).done(function(isUser) { showAccountNameValidityFeedback(isUser ? AccountState.Valid : AccountState.Unknown); }); } else { showAccountNameValidityFeedback(AccountState.Invalid); } }); } } else { hideAccountNameValidityFeedback(); } } return undefined; } var messages = []; var nextMessageName = undefined; var showingMessage = false; messages.push("resolvingAccount"); messages.push("accountValid"); messages.push("accountInvalid"); messages.push("accountUnknown"); function showFeedbackMessage(messageName) { if (showingMessage) { nextMessageName = messageName; } else { showingMessage = true; hideAccountNameValidityFeedback(messageName); if (messageName == null) showingMessage = false; else $("#" + messageName).fadeIn(function () { showingMessage = false; if (nextMessageName != undefined) { messageName = nextMessageName; nextMessageName = undefined; showFeedbackMessage(messageName); } }); } } function hideAccountNameValidityFeedback(messageName) { for (let i = 0; i < messages.length; i++) { if (messages[i] !== messageName) $("#" + messages[i]).hide(); } } function showAccountNameValidityFeedback(accountState) { switch (accountState) { case AccountState.Valid: showFeedbackMessage("accountValid"); break; case AccountState.Invalid: showFeedbackMessage("accountInvalid"); break; case AccountState.Unknown: showFeedbackMessage("accountUnknown"); break; case AccountState.Resolving: showFeedbackMessage("resolvingAccount"); break; } } $(viewModel).on("pageRecordsQueried", function(event, records) { // Add a virtual observable field called "AccountName" that will resolve SID values to account names for (var i = 0; i < records.length; i++) { records[i].AccountName = ko.observable("..."); lookupAccountName(records[i]); } }); $(viewModel).on("newRecord", function(event, newRecord) { // Make sure new records get virtual field newRecord.AccountName = ko.observable(""); }); $(viewModel).on("beforeEdit", function(event, observableRecord) { // Create a virtual UseDBAuthentication field that is negated state of actual UseADAuthentication field observableRecord.UseDBAuthentication = ko.observable(!observableRecord.UseADAuthentication()); // Load human readable account name into Name field if (observableRecord.AccountName) observableRecord.Name(observableRecord.AccountName()); @* Cannot decrypt a password hash, have to clear it and force re-entry *@ if (!observableRecord.UseADAuthentication()) observableRecord.Password(null); }); $(viewModel).on("beforeSave", function(event, observableRecord, promises) { // Set state of actual field to be negated state of virtual field observableRecord.UseADAuthentication(!observableRecord.UseDBAuthentication()); // Remove virtual observable fields before serialization for record updates delete observableRecord.UseDBAuthentication; delete observableRecord.AccountName; // Push promise to convert user name back to SID before updating record if (viewModel.dataHubIsConnected() && observableRecord.UseADAuthentication()) { promises.push($.Deferred(function(deferred) { securityHub.userNameToSID(observableRecord.Name()).done(function(sid) { observableRecord.Name(sid); deferred.resolve(); }); }).promise()); } }); function openRolesEditor(record) { $("#currentUserID").text(record.ID); $("#currentUserName").text(record.AccountName()); $("#editRolesDialog input:checkbox").prop("checked", false); if (viewModel.dataHubIsConnected()) { securityHub.queryApplicationRoles().done(function(roles) { var evaluations = roles.length; if (evaluations > 0) $("#loadingRoles").show(); for (var i = 0; i < roles.length; i++) { securityHub.userIsInRole(record.ID, roles[i].ID).done(function(roleID) { return function(result) { $("#" + roleID).prop("checked", result); evaluations--; if (evaluations <= 0) $("#loadingRoles").hide(); }; }(roles[i].ID)); } $("#editRolesDialog").modal("show"); }); } } $(function() { $("#editRolesDialog").modal({ show: false, backdrop: "static" }); $("#saveRolesButton").click(function() { if (viewModel.dataHubIsConnected()) { const userID = $("#currentUserID").text(); const roles = $("#editRolesDialog input:checkbox"); for (var i = 0; i < roles.length; i++) { const element = $(roles[i]); const roleID = element.attr("id"); if (element.prop("checked")) { securityHub.addUserToRole(userID, roleID).fail(function(error) { showErrorMessage(error, null, true); }); } else { securityHub.removeUserFromRole(userID, roleID).fail(function(error) { showErrorMessage(error, null, true); }); } } } }); }); </script>}@{ ViewBag.StyleSheetsSection = RenderSection("StyleSheets").ToString(); ViewBag.ScriptsSection = RenderSection("Scripts").ToString();}
@*******************************************************************************************************
// Users.cshtml - Gbtc
//
// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
// file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 03/03/2016 - J. Ritchie Carroll
// Generated original version of source code.
//
//******************************************************************************************************
// To use in an ASP.NET destination project, include a Users.cshtml view with following content:
//
// @using GSF.Web
// @section StyleSheets{@Html.Raw(ViewBag.StyleSheetsSection?.ToString())}
// @{ViewBag.HelpTarget = Url.Action("Help", "Main");}
// @Html.RenderResource("GSF.Web.Security.Views.Users.cshtml")
// @section Scripts{@Html.Raw(ViewBag.ScriptsSection?.ToString())}
//
//******************************************************************************************************
// To use in a self-hosted web project, include a Users.cshtml view with following content:
//
// @using GSF.Web.Model
// @inherits ExtendedTemplateBase<AppModel>
// @section StyleSheets{@Html.Raw(ViewBag.StyleSheetsSection.ToString())}
// @{Layout = "Layout.cshtml";}
// @Html.RenderResource("GSF.Web.Security.Views.Users.cshtml")
// @section Scripts{@Html.Raw(ViewBag.ScriptsSection.ToString())}
//
//*****************************************************************************************************@
@using GSF.Configuration
@using GSF.Data.Model
@using GSF.Security
@using GSF.Security.Model
@using GSF.Web.Model
@using GSF.Web.Security
@inherits ExtendedTemplateBase
@section StyleSheets{
<style>
.account-feedback-wrapper {
position: relative;
display: table;
}
.account-feedback-message {
position: absolute;
vertical-align: middle;
display: table-cell;
left: 10px;
width: 425px;
display: none;
}
.account-feedback-icon {
font-size: large;
vertical-align: middle;
margin-top: -5px;
}
</style>
}
@{
// Layout will depend on rendering environment, so do not specify one
DataContext dataContext = ViewBag.DataContext;
ViewBag.Title = "Users";
ViewBag.ShowSearchFilter = true;
// Define column headers, use null for field name to make column non-sortable
ViewBag.HeaderColumns = new[]
{ // { "Field", "Label", "Classes" }
new[] { "FirstName", "First Name", "text-center valign-middle" },
new[] {"LastName", "Last Name", "text-center valign-middle"},
new[] { null, "User Name", "text-center valign-middle"},
new[] { "UseADAuthentication", "Is AD User", "text-center" },
new[] { "CreatedOn", "Date Added", "text-center" },
new[] { null, "User Roles", "text-center valign-middle" }
};
if (ViewBag.HelpTarget == null) {
ViewBag.HelpTarget = "Help.cshtml";
}
// Make sure default settings exist
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[SecurityProviderBase.DefaultSettingsCategory];
settings.Add("ApplicationName", SecurityProviderBase.DefaultApplicationName, "Name of the application being secured as defined in the backend security data store.");
settings.Add("PasswordRequirementsRegex", AdoSecurityProvider.DefaultPasswordRequirementsRegex, "Regular expression used to validate new passwords for database users.");
settings.Add("PasswordRequirementsError", AdoSecurityProvider.DefaultPasswordRequirementsError, "Error message to be displayed when new database user password fails regular expression test.");
// Get configured settings
string applicationName = settings["ApplicationName"].ValueAs(SecurityProviderBase.DefaultApplicationName);
string passwordRequirementsRegex = settings["PasswordRequirementsRegex"].ValueAs(AdoSecurityProvider.DefaultPasswordRequirementsRegex);
string passwordRequirementsError = settings["PasswordRequirementsError"].ValueAs(AdoSecurityProvider.DefaultPasswordRequirementsError);
ViewBag.BodyRows = BodyRows().ToString();
ViewBag.AddNewEditDialog = AddNewEditDialog(dataContext, passwordRequirementsRegex, passwordRequirementsError).ToString();
ViewBag.AddNewEditTitle = "User";
}
@helper BodyRows()
{
<td width="20%" class="text-center valign-middle" nowrap><button type="button" class="btn btn-link" data-bind="text: FirstName"></button></td>
<td width="20%" class="text-center valign-middle" nowrap><button type="button" class="btn btn-link" data-bind="text: LastName"></button></td>
<td width="25%" class="text-center valign-middle" nowrap><button type="button" class="btn btn-link" data-bind="text: AccountName(), attr: {title: Name}, click: $parent.viewPageRecord"></button></td>
<td width="10%" class="text-center valign-middle"><input type="checkbox" id="checkEnabled" data-bind="checked: UseADAuthentication" disabled></td>
<td width="10%" class="text-center valign-middle" data-bind="text: CreatedOn.formatDate(DateFormat)"></td>
<td width="10%" class="text-center valign-middle"><button type="button" class="btn btn-default btn-sm" data-bind="click: openRolesEditor.bind($data), enable: $parent.canEdit() && $parent.dataHubIsConnected()">Roles...</button></td>
<td width="5%" class="text-center valign-middle" nowrap>
<button type="button" class="btn btn-xs" data-bind="click: $parent.editPageRecord, enable: $parent.canEdit() && $parent.dataHubIsConnected()"><span class="glyphicon glyphicon-pencil"></span></button>
<button type="button" class="btn btn-xs" data-bind="click: $parent.removePageRecord, enable: $parent.canDelete() && $parent.dataHubIsConnected()"><span class="glyphicon glyphicon-remove"></span></button>
</td>
}
@helper AccountValidationFeedback()
{
<div class="pull-right account-feedback-wrapper">
<span id="resolvingAccount" class="account-feedback-message"><span class="glyphicon glyphicon-refresh glyphicon-spin account-feedback-icon"></span> <em class="small">Resolving account details...</em></span>
<span id="accountValid" class="account-feedback-message"><span class="glyphicon glyphicon-ok-circle account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'green'}"></span> <em class="small">Resolved account name</em></span>
<span id="accountInvalid" class="account-feedback-message"><span class="glyphicon glyphicon-remove-circle account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'red'}"></span> <em class="small">Cannot resolve account name</em></span>
<span id="accountUnknown" class="account-feedback-message"><span class="glyphicon glyphicon-warning-sign account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'orange'}"></span> <em class="small">Valid account name is not a user or Active Directory access is limited</em></span>
</div>
}
@helper AddNewEditDialog(DataContext dataContext, string passwordRequirementsRegex, string passwordRequirementsError)
{
<div class="col-md-12">
@Raw(dataContext.AddInputField<UserAccount>("Name", fieldLabel: "Name" + AccountValidationFeedback().ToString().Trim(), customDataBinding: "attr: {'acct-valid': validateAccount($data), 'autocomplete': 'off'}", initialFocus: true))
<div class="panel panel-default" data-bind="css: {'panel-readonly': $root.recordMode()===RecordMode.View, 'panel-primary': UseDBAuthentication}">
<div class="panel-heading">
@* Adding actual UseADAuthentication field check-box, but hidden, to pick-up any model based field initializers *@
<div style="display: none">
@Raw(dataContext.AddCheckBoxField<UserAccount>("UseADAuthentication"))
</div>
@* Adding radio buttons bound to virtual UseDBAuthentication that models negated state of UseADAuthentication field for simplified dependency field initialization *@
<div class="row">
<div class="col-md-8">
<div class="row">
<div class="col-md-4">
<label class="radio-inline"><input type="radio" name="radioUseDBAuthentication" data-bind="checkedValue: false, checked: UseDBAuthentication, attr: {'disabled': ($root.recordMode()===RecordMode.View ? true : undefined)}"> Active Directory User</label>
</div>
<div class="col-md-8">
<label class="radio-inline"><input type="radio" name="radioUseDBAuthentication" data-bind="checkedValue: true, checked: UseDBAuthentication, attr: {'disabled': ($root.recordMode()===RecordMode.View ? true : undefined)}"> Database User</label>
</div>
</div>
</div>
<div class="col-md-4">
</div>
</div>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div data-bind="visible: UseDBAuthentication">
@Raw(dataContext.AddInputField<UserAccount>("Password", "password", dependencyFieldName: "UseDBAuthentication"))
</div>
@Raw(dataContext.AddInputField<UserAccount>("FirstName"))
@Raw(dataContext.AddInputField<UserAccount>("LastName"))
</div>
<div class="col-md-6">
<div data-bind="visible: UseDBAuthentication">
@Raw(dataContext.AddInputField<UserAccount>("Phone", dependencyFieldName: "UseDBAuthentication"))
</div>
@Raw(dataContext.AddInputField<UserAccount>("Email"))
<div data-bind="visible: UseDBAuthentication">
@Raw(dataContext.AddDateField<UserAccount>("ChangePasswordOn", dependencyFieldName: "UseDBAuthentication"))
@{
dataContext.AddPageDefinedFieldInitialization("Password", "\"\"",
/* RegEx Pattern: */ passwordRequirementsRegex,
/* Error Message: */ passwordRequirementsError);
dataContext.AddPageDefinedFieldInitialization("ChangePasswordOn", "(new Date()).addDays(90)");
}
</div>
</div>
</div>
</div>
</div>
<div class="pull-right">
@Raw(dataContext.AddCheckBoxField<UserAccount>("LockedOut"))
</div>
</div>
}
@Html.RenderResource("GSF.Web.Model.Views.PagedViewModel.cshtml")
<div id="editRolesDialog" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Edit Roles for <span id="currentUserName"></span>:</h4>
</div>
<div class="modal-body">
<div class="modal-body auto-height text-center">
<form role="form">
<input type="hidden" id="currentUserID" />
<div class="center-block">
<div id="loadingRoles">Loading role assignments <span class="glyphicon glyphicon-refresh glyphicon-spin"></span><hr class="full-break" /></div>
<table class="table-condensed table-center">
@foreach (ApplicationRole role in dataContext.Table<ApplicationRole>().QueryRecords("Name", new RecordRestriction("NodeID={0}", SecurityHub.DefaultNodeID))) {
<tr>
<td class="text-left valign-middle">
<div class="checkbox" style="margin: 0">
<label for="@role.ID"><input type="checkbox" id="@role.ID"> @role.Description (@role.Name)</label>
</div>
</td>
</tr>
}
</table>
<br />
<div class="text-center">
@* ReSharper disable once Html.IdNotResolved *@
<a href="@ViewBag.HelpTarget#Roles" target="_blank">@applicationName Role Definitions</a>
</div>
</div>
</form>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" data-dismiss="modal" id="saveRolesButton" data-bind="enable: canEdit()">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
@section Scripts {
<script>
"use strict";
@Raw(dataContext.RenderViewModelConfiguration<UserAccount, SecurityHub>(ViewBag))
var AccountState = {
Invalid: 0,
Valid: 1,
Unknown: 2,
Resolving: 3
}
function lookupAccountName(record) {
if (viewModel.dataHubIsConnected()) {
securityHub.sidToAccountName(record.Name).done(function(accountName) {
record.AccountName(accountName);
});
}
}
function validateAccount(observableRecord) {
showAccountNameValidityFeedback(AccountState.Resolving);
if (observableRecord.UseDBAuthentication == null || observableRecord.UseDBAuthentication()) {
hideAccountNameValidityFeedback();
} else {
if (viewModel.dataHubIsConnected()) {
const accountName = observableRecord.Name();
if (isEmpty(accountName)) {
showAccountNameValidityFeedback(AccountState.Invalid);
} else {
securityHub.userNameToSID(accountName).done(function(sid) {
if (accountName !== sid && accountName.countOccurrences("\\") < 2) {
securityHub.isUserSID(sid).done(function(isUser) {
showAccountNameValidityFeedback(isUser ? AccountState.Valid : AccountState.Unknown);
});
} else {
showAccountNameValidityFeedback(AccountState.Invalid);
}
});
}
} else {
hideAccountNameValidityFeedback();
}
}
return undefined;
}
var messages = [];
var nextMessageName = undefined;
var showingMessage = false;
messages.push("resolvingAccount");
messages.push("accountValid");
messages.push("accountInvalid");
messages.push("accountUnknown");
function showFeedbackMessage(messageName) {
if (showingMessage) {
nextMessageName = messageName;
} else {
showingMessage = true;
hideAccountNameValidityFeedback(messageName);
if (messageName == null)
showingMessage = false;
else
$("#" + messageName).fadeIn(function () {
showingMessage = false;
if (nextMessageName != undefined) {
messageName = nextMessageName;
nextMessageName = undefined;
showFeedbackMessage(messageName);
}
});
}
}
function hideAccountNameValidityFeedback(messageName) {
for (let i = 0; i < messages.length; i++) {
if (messages[i] !== messageName)
$("#" + messages[i]).hide();
}
}
function showAccountNameValidityFeedback(accountState) {
switch (accountState) {
case AccountState.Valid:
showFeedbackMessage("accountValid");
break;
case AccountState.Invalid:
showFeedbackMessage("accountInvalid");
break;
case AccountState.Unknown:
showFeedbackMessage("accountUnknown");
break;
case AccountState.Resolving:
showFeedbackMessage("resolvingAccount");
break;
}
}
$(viewModel).on("pageRecordsQueried", function(event, records) {
// Add a virtual observable field called "AccountName" that will resolve SID values to account names
for (var i = 0; i < records.length; i++) {
records[i].AccountName = ko.observable("...");
lookupAccountName(records[i]);
}
});
$(viewModel).on("newRecord", function(event, newRecord) {
// Make sure new records get virtual field
newRecord.AccountName = ko.observable("");
});
$(viewModel).on("beforeEdit", function(event, observableRecord) {
// Create a virtual UseDBAuthentication field that is negated state of actual UseADAuthentication field
observableRecord.UseDBAuthentication = ko.observable(!observableRecord.UseADAuthentication());
// Load human readable account name into Name field
if (observableRecord.AccountName)
observableRecord.Name(observableRecord.AccountName());
@* Cannot decrypt a password hash, have to clear it and force re-entry *@
if (!observableRecord.UseADAuthentication())
observableRecord.Password(null);
});
$(viewModel).on("beforeSave", function(event, observableRecord, promises) {
// Set state of actual field to be negated state of virtual field
observableRecord.UseADAuthentication(!observableRecord.UseDBAuthentication());
// Remove virtual observable fields before serialization for record updates
delete observableRecord.UseDBAuthentication;
delete observableRecord.AccountName;
// Push promise to convert user name back to SID before updating record
if (viewModel.dataHubIsConnected() && observableRecord.UseADAuthentication()) {
promises.push($.Deferred(function(deferred) {
securityHub.userNameToSID(observableRecord.Name()).done(function(sid) {
observableRecord.Name(sid);
deferred.resolve();
});
}).promise());
}
});
function openRolesEditor(record) {
$("#currentUserID").text(record.ID);
$("#currentUserName").text(record.AccountName());
$("#editRolesDialog input:checkbox").prop("checked", false);
if (viewModel.dataHubIsConnected()) {
securityHub.queryApplicationRoles().done(function(roles) {
var evaluations = roles.length;
if (evaluations > 0)
$("#loadingRoles").show();
for (var i = 0; i < roles.length; i++) {
securityHub.userIsInRole(record.ID, roles[i].ID).done(function(roleID) {
return function(result) {
$("#" + roleID).prop("checked", result);
evaluations--;
if (evaluations <= 0)
$("#loadingRoles").hide();
};
}(roles[i].ID));
}
$("#editRolesDialog").modal("show");
});
}
}
$(function() {
$("#editRolesDialog").modal({ show: false, backdrop: "static" });
$("#saveRolesButton").click(function() {
if (viewModel.dataHubIsConnected()) {
const userID = $("#currentUserID").text();
const roles = $("#editRolesDialog input:checkbox");
for (var i = 0; i < roles.length; i++) {
const element = $(roles[i]);
const roleID = element.attr("id");
if (element.prop("checked")) {
securityHub.addUserToRole(userID, roleID).fail(function(error) {
showErrorMessage(error, null, true);
});
} else {
securityHub.removeUserFromRole(userID, roleID).fail(function(error) {
showErrorMessage(error, null, true);
});
}
}
}
});
});
</script>
}
@{
ViewBag.StyleSheetsSection = RenderSection("StyleSheets").ToString();
ViewBag.ScriptsSection = RenderSection("Scripts").ToString();
}