forked from extern/egroupware
* Etemplate: Password widget improvements - suggest password button, added as custom field type (stored encrypted)
This commit is contained in:
parent
d83c567703
commit
aab5415873
@ -82,7 +82,8 @@ class admin_customfields
|
|||||||
'search' => 'set get_rows, get_title and id_field, or use @path to read options from a file in EGroupware directory',
|
'search' => 'set get_rows, get_title and id_field, or use @path to read options from a file in EGroupware directory',
|
||||||
'select' => 'each value is a line like id[=label], or use @path to read options from a file in EGroupware directory',
|
'select' => 'each value is a line like id[=label], or use @path to read options from a file in EGroupware directory',
|
||||||
'radio' => 'each value is a line like id[=label], or use @path to read options from a file in EGroupware directory',
|
'radio' => 'each value is a line like id[=label], or use @path to read options from a file in EGroupware directory',
|
||||||
'button' => 'each value is a line like label=[javascript]'
|
'button' => 'each value is a line like label=[javascript]',
|
||||||
|
'password'=> 'set length=# for minimum password length, strength=# for password strength'
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,6 +93,7 @@ class admin_customfields
|
|||||||
public static $type_attribute_flags = array(
|
public static $type_attribute_flags = array(
|
||||||
'text' => array('cf_len' => true, 'cf_rows' => true),
|
'text' => array('cf_len' => true, 'cf_rows' => true),
|
||||||
'float' => array('cf_len' => true),
|
'float' => array('cf_len' => true),
|
||||||
|
'passwd'=> array('cf_len' => true, 'cf_rows' => false, 'cf_values' => true),
|
||||||
'label' => array('cf_values' => true),
|
'label' => array('cf_values' => true),
|
||||||
'select' => array('cf_len' => false, 'cf_rows' => true, 'cf_values' => true),
|
'select' => array('cf_len' => false, 'cf_rows' => true, 'cf_values' => true),
|
||||||
'date' => array('cf_len' => true, 'cf_rows' => false, 'cf_values' => true),
|
'date' => array('cf_len' => true, 'cf_rows' => false, 'cf_values' => true),
|
||||||
|
@ -354,6 +354,12 @@ var et2_customfields_list = /** @class */ (function (_super) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
et2_customfields_list.prototype._setup_passwd = function (field_name, field, attrs) {
|
||||||
|
// No label on the widget itself
|
||||||
|
delete (attrs.label);
|
||||||
|
attrs['viewable'] = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
et2_customfields_list.prototype._setup_ajax_select = function (field_name, field, attrs) {
|
et2_customfields_list.prototype._setup_ajax_select = function (field_name, field, attrs) {
|
||||||
var attributes = ['get_rows', 'get_title', 'id_field', 'template'];
|
var attributes = ['get_rows', 'get_title', 'id_field', 'template'];
|
||||||
if (field.values) {
|
if (field.values) {
|
||||||
|
@ -480,6 +480,14 @@ export class et2_customfields_list extends et2_valueWidget implements et2_IDetac
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
_setup_passwd( field_name, field, attrs)
|
||||||
|
{
|
||||||
|
// No label on the widget itself
|
||||||
|
delete (attrs.label);
|
||||||
|
attrs['viewable'] = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
_setup_ajax_select( field_name, field, attrs)
|
_setup_ajax_select( field_name, field, attrs)
|
||||||
{
|
{
|
||||||
const attributes = ['get_rows', 'get_title', 'id_field', 'template'];
|
const attributes = ['get_rows', 'get_title', 'id_field', 'template'];
|
||||||
|
307
api/js/etemplate/et2_widget_password.ts
Normal file
307
api/js/etemplate/et2_widget_password.ts
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
/**
|
||||||
|
* EGroupware eTemplate2 - JS Textbox object
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
* @package etemplate
|
||||||
|
* @subpackage api
|
||||||
|
* @link http://www.egroupware.org
|
||||||
|
* @author Andreas Stöckel
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*egw:uses
|
||||||
|
/vendor/bower-asset/jquery/dist/jquery.js;
|
||||||
|
et2_core_inputWidget;
|
||||||
|
et2_core_valueWidget;
|
||||||
|
*/
|
||||||
|
|
||||||
|
import './et2_core_common';
|
||||||
|
import {ClassWithAttributes} from "./et2_core_inheritance";
|
||||||
|
import {et2_createWidget, et2_register_widget, WidgetConfig} from "./et2_core_widget";
|
||||||
|
import {et2_valueWidget} from './et2_core_valueWidget'
|
||||||
|
import {et2_inputWidget} from './et2_core_inputWidget'
|
||||||
|
import {et2_button} from './et2_widget_button'
|
||||||
|
import {et2_textbox} from "./et2_widget_textbox";
|
||||||
|
import {et2_dialog} from "./et2_widget_dialog";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class which implements the "textbox" XET-Tag
|
||||||
|
*
|
||||||
|
* @augments et2_inputWidget
|
||||||
|
*/
|
||||||
|
export class et2_password extends et2_textbox
|
||||||
|
{
|
||||||
|
static readonly _attributes : any = {
|
||||||
|
"autocomplete": {
|
||||||
|
"name": "Autocomplete",
|
||||||
|
"type": "string",
|
||||||
|
"default": "Off",
|
||||||
|
"description": "Whether or not browser should autocomplete that field: 'on', 'off', 'default' (use attribute from form). Default value is set to off."
|
||||||
|
},
|
||||||
|
"viewable": {
|
||||||
|
"name": "Viewable",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Allow password to be shown"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
name: "Suggest password",
|
||||||
|
type: "integer",
|
||||||
|
default: 16,
|
||||||
|
description: "Suggest password length (0 for off)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly DEFAULT_LENGTH = 16;
|
||||||
|
wrapper : JQuery;
|
||||||
|
private suggest_button: et2_button;
|
||||||
|
private show_button: et2_button;
|
||||||
|
|
||||||
|
// The password is stored encrypted server side, and passed encrypted.
|
||||||
|
// This flag is for if we've decrypted the password to show it already
|
||||||
|
private encrypted : boolean = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor(_parent, _attrs? : WidgetConfig, _child? : object)
|
||||||
|
{
|
||||||
|
// Call the inherited constructor
|
||||||
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_password._attributes, _child || {}));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
createInputWidget()
|
||||||
|
{
|
||||||
|
this.wrapper = jQuery(document.createElement("div"))
|
||||||
|
.addClass("et2_password");
|
||||||
|
this.input = jQuery(document.createElement("input"))
|
||||||
|
|
||||||
|
this.input.attr("type", "password");
|
||||||
|
// Make autocomplete default value off for password field
|
||||||
|
// seems browsers not respecting 'off' anymore and started to
|
||||||
|
// implement a new key called "new-password" considered as switching
|
||||||
|
// autocomplete off.
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
|
||||||
|
if (this.options.autocomplete === "" || this.options.autocomplete == "off") this.options.autocomplete = "new-password";
|
||||||
|
|
||||||
|
if(this.options.size) {
|
||||||
|
this.set_size(this.options.size);
|
||||||
|
}
|
||||||
|
if(this.options.blur) {
|
||||||
|
this.set_blur(this.options.blur);
|
||||||
|
}
|
||||||
|
if(this.options.readonly) {
|
||||||
|
this.set_readonly(true);
|
||||||
|
}
|
||||||
|
this.input.addClass("et2_textbox")
|
||||||
|
.appendTo(this.wrapper);
|
||||||
|
this.setDOMNode(this.wrapper[0]);
|
||||||
|
if(this.options.value)
|
||||||
|
{
|
||||||
|
this.set_value(this.options.value);
|
||||||
|
}
|
||||||
|
if (this.options.onkeypress && typeof this.options.onkeypress == 'function')
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
this.input.on('keypress', function(_ev)
|
||||||
|
{
|
||||||
|
return self.options.onkeypress.call(this, _ev, self);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.input.on('change', function() {
|
||||||
|
this.encrypted = false;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
// Show button is needed from start as you can't turn viewable on via JS
|
||||||
|
let attrs = {
|
||||||
|
class: "show_hide",
|
||||||
|
image: "visibility",
|
||||||
|
onclick: this.toggle_visibility.bind(this),
|
||||||
|
statustext: this.egw().lang("Show password")
|
||||||
|
};
|
||||||
|
if(this.options.viewable)
|
||||||
|
{
|
||||||
|
this.show_button = <et2_button>et2_createWidget("button", attrs, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputNode()
|
||||||
|
{
|
||||||
|
return this.input[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the parent set_id method to manuipulate the input DOM node
|
||||||
|
*
|
||||||
|
* @param {type} _value
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
set_id(_value)
|
||||||
|
{
|
||||||
|
super.set_id(_value);
|
||||||
|
|
||||||
|
// Remove the name attribute inorder to affect autocomplete="off"
|
||||||
|
// for no password save. ATM seems all browsers ignore autocomplete for
|
||||||
|
// input field inside the form
|
||||||
|
if (this.options.autocomplete === "off") this.input.removeAttr('name');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether or not the password is allowed to be shown in clear text.
|
||||||
|
*
|
||||||
|
* @param viewable
|
||||||
|
*/
|
||||||
|
set_viewable(viewable: boolean)
|
||||||
|
{
|
||||||
|
this.options.viewable = viewable;
|
||||||
|
|
||||||
|
if(viewable)
|
||||||
|
{
|
||||||
|
jQuery('.show_hide', this.wrapper).show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jQuery('.show_hide', this.wrapper).hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on or off the suggest password button.
|
||||||
|
*
|
||||||
|
* When clicked, a password of the set length will be generated.
|
||||||
|
*
|
||||||
|
* @param length Length of password to generate. 0 to disable.
|
||||||
|
*/
|
||||||
|
set_suggest(length: number)
|
||||||
|
{
|
||||||
|
if(typeof length !== "number")
|
||||||
|
{
|
||||||
|
length = typeof length === "string" ? parseInt(length) : (length ? et2_password.DEFAULT_LENGTH : 0);
|
||||||
|
}
|
||||||
|
this.options.suggest = length;
|
||||||
|
|
||||||
|
if(length && !this.suggest_button)
|
||||||
|
{
|
||||||
|
let attrs = {
|
||||||
|
class: "generate_password",
|
||||||
|
image: "generate_password",
|
||||||
|
onclick: this.suggest_password.bind(this),
|
||||||
|
statustext: this.egw().lang("Suggest password")
|
||||||
|
};
|
||||||
|
this.suggest_button = <et2_button> et2_createWidget("button", attrs, this);
|
||||||
|
}
|
||||||
|
if(length)
|
||||||
|
{
|
||||||
|
jQuery('.suggest', this.wrapper).show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jQuery('.suggest', this.wrapper).hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the password is viewable, toggle the visibility.
|
||||||
|
* If the password is still encrypted, we'll ask for the user's password then have the server decrypt it.
|
||||||
|
*
|
||||||
|
* @param on
|
||||||
|
*/
|
||||||
|
toggle_visibility(on : boolean | undefined)
|
||||||
|
{
|
||||||
|
if(typeof on !== "boolean")
|
||||||
|
{
|
||||||
|
on = this.input.attr("type") == "password";
|
||||||
|
}
|
||||||
|
if(!this.options.viewable)
|
||||||
|
{
|
||||||
|
this.input.attr("type", "password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(this.show_button)
|
||||||
|
{
|
||||||
|
this.show_button.set_image(this.egw().image(on ? 'visibility_off' : 'visibility'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are not encrypted or not showing it, we're done
|
||||||
|
if(!this.encrypted || !on)
|
||||||
|
{
|
||||||
|
this.input.attr("type",on ? "textbox" : "password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Need username & password to decrypt
|
||||||
|
let callback = function(button, user_password)
|
||||||
|
{
|
||||||
|
if(button == et2_dialog.CANCEL_BUTTON)
|
||||||
|
{
|
||||||
|
return this.toggle_visibility(false);
|
||||||
|
}
|
||||||
|
let request = egw.json(
|
||||||
|
"EGroupware\\Api\\Etemplate\\Widget\\Password::ajax_decrypt",
|
||||||
|
[user_password, this.options.value],
|
||||||
|
function(decrypted)
|
||||||
|
{
|
||||||
|
if(decrypted)
|
||||||
|
{
|
||||||
|
this.encrypted = false;
|
||||||
|
this.input.val(decrypted);
|
||||||
|
this.input.attr("type", "textbox");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.set_validation_error(this.egw().lang("invalid password"));
|
||||||
|
window.setTimeout(function() {
|
||||||
|
this.set_validation_error(false);
|
||||||
|
}.bind(this), 2000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this,true,this
|
||||||
|
).sendRequest();
|
||||||
|
}.bind(this);
|
||||||
|
let prompt = et2_dialog.show_prompt(
|
||||||
|
callback,
|
||||||
|
this.egw().lang("Enter your password"),
|
||||||
|
this.egw().lang("Authenticate")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make the password prompt a password field
|
||||||
|
prompt.div.on("load", function() {
|
||||||
|
jQuery(prompt.template.widgetContainer.getWidgetById('value').getInputNode())
|
||||||
|
.attr("type","password");
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask the server for a password suggestion
|
||||||
|
*/
|
||||||
|
suggest_password()
|
||||||
|
{
|
||||||
|
// They need to see the suggestion
|
||||||
|
this.encrypted = false;
|
||||||
|
this.options.viewable = true;
|
||||||
|
this.toggle_visibility(true);
|
||||||
|
|
||||||
|
let suggestion = "Suggestion";
|
||||||
|
let request = egw.json("EGroupware\\Api\\Etemplate\\Widget\\Password::ajax_suggest",
|
||||||
|
[this.options.suggest],
|
||||||
|
function(suggestion) {
|
||||||
|
this.encrypted = false;
|
||||||
|
this.input.val(suggestion);
|
||||||
|
},
|
||||||
|
this,true,this
|
||||||
|
).sendRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy()
|
||||||
|
{
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue()
|
||||||
|
{
|
||||||
|
return this.input.val();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
et2_register_widget(et2_password, [ "passwd"]);
|
@ -64,16 +64,6 @@ var et2_textbox = /** @class */ (function (_super) {
|
|||||||
else {
|
else {
|
||||||
this.input = jQuery(document.createElement("input"));
|
this.input = jQuery(document.createElement("input"));
|
||||||
switch (this.options.type) {
|
switch (this.options.type) {
|
||||||
case "passwd":
|
|
||||||
this.input.attr("type", "password");
|
|
||||||
// Make autocomplete default value off for password field
|
|
||||||
// seems browsers not respecting 'off' anymore and started to
|
|
||||||
// impelement a new key called "new-password" considered as switching
|
|
||||||
// autocomplete off.
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
|
|
||||||
if (this.options.autocomplete === "" || this.options.autocomplete == "off")
|
|
||||||
this.options.autocomplete = "new-password";
|
|
||||||
break;
|
|
||||||
case "hidden":
|
case "hidden":
|
||||||
this.input.attr("type", "hidden");
|
this.input.attr("type", "hidden");
|
||||||
break;
|
break;
|
||||||
@ -102,21 +92,6 @@ var et2_textbox = /** @class */ (function (_super) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* Override the parent set_id method to manuipulate the input DOM node
|
|
||||||
*
|
|
||||||
* @param {type} _value
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
et2_textbox.prototype.set_id = function (_value) {
|
|
||||||
_super.prototype.set_id.call(this, _value);
|
|
||||||
// Remove the name attribute inorder to affect autocomplete="off"
|
|
||||||
// for no password save. ATM seems all browsers ignore autocomplete for
|
|
||||||
// input field inside the form
|
|
||||||
if (this.options.type === "passwd"
|
|
||||||
&& this.options.autocomplete === "off")
|
|
||||||
this.input.removeAttr('name');
|
|
||||||
};
|
|
||||||
et2_textbox.prototype.destroy = function () {
|
et2_textbox.prototype.destroy = function () {
|
||||||
var node = this.getInputNode();
|
var node = this.getInputNode();
|
||||||
if (node)
|
if (node)
|
||||||
@ -268,12 +243,6 @@ var et2_textbox = /** @class */ (function (_super) {
|
|||||||
"default": et2_no_init,
|
"default": et2_no_init,
|
||||||
"description": "Perl regular expression eg. '/^[0-9][a-f]{4}$/i'"
|
"description": "Perl regular expression eg. '/^[0-9][a-f]{4}$/i'"
|
||||||
},
|
},
|
||||||
"autocomplete": {
|
|
||||||
"name": "Autocomplete",
|
|
||||||
"type": "string",
|
|
||||||
"default": "",
|
|
||||||
"description": "Weither or not browser should autocomplete that field: 'on', 'off', 'default' (use attribute from form). Default value for type password is set to off."
|
|
||||||
},
|
|
||||||
onkeypress: {
|
onkeypress: {
|
||||||
name: "onKeypress",
|
name: "onKeypress",
|
||||||
type: "js",
|
type: "js",
|
||||||
@ -285,7 +254,7 @@ var et2_textbox = /** @class */ (function (_super) {
|
|||||||
return et2_textbox;
|
return et2_textbox;
|
||||||
}(et2_core_inputWidget_1.et2_inputWidget));
|
}(et2_core_inputWidget_1.et2_inputWidget));
|
||||||
exports.et2_textbox = et2_textbox;
|
exports.et2_textbox = et2_textbox;
|
||||||
et2_core_widget_1.et2_register_widget(et2_textbox, ["textbox", "passwd", "hidden"]);
|
et2_core_widget_1.et2_register_widget(et2_textbox, ["textbox", "hidden"]);
|
||||||
/**
|
/**
|
||||||
* et2_textbox_ro is the dummy readonly implementation of the textbox.
|
* et2_textbox_ro is the dummy readonly implementation of the textbox.
|
||||||
*
|
*
|
||||||
|
@ -72,12 +72,6 @@ export class et2_textbox extends et2_inputWidget implements et2_IResizeable
|
|||||||
"default": et2_no_init,
|
"default": et2_no_init,
|
||||||
"description": "Perl regular expression eg. '/^[0-9][a-f]{4}$/i'"
|
"description": "Perl regular expression eg. '/^[0-9][a-f]{4}$/i'"
|
||||||
},
|
},
|
||||||
"autocomplete": {
|
|
||||||
"name": "Autocomplete",
|
|
||||||
"type": "string",
|
|
||||||
"default": "",
|
|
||||||
"description": "Weither or not browser should autocomplete that field: 'on', 'off', 'default' (use attribute from form). Default value for type password is set to off."
|
|
||||||
},
|
|
||||||
onkeypress: {
|
onkeypress: {
|
||||||
name: "onKeypress",
|
name: "onKeypress",
|
||||||
type: "js",
|
type: "js",
|
||||||
@ -125,15 +119,6 @@ export class et2_textbox extends et2_inputWidget implements et2_IResizeable
|
|||||||
this.input = jQuery(document.createElement("input"));
|
this.input = jQuery(document.createElement("input"));
|
||||||
switch(this.options.type)
|
switch(this.options.type)
|
||||||
{
|
{
|
||||||
case "passwd":
|
|
||||||
this.input.attr("type", "password");
|
|
||||||
// Make autocomplete default value off for password field
|
|
||||||
// seems browsers not respecting 'off' anymore and started to
|
|
||||||
// impelement a new key called "new-password" considered as switching
|
|
||||||
// autocomplete off.
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
|
|
||||||
if (this.options.autocomplete === "" || this.options.autocomplete == "off") this.options.autocomplete = "new-password";
|
|
||||||
break;
|
|
||||||
case "hidden":
|
case "hidden":
|
||||||
this.input.attr("type", "hidden");
|
this.input.attr("type", "hidden");
|
||||||
break;
|
break;
|
||||||
@ -166,23 +151,6 @@ export class et2_textbox extends et2_inputWidget implements et2_IResizeable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Override the parent set_id method to manuipulate the input DOM node
|
|
||||||
*
|
|
||||||
* @param {type} _value
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
set_id(_value)
|
|
||||||
{
|
|
||||||
super.set_id(_value);
|
|
||||||
|
|
||||||
// Remove the name attribute inorder to affect autocomplete="off"
|
|
||||||
// for no password save. ATM seems all browsers ignore autocomplete for
|
|
||||||
// input field inside the form
|
|
||||||
if (this.options.type === "passwd"
|
|
||||||
&& this.options.autocomplete === "off") this.input.removeAttr('name');
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy()
|
destroy()
|
||||||
{
|
{
|
||||||
var node = this.getInputNode();
|
var node = this.getInputNode();
|
||||||
@ -312,7 +280,7 @@ export class et2_textbox extends et2_inputWidget implements et2_IResizeable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
et2_register_widget(et2_textbox, ["textbox", "passwd", "hidden"]);
|
et2_register_widget(et2_textbox, ["textbox", "hidden"]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* et2_textbox_ro is the dummy readonly implementation of the textbox.
|
* et2_textbox_ro is the dummy readonly implementation of the textbox.
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
et2_widget_entry;
|
et2_widget_entry;
|
||||||
et2_widget_textbox;
|
et2_widget_textbox;
|
||||||
et2_widget_number;
|
et2_widget_number;
|
||||||
|
et2_widget_password;
|
||||||
et2_widget_url;
|
et2_widget_url;
|
||||||
et2_widget_selectbox;
|
et2_widget_selectbox;
|
||||||
et2_widget_checkbox;
|
et2_widget_checkbox;
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
et2_widget_entry;
|
et2_widget_entry;
|
||||||
et2_widget_textbox;
|
et2_widget_textbox;
|
||||||
et2_widget_number;
|
et2_widget_number;
|
||||||
|
et2_widget_password;
|
||||||
et2_widget_url;
|
et2_widget_url;
|
||||||
et2_widget_selectbox;
|
et2_widget_selectbox;
|
||||||
et2_widget_checkbox;
|
et2_widget_checkbox;
|
||||||
|
@ -32,6 +32,7 @@ class Customfields extends Transformer
|
|||||||
*/
|
*/
|
||||||
protected static $cf_types = array(
|
protected static $cf_types = array(
|
||||||
'text' => 'Text',
|
'text' => 'Text',
|
||||||
|
'passwd' => 'Password',
|
||||||
'int' => 'Integer',
|
'int' => 'Integer',
|
||||||
'float' => 'Float',
|
'float' => 'Float',
|
||||||
'label' => 'Label',
|
'label' => 'Label',
|
||||||
@ -322,6 +323,9 @@ class Customfields extends Transformer
|
|||||||
|
|
||||||
case 'text':
|
case 'text':
|
||||||
break;
|
break;
|
||||||
|
case 'passwd':
|
||||||
|
$widget->attrs['viewable'] = true;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (substr($type, 0, 7) !== 'select-' && $type != 'ajax_select') break;
|
if (substr($type, 0, 7) !== 'select-' && $type != 'ajax_select') break;
|
||||||
|
142
api/src/Etemplate/Widget/Password.php
Normal file
142
api/src/Etemplate/Widget/Password.php
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* EGroupware - eTemplate serverside textbox widget
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
* @package api
|
||||||
|
* @subpackage etemplate
|
||||||
|
* @link http://www.egroupware.org
|
||||||
|
* @author Ralf Becker <RalfBecker@outdoor-training.de>
|
||||||
|
* @copyright 2002-16 by RalfBecker@outdoor-training.de
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace EGroupware\Api\Etemplate\Widget;
|
||||||
|
|
||||||
|
use EGroupware\Api\Etemplate;
|
||||||
|
use EGroupware\Api\Auth;
|
||||||
|
use EGroupware\Api\Mail\Credentials;
|
||||||
|
use XMLReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* eTemplate password widget
|
||||||
|
*
|
||||||
|
* passwords are not sent to client, instead a number of asterisks is send and replaced again!
|
||||||
|
*
|
||||||
|
* User must authenticate before password is decrypted & sent
|
||||||
|
*/
|
||||||
|
class Password extends Etemplate\Widget\Textbox
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string|XMLReader $xml string with xml or XMLReader positioned on the element to construct
|
||||||
|
* @throws Api\Exception\WrongParameter
|
||||||
|
*/
|
||||||
|
public function __construct($xml)
|
||||||
|
{
|
||||||
|
parent::__construct($xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up what we know on the server side.
|
||||||
|
*
|
||||||
|
* @param string $cname
|
||||||
|
* @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont'
|
||||||
|
*/
|
||||||
|
public function beforeSendToClient($cname, array $expand=null)
|
||||||
|
{
|
||||||
|
$form_name = self::form_name($cname, $this->id, $expand);
|
||||||
|
$value =& self::get_array(self::$request->content, $form_name);
|
||||||
|
if (!empty($value))
|
||||||
|
{
|
||||||
|
$preserv =& self::get_array(self::$request->preserv, $form_name, true);
|
||||||
|
$preserv = (string)$value;
|
||||||
|
|
||||||
|
if (!empty($value) && array_key_exists('viewable', $this->attrs) && $this->attrs['viewable'] == 'false')
|
||||||
|
{
|
||||||
|
$value = str_repeat('*', strlen($preserv));
|
||||||
|
}
|
||||||
|
//$value = str_repeat('*', strlen($preserv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate input
|
||||||
|
*
|
||||||
|
* We check if the password is unchanged or if the new value is the decrypted
|
||||||
|
* version of the current value to avoid unneeded changes
|
||||||
|
*
|
||||||
|
* @param string $cname current namespace
|
||||||
|
* @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont'
|
||||||
|
* @param array $content
|
||||||
|
* @param array &$validated=array() validated content
|
||||||
|
* @param array $expand=array values for keys 'c', 'row', 'c_', 'row_', 'cont'
|
||||||
|
*/
|
||||||
|
public function validate($cname, array $expand, array $content, &$validated=array())
|
||||||
|
{
|
||||||
|
$form_name = self::form_name($cname, $this->id, $expand);
|
||||||
|
|
||||||
|
if (!$this->is_readonly($cname, $form_name))
|
||||||
|
{
|
||||||
|
$value = $value_in = self::get_array($content, $form_name);
|
||||||
|
|
||||||
|
// Non-viewable passwords are not transmitted back to client (just asterisks)
|
||||||
|
// therefore we need to replace it again with preserved value
|
||||||
|
$preserv = self::get_array(self::$request->preserv, $form_name);
|
||||||
|
if ($value == str_repeat('*', strlen($preserv)))
|
||||||
|
{
|
||||||
|
$value = $preserv;
|
||||||
|
}
|
||||||
|
else if ($value_in == Credentials::decrypt(array('cred_password' => $preserv,'cred_pw_enc' => Credentials::SYSTEM_AES)))
|
||||||
|
{
|
||||||
|
// Don't change if they submitted the decrypted version
|
||||||
|
$value = $preserv;
|
||||||
|
}
|
||||||
|
else if ($value_in !== $preserv)
|
||||||
|
{
|
||||||
|
// Store encrypted
|
||||||
|
$encryption = null;
|
||||||
|
$value = Credentials::encrypt($value_in, 0, $encryption);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((string)$value === '' && $this->attrs['needed'])
|
||||||
|
{
|
||||||
|
self::set_validation_error($form_name,lang('Field must not be empty !!!'),'');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($value))
|
||||||
|
{
|
||||||
|
self::set_array($validated, $form_name, $value);
|
||||||
|
//error_log(__METHOD__."() $form_name: ".array2string($value_in).' --> '.array2string($value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suggest a password
|
||||||
|
*/
|
||||||
|
public static function ajax_suggest($size = 12)
|
||||||
|
{
|
||||||
|
$password = Auth::randomstring($size, false);
|
||||||
|
|
||||||
|
$response = \EGroupware\Api\Json\Response::get();
|
||||||
|
$response->data($password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give up the password
|
||||||
|
*/
|
||||||
|
public static function ajax_decrypt($user_password, $password)
|
||||||
|
{
|
||||||
|
$response = \EGroupware\Api\Json\Response::get();
|
||||||
|
$decrypted = '';
|
||||||
|
|
||||||
|
if($GLOBALS['egw']->auth->authenticate($GLOBALS['egw_info']['user']['account_lid'],$user_password))
|
||||||
|
{
|
||||||
|
$decrypted = Credentials::decrypt(array('cred_password' => $password,'cred_pw_enc' => Credentials::SYSTEM_AES));
|
||||||
|
}
|
||||||
|
$response->data($decrypted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Password', array('passwd'));
|
@ -23,7 +23,6 @@ use XMLReader;
|
|||||||
* - float
|
* - float
|
||||||
* - hidden
|
* - hidden
|
||||||
* - colorpicker
|
* - colorpicker
|
||||||
* - passwd (passwords are never send back to client, instead a number of asterisks is send and replaced again!)
|
|
||||||
* sub-types are either passed to constructor or set via 'type' attribute!
|
* sub-types are either passed to constructor or set via 'type' attribute!
|
||||||
*/
|
*/
|
||||||
class Textbox extends Etemplate\Widget
|
class Textbox extends Etemplate\Widget
|
||||||
@ -82,18 +81,7 @@ class Textbox extends Etemplate\Widget
|
|||||||
*/
|
*/
|
||||||
public function beforeSendToClient($cname, array $expand=null)
|
public function beforeSendToClient($cname, array $expand=null)
|
||||||
{
|
{
|
||||||
// to NOT transmit passwords back to client, we need to store (non-empty) value in preserv
|
|
||||||
if ($this->attrs['type'] == 'passwd' || $this->type == 'passwd')
|
|
||||||
{
|
|
||||||
$form_name = self::form_name($cname, $this->id, $expand);
|
|
||||||
$value =& self::get_array(self::$request->content, $form_name);
|
|
||||||
if (!empty($value))
|
|
||||||
{
|
|
||||||
$preserv =& self::get_array(self::$request->preserv, $form_name, true);
|
|
||||||
if (true) $preserv = (string)$value;
|
|
||||||
$value = str_repeat('*', strlen($preserv));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,17 +125,6 @@ class Textbox extends Etemplate\Widget
|
|||||||
|
|
||||||
$value = $value_in = self::get_array($content, $form_name);
|
$value = $value_in = self::get_array($content, $form_name);
|
||||||
|
|
||||||
// passwords are not transmitted back to client (just asterisks)
|
|
||||||
// therefore we need to replace it again with preserved value
|
|
||||||
if (($this->attrs['type'] == 'passwd' || $this->type == 'passwd'))
|
|
||||||
{
|
|
||||||
$preserv = self::get_array(self::$request->preserv, $form_name);
|
|
||||||
if ($value == str_repeat('*', strlen($preserv)))
|
|
||||||
{
|
|
||||||
$value = $preserv;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((string)$value === '' && $this->attrs['needed'])
|
if ((string)$value === '' && $this->attrs['needed'])
|
||||||
{
|
{
|
||||||
self::set_validation_error($form_name,lang('Field must not be empty !!!'),'');
|
self::set_validation_error($form_name,lang('Field must not be empty !!!'),'');
|
||||||
@ -199,4 +176,4 @@ class Textbox extends Etemplate\Widget
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Textbox', array('textbox','text','int','integer','float','passwd','hidden','colorpicker','hidden'));
|
Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Textbox', array('textbox','text','int','integer','float','hidden','colorpicker','hidden'));
|
||||||
|
@ -393,7 +393,7 @@ class Credentials
|
|||||||
* @param int& $pw_enc on return encryption used
|
* @param int& $pw_enc on return encryption used
|
||||||
* @return string encrypted password
|
* @return string encrypted password
|
||||||
*/
|
*/
|
||||||
protected static function encrypt($password, $account_id, &$pw_enc)
|
public static function encrypt($password, $account_id, &$pw_enc)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return self::encrypt_openssl_aes($password, $account_id, $pw_enc);
|
return self::encrypt_openssl_aes($password, $account_id, $pw_enc);
|
||||||
@ -524,7 +524,7 @@ class Credentials
|
|||||||
* @throws Api\Exception\WrongParameter
|
* @throws Api\Exception\WrongParameter
|
||||||
* @throws Api\Exception\AssertionFailed if neither OpenSSL nor MCrypt extension available
|
* @throws Api\Exception\AssertionFailed if neither OpenSSL nor MCrypt extension available
|
||||||
*/
|
*/
|
||||||
protected static function decrypt(array $row, $key=null)
|
public static function decrypt(array $row, $key=null)
|
||||||
{
|
{
|
||||||
// empty/unset passwords only give warnings ...
|
// empty/unset passwords only give warnings ...
|
||||||
if (empty($row['cred_password'])) return '';
|
if (empty($row['cred_password'])) return '';
|
||||||
|
@ -97,6 +97,30 @@ div.et2_hbox > div {
|
|||||||
background: inherit;
|
background: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password widget
|
||||||
|
*/
|
||||||
|
.et2_password {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
padding-right: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.et2_password > input {
|
||||||
|
border: none;
|
||||||
|
margin-right: -23px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
.et2_password .show_hide {
|
||||||
|
margin-left: -5px;
|
||||||
|
}
|
||||||
|
.et2_password > * {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.et2_password > img {
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Placeholder widget - used for un-implemented widgets
|
* Placeholder widget - used for un-implemented widgets
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user