AEM 6 SP1 - Extending CRXDE Lite to store Password Type


This is NOT a supported way to extend CRXDE Lite, infact afaik there is no supported/documented way to extend CRXDE Lite. If you always truly :) follow best practices in a project, STOP here, do not read further


Goal


Create a Password Type in CRXDE Lite for users to enter passwords (mask characters while typing password type attribute values). Password type values can be stored in plain text or Encrypted on file system. Please comment if you find bugs and a possible fix...

For a supported way to hide passwords (or other attributes) check this post

Demo | Package Install

Password Type in CRXDE Lite




Sample password stored as plain text (eg. author\crx-quickstart\launchpad\config\com\experienceaem\crxde\lite)

service.pid="com.experienceaem.crxde.lite.ExtensionConfig"
testMulti=["hi","there"]
testPassword="Password:password_changed"
testString="hithere"
testLong=L"1"

Sample password stored Encrypted

service.pid="com.experienceaem.crxde.lite.ExtensionConfig"
testMulti=["hi","there"]
testPassword="Password:{d498726918ffd35347bd536b4421915082c97e26074b499d136084ab905ded2c}"
testString="hithere"
testLong=L"1"


Solution


Follow the two steps below to extend CRXDE Lite and add necessary JS code to create Password type. First step is Not Upgrade-Proof, so when you upgrade CQ, the first step may have to be performed again

Step 1 - Update CRXDE Lite Jar

All we do in this step is copy (back it up just in case if something goes wrong) the serialized CRXDE lite jar, open it and add a small chunk of JS code in it so that any extensions we code are loaded by the added JS logic when lite is opened in browser. The following steps are also shown in demo

1) Access bundles console http://localhost:4502/system/console/bundles and find the CRXDE Support bundle





2) Search for the serialized bundle on filesystem and copy it to a temp location (take a backup before you modify). On my AEM 6 SP1 its available in author\crx-quickstart\launchpad\installer (rsrc-com.adobe.granite.crxde-lite-1.0.66-CQ600-B0001.jar-1415034571045.ser)

3) Rename the copied .ser file to .jar (eg. rsrc-com.adobe.granite.crxde-lite-1.0.66-CQ600-B0001.jar-1415034571045.ser -> rsrc-com.adobe.granite.crxde-lite-1.0.66-CQ600-B0001.jar)

4) Open the jar using zip executable (say winrar), open file docroot\js\start.js in any text editor and add following code at the end. Save file and a winrar confirmation should popup asking if the jar should be updated with saved file.

Ext.onReady(function() {
    var loadLiteExtns = function(){
        Ext.Ajax.request({
            url: "/apps/ext-crx-delite/files.txt",
            success: function(response, options) {
                var js = response.responseText;

                if(!js){
                    return;
                }

                js = js.split("\n");

                Ext.each(js, function(jsPath) {
                    Ext.Ajax.request({
                        url: jsPath,
                        success: function(response, options) {
                            eval(response.responseText);
                        }
                    });
                });
            }
        });
    };

    CRX.util.Util.on("reinit", function(){
        location.reload();
    });

    loadLiteExtns();
});


5) In the above steps we add necessary code to load the extension files entered in /apps/ext-crx-delite/files.txt. So whenever a new CRXDE Lite extension is needed a new line with extension file path can be added in /apps/ext-crx-delite/files.txt

6) Access http://localhost:4502/system/console/bundles, click Install/Update... to upload and update CQ with the new CRXDE Support jar having necessary code to load the CRXDE Lite extension files.

Step 2 - Add extension files in CRX

In this step we add the JS file containing logic to create a Password type (like String type)

1) Access http://localhost:4502/crx/de

2) Create node /apps/ext-crx-delite of type nt:folder

3) Create node /apps/ext-crx-delite/files.txt of type nt:file and add the following line. The logic added in Step 1 reads this file for loading JS extension files added as paths

                                 /apps/ext-crx-delite/hide-password.js

4) Create node /apps/ext-crx-delite/hide-password.js of type nt:file and add the following code

Ext.ns("ExperienceAEM");

ExperienceAEM.PASSWORD = "Password";

ExperienceAEM.PropertyPanel = Ext.extend(CRX.ide.PropertyPanel,{
    MASK: "****",

    constructor: function(config){
        ExperienceAEM.PropertyPanel.superclass.constructor.call(this, config);

        var eThis = this;

        this.types.store.loadData([ExperienceAEM.PASSWORD],true);

        var valueColumn = this.getColumnModel().getColumnById("value");

        valueColumn.renderer = function(value, p, record) {
            var rValue = Ext.grid.Column.prototype.renderer.call(this, value, p, record);

            if( (typeof rValue) != "string"){
                return rValue;
            }

            var indexOfPassword = rValue.indexOf(ExperienceAEM.PASSWORD + ":");

            if( indexOfPassword == 0){
                record.data.type = ExperienceAEM.PASSWORD;
            }

            return (record.data.type == ExperienceAEM.PASSWORD) ? eThis.MASK : rValue;
        };
    },

    initValue: function() {
        ExperienceAEM.PropertyPanel.superclass.initValue.call(this);

        this.value[ExperienceAEM.PASSWORD] = {
            xtype: "textfield",
            inputType: "password",
            allowBlank: true,
            disableKeyFilter: true,
            cls: "x-form-text",
            tabIndex: 102
        };
    }
});

Ext.reg("propertypanel", ExperienceAEM.PropertyPanel);

(function(){
    var handler = CRX.ide.SaveAllAction.initialConfig.handler;

    CRX.ide.SaveAllAction.initialConfig.handler = function(){
        var workspaces = CRX.Util.getWorkspaces();
        var dirtyPassRecs = [];

        workspaces.each(function(workspace) {
            var paths = CRX.State.getChangedPaths("/" + workspace + "/");

            if (paths.length == 0) {
                return;
            }

            Ext.each(paths, function(path){
                var records = CRX.State.getPropertyRecords(path);

                Ext.each(records, function(record) {
                    if ( (record.data.type !== ExperienceAEM.PASSWORD) || !record.dirty){
                        return;
                    }

                    record.data.type = CRX.util.STRING;
                    //comment the below line if you use encryption
                    record.data.value = ExperienceAEM.PASSWORD + ":" + record.data.value;

                    dirtyPassRecs.push(record);
                });
            });
        });

        var words = [];

        Ext.each(dirtyPassRecs, function(record) {
            words.push(record.data.value);
        });

        /*
        uncomment this block to use encryption
        //http://experience-aem.blogspot.com/2014/11/aem-6-sp1-servlet-for-encryption-decryption.html
        Ext.Ajax.request({
            url: "/bin/experienceaem/encrypt?words=" + words.join(","),
            success: function(response) {
                var words = JSON.parse(response.responseText);

                Ext.each(dirtyPassRecs, function(record) {
                    record.data.value = ExperienceAEM.PASSWORD + ":" + words[record.data.value];
                });

                handler();

                Ext.each(dirtyPassRecs, function(record) {
                    record.data.type = ExperienceAEM.PASSWORD;
                });
            }
        });*/

        //start: comment the below lines to use encryption
        handler();

        Ext.each(dirtyPassRecs, function(record) {
            record.data.type = ExperienceAEM.PASSWORD;
        });
        //end:
    }
})();


5) Make sure logic strips off the leading Password: string when reading a stored password, from CRX, to get the actual password (further if password is encrypted, make sure you decrypt it before using...)

No comments:

Post a Comment