From fb5db213cda2ccc986f140e4a29959b0f3521454 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 25 Feb 2015 15:43:10 -0500 Subject: [PATCH] password field changes and configuration fixes * create a new sensitive input type to replace password * added a new input configuration for the sensitive type called hasShowInputButton (this shows a button to the left of the field that toggles the display of plain text) * updated the credentials form configuration * removed confirm password boxes for *entering* sensitve information * updated the Keyphrase label * fixed a bug in the credentials controller's ask function, which did not check to see if a confirmation input was defined or not --- awx/ui/static/js/app.js | 1 - awx/ui/static/js/controllers/Credentials.js | 37 ++-- awx/ui/static/js/forms/Credentials.js | 108 ++---------- awx/ui/static/js/forms/Users.js | 4 +- awx/ui/static/js/helpers/Credentials.js | 4 +- awx/ui/static/js/shared/directives.js | 74 ++++---- awx/ui/static/js/shared/form-generator.js | 181 +++++++++++++++++++- 7 files changed, 255 insertions(+), 154 deletions(-) diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 13bec9fed3..85fdeda856 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -44,7 +44,6 @@ import {AdminsList} from 'tower/controllers/Admins'; import {UsersList, UsersAdd, UsersEdit} from 'tower/controllers/Users'; import {TeamsList, TeamsAdd, TeamsEdit} from 'tower/controllers/Teams'; import {PermissionsAdd, PermissionsList, PermissionsEdit} from 'tower/controllers/Permissions'; - import 'tower/shared/RestServices'; import 'tower/shared/api-loader'; import 'tower/shared/form-generator'; diff --git a/awx/ui/static/js/controllers/Credentials.js b/awx/ui/static/js/controllers/Credentials.js index aca4a0899d..8ce0ff3c1c 100644 --- a/awx/ui/static/js/controllers/Credentials.js +++ b/awx/ui/static/js/controllers/Credentials.js @@ -259,14 +259,23 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log, $r // Respond to 'Ask at runtime?' checkbox $scope.ask = function (fld, associated) { - if ($scope[fld + '_ask']) { + console.log("got here"); + debugger; + if ($scope[fld] === 'ASK') { + $scope[fld + "_ask"] = true; + } + else if ($scope[fld + '_ask']) { $scope[fld] = 'ASK'; - $scope[associated] = ''; - $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + if (associated !== "undefined") { + $scope[associated] = ''; + $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + } } else { $scope[fld] = ''; - $scope[associated] = ''; - $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + if (associated !== "undefined") { + $scope[associated] = ''; + $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + } } }; @@ -306,7 +315,7 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log, $ function setAskCheckboxes() { var fld, i; for (fld in form.fields) { - if (form.fields[fld].type === 'password' && $scope[fld] === 'ASK') { + if (form.fields[fld].type === 'sensitive' && $scope[fld] === 'ASK') { // turn on 'ask' checkbox for password fields with value of 'ASK' $("#" + fld + "-clear-btn").attr("disabled", "disabled"); $scope[fld + '_ask'] = true; @@ -546,15 +555,17 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log, $ // Respond to 'Ask at runtime?' checkbox $scope.ask = function (fld, associated) { if ($scope[fld + '_ask']) { - $("#" + fld + "-clear-btn").attr("disabled", "disabled"); - $scope[fld] = 'ASK'; - $scope[associated] = ''; - $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + $scope[fld] = 'ASK' + if (associated !== "undefined") { + $scope[associated] = ''; + $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + } } else { - $("#" + fld + "-clear-btn").removeAttr("disabled"); $scope[fld] = ''; - $scope[associated] = ''; - $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + if (associated !== "undefined") { + $scope[associated] = 'ASK'; + $scope[form.name + '_form'][associated].$setValidity('awpassmatch', true); + } } }; diff --git a/awx/ui/static/js/forms/Credentials.js b/awx/ui/static/js/forms/Credentials.js index ca928faad8..c918b1ae48 100644 --- a/awx/ui/static/js/forms/Credentials.js +++ b/awx/ui/static/js/forms/Credentials.js @@ -141,7 +141,7 @@ export default }, secret_key: { label: 'Secret Key', - type: 'password', + type: 'sensitive', ngShow: "kind.value == 'aws'", awRequiredWhen: { variable: "aws_required", @@ -150,6 +150,7 @@ export default autocomplete: false, ask: false, clear: false, + hasShowInputButton: true, apiField: 'passwowrd' }, "host": { @@ -206,7 +207,7 @@ export default }, "api_key": { label: 'API Key', - type: 'password', + type: 'sensitive', ngShow: "kind.value == 'rax'", awRequiredWhen: { variable: "rackspace_required", @@ -214,34 +215,19 @@ export default }, autocomplete: false, ask: false, + hasShowInputButton: true, clear: false, - // apiField: 'passwowrd' }, "password": { label: 'Password', - type: 'password', + type: 'sensitive', ngShow: "kind.value == 'scm' || kind.value == 'vmware'", addRequired: false, editRequired: false, - ngChange: "clearPWConfirm('password_confirm')", ask: false, clear: false, - associated: 'password_confirm', - autocomplete: false, - awRequiredWhen: { - variable: "password_required", - init: false - } - }, - "password_confirm": { - label: 'Confirm Password', - type: 'password', - ngShow: "kind.value == 'scm' || kind.value == 'vmware'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'password', autocomplete: false, + hasShowInputButton: true, awRequiredWhen: { variable: "password_required", init: false @@ -249,24 +235,12 @@ export default }, "ssh_password": { label: 'Password', // formally 'SSH Password' - type: 'password', + type: 'sensitive', ngShow: "kind.value == 'ssh'", - ngChange: "clearPWConfirm('ssh_password_confirm')", addRequired: false, editRequired: false, ask: true, - clear: true, - associated: 'ssh_password_confirm', - autocomplete: false - }, - "ssh_password_confirm": { - label: 'Confirm Password', // formally 'Confirm SSH password' - type: 'password', - ngShow: "kind.value == 'ssh'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'ssh_password', + hasShowInputButton: true, autocomplete: false }, "ssh_key_data": { @@ -291,25 +265,14 @@ export default dataContainer: "body" }, "ssh_key_unlock": { - label: 'Key Password', - type: 'password', + label: 'Private Key Passphrase', + type: 'sensitive', ngShow: "kind.value == 'ssh' || kind.value == 'scm'", addRequired: false, editRequired: false, - ngChange: "clearPWConfirm('ssh_key_unlock_confirm')", - associated: 'ssh_key_unlock_confirm', ask: true, + hasShowInputButton: true, askShow: "kind.value == 'ssh'", // Only allow ask for machine credentials - clear: true - }, - "ssh_key_unlock_confirm": { - label: 'Confirm Key Password', - type: 'password', - ngShow: "kind.value == 'ssh' || kind.value == 'scm'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'ssh_key_unlock' }, "login_method": { label: "Privilege Escalation Credentials", @@ -342,24 +305,12 @@ export default }, "sudo_password": { label: 'Sudo Password', - type: 'password', + type: 'sensitive', ngShow: "kind.value == 'ssh' && login_method == 'sudo'", addRequired: false, editRequired: false, - ngChange: "clearPWConfirm('sudo_password_confirm')", ask: true, - clear: true, - associated: 'sudo_password_confirm', - autocomplete: false - }, - "sudo_password_confirm": { - label: 'Confirm Sudo Password', - type: 'password', - ngShow: "kind.value == 'ssh' && login_method == 'sudo'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'sudo_password', + hasShowInputButton: true, autocomplete: false }, "su_username": { @@ -372,24 +323,12 @@ export default }, "su_password": { label: 'Su Password', - type: 'password', + type: 'sensitive', ngShow: "kind.value == 'ssh' && login_method == 'su'", addRequired: false, editRequired: false, - ngChange: "clearPWConfirm('su_password_confirm')", ask: true, - clear: true, - associated: 'su_password_confirm', - autocomplete: false - }, - "su_password_confirm": { - label: 'Confirm Su Password', - type: 'password', - ngShow: "kind.value == 'ssh' && login_method == 'su'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'su_password', + hasShowInputButton: true, autocomplete: false }, "project": { @@ -408,27 +347,14 @@ export default editRequired: false, autocomplete: false }, - "vault_password": { label: "Vault Password", - type: 'password', + type: 'sensitive', ngShow: "kind.value == 'ssh'", addRequired: false, editRequired: false, - ngChange: "clearPWConfirm('vault_password_confirm')", ask: true, - clear: true, - associated: 'vault_password_confirm', - autocomplete: false - }, - "vault_password_confirm": { - label: "Confirm Vault Password", - type: 'password', - ngShow: "kind.value == 'ssh'", - addRequired: false, - editRequired: false, - awPassMatch: true, - associated: 'vault_password', + hasShowInputButton: true, autocomplete: false } }, diff --git a/awx/ui/static/js/forms/Users.js b/awx/ui/static/js/forms/Users.js index 4d76ac4fd0..3d88c49a9f 100644 --- a/awx/ui/static/js/forms/Users.js +++ b/awx/ui/static/js/forms/Users.js @@ -83,7 +83,7 @@ export default }, password: { label: 'Password', - type: 'password', + type: 'sensitive', ngShow: 'ldap_user == false', addRequired: true, editRequired: false, @@ -93,7 +93,7 @@ export default }, password_confirm: { label: 'Confirm Password', - type: 'password', + type: 'sensitive', ngShow: 'ldap_user == false', addRequired: false, editRequired: false, diff --git a/awx/ui/static/js/helpers/Credentials.js b/awx/ui/static/js/helpers/Credentials.js index 12f54457d5..9d4c6eaec7 100644 --- a/awx/ui/static/js/helpers/Credentials.js +++ b/awx/ui/static/js/helpers/Credentials.js @@ -35,7 +35,7 @@ angular.module('CredentialsHelper', ['Utilities']) scope.aws_required = false; scope.email_required = false; scope.rackspace_required = false; - scope.sshKeyDataLabel = 'SSH Private Key'; + scope.sshKeyDataLabel = 'Private Key'; scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE) scope.key_required = false; // JT -- doing the same for key and project scope.project_required = false; @@ -59,7 +59,7 @@ angular.module('CredentialsHelper', ['Utilities']) scope.aws_required = false; scope.email_required = false; scope.rackspace_required = false; - scope.sshKeyDataLabel = 'SSH Private Key'; + scope.sshKeyDataLabel = 'Private Key'; scope.username_required = false; // JT-- added username_required b/c mutliple 'kinds' need username to be required (GCE) scope.key_required = false; // JT -- doing the same for key and project scope.project_required = false; diff --git a/awx/ui/static/js/shared/directives.js b/awx/ui/static/js/shared/directives.js index 4e82557d69..9fa121b43c 100644 --- a/awx/ui/static/js/shared/directives.js +++ b/awx/ui/static/js/shared/directives.js @@ -403,46 +403,48 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job * default placement to the left and delay to the config setting. */ .directive('awToolTip', ['$sce', function($sce) { - return function(scope, element, attrs) { - var delay = (attrs.delay !== undefined && attrs.delay !== null) ? attrs.delay : ($AnsibleConfig) ? $AnsibleConfig.tooltip_delay : {show: 500, hide: 100}, - placement; - if (attrs.awTipPlacement) { - placement = attrs.awTipPlacement; - } - else { - placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left'; - } + return { + link: function(scope, element, attrs) { + var delay = (attrs.delay !== undefined && attrs.delay !== null) ? attrs.delay : ($AnsibleConfig) ? $AnsibleConfig.tooltip_delay : {show: 500, hide: 100}, + placement; + if (attrs.awTipPlacement) { + placement = attrs.awTipPlacement; + } + else { + placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left'; + } - $(element).on('hidden.bs.tooltip', function( ) { - // TB3RC1 is leaving behind tooltip
elements. This will remove them - // after a tooltip fades away. If not, they lay overtop of other elements and - // honk up the page. - $('.tooltip').each(function() { - $(this).remove(); + $(element).on('hidden.bs.tooltip', function( ) { + // TB3RC1 is leaving behind tooltip
elements. This will remove them + // after a tooltip fades away. If not, they lay overtop of other elements and + // honk up the page. + $('.tooltip').each(function() { + $(this).remove(); + }); }); - }); - attrs.awToolTip = attrs.awToolTip.replace(//g, ">"); - attrs.awToolTip = $sce.getTrustedHtml(attrs.awToolTip); - $(element).tooltip({ - placement: placement, - delay: delay, - html: true, - title: attrs.awToolTip, - container: 'body', - trigger: 'hover focus' - }); - - if (attrs.tipWatch) { - // Add dataTipWatch: 'variable_name' - scope.$watch(attrs.tipWatch, function(newVal, oldVal) { - if (newVal !== oldVal) { - // Where did fixTitle come from?: - // http://stackoverflow.com/questions/9501921/change-twitter-bootstrap-tooltip-content-on-click - $(element).tooltip('hide').attr('data-original-title', newVal).tooltip('fixTitle'); - } + attrs.awToolTip = attrs.awToolTip.replace(//g, ">"); + attrs.awToolTip = $sce.getTrustedHtml(attrs.awToolTip); + $(element).tooltip({ + placement: placement, + delay: delay, + html: true, + title: attrs.awToolTip, + container: 'body', + trigger: 'hover focus' }); + + if (attrs.tipWatch) { + // Add dataTipWatch: 'variable_name' + scope.$watch(attrs.tipWatch, function(newVal, oldVal) { + if (newVal !== oldVal) { + // Where did fixTitle come from?: + // http://stackoverflow.com/questions/9501921/change-twitter-bootstrap-tooltip-content-on-click + $(element).tooltip('hide').attr('data-original-title', newVal).tooltip('fixTitle'); + } + }); + } } }; }]) diff --git a/awx/ui/static/js/shared/form-generator.js b/awx/ui/static/js/shared/form-generator.js index e7a5f0b965..53582e7d5a 100644 --- a/awx/ui/static/js/shared/form-generator.js +++ b/awx/ui/static/js/shared/form-generator.js @@ -94,7 +94,7 @@ * | spinner | true or false. If true, adds aw-spinner directive. Optionally add min and max attributes to control the range of allowed values. | * | type | String containing one of the following types defined in buildField: alertblock, hidden, text, password, email, textarea, select, number, checkbox, checkbox_group, radio, radio_group, lookup, custom. | * | trueValue | For radio buttons and checkboxes. Value to set the model to when the checkbox or radio button is selected. | - * + * | hasShowInputButton (sensitive type only) | This creates a button next to the input that toggles the input as text and password types. | * The form object contains a buttons object for defining any buttons to be included in the generated HTML. Generally all forms will have a Reset and a Submit button. If no buttons should be generated define buttons as an empty object, or set the showButtons option to false. * * The icon used for the button is determined by SelectIcon() found in lib/ansible/generator-helpers.js. @@ -197,6 +197,11 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', ListGenerat this.scope = element.scope(); } + for (fld in form.fields) { + this.scope[fld + '_field'] = form.fields[fld]; + this.scope[fld + '_field'].name = fld; + } + $compile(element)(this.scope); if (!options.html) { @@ -662,16 +667,16 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', ListGenerat html += "\" "; } html += (field.labelNGClass) ? "ng-class=\"" + field.labelNGClass + "\" " : ""; - html += "for=\"" + fld + '">'; + html += "for=\"" + fld + '">\n'; html += (field.icon) ? Icon(field.icon) : ""; if (field.labelBind) { - html += ""; + html += "\t\t\n\t\t"; } else { - html += "" + field.label + ""; + html += "\t\t\n\t\t\t" + field.label + "\n\t\t"; } html += (field.awPopOver && !field.awPopOverRight) ? Attr(field, 'awPopOver', fld) : ""; - html += (field.hintText) ? " Hint: " + field.hintText + "" : ""; - html += "\n"; + html += (field.hintText) ? "\n\t\t\n\t\t\t\n\t\t\t\n\t\t\tHint: " + field.hintText + "\n\t\t" : ""; + html += "\n\t\n"; } return html; } @@ -704,7 +709,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', ListGenerat } if ((!field.readonly) || (field.readonly && options.mode === 'edit')) { - html += "
-1) { + $(buttonId).html(""); + $(inputId).attr("type", "text"); + } else { + $(buttonId).html("ABC"); + $(inputId).attr("type", "password"); + } + } + html += "\
\n"; + // TODO: make it so that the button won't show up if the mode is edit, hasShowInputButton !== true, and there are no contents in the field. + html += "\n"; + html += "\n"; + // html += "\n
\n"; + // + // } + + if (field.ask) { + html += "
\n"; + // } + + // Add error messages + if ((options.mode === 'add' && field.addRequired) || (options.mode === 'edit' && field.editRequired) || + field.awRequiredWhen) { + html += "
\nPlease enter a value.\n
\n"; + } + if (field.type === "email") { + html += "
\nPlease enter a valid email address.\n
\n"; + } + if (field.awPassMatch) { + html += "
\nThis value does not match the password you entered previously. Please confirm that password.\n
\n"; + } + if (field.awValidUrl) { + html += "
\nPlease enter a URL that begins with ssh, http or https. The URL may not contain the '@' character.\n
\n"; + } + + html += "
\n
\n"; + + if (field.chkPass) { + // complexity error + html += "
Please enter a stronger password (see strength bar below).
\n"; + + // progress bar + html += "
\n"; + html += "
\n"; + html += "
\n"; + html += "
\n"; + + // help panel + html += HelpCollapse({ + hdr: 'Password Complexity', + content: "

A password with reasonable strength is required. As you type the password " + + "a progress bar will measure the strength. Sufficient strength is reached when the bar turns green.

" + + "

Password strength is judged using the following:

" + + "
    " + + "
  • Minimum 8 characters in length
  • \n" + + "
  • Contains a sufficient combination of the following items:\n" + + "
      \n" + + "
    • UPPERCASE letters
    • \n" + + "
    • Numbers
    • \n" + + "
    • Symbols _!@#$%^&*()
    • \n" + + "
    \n" + + "
  • \n" + + "
\n", + idx: this.accordion_count++, + show: null + }); + html += "
\n"; + } + + // Add help panel(s) + html += (field.helpCollapse) ? this.buildHelpCollapse(field.helpCollapse) : ''; + } + //textarea fields if (field.type === 'textarea') { @@ -1524,8 +1689,6 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', ListGenerat html += "
\n"; // accordion body } - //console.log(html); - return html; },