diff --git a/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/angular/passwordForm.js b/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/angular/passwordForm.js index 09ea459abdd..a769af75bc4 100644 --- a/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/angular/passwordForm.js +++ b/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/angular/passwordForm.js @@ -89,10 +89,7 @@ passwordModule.controller("passwordController", function($scope) { $scope.current.isScript = isScript; } defaultHash = currentHash - if (current === undefined) { - currentAction = "change" - $scope.action = "change"; - } + if (currentAction === "change") { $scope.newPassword.password=current; $scope.newPassword.hash = currentHash; @@ -114,7 +111,7 @@ passwordModule.controller("passwordController", function($scope) { } $scope.hashes = hashes; $scope.displayedPass = $scope.current.password; - $scope.action = currentAction; + $scope.action = currentAction === undefined ? "change" : currentAction; $scope.otherPasswords = otherPasswords; $scope.canBeDeleted = canBeDeleted; $scope.scriptEnabled = scriptEnabled; diff --git a/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/directive-inputs.js b/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/directive-inputs.js index 50d5fd2722d..5e403ad7a63 100644 --- a/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/directive-inputs.js +++ b/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/directive-inputs.js @@ -36,6 +36,7 @@ // Dict containing all directive input data var directiveInputs = new Object(); +var passwordForms = new Object(); function newInputText(id, value, prefix, featureEnabled){ // New input data @@ -93,4 +94,333 @@ function updateResult(element){ var newValue = inputModel.feature ? (inputModel.prefix + value) : value; var inputResult = $("#" + formId + "-value"); inputResult.val(newValue); +} + + +// === Password field +class PasswordForm { + // Current password, and 'other passwords' defined if there is a 'slave' field, displayedPass is the pass we currently display + current = + { password : undefined + , hash : "md5" + , show : false + , isScript : false + }; + + otherPasswords = undefined; + displayedPass = undefined; + + // New password if we want to change it + newPassword = + { password : undefined + , hash : "md5" + , show : false + , isScript : false + }; + + // Possible hashes defined for this password input + hashes = {}; + #defaultHash = undefined; + + // Default value + action = "keep"; + formType = "withHashes"; + canBeDeleted = false; + scriptEnabled = false; + + // Result (the value that will be sent back to Lift form), initialized as undefined, but will be update directly and on every change of action and the new password (so will be changed on init) + result = undefined; + + formId = ""; + + constructor(currentValue, currentHash, isScript, currentAction, hashes, otherPasswords, canBeDeleted, scriptEnabled, previousHash, previousAlgo, previousIsScript, formId) { + + this.defaultHash = currentHash; + + if (currentAction === "keep") { + this.current.password = currentValue; + this.current.hash = currentHash; + this.current.isScript = isScript; + + } else if (currentAction === "change") { + this.newPassword.password = currentValue; + this.newPassword.hash = currentHash; + this.newPassword.isScript = isScript; + + if (isScript) { + this.formType = "script"; + } else if (currentHash === "pre-hashed") { + this.formType = "preHashed"; + } else if (currentHash === "plain") { + this.formType = "clearText"; + } else { + this.formType = "withHashes"; + } + + this.current.password = previousPass; + this.current.hash = previousHash; + this.current.isScript = previousIsScript; + } + + this.hashes = hashes; + this.displayedPass = this.current.password; + this.action = currentAction === undefined ? "change" : currentAction; + this.otherPasswords = otherPasswords; + this.canBeDeleted = canBeDeleted; + this.scriptEnabled = scriptEnabled; + + this.formId = formId; + } + + updateView() { + updatePasswordFormView(this.formId); + } + + updateResult() { + // Keep and delete, use current password as base + let result = this.action === "change" ? Object.assign({}, this.newPassword) : Object.assign({}, this.current) + + if (result.hash === "plain" && result.isScript) { + result.password = "evaljs:" + result.password; + } + + // Action will allow to differentiate between 'delete' and 'keep' and is used for 'change' too + result.action = this.action + this.result = JSON.stringify(result); + + // Update html + this.updateView(); + } + + displayCurrentHash() { + if (this.current.hash === "plain") { + return "Clear text password"; + } else if (this.current.hash === "pre-hashed") { + return "Pre hashed password"; + } else { + return this.hashes[this.current.hash] + " hash"; + } + } + + changeDisplayPass(password) { + this.displayedPass = password; + this.updateView(); + } + + passwordType(formType) { + this.newPassword.isScript = false; + if(formType === "withHashes") { + // If no hash was set put it to default hash + this.newPassword.hash = this.defaultHash; + } else if (formType === "clearText") { + this.newPassword.hash = "plain"; + } else if (formType === "preHashed") { + this.newPassword.hash = "pre-hashed"; + } else if (formType === "script") { + this.newPassword.hash = "plain"; + this.newPassword.isScript = true; + } + this.formType = formType; + + console.log("-- Change form type & update result & view"); + this.updateResult(); + } + + changeAction(action) { + this.action = action; + if (action === "change") { + if (this.current.isScript) { + this.formType = "script"; + this.newPassword = this.current; + } else if (this.current.hash === "pre-hashed") { + this.formType = "preHashed"; + } else if (this.current.hash === "plain") { + this.formType = "clearText"; + } else { + this.formType = "withHashes"; + } + this.newPassword.hash = this.current.hash; + } + console.log("-- Change action & update result & view"); + this.updateResult(); + } + + // Do not display current password if undefined or if you want to delete password + displayCurrent() { + return this.current.password !== undefined && this.action !== 'delete'; + } + + revealPassword(passwd){ + if (passwd === "current"){ + this.current.show = !this.current.show; + }else{ + this.newPassword.show = !this.newPassword.show; + } + this.updateView(); + } +} + + +function initPasswordFormEvents(formId){ + // Get passwordForm data + var passwordForm = passwordForms[formId]; + if (passwordForm === undefined) return false; + + var formContainer = $('#' + formId); + + formContainer.find(".btn.reveal-password").on('click', function(){ + var data = $(this).attr('data-reveal'); + passwordForm.revealPassword(data); + }); + + // Init buttons that change the password type + formContainer.find("[data-form]").on('click', function(){ + var type = $(this).attr('data-form'); + passwordForm.passwordType(type); + }); + + // Init buttons that change the current action + formContainer.find("[data-action]").on('click', function(){ + var action = $(this).attr("data-action"); + passwordForm.changeAction(action); + }); + + // Init passwords dropdown list + var btnToggle = formContainer.find(".dropdown-toggle"); + var ulToggle = formContainer.find(".dropdown-menu"); + if(passwordForm.otherPasswords !== undefined){ + var li = $("
  • "); + var a = $("").text("Default").on('click', function(){ + passwordForm.changeDisplayPass(passwordForm.otherPasswords[passwordForm.current.password]); + }); + li.append(a); + ulToggle.append(li) + for (pwd in passwordForm.otherPasswords){ + li = $("
  • "); + a = $("").text(pwd).on('click', function(){ + passwordForm.changeDisplayPass(passwordForm.otherPasswords[pwd]); + }); + li.append(a); + ulToggle.append(li) + } + }else{ + btnToggle.hide(); + } + + // Update model when password changes + formContainer.find(".toggle-type").on("input", function(){ + var newVal = this.value; + passwordForm.current.password = newVal; + passwordForm.displayedPass = newVal; + console.log("-- Update value: "+ newVal) + }); + +} +function updatePasswordFormView(formId){ + // Get passwordForm data + var passwordForm = passwordForms[formId]; + if (passwordForm === undefined) return false; + + var formContainer = $('#' + formId); + formContainer.find('.action-section').hide(); + + var actionSection = passwordForm.action; + var current = passwordForm.current + + var passwdContainerClass = current.isScript ? ".is-script" : ".is-passwd"; + var passwdContainer = formContainer.find(passwdContainerClass); + + formContainer.find(".current-password").show(); + switch(passwordForm.action){ + case "delete" : + formContainer.find(".current-password").hide(); + break; + + case "change" : + // Update buttons that change the form type + formContainer.find("[data-form]").each(function(){ + var formType = $(this).attr("data-form"); + if(formType !== "script"){ + $(this).hide(); + } + $(this).show(); + var btnClass = passwordForm.formType == formType ? "active" : ""; + $(this).removeClass('active').addClass(btnClass); + }); + + // Display the correct form type + var formClass = "action-"; + formContainer.find('.bloc-action').hide(); + if(passwordForm.formType === "withHashes" || passwordForm.formType === "script"){ + formClass += passwordForm.formType; + } else { + if(passwordForm.formType === "clearText"){ + formContainer.find(".cleartext-reveal").show(); + }else{ + formContainer.find(".cleartext-reveal").hide(); + } + formClass += 'newPassword' + } + formContainer.find(".bloc-action."+formClass).show(); + break; + + default: + if(passwordForm.current.password !== undefined){ + actionSection = "keep"; + var inputPasswd; + passwdContainer.show(); + if(current.isScript){ + formContainer.find(".is-passwd").hide(); + passwdContainer = formContainer.find(".is-script").show(); + + inputPasswd = passwdContainer.find(".toggle-type"); + inputPasswd.val(passwordForm.current.password); + } else { + // Display current hash + formContainer.find("[current-hash]").html(passwordForm.displayCurrentHash()); + + passwdContainer = formContainer.find(".is-passwd").show(); + formContainer.find(".is-script").hide(); + + inputPasswd = passwdContainer.find(".toggle-type"); + inputPasswd.val(passwordForm.displayedPass); + } + }else{ + createErrorNotification("Error while loading password form") + } + } + // Update the action change buttons + var btnChange = passwdContainer.find("[data-action='change']"); + var btnKeep = passwdContainer.find("[data-action='keep'] "); + var btnDelete = passwdContainer.find("[data-action='delete']"); + if(passwordForm.action === "change"){ + btnChange.hide(); + btnKeep.show(); + }else{ + btnChange.show(); + btnKeep.hide(); + } + if(passwordForm.canBeDeleted){ + btnDelete.show(); + }else{ + btnDelete.hide(); + } + + // Display the section according to the current action (change/delete/...) + formContainer.find('.action-section.action-' + actionSection).show(); + + // Update reveal password buttons and show/hide the current password value by changing input type (text/password); + formContainer.find(".reveal-password").each(function(){ + var data = $(this).attr("data-reveal"); + var reveal = data === "current" ? passwordForm.current.show : passwordForm.newPassword.show + var iconClass = reveal ? "glyphicon glyphicon-eye-close" : "glyphicon glyphicon-eye-open" + var inputType = reveal ? "text" : "password"; + $(this).find(".glyphicon").attr("class", iconClass); + if(data === "current"){ + formContainer.find(".current-passwd").attr("type", inputType); + }else{ + formContainer.find(".new-passwd").attr("type", inputType); + } + }); + return true; } \ No newline at end of file diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/model/DirectiveFieldEditors.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/model/DirectiveFieldEditors.scala index 15c2f5003e9..cee911500b0 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/model/DirectiveFieldEditors.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/model/DirectiveFieldEditors.scala @@ -808,7 +808,7 @@ class PasswordField( def toForm = { val hashes = JsObj(algos.filterNot(x => x == PLAIN || x == PreHashed).map(a => (a.prefix, Str(a.name))): _*) val formId = Helpers.nextFuncName - val valueInput = SHtml.text("", s => parseClient(s), ("ng-model", "result"), ("ng-hide", "true")) + val valueInput = SHtml.text("", s => parseClient(s), ("class", "input-result")) val otherPasswords = if (slavesValues().size == 0) "undefined" else JsObj(slavesValues().view.mapValues(Str(_)).toSeq: _*).toJsCmd val (scriptEnabled, isScript, currentValue) = scriptSwitch().getOrElse(Disabled) match { @@ -846,23 +846,25 @@ class PasswordField( val initScript = { Script(OnLoad(JsRaw(s""" - angular.bootstrap("#${formId}", ['password']); - var scope = angular.element($$("#${formId}-controller")).scope(); - scope.$$apply(function(){ - scope.init( - ${currentValue.map(Str(_).toJsCmd).getOrElse("undefined")} - , ${currentAlgo.map(x => Str(x.prefix).toJsCmd).getOrElse("undefined")} - , ${isScript} - , ${Str(currentAction).toJsCmd} - , ${hashes.toJsCmd} - , ${otherPasswords} - , ${canBeDeleted} - , ${scriptEnabled} - , ${prevHash.map(Str(_).toJsCmd).getOrElse("undefined")} - , ${prevAlgo.map(x => Str(x.prefix).toJsCmd).getOrElse("undefined")} - , ${previousScript} - ); - });"""))) + var passwordForm = new PasswordForm( + ${currentValue.map(Str(_).toJsCmd).getOrElse("undefined")} + , ${currentAlgo.map(x => Str(x.prefix).toJsCmd).getOrElse("undefined")} + , ${isScript} + , ${Str(currentAction).toJsCmd} + , ${hashes.toJsCmd} + , ${otherPasswords} + , ${canBeDeleted} + , ${scriptEnabled} + , ${prevHash.map(Str(_).toJsCmd).getOrElse("undefined")} + , ${prevAlgo.map(x => Str(x.prefix).toJsCmd).getOrElse("undefined")} + , ${previousScript} + , "${formId}" + ); + passwordForms["${formId}"] = passwordForm; + console.log(passwordForms); + initPasswordFormEvents("${formId}"); + updatePasswordFormView("${formId}"); + """))) } val form = (".password-section *+" #> valueInput).apply(PasswordField.xml(formId)) ++ initScript diff --git a/webapp/sources/rudder/rudder-web/src/main/style/rudder/rudder-directives.css b/webapp/sources/rudder/rudder-web/src/main/style/rudder/rudder-directives.css index dad72fffb2f..c90a69a5db2 100644 --- a/webapp/sources/rudder/rudder-web/src/main/style/rudder/rudder-directives.css +++ b/webapp/sources/rudder/rudder-web/src/main/style/rudder/rudder-directives.css @@ -286,6 +286,9 @@ .directive-input-group .btn-group.input-feature + textarea.form-control{ border-top-left-radius: 0 !important; } +.password-app .input-result{ + display: none !important; +} /* === OVERRIDE TEMPLATE === */ .main-header.no-header, diff --git a/webapp/sources/rudder/rudder-web/src/main/style/rudder/rudder-main.css b/webapp/sources/rudder/rudder-web/src/main/style/rudder/rudder-main.css index 18275130e7f..96ea1836abc 100644 --- a/webapp/sources/rudder/rudder-web/src/main/style/rudder/rudder-main.css +++ b/webapp/sources/rudder/rudder-web/src/main/style/rudder/rudder-main.css @@ -272,6 +272,10 @@ body > .modal-backdrop.fade.in{ top: calc(50% - 7px); } +.content-wrapper .form-control[readonly] { + background-color: #F8F9FC; + cursor: default; +} /* SPINNERS */ .content-wrapper .btn i.fa-spin { position: absolute; diff --git a/webapp/sources/rudder/rudder-web/src/main/webapp/index.html b/webapp/sources/rudder/rudder-web/src/main/webapp/index.html index c5f57693f5c..95fd4d67c18 100644 --- a/webapp/sources/rudder/rudder-web/src/main/webapp/index.html +++ b/webapp/sources/rudder/rudder-web/src/main/webapp/index.html @@ -37,7 +37,7 @@ Username This field is required - +
    diff --git a/webapp/sources/rudder/rudder-web/src/main/webapp/templates-hidden/components/passwordInput.html b/webapp/sources/rudder/rudder-web/src/main/webapp/templates-hidden/components/passwordInput.html index a0efbe17071..507bc477926 100644 --- a/webapp/sources/rudder/rudder-web/src/main/webapp/templates-hidden/components/passwordInput.html +++ b/webapp/sources/rudder/rudder-web/src/main/webapp/templates-hidden/components/passwordInput.html @@ -1,121 +1,123 @@
    -
    -
    -
    - - +
    + +
    +
    + +
    + + + + + + + + + +
    +
    -
    +
    - - - - + + + +
    - +
    +
    -
    -

    Password will not be managed by Rudder anymore - + +
    +

    Password will not be managed by Rudder anymore +

    -
    + +
    -
    -
    -
    - - + +
    +
    +
    + +
    + + + +
    -
    -
    - -
    - - - This password will be hashed using the {{hashes[newPassword.hash]}} algorithm and stored and distributed only as a hash. - The plain text value entered above will not be stored. - +
    + +
    + + + This password will be hashed using the {{hashes[newPassword.hash]}} algorithm and stored and distributed only as a hash. The plain text value entered above will not be stored. + +
    -
    -
    -
    + +
    -
    - - - - -
    -
    - -
    - This {{formType === 'preHashed' ? "hash" : "password"}} will be stored and distributed verbatim (plain text). - + This {{formType === 'preHashed' ? "hash" : "password"}} will be stored and distributed verbatim (plain text). +
    -
    -
    -
    -
    - -
    - - - You can enter a JavaScript expression here. See documentation here for details.
    - Passwords will be computed for each node during policy generation. To use hashed passwords, make sure to call the rudder.password function in the script above. + +
    +
    +
    + +
    + + + You can enter a JavaScript expression here. See documentation here for details.
    + Passwords will be computed for each node during policy generation. To use hashed passwords, make sure to call the rudder.password function in the script above.
    +
    +
    +
    -
    -
    - + \ No newline at end of file