diff --git a/CHANGELOG.md b/CHANGELOG.md index c7aa45c..712bf18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ ## 1.0 (17th Dec 2013) - - Password change functionality when using DB or LDAP plugin \ No newline at end of file + - Password change functionality when using DB or LDAP plugin + +## 1.1 (19th Dec 2013) + + - Bug fixes for LDAP plugin + +## 1.2 (20th May 2015) + + - Bug fixes for LDAP plugin + - Improved UI + +## 1.3 (6th July 2017) + + - Add config to enable/disable strict password checking + - use encryptionstore to retrieve/store passwords + - move strict password checking to client side \ No newline at end of file diff --git a/LICENSE b/LICENSE.md similarity index 91% rename from LICENSE rename to LICENSE.md index a072de3..4d87ad5 100644 --- a/LICENSE +++ b/LICENSE.md @@ -1,5 +1,5 @@ Zarafa Webapp Password Change Plugin -Copyright (C) 2013 Saket Patel silentsakky@gmail.com +Copyright (C) 2013 - 2017 Saket Patel This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by diff --git a/README.md b/README.md index 825fe92..665167e 100644 --- a/README.md +++ b/README.md @@ -7,32 +7,46 @@ This plugin is largely based on the Passwd plugin by Andreas Brodowski. For his original work check this [link](https://community.zarafa.com/pg/plugins/project/157/developer/dw2412/passwd-plugin) ## How to install -1. If you want to use this plugin with production / debug version of webapp then please download package from [community](https://community.zarafa.com/pg/plugins/project/23147/developer/silentsakky/webapp-password-change) +1. If you want to use this plugin with production / debug version of webapp then please download package from [release page](https://github.com/silentsakky/zarafa-webapp-passwd/releases) 2. If you want to use this plugin with source copy of webapp then you can just download this whole project -3. Extract contents of this plugin to /plugins directory +3. Extract contents of this plugin to /plugins directory, in Ubuntu 16 refers to /usr/share/kopano-webapp 4. Give read permissions to apache for /plugins/passwd directory 5. If you are using LDAP plugin then change PLUGIN_PASSWD_LDAP to true and also set proper values for PLUGIN_PASSWD_LDAP_BASEDN and PLUGIN_PASSWD_LDAP_URI configurations 6. If you are using DB plugin then no need to change anything, default configurations should be fine 5. Restart apache, reload webapp after clearing cache 6. If you want to enable this plugin by default for all users then edit config.php file and change PLUGIN_PASSWD_USER_DEFAULT_ENABLE setting to true + ## How to enable 1. Go to settings section 2. Go to Plugins tab 3. Enable password change plugin and reload webapp -4. Go to Change Password tab of settings section -5. Provide current password and new password -6. Click on apply + ## How to disable 1. Go to settings section 2. Go to Plugins tab 3. Disable password change plugin and reload webapp + +## How to use +1. Go to Change Password tab of settings section +2. Provide current password and new password +3. Click on apply + + ## Notes - Feedback/Bug Reports are welcome -- If anyone is good at creating icons then please help me add a good icon to change password tab (credits will be given) +- thanks to h44z for adding password meter and icon for the plugin -## Todo -- Add password strength meter on client side, so user can create complex passwords -- Check on client side for empty fields \ No newline at end of file + +## Dependencies: +- php ldap extension is required if you are using LDAP plugin +- if you have ubuntu 16 then follow below steps (this should ideally work with all distros) + 1) sudo apt-get install php-ldap + 2) sudo phpenmod ldap + 3) check if ldap extension is enabled using below command + php -i "(command-line 'phpinfo()')" | grep ldap + + +Initially releases of this plugin were maintained in [community](https://community.zarafa.com/pg/plugins/project/23147/developer/silentsakky/webapp-password-change), but now users can download latest builds from [github release page](https://github.com/silentsakky/zarafa-webapp-passwd/releases) \ No newline at end of file diff --git a/build.xml b/build.xml index ee538f7..543e6fb 100644 --- a/build.xml +++ b/build.xml @@ -3,6 +3,7 @@ + @@ -42,6 +43,10 @@ + + + + @@ -156,8 +161,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -193,7 +230,16 @@ + + + + + + + + + diff --git a/builds/passwd-1.0.zip b/builds/passwd-1.0.zip deleted file mode 100644 index 0235122..0000000 Binary files a/builds/passwd-1.0.zip and /dev/null differ diff --git a/config.php b/config.php index 2ad5e02..5b55c11 100644 --- a/config.php +++ b/config.php @@ -2,12 +2,28 @@ /** Enable the passwd plugin for all clients **/ define('PLUGIN_PASSWD_USER_DEFAULT_ENABLE', false); -/** Define zarafa installtion uses LDAP **/ +/** Enable the passwd plugin for all clients **/ +define('PLUGIN_PASSWD_STRICT_CHECK_ENABLE', true); + +/** Define passwd plugin installation uses LDAP **/ define('PLUGIN_PASSWD_LDAP', false); /** Base DN to access LDAP users **/ define('PLUGIN_PASSWD_LDAP_BASEDN', 'dc=example,dc=com'); /** URI to access LDAP server **/ -define('PLUGIN_PASSWD_LDAP_URI', 'localhost'); +define('PLUGIN_PASSWD_LDAP_URI', 'ldap://localhost'); + +/** Use TLS to access LDAP server **/ +define('PLUGIN_PASSWD_LDAP_USE_TLS', true); + +/** Bind DN to access LDAP server (keep empty for anonymous bind) **/ +define('PLUGIN_PASSWD_LDAP_BIND_DN', ""); + +/** Bind password to access LDAP server (keep empty for anonymous bind) **/ +define('PLUGIN_PASSWD_LDAP_BIND_PW', ""); + +/** Set to true if you login with username@tenantname **/ +define('PLUGIN_PASSWD_LOGIN_WITH_TENANT', false); + ?> \ No newline at end of file diff --git a/js/ABOUT.js b/js/ABOUT.js index 8381cbc..3cdb890 100644 --- a/js/ABOUT.js +++ b/js/ABOUT.js @@ -6,7 +6,7 @@ Ext.namespace('Zarafa.plugins.passwd'); * The copyright string holding the copyright notice for the Zarafa passwd Plugin. */ Zarafa.plugins.passwd.ABOUT = "" - + "

Copyright (C) 2013 Saket Patel <silentsakky@gmail.com>

" + + "

Copyright (C) 2013 - 2017 Saket Patel <silentsakky@gmail.com>

" + "

This program is free software: you can redistribute it and/or modify " + "it under the terms of the GNU Affero General Public License as " diff --git a/js/PasswdPlugin.js b/js/PasswdPlugin.js index 40fb570..66116ed 100644 --- a/js/PasswdPlugin.js +++ b/js/PasswdPlugin.js @@ -47,7 +47,7 @@ Zarafa.plugins.passwd.PasswdPlugin = Ext.extend(Zarafa.core.Plugin, { Zarafa.onReady(function() { container.registerPlugin(new Zarafa.core.PluginMetaData({ name : 'passwd', - displayName : _('Password Change Plugin'), + displayName : dgettext("plugin_passwd", 'Password Change Plugin'), about : Zarafa.plugins.passwd.ABOUT, pluginConstructor : Zarafa.plugins.passwd.PasswdPlugin })); diff --git a/js/data/PasswdResponseHandler.js b/js/data/PasswdResponseHandler.js index 0577515..dae1c28 100644 --- a/js/data/PasswdResponseHandler.js +++ b/js/data/PasswdResponseHandler.js @@ -24,13 +24,13 @@ Zarafa.plugins.passwd.data.PasswdResponseHandler = Ext.extend(Zarafa.core.data.A */ doError : function(response) { - var displayMessage = _('An unknown error occurred while changing password.'); + var displayMessage = dgettext("plugin_passwd", 'An unknown error occurred while changing password.'); if(response.info) { displayMessage = response.info.display_message; } - Ext.MessageBox.alert(_('Error'), displayMessage); + Ext.MessageBox.alert(dgettext("plugin_passwd", 'Error'), displayMessage); this.callbackFn.apply(this.scope || this, [ false, response ]); }, @@ -41,14 +41,14 @@ Zarafa.plugins.passwd.data.PasswdResponseHandler = Ext.extend(Zarafa.core.data.A */ doSuccess : function(response) { - var displayMessage = _('Password is changed successfully.'); + var displayMessage = dgettext("plugin_passwd", 'Password is changed successfully.'); if(response.info) { displayMessage = response.info.display_message; } - Ext.MessageBox.alert(_('Success'), displayMessage); + Ext.MessageBox.alert(dgettext("plugin_passwd", 'Success'), displayMessage); this.callbackFn.apply(this.scope || this, [ true, response ]); } -}); \ No newline at end of file +}); diff --git a/js/external/PasswordMeter.js b/js/external/PasswordMeter.js new file mode 100644 index 0000000..7afbf91 --- /dev/null +++ b/js/external/PasswordMeter.js @@ -0,0 +1,192 @@ +Ext.namespace('Ext.ux.form.field'); + +/** + * @class Ext.ux.form.field.PasswordMeter + * @extends Ext.form.TextField + * @xtype ux.passwordmeterfield + * + * @author Christoph Haas + * @version 0.1 + * @license MIT License: http://www.opensource.org/licenses/mit-license.php + * + * This implementation of the password fields shows a nice graph of how + * secure the password is. + * Original implementation for ExtJS 1.1 from http://testcases.pagebakers.com/PasswordMeter/. + */ +Ext.ux.form.field.PasswordMeter = Ext.extend(Ext.form.TextField, { + + /** + * @constructor + * @param {Object} config configuration object + */ + constructor : function(config) + { + config = config || {}; + + Ext.applyIf(config, { + xtype : 'ux.passwordmeterfield', + inputType: 'password', + enableKeyEvents: true + }); + + Ext.ux.form.field.PasswordMeter.superclass.constructor.call(this, config); + }, + + // private + initComponent:function() + { + Ext.ux.form.field.PasswordMeter.superclass.initComponent.apply(this, arguments); + }, + + // private + reset: function() + { + Ext.ux.form.field.PasswordMeter.superclass.reset.call(this); + this.updateMeter(); + }, + + // private + onKeyUp : function(event) + { + Ext.ux.form.field.PasswordMeter.superclass.onKeyUp.call(this); + + this.updateMeter(this.getValue()); + + this.fireEvent('keyup', this, event); + }, + + // private + afterRender: function() + { + Ext.ux.form.field.PasswordMeter.superclass.afterRender.call(this); + + + var width = this.getEl().getWidth(); + var newID = Ext.id(); + this.strengthMeterID = newID; + this.scoreBarID = Ext.id(); + var objMeter = Ext.DomHelper.insertAfter(this.getEl(), { + tag: "div", + 'class': "x-form-strengthmeter", + 'id': this.strengthMeterID, + 'style' : { + width: width + 'px' + } + }); + Ext.DomHelper.append(objMeter, { + tag: "div", + 'class': "x-form-strengthmeter-scorebar", + 'id': this.scoreBarID + }); + + this.fireEvent('afterrender', this); + }, + + /** + * Return the score of the entered password. + * It is a number between 0 and 100 where 100 is a very safe password. + * + * @returns {Number} + */ + getScore : function() + { + return this.calcStrength(this.getValue()); + }, + + /** + * Sets the width of the meter, based on the score + * + * @param {String} val The current password + */ + updateMeter : function(val) + { + var maxWidth, score, scoreWidth, objMeter, scoreBar; + + objMeter = Ext.get(this.strengthMeterID); + scoreBar = Ext.get(this.scoreBarID); + + maxWidth = objMeter.getWidth(); + if (val){ + score = this.calcStrength(val); + scoreWidth = maxWidth - (maxWidth / 100) * score; + scoreBar.applyStyles({margin: "0 0 0 " + (maxWidth - scoreWidth) + "px"}); // move the overlay to the right + scoreBar.setWidth(scoreWidth, false); // downsize the overlay + } else { + scoreBar.applyStyles({margin: "0"}); + scoreBar.setWidth(maxWidth, false); + } + }, + + /** + * Calculates the strength of a password + * + * @param {Object} p The password that needs to be calculated + * @return {int} intScore The strength score of the password + */ + calcStrength: function(p) + { + // PASSWORD LENGTH + var len = p.length, score = len; + + if (len > 0 && len <= 4) { // length 4 or + // less + score += len + } else if (len >= 5 && len <= 7) { + // length between 5 and 7 + score += 6; + } else if (len >= 8 && len <= 15) { + // length between 8 and 15 + score += 12; + } else if (len >= 16) { // length 16 or more + score += 18; + } + + // LETTERS (Not exactly implemented as dictacted above + // because of my limited understanding of Regex) + if (p.match(/[a-z]/)) { + // [verified] at least one lower case letter + score += 1; + } + if (p.match(/[A-Z]/)) { // [verified] at least one upper + // case letter + score += 5; + } + // NUMBERS + if (p.match(/\d/)) { // [verified] at least one + // number + score += 5; + } + if (p.match(/(?:.*?\d){3}/)) { + // [verified] at least three numbers + score += 5; + } + + // SPECIAL CHAR + if (p.match(/[\!,@,#,$,%,\^,&,\*,\?,_,~]/)) { + // [verified] at least one special character + score += 5; + } + // [verified] at least two special characters + if (p.match(/(?:.*?[\!,@,#,$,%,\^,&,\*,\?,_,~]){2}/)) { + score += 5; + } + + // COMBOS + if (p.match(/(?=.*[a-z])(?=.*[A-Z])/)) { + // [verified] both upper and lower case + score += 2; + } + if (p.match(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/)) { + // [verified] both letters and numbers + score += 2; + } + // [verified] letters, numbers, and special characters + if (p.match(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\!,@,#,$,%,\^,&,\*,\?,_,~])/)) { + score += 2; + } + + return Math.min(Math.round(score * 2), 100); + } +}); + +Ext.reg('ux.passwordmeterfield', Ext.ux.form.field.PasswordMeter); \ No newline at end of file diff --git a/js/settings/PasswdPanel.js b/js/settings/PasswdPanel.js index a9c9f70..730d695 100644 --- a/js/settings/PasswdPanel.js +++ b/js/settings/PasswdPanel.js @@ -18,7 +18,7 @@ Zarafa.plugins.passwd.settings.PasswdPanel = Ext.extend(Ext.form.FormPanel, { Ext.applyIf(config, { xtype : 'zarafa.passwdpanel', - labelWidth : 150, + labelWidth : 200, defaults : { width : 200 }, @@ -26,30 +26,34 @@ Zarafa.plugins.passwd.settings.PasswdPanel = Ext.extend(Ext.form.FormPanel, { items : [{ xtype : 'displayfield', name : 'username', - fieldLabel : 'User name' + fieldLabel : dgettext("plugin_passwd", 'User name') }, { xtype : 'textfield', name : 'current_password', - fieldLabel : 'Current password', + ref : 'current_password', + fieldLabel : dgettext("plugin_passwd", 'Current password'), inputType : 'password', + allowBlank : false, listeners : { change : this.onFieldChange, scope : this } }, { - xtype : 'textfield', + xtype : 'ux.passwordmeterfield', name : 'new_password', - fieldLabel : 'New password', - inputType : 'password', + ref : 'new_password', + allowBlank : false, + fieldLabel : dgettext("plugin_passwd", 'New password'), listeners : { change : this.onFieldChange, scope : this } }, { - xtype : 'textfield', + xtype : 'ux.passwordmeterfield', name : 'new_password_repeat', - fieldLabel : 'Retype new password', - inputType : 'password', + allowBlank : false, + ref : 'new_password_repeat', + fieldLabel : dgettext("plugin_passwd", 'Retype new password'), listeners : { change : this.onFieldChange, scope : this @@ -92,4 +96,4 @@ Zarafa.plugins.passwd.settings.PasswdPanel = Ext.extend(Ext.form.FormPanel, { } }); -Ext.reg('zarafa.passwdpanel', Zarafa.plugins.passwd.settings.PasswdPanel); \ No newline at end of file +Ext.reg('zarafa.passwdpanel', Zarafa.plugins.passwd.settings.PasswdPanel); diff --git a/js/settings/SettingsPasswdCategory.js b/js/settings/SettingsPasswdCategory.js index 87f3753..fd73c68 100644 --- a/js/settings/SettingsPasswdCategory.js +++ b/js/settings/SettingsPasswdCategory.js @@ -17,13 +17,16 @@ Zarafa.plugins.passwd.settings.SettingsPasswdCategory = Ext.extend(Zarafa.settin config = config || {}; Ext.applyIf(config, { - title : _('Change Password'), + title : dgettext("plugin_passwd", 'Change Password'), categoryIndex : 9997, + iconCls : 'zarafa-settings-category-passwd', xtype : 'zarafa.settingspasswdcategory', items : [{ xtype : 'zarafa.settingspasswdwidget', settingsContext : config.settingsContext - }] + }, + container.populateInsertionPoint('context.settings.category.passwd', this) + ] }); Zarafa.plugins.passwd.settings.SettingsPasswdCategory.superclass.constructor.call(this, config); diff --git a/js/settings/SettingsPasswdWidget.js b/js/settings/SettingsPasswdWidget.js index ed2debb..7c7b77d 100644 --- a/js/settings/SettingsPasswdWidget.js +++ b/js/settings/SettingsPasswdWidget.js @@ -19,14 +19,9 @@ Zarafa.plugins.passwd.settings.SettingsPasswdWidget = Ext.extend(Zarafa.settings config = config || {}; Ext.applyIf(config, { - height : 175, - width : 400, - title : _('Change Password'), + title : dgettext("plugin_passwd", 'Change Password'), xtype : 'zarafa.settingspasswdwidget', - layout : { - // override from SettingsWidget - type : 'fit' - }, + layout: 'form', items : [{ xtype : 'zarafa.passwdpanel', ref : 'passwdPanel', @@ -51,10 +46,43 @@ Zarafa.plugins.passwd.settings.SettingsPasswdWidget = Ext.extend(Zarafa.settings // listen to savesettings and discardsettings to save/discard delegation data var contextModel = this.settingsContext.getModel(); + this.mon(contextModel, 'beforesavesettings', this.onBeforeSaveSettings, this); this.mon(contextModel, 'savesettings', this.onSaveSettings, this); this.mon(contextModel, 'discardsettings', this.onDiscardSettings, this); }, + /** + * Event handler will be called when {@link Zarafa.settings.SettingsContextModel#beforesavesettings} event is fired. + * This function will validate the formdata. + * + * @private + */ + onBeforeSaveSettings : function() + { + // do some quick checks before submitting + if(this.passwdPanel.new_password.getValue() != this.passwdPanel.new_password_repeat.getValue()) { + Ext.MessageBox.alert(dgettext("plugin_passwd", 'Error'), dgettext("plugin_passwd", 'New passwords do not match.')); + return false; + } else if(Ext.isEmpty(this.passwdPanel.current_password.getValue())) { + Ext.MessageBox.alert(dgettext("plugin_passwd", 'Error'), dgettext("plugin_passwd", 'Current password is empty.')); + return false; + } else if(Ext.isEmpty(this.passwdPanel.new_password.getValue()) || Ext.isEmpty(this.passwdPanel.new_password_repeat.getValue())) { + Ext.MessageBox.alert(dgettext("plugin_passwd", 'Error'), dgettext("plugin_passwd", 'New password is empty.')); + return false; + } else if(!this.passwdPanel.getForm().isValid()) { + Ext.MessageBox.alert(dgettext("plugin_passwd", 'Error'), dgettext("plugin_passwd", 'One or more fields does contain errors.')); + return false; + } else if (container.getSettingsModel().get("zarafa/v1/plugins/passwd/enable_strict_check")) { + // do a quick score check: + if(this.passwdPanel.new_password.getScore() < 70) { + Ext.MessageBox.alert(dgettext("plugin_passwd", 'Error'), dgettext("plugin_passwd", 'Password is weak. Password should contain capital, non-capital letters and numbers. Password should have 8 to 20 characters.')); + return false; + } + } + + return true; + }, + /** * Event handler will be called when {@link Zarafa.settings.SettingsContextModel#savesettings} event is fired. * This will relay this event to {@link Zarafa.plugins.passwd.settings.PasswdPanel PasswdPanel} so it can @@ -71,8 +99,11 @@ Zarafa.plugins.passwd.settings.SettingsPasswdWidget = Ext.extend(Zarafa.settings // send request container.getRequest().singleRequest('passwdmodule', 'save', data, new Zarafa.plugins.passwd.data.PasswdResponseHandler({ - callbackFn : function(success, response) { + callbackFn: function (success, response) { this.ownerCt.hideSavingMask(success); + if(success) { + this.passwdPanel.getForm().reset(); + } }, scope : this })); diff --git a/language/de_DE.UTF-8/LC_MESSAGES/plugin_passwd.po b/language/de_DE.UTF-8/LC_MESSAGES/plugin_passwd.po new file mode 100644 index 0000000..4de5989 --- /dev/null +++ b/language/de_DE.UTF-8/LC_MESSAGES/plugin_passwd.po @@ -0,0 +1,93 @@ +msgid "" +msgstr "" +"Project-Id-Version: zarafa-webapp-plugin-passwd\n" +"POT-Creation-Date: 2014-03-04 13:53+0100\n" +"PO-Revision-Date: 2014-03-04 14:34+0100\n" +"Last-Translator: Robert Scheck \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: de\n" + +# php/class.passwdmodule.php:44: +msgid "No data received." +msgstr "Keine Daten empfangen!" + +# php/class.passwdmodule.php:48: +msgid "User name is empty." +msgstr "Benutzername ist leer!" + +# php/class.passwdmodule.php:52: +msgid "Current password is empty." +msgstr "Aktuelles Passwort ist leer!" + +# php/class.passwdmodule.php:56: +msgid "New password is empty." +msgstr "Neues Passwort ist leer!" + +# php/class.passwdmodule.php:60: +msgid "New passwords do not match." +msgstr "Neue Passwörter stimmen nicht überein!" + +# php/class.passwdmodule.php:137: +# php/class.passwdmodule.php:196: +# js/data/PasswdResponseHandler.js:44: +msgid "Password is changed successfully." +msgstr "Passwort wurde erfolgreich geändert!" + +# php/class.passwdmodule.php:141: +# php/class.passwdmodule.php:200: +msgid "Password is not changed." +msgstr "Passwort wurde nicht geändert!" + +# php/class.passwdmodule.php:144: +# php/class.passwdmodule.php:203: +msgid "" +"Password is weak. Password should contain capital, non-capital letters and " +"numbers. Password should have 8 to 20 characters." +msgstr "" +"Das eingegebene Passwort ist schwach; es sollte große und kleine Buchstaben " +"sowie Ziffern enthalten und zwischen 8 und 20 Zeichen lang sein!" + +# php/class.passwdmodule.php:147: +msgid "Current password does not match." +msgstr "Aktuelles Passwort ist falsch!" + +# js/data/PasswdResponseHandler.js:27: +msgid "An unknown error occurred while changing password." +msgstr "Beim Ändern des Passworts ist ein unbekannter Fehler aufgetreten!" + +# js/data/PasswdResponseHandler.js:33: +msgid "Error" +msgstr "Fehler" + +# js/data/PasswdResponseHandler.js:50: +msgid "Success" +msgstr "Erfolg" + +# js/settings/SettingsPasswdWidget.js:24: +# js/settings/SettingsPasswdCategory.js:20: +msgid "Change Password" +msgstr "Passwort ändern" + +# js/PasswdPlugin.js:50: +msgid "Password Change Plugin" +msgstr "Passwortänderungsplugin" + +# js/settings/PasswdPanel.js:29: +msgid "User name" +msgstr "Benutzername" + +# js/settings/PasswdPanel.js:33: +msgid "Current password" +msgstr "Aktuelles Passwort" + +# js/settings/PasswdPanel.js:42: +msgid "New password" +msgstr "Neues Passwort" + +#js/settings/PasswdPanel.js:51: +msgid "Retype new password" +msgstr "Wiederholung" diff --git a/language/hu_HU.UTF-8/LC_MESSAGES/plugin_passwd.mo b/language/hu_HU.UTF-8/LC_MESSAGES/plugin_passwd.mo new file mode 100644 index 0000000..e9141a1 Binary files /dev/null and b/language/hu_HU.UTF-8/LC_MESSAGES/plugin_passwd.mo differ diff --git a/language/hu_HU.UTF-8/LC_MESSAGES/plugin_passwd.po b/language/hu_HU.UTF-8/LC_MESSAGES/plugin_passwd.po new file mode 100644 index 0000000..4bc504e --- /dev/null +++ b/language/hu_HU.UTF-8/LC_MESSAGES/plugin_passwd.po @@ -0,0 +1,94 @@ +msgid "" +msgstr "" +"Project-Id-Version: zarafa-webapp-plugin-passwd\n" +"POT-Creation-Date: 2014-03-04 13:53+0100\n" +"PO-Revision-Date: 2016-04-08 21:28+0200\n" +"Last-Translator: David Horvath \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: hu\n" +"X-Generator: Poedit 1.8.7.1\n" + +# php/class.passwdmodule.php:44: +msgid "No data received." +msgstr "Nem érkezett adat." + +# php/class.passwdmodule.php:48: +msgid "User name is empty." +msgstr "Felhasználónév mező üres." + +# php/class.passwdmodule.php:52: +msgid "Current password is empty." +msgstr "Jelenlegi jelszó mező üres." + +# php/class.passwdmodule.php:56: +msgid "New password is empty." +msgstr "Új jelszó mező üres." + +# php/class.passwdmodule.php:60: +msgid "New passwords do not match." +msgstr "Az új jelszavak nem egyeznek." + +# php/class.passwdmodule.php:137: +# php/class.passwdmodule.php:196: +# js/data/PasswdResponseHandler.js:44: +msgid "Password is changed successfully." +msgstr "A jelszót sikeresen módosította." + +# php/class.passwdmodule.php:141: +# php/class.passwdmodule.php:200: +msgid "Password is not changed." +msgstr "A jelszó nem módosult." + +# php/class.passwdmodule.php:144: +# php/class.passwdmodule.php:203: +msgid "" +"Password is weak. Password should contain capital, non-capital letters and " +"numbers. Password should have 8 to 20 characters." +msgstr "" +"A jelszó gyenge. Tartalmaznia kell számot, nagy- és kisbetűt. A jelszó " +"minimum 8, maximum 20 karakter legyen." + +# php/class.passwdmodule.php:147: +msgid "Current password does not match." +msgstr "A jelenlegi jelszó nem megfelelő." + +# js/data/PasswdResponseHandler.js:27: +msgid "An unknown error occurred while changing password." +msgstr "Ismeretlen hiba történt a jelszó megváltoztatásakor." + +# js/data/PasswdResponseHandler.js:33: +msgid "Error" +msgstr "Hiba" + +# js/data/PasswdResponseHandler.js:50: +msgid "Success" +msgstr "Sikeres" + +# js/settings/SettingsPasswdWidget.js:24: +# js/settings/SettingsPasswdCategory.js:20: +msgid "Change Password" +msgstr "Jelszó megváltoztatása" + +# js/PasswdPlugin.js:50: +msgid "Password Change Plugin" +msgstr "Jelszóváltoztatás kiegészítő" + +# js/settings/PasswdPanel.js:29: +msgid "User name" +msgstr "Felhasználónév" + +# js/settings/PasswdPanel.js:33: +msgid "Current password" +msgstr "Jelenlegi jelszó" + +# js/settings/PasswdPanel.js:42: +msgid "New password" +msgstr "Új jelszó" + +# js/settings/PasswdPanel.js:51: +msgid "Retype new password" +msgstr "Új jelszó mégegyszer" diff --git a/language/nl_NL.UTF-8/LC_MESSAGES/plugin_passwd.po b/language/nl_NL.UTF-8/LC_MESSAGES/plugin_passwd.po new file mode 100644 index 0000000..6f7ed98 --- /dev/null +++ b/language/nl_NL.UTF-8/LC_MESSAGES/plugin_passwd.po @@ -0,0 +1,95 @@ +msgid "" +msgstr "" +"Project-Id-Version: zarafa-webapp-plugin-passwd\n" +"POT-Creation-Date: 2014-03-04 13:53+0100\n" +"PO-Revision-Date: 2014-03-04 14:34+0100\n" +"Last-Translator: Sander Hoentjen \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.4\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: nl\n" + +# php/class.passwdmodule.php:44: +msgid "No data received." +msgstr "Geen data ontvangen." + +# php/class.passwdmodule.php:48: +msgid "User name is empty." +msgstr "Gebruikersnaam is leeg." + +# php/class.passwdmodule.php:52: +msgid "Current password is empty." +msgstr "Huidig wachtwoord is leeg." + +# php/class.passwdmodule.php:56: +msgid "New password is empty." +msgstr "Nieuw wachtwoord is leeg." + +# php/class.passwdmodule.php:60: +msgid "New passwords do not match." +msgstr "Nieuwe wachtwoorden zijn niet gelijk." + +# php/class.passwdmodule.php:137: +# php/class.passwdmodule.php:196: +# js/data/PasswdResponseHandler.js:44: +msgid "Password is changed successfully." +msgstr "Wachtwoord succesvol gewijzigd." + +# php/class.passwdmodule.php:141: +# php/class.passwdmodule.php:200: +msgid "Password is not changed." +msgstr "Wachtwoord is niet aangepast." + +# php/class.passwdmodule.php:144: +# php/class.passwdmodule.php:203: +msgid "" +"Password is weak. Password should contain capital, non-capital letters and " +"numbers. Password should have 8 to 20 characters." +msgstr "" +"Wachtwoord is zwak. Een wachtwoord moet hoofdletters, kleine letters en " +"cijfers bevatten. Een wachtwoord moet 8 tot 20 karakters bevatten." + +# php/class.passwdmodule.php:147: +msgid "Current password does not match." +msgstr "Huidig wachtwoord klopt niet." + +# js/data/PasswdResponseHandler.js:27: +msgid "An unknown error occurred while changing password." +msgstr "Er is een onbekende fout opgetreden tijdens de wachtwoord wijziging." + +# js/data/PasswdResponseHandler.js:33: +msgid "Error" +msgstr "Fout" + +# js/data/PasswdResponseHandler.js:50: +msgid "Success" +msgstr "Succes" + +# js/settings/SettingsPasswdWidget.js:24: +# js/settings/SettingsPasswdCategory.js:20: +msgid "Change Password" +msgstr "Wijzig wachtwoord" + +# js/PasswdPlugin.js:50: +msgid "Password Change Plugin" +msgstr "Wachtwoord wijzigen plugin" + +# js/settings/PasswdPanel.js:29: +msgid "User name" +msgstr "Gebruikersnaam" + +# js/settings/PasswdPanel.js:33: +msgid "Current password" +msgstr "Huidig wachtwoord" + +# js/settings/PasswdPanel.js:42: +msgid "New password" +msgstr "Nieuw wachtwoord" + +#js/settings/PasswdPanel.js:51: +msgid "Retype new password" +msgstr "Herhaal nieuw wachtwoord" + diff --git a/lib/PasswordMeter.js b/lib/PasswordMeter.js deleted file mode 100644 index 7406beb..0000000 --- a/lib/PasswordMeter.js +++ /dev/null @@ -1,158 +0,0 @@ -// Create user extensions namespace (Ext.ux) -Ext.namespace('Ext.ux'); - -/** - * Ext.ux.PasswordMeter Extension Class - * - * @author Eelco Wiersma - * @version 0.2 - * - * Algorithm based on code of Tane - * http://digitalspaghetti.me.uk/index.php?q=jquery-pstrength - * and Steve Moitozo - * http://www.geekwisdom.com/dyn/passwdmeter - * - * @license MIT License: http://www.opensource.org/licenses/mit-license.php - * - * @class Ext.ux.PasswordMeter - * @extends Ext.form.TextField - * @constructor - * Creates new Ext.ux.PasswordMeter - */ -Ext.ux.PasswordMeter = function(config) { - - // call parent constructor - Ext.ux.PasswordMeter.superclass.constructor.call(this, config); - -}; - -Ext.extend(Ext.ux.PasswordMeter, Ext.form.TextField, { - /** - * @cfg {String} inputType The type attribute for input fields -- e.g. text, password (defaults to "password"). - */ - inputType: 'text', - // private - onRender: function(ct, position) { - Ext.ux.PasswordMeter.superclass.onRender.call(this, ct, position); - - var elp = this.el.findParent('.x-form-element', 5, true); - this.objMeter = ct.createChild({tag: "div", 'class': "strengthMeter"}); - - this.objMeter.setWidth(elp.getWidth(true)-17); - this.scoreBar = this.objMeter.createChild({tag: "div", 'class': "scoreBar"}); - - if(Ext.isIE && !Ext.isIE7) { // Fix style for IE6 - this.objMeter.setStyle('margin-left', '3px'); - } - }, - // private - initEvents: function() { - Ext.ux.PasswordMeter.superclass.initEvents.call(this); - - this.el.on('keyup', this.updateMeter, this); - }, - /** - * Sets the width of the meter, based on the score - * @param {Object} e - * Private function - */ - updateMeter: function(e) { - var score = 0 - var p = e.target.value; - - var maxWidth = this.objMeter.getWidth() - 2; - - var nScore = this.calcStrength(p); - - // Set new width - var nRound = Math.round(nScore * 2); - - if (nRound > 100) { - nRound = 100; - } - - var scoreWidth = (maxWidth / 100) * nRound; - this.scoreBar.setWidth(scoreWidth, true); - }, - /** - * Calculates the strength of a password - * @param {Object} p The password that needs to be calculated - * @return {int} intScore The strength score of the password - */ - calcStrength: function(p) { - var intScore = 0; - - // PASSWORD LENGTH - intScore += p.length; - - if(p.length > 0 && p.length <= 4) { // length 4 or less - intScore += p.length; - } - else if (p.length >= 5 && p.length <= 7) { // length between 5 and 7 - intScore += 6; - } - else if (p.length >= 8 && p.length <= 15) { // length between 8 and 15 - intScore += 12; - //alert(intScore); - } - else if (p.length >= 16) { // length 16 or more - intScore += 18; - //alert(intScore); - } - - // LETTERS (Not exactly implemented as dictacted above because of my limited understanding of Regex) - if (p.match(/[a-z]/)) { // [verified] at least one lower case letter - intScore += 1; - } - if (p.match(/[A-Z]/)) { // [verified] at least one upper case letter - intScore += 5; - } - // NUMBERS - if (p.match(/\d/)) { // [verified] at least one number - intScore += 5; - } - if (p.match(/.*\d.*\d.*\d/)) { // [verified] at least three numbers - intScore += 5; - } - - // SPECIAL CHAR - if (p.match(/[!,@,#,$,%,^,&,*,?,_,~]/)) { // [verified] at least one special character - intScore += 5; - } - // [verified] at least two special characters - if (p.match(/.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~]/)) { - intScore += 5; - } - - // COMBOS - if (p.match(/(?=.*[a-z])(?=.*[A-Z])/)) { // [verified] both upper and lower case - intScore += 2; - } - if (p.match(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/)) { // [verified] both letters and numbers - intScore += 2; - } - // [verified] letters, numbers, and special characters - if (p.match(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!,@,#,$,%,^,&,*,?,_,~])/)) { - intScore += 2; - } - - return intScore; - - }, - // private - onFocus: function() { - Ext.ux.PasswordMeter.superclass.onFocus.call(this); - - if(!Ext.isOpera) { // don't touch in Opera - this.objMeter.addClass('strengthMeter-focus'); - } - }, - // private - onBlur: function() { - Ext.ux.PasswordMeter.superclass.onBlur.call(this); - - if(!Ext.isOpera) { // don't touch in Opera - this.objMeter.removeClass('strengthMeter-focus'); - } - } -}); \ No newline at end of file diff --git a/manifest.xml b/manifest.xml index eecb0ea..7479624 100644 --- a/manifest.xml +++ b/manifest.xml @@ -2,30 +2,21 @@ - 1.0 + 1.3 Passwd Password Change Plugin Saket Patel https://github.com/silentsakky - Change your password from zarafa webapp + Change your password from webapp config.php + + language + + - @@ -35,13 +26,20 @@ js/passwd.js js/passwd-debug.js + js/PasswdPlugin.js js/settings/SettingsPasswdCategory.js js/settings/SettingsPasswdWidget.js js/settings/PasswdPanel.js js/data/PasswdResponseHandler.js js/ABOUT.js + js/external/PasswordMeter.js + + resources/css/passwd.css + resources/css/passwd.css + resources/css/passwd-main.css + diff --git a/php/class.passwdmodule.php b/php/class.passwdmodule.php index e11e638..88d4fba 100644 --- a/php/class.passwdmodule.php +++ b/php/class.passwdmodule.php @@ -41,23 +41,23 @@ class PasswdModule extends Module // some sanity checks if(empty($data)) { - $errorMessage = _('No data received.'); + $errorMessage = dgettext("plugin_passwd", 'No data received.'); } if(empty($data['username'])) { - $errorMessage = _('User name is empty.'); + $errorMessage = dgettext("plugin_passwd", 'User name is empty.'); } if(empty($data['current_password'])) { - $errorMessage = _('Current password is empty.'); + $errorMessage = dgettext("plugin_passwd", 'Current password is empty.'); } if(empty($data['new_password']) || empty($data['new_password_repeat'])) { - $errorMessage = _('New password is empty.'); + $errorMessage = dgettext("plugin_passwd", 'New password is empty.'); } if($data['new_password'] !== $data['new_password_repeat']) { - $errorMessage = _('New passwords does not match.'); + $errorMessage = dgettext("plugin_passwd", 'New passwords do not match.'); } if(empty($errorMessage)) { @@ -83,6 +83,9 @@ class PasswdModule extends Module public function saveInLDAP($data) { $errorMessage = ''; + $userName = $data['username']; + $newPassword = $data['new_password']; + $sessionPass = ''; // connect to LDAP server $ldapconn = ldap_connect(PLUGIN_PASSWD_LDAP_URI); @@ -90,57 +93,74 @@ class PasswdModule extends Module // check connection is successfull if(ldap_errno($ldapconn) === 0) { // get the users uid, if we have a multi tenant installation then remove company name from user name - $parts = explode('@', $data['username']); - $uid = $parts[0]; + if (PLUGIN_PASSWD_LOGIN_WITH_TENANT){ + $parts = explode('@', $userName); + $uid = $parts[0]; + } else { + $uid = $userName; + } + + // check if we should use tls! + if(strrpos(PLUGIN_PASSWD_LDAP_URI, "ldaps://", -strlen(PLUGIN_PASSWD_LDAP_URI)) === FALSE && PLUGIN_PASSWD_LDAP_USE_TLS === true) { + ldap_start_tls($ldapconn); + } + + // set connection parametes + ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3); + ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, 0); + + // now bind to the ldap server to search the user dn + ldap_bind($ldapconn, PLUGIN_PASSWD_LDAP_BIND_DN, PLUGIN_PASSWD_LDAP_BIND_PW); // search for the user dn that will be used to do login into LDAP $userdn = ldap_search ( $ldapconn, // connection-identify PLUGIN_PASSWD_LDAP_BASEDN, // basedn - 'uid=' . $uid, // search filter - array('dn') // needed attributes. we need the dn + 'uid=' . $uid, // search filter + array('dn', 'objectClass') // needed attributes. we need dn and objectclass ); if ($userdn) { - $userdn = ldap_get_entries($ldapconn, $userdn); - $userdn = $userdn[0]['dn']; + $entries = ldap_get_entries($ldapconn, $userdn); + $userdn = $entries[0]['dn']; // bind to ldap directory - ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3); - // login with current password if that fails then current password is wrong - $bind = ldap_bind($ladpconn, $userdn, $data['current_password']); + ldap_bind($ldapconn, $userdn, $data['current_password']); if(ldap_errno($ldapconn) === 0) { - $passwd = $data['new_password']; - $passwdRepeat = $data['new_password_repeat']; + $password_hash = $this->sshaEncode($newPassword); + $entry = array('userPassword' => $password_hash); - if($this->checkPasswordStrenth($passwd)) { - $password_hash = $this->sshaEncode($passwd); - $entry = array('userPassword' => $password_hash); - $return_mod = ldap_modify($ldapconn, $userdn, $entry); - if (ldap_errno($ldapconn) === 0) { - // password changed successfully + if (in_array('sambaSamAccount', $entries[0]['objectclass'])) { + $nthash = strtoupper(bin2hex(mhash(MHASH_MD4, iconv("UTF-8", "UTF-16LE", $newPassword)))); + $entry['sambaNTPassword'] = $nthash; + $entry['sambaPwdLastSet'] = strval(time()); + } - // write new password to session because we don't want user to re-authenticate - session_start(); - $_SESSION['password'] = $passwd; - session_write_close(); + ldap_modify($ldapconn, $userdn, $entry); + if (ldap_errno($ldapconn) === 0) { + // password changed successfully - // send feedback to client - $this->sendFeedback(true, array( - 'info' => array( - 'display_message' => _('Password is changed successfully.') - ) - )); - } else { - $errorMessage = _('Password is not changed.'); - } + // send feedback to client + $this->sendFeedback(true, array( + 'info' => array( + 'display_message' => dgettext("plugin_passwd", 'Password is changed successfully.') + ) + )); + + // write new password to session because we don't want user to re-authenticate + session_start(); + $encryptionStore = EncryptionStore::getInstance(); + $encryptionStore->add('password', $newPassword); + session_write_close(); + + return true; } else { - $errorMessage = _('Password is weak. Password should contain capital, non capital letters and numbers. Password shuold have 8 to 20 characters.'); + $errorMessage = dgettext("plugin_passwd", 'Password is not changed.'); } } else { - $errorMessage = _('Current password does not match.'); + $errorMessage = dgettext("plugin_passwd", 'Current password does not match.'); } // release ldap-bind @@ -152,8 +172,8 @@ class PasswdModule extends Module $this->sendFeedback(false, array( 'type' => ERROR_ZARAFA, 'info' => array( - 'ldap_error' => ldap_errno(), - 'ldap_error_name' => ldap_error(), + 'ldap_error' => ldap_errno($ldapconn), + 'ldap_error_name' => ldap_error($ldapconn), 'display_message' => $errorMessage ) )); @@ -161,42 +181,56 @@ class PasswdModule extends Module } /** - * Function will execute zarafa-passwd command and will try to change user's password, - * this method is unsecure and unreliable. + * Function will try to change user's password via MAPI in SOAP connection. * @param {Array} $data data sent by client. */ public function saveInDB($data) { $errorMessage = ''; - $passwd = $data['new_password']; - $passwdRepeat = $data['new_password_repeat']; + $userName = $data['username']; + $newPassword = $data['new_password']; + $sessionPass = ''; - if($this->checkPasswordStrenth($passwd)) { - $passwd_cmd = '/usr/bin/zarafa-passwd -u %s -o %s -p %s'; + // get current session password + // if this plugin is used on a webapp version with EncryptionStore, + // $_SESSION['password'] is no longer available. User EncryptionStore + // in this case. + // EncryptionStore was introduced in webapp core somewhere after + // version 2.1.2, and with or before version 2.2.0.414. + // tested with Zarafa WebApp 2.2.1.43-199.1 running with + // Zarafa Server 7.2.4.29-99.1 + if(class_exists("EncryptionStore")) { + $encryptionStore = EncryptionStore::getInstance(); + $sessionPass = $encryptionStore->get("password"); + } + if($data['current_password'] === $sessionPass) { // all information correct, change password - $cmd = sprintf($passwd_cmd, $data['username'], $data['current_password'], $passwd); - exec($cmd, $arrayout, $retval); + $store = $GLOBALS['mapisession']->getDefaultMessageStore(); + $userinfo = mapi_zarafa_getuser_by_name($store, $userName); - if ($retval === 0) { + if (mapi_zarafa_setuser($store, $userinfo['userid'], $userName, $userinfo['fullname'], $userinfo['emailaddress'], $newPassword, 0, $userinfo['admin'])) { // password changed successfully - // write new password to session because we don't want user to re-authenticate - session_start(); - $_SESSION['password'] = $passwd; - session_write_close(); - // send feedback to client $this->sendFeedback(true, array( 'info' => array( - 'display_message' => _('Password is changed successfully.') + 'display_message' => dgettext("plugin_passwd", 'Password is changed successfully.') ) )); + + // write new password to session because we don't want user to re-authenticate + session_start(); + $encryptionStore = EncryptionStore::getInstance(); + $encryptionStore->add('password', $newPassword); + session_write_close(); + + return true; } else { - $errorMessage = _('Password is not changed.'); + $errorMessage = dgettext("plugin_passwd", 'Password is not changed.'); } } else { - $errorMessage = _('Password is weak. Password should contain capital, non capital letters and numbers. Password shuold have 8 to 20 characters.'); + $errorMessage = dgettext("plugin_passwd", 'Current password does not match.'); } if(!empty($errorMessage)) { @@ -209,26 +243,6 @@ class PasswdModule extends Module } } - /** - * Function will check strength of the password and if it does not meet minimum requirements then - * will return false. - * Password should meet the following criteria: - * - min. 8 chars, max. 20 - * - contain caps and noncaps characters - * - contain numbers - * @param {String} $password password which should be checked. - * @return {Boolean} true if password passes the minimum requirement else false. - */ - public function checkPasswordStrenth($password) - { - // @FIXME should be moved to client side - if (preg_match("#.*^(?=.{8,20})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).*$#", $password)) { - return true; - } else { - return false; - } - } - /** * Function will generate SSHA hash to use to store user's password in LDAP. * @param {String} $text text based on which hash will be generated. @@ -245,4 +259,4 @@ class PasswdModule extends Module return $hash; } } -?> \ No newline at end of file +?> diff --git a/php/plugin.passwd.php b/php/plugin.passwd.php index ef8fe17..85bd809 100644 --- a/php/plugin.passwd.php +++ b/php/plugin.passwd.php @@ -42,10 +42,10 @@ class Pluginpasswd extends Plugin { 'zarafa' => Array( 'v1' => Array( 'plugins' => Array( - 'sugarcrm' => Array( - 'enable' => PLUGIN_PASSWD_USER_DEFAULT_ENABLE, + 'passwd' => Array( + 'enable' => PLUGIN_PASSWD_USER_DEFAULT_ENABLE, + 'enable_strict_check' => PLUGIN_PASSWD_STRICT_CHECK_ENABLE, ) - ) ) ) diff --git a/resources/css/passwd-main.css b/resources/css/passwd-main.css new file mode 100644 index 0000000..7091cd7 --- /dev/null +++ b/resources/css/passwd-main.css @@ -0,0 +1,27 @@ +.zarafa-settings-category-passwd { + background-image: url(../images/icon_lock.png) !important; +} + +/* STYLES FOR THE PASSWORD METER */ +.x-form-strengthmeter { + width : 100%; + height: 5px; + /*float: right;*/ + background: #ff4c00; /* Old browsers */ + /* IE9 SVG, needs conditional override of 'filter' to 'none' */ + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmNGMwMCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUwJSIgc3RvcC1jb2xvcj0iI2VkZDcxMiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMxYWZmMDAiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); + background: -moz-linear-gradient(left, #ff4c00 0%, #edd712 50%, #1aff00 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, right top, color-stop(0%,#ff4c00), color-stop(50%,#edd712), color-stop(100%,#1aff00)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(left, #ff4c00 0%,#edd712 50%,#1aff00 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(left, #ff4c00 0%,#edd712 50%,#1aff00 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(left, #ff4c00 0%,#edd712 50%,#1aff00 100%); /* IE10+ */ + background: linear-gradient(left, #ff4c00 0%,#edd712 50%,#1aff00 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff4c00', endColorstr='#1aff00',GradientType=1 ); /* IE6-8 */ +} + +.x-form-strengthmeter-scorebar { + background-color: white; + opacity: 0.7; + height: 5px; + width: 100%; +} \ No newline at end of file diff --git a/resources/images/icon_lock.png b/resources/images/icon_lock.png new file mode 100644 index 0000000..be4689e Binary files /dev/null and b/resources/images/icon_lock.png differ diff --git a/resources/images/icon_lock.xcf b/resources/images/icon_lock.xcf new file mode 100644 index 0000000..38f1853 Binary files /dev/null and b/resources/images/icon_lock.xcf differ