441 lines · 22.3 KB · Text
RawDownload
@*******************************************************************************************************//  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&nbsp;AD&nbsp;User", "text-center" },        new[] { "CreatedOn", "Date&nbsp;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>&nbsp;<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>&nbsp;<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>&nbsp;<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>&nbsp;<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)}">&nbsp;Active&nbsp;Directory&nbsp;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)}">&nbsp;Database&nbsp;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">&times;</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&nbsp;&nbsp;<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();}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t