Commits

Clemens Rabe committed 8a281d9

Added sources for the plugin.

Comments (0)

Files changed (8)

rebuildSecondPassPlugin.sh

+#!/bin/bash
+#
+# Rebuild the scm-secondpass-plugin
+#
+# (c) 2014 by Clemens Rabe <clemens.rabe@gmail.com>
+#
+
+# Ensure we are in the right directory
+if [ ! -e pom.xml ]; then
+    echo "Please execute this script from the scm-secondpass-plugin source directory!"
+    exit 1
+fi
+
+mvn scmp:install -DscmHome=../scm-secondpass-plugin-dist
+mvn scmp:package
+
+cd ..
+rm -rf dist/scm-secondpass-plugin
+
+mkdir -p dist/scm-secondpass-plugin
+mv scm-secondpass-plugin-dist/plugins/*                 dist/scm-secondpass-plugin
+
+mkdir -p dist/scmp
+cp scm-secondpass-plugin/target/*.scmp                  dist/scmp
+
+echo
+echo "Finished building second pass plugin."
+echo
+echo "The new created elements are located in the directory $PWD/dist:"
+echo " $PWD/dist/scm-secondpass-plugin : The scm-secondpass-plugin plugin."
+echo " $PWD/dist/scmp                  : The plugins as SCMP files."
+echo

src/main/java/sonia/scm/plugins/secondpass/SecondPassAuthenticationHandler.java

+/**
+ * Copyright (c) 2014, Clemens Rabe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package sonia.scm.plugins.secondpass;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import sonia.scm.SCMContextProvider;
+import sonia.scm.plugin.ext.Extension;
+import sonia.scm.store.Store;
+import sonia.scm.store.StoreFactory;
+import sonia.scm.user.User;
+import sonia.scm.user.UserManager;
+import sonia.scm.web.security.AuthenticationHandler;
+import sonia.scm.web.security.AuthenticationResult;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * The authentication handler to support the secondary password.
+ * 
+ * @author clemens
+ * 
+ */
+@Singleton
+@Extension
+public class SecondPassAuthenticationHandler implements AuthenticationHandler {
+	/** The authentication type. */
+	public static final String TYPE = "secondPass";
+
+	/** The type used for the store. */
+	public static final String STORETYPE = "secondPass";
+
+	/** the logger for AutoLoginAuthenticationHandler */
+	private static final Logger logger = LoggerFactory
+			.getLogger(SecondPassAuthenticationHandler.class);
+
+	/** The configuration of the plugin. */
+	private SecondPassConfig config;
+
+	/** The store of the configuration. */
+	private Store<SecondPassConfig> store;
+
+	/** The user manager. */
+	private UserManager userManager;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param userManager
+	 *            - The user manager.
+	 * @param storeFactory
+	 *            - The factory to get the store.
+	 */
+	@Inject
+	public SecondPassAuthenticationHandler(UserManager userManager,
+			StoreFactory storeFactory) {
+		this.userManager = userManager;
+		store = storeFactory.getStore(SecondPassConfig.class, STORETYPE);
+	}
+
+	/**
+	 * Initialize the AutoLoginAuthenticationHandler.
+	 */
+	@Override
+	public void init(SCMContextProvider context) {
+		config = store.get();
+
+		if (config == null) {
+			config = new SecondPassConfig();
+			store.set(config);
+		}
+	}
+
+	/**
+	 * Close the AutoLoginAuthenticationHandler.
+	 */
+	@Override
+	public void close() throws IOException {
+	}
+
+	/**
+	 * Get the type of the AutoLoginAuthenticationHandler.
+	 */
+	@Override
+	public String getType() {
+		return TYPE;
+	}
+
+	@Override
+	public AuthenticationResult authenticate(HttpServletRequest request,
+			HttpServletResponse response, String username, String password) {
+		AuthenticationResult result = AuthenticationResult.NOT_FOUND;
+
+		// Search for the user in the user manager
+		User user = userManager.get(username);
+
+		if (user == null) {
+			if (logger.isDebugEnabled()) {
+				logger.debug("user {} not available from the user manager",
+						username);
+			}
+		} else {
+			// Search for the user in the configuration
+			boolean userFound = false;
+
+			for (SecondPassConfigEntry entry : config.getUsers()) {
+				if (entry.getUsername().equals(username)) {
+					userFound = true;
+
+					if (logger.isDebugEnabled()) {
+						logger.debug("entry for user {} found", username);
+					}
+
+					if (entry.getSecondPass().equals(password)) {
+						result = new AuthenticationResult(user);
+					}
+
+					if (logger.isDebugEnabled()) {
+						if (result != null) {
+							logger.debug(
+									"user {} authenticated using alternative password",
+									username);
+						} else {
+							logger.debug(
+									"alternative password for user {} does not match",
+									username);
+						}
+					}
+
+					break;
+				} else {
+					if (logger.isDebugEnabled()) {
+						logger.debug(
+								"secondPass entry for user {} found, that does not match current user",
+								entry.getUsername());
+					}
+				}
+			}
+
+			if (!userFound) {
+				if (logger.isDebugEnabled()) {
+					logger.debug("no entry for user {} found", username);
+				}
+			}
+		}
+
+		return result;
+	}
+
+	public SecondPassConfigEntry getCurrentUserEntry() {
+		Subject subject = SecurityUtils.getSubject();
+		User currentUser = subject.getPrincipals().oneByType(User.class);
+
+		for (SecondPassConfigEntry entry : config.getUsers()) {
+			if (entry.getUsername().equals(currentUser.getName())) {
+				if (logger.isDebugEnabled()) {
+					logger.debug("found secondpass entry for user {}",
+							currentUser.getName());
+				}
+
+				return entry;
+			}
+		}
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("create temporary secondpass entry for user {}",
+					currentUser.getName());
+		}
+		SecondPassConfigEntry entry = new SecondPassConfigEntry();
+		entry.setUsername( currentUser.getName() );
+		entry.setSecondPass( SecondPassHelper.generateRandomPassword(30) );
+		return entry;
+	}
+
+	public void setCurrentUserEntry(SecondPassConfigEntry entry) {
+		Subject subject = SecurityUtils.getSubject();
+		User currentUser = subject.getPrincipals().oneByType(User.class);
+
+		// The entry must have the same username
+		if (! entry.getUsername().equals(currentUser.getName())) {
+			logger.error(
+					"Username in entry is {}, but current user is {}. Abort update.",
+					entry.getUsername(), currentUser.getName());
+			return;
+		}
+
+		for (SecondPassConfigEntry configEntry : config.getUsers()) {
+			if (configEntry.getUsername().equals(entry.getUsername())) {
+				if (logger.isDebugEnabled()) {
+					logger.debug("found secondpass entry for user {}",
+							entry.getUsername());
+				}
+
+				configEntry.setSecondPass(entry.getSecondPass());
+				store.set(config);
+
+				if (logger.isDebugEnabled()) {
+					logger.debug("updated second password for user {}",
+							entry.getUsername());
+				}
+
+				return;
+			}
+		}
+
+		// Still here, then there is no existing record. Add it to the list...
+		config.getUsers().add(entry);
+		store.set(config);
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("added new entry for user {}", entry.getUsername());
+		}
+	}
+}

src/main/java/sonia/scm/plugins/secondpass/SecondPassConfig.java

+/**
+ * Copyright (c) 2014, Clemens Rabe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package sonia.scm.plugins.secondpass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import com.google.inject.Singleton;
+
+
+/**
+ * Configuration container of the SecondPass plugin.
+ * 
+ * @author Clemens Rabe
+ *
+ */
+@Singleton
+@XmlRootElement(name = "config")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class SecondPassConfig
+{
+	private List<SecondPassConfigEntry> users = new ArrayList<SecondPassConfigEntry>();
+	
+	public SecondPassConfig()
+	{
+		SecondPassConfigEntry entry = new SecondPassConfigEntry();
+		entry.setUsername("clemens");
+		entry.setSecondPass("doof");
+		users.add( entry );
+	}
+	
+	public List<SecondPassConfigEntry> getUsers()
+	{
+		return users;
+	}
+}

src/main/java/sonia/scm/plugins/secondpass/SecondPassConfigEntry.java

+/**
+ * Copyright (c) 2014, Clemens Rabe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package sonia.scm.plugins.secondpass;
+
+/**
+ * Configuration entry for a single user and its alternative password.
+ * 
+ * @author Clemens Rabe
+ *
+ */
+public class SecondPassConfigEntry
+{
+	private String username;
+	private String secondPass;
+	
+	public String getUsername()
+	{
+		return username;
+	}
+	
+	public String getSecondPass()
+	{
+		return secondPass;
+	}
+	
+	public void setUsername( String username )
+	{
+		this.username = username;
+	}
+	
+	public void setSecondPass( String secondPass )
+	{
+		this.secondPass = secondPass;
+	}
+}

src/main/java/sonia/scm/plugins/secondpass/SecondPassConfigRessource.java

+/**
+ * Copyright (c) 2014, Clemens Rabe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package sonia.scm.plugins.secondpass;
+
+import java.io.IOException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Ressource for the configuration.
+ * 
+ * @author Clemens Rabe
+ */
+@Singleton
+@Path("plugins/secondpass")
+public class SecondPassConfigRessource {
+	private SecondPassAuthenticationHandler authenticationHandler;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param authenticationHandler
+	 *            - The AutoLoginAuthenticationHandler.
+	 */
+	@Inject
+	public SecondPassConfigRessource(
+			SecondPassAuthenticationHandler authenticationHandler) {
+		this.authenticationHandler = authenticationHandler;
+	}
+
+	/**
+	 * Get the configuration.
+	 * 
+	 * @return The configuration.
+	 */
+	@GET
+	@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+	public SecondPassConfigEntry getConfig() {
+		return authenticationHandler.getCurrentUserEntry();
+	}
+
+	/**
+	 * Set the configuration.
+	 */
+	@POST
+	@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+	public Response setConfig(@Context UriInfo uriInfo,
+			SecondPassConfigEntry config) throws IOException {
+		authenticationHandler.setCurrentUserEntry(config);
+		return Response.created(uriInfo.getRequestUri()).build();
+	}
+
+}

src/main/java/sonia/scm/plugins/secondpass/SecondPassHelper.java

+/**
+ * Copyright (c) 2014, Clemens Rabe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package sonia.scm.plugins.secondpass;
+
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Helper for the SecondPass plugin.
+ * 
+ * @author Clemens Rabe
+ */
+public class SecondPassHelper
+{
+
+  /**
+   * Generate a random password string.
+   * 
+   * @param length
+   *          - The length of the password.
+   * @return The random password string.
+   */
+  public static String generateRandomPassword(int length)
+  {
+    Random random = new Random();
+    String charSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+    char[] text = new char[length];
+    for (int i = 0; i < length; i++)
+    {
+      text[i] = charSet.charAt(random.nextInt(charSet.length()));
+    }
+
+    return new String(text);
+  }
+
+  
+  /**
+   * Split the given comma separated string and return a set of strings.
+   * 
+   * @param groups
+   *          - The comma separated string
+   * @return The set of strings.
+   */
+  public static Set<String> splitGroups(String groups)
+  {
+    Set<String> groupList = new HashSet<String>();
+
+    for (String element : groups.split(","))
+    {
+      String trimmed = element.trim();
+
+      if (!trimmed.isEmpty())
+        groupList.add(trimmed);
+    }
+
+    return groupList;
+  }
+}

src/main/resources/META-INF/scm/plugin.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<plugin>
+
+  <information>
+    <author>Clemens Rabe</author>
+    <groupId>${project.groupId}</groupId>
+    <artifactId>${project.artifactId}</artifactId>
+    <version>${project.version}</version>
+    <name>${project.name}</name>
+    <description>${project.description}</description>
+    <url>${project.url}</url>
+    <category>Authentication</category>
+  </information>
+
+  <conditions>
+    <min-version>${project.parent.version}</min-version>
+  </conditions>
+
+  <packages>
+    <package>sonia.scm.plugins.secondpass</package>
+  </packages>
+
+  <resources>
+    <script>/scm/secondpass.js</script>
+  </resources>
+
+</plugin>

src/main/resources/scm/secondpass.js

+/**
+ * Copyright (c) 2014, Clemens Rabe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+ChangeAlternativePasswordWindow = Ext.extend(Ext.Window,{
+
+    titleText: 'Change Alternative Password',
+    passwordText: 'Password',
+    okText: 'Ok',
+    cancelText: 'Cancel',
+    connectingText: 'Connecting',
+    failedText: 'change password failed!',
+    waitMsgText: 'Sending data...',
+    
+    submitText: 'Submit ...',
+    loadingText: 'Loading ...',
+    
+    errorTitleText: 'Error',
+    errorMsgText: 'Could not load config.',
+    errorSubmitMsgText: 'Could not submit config.',
+
+    configUrl: restUrl + 'plugins/secondpass.json',
+    loadMethod: 'GET',
+    submitMethod: 'POST',
+    
+    initComponent: function(){
+	
+	var config = {
+	    layout:'fit',
+	    width:300,
+	    height:170,
+	    closable: false,
+	    resizable: false,
+	    plain: true,
+	    border: false,
+	    modal: true,
+	    title: this.titleText,
+	    
+	    items: [{
+		id: 'changePasswordForm',
+		xtype : 'form',
+		url: restUrl + 'plugins/secondpass.json',
+		frame: true,
+		monitorValid: true,
+		defaultType: 'textfield',
+
+		listeners: {
+		    render: function(){
+			if ( this.onLoad && Ext.isFunction( this.onLoad ) ){
+			    this.onLoad(this.el);
+			}
+		    },
+		    scope: this
+		},
+
+		items : [{
+		    xtype: 'textfield',
+		    name: 'username',
+		    fieldLabel: "Username",
+		    helpText: "Username - do not change",
+		    allowBlank : false,
+		    hidden : true
+		},{
+		    xtype: 'textfield',
+		    name: 'secondPass',
+		    fieldLabel: "Alternative Password",
+		    helpText: "Alternative password",
+		    allowBlank : false
+		}],
+
+		buttons: [{
+		    text: "Save",
+		    scope: this,
+		    formBind: true,
+		    handler: this.submitForm
+		},{
+		    text: "Generate",
+		    scope: this,
+		    handler: this.generateRandomPassword
+		},{
+		    text: "Cancel",
+		    scope: this,
+		    handler: this.cancel
+		}]
+
+	    }]
+	};
+	
+	Ext.apply(this, Ext.apply(this.initialConfig, config));
+	ChangeAlternativePasswordWindow.superclass.initComponent.apply(this, arguments);
+    },
+    
+
+    load: function(values){
+	Ext.getCmp('changePasswordForm').getForm().loadRecord({
+	    success: true,
+	    data: values
+	});
+    },
+
+    submitForm: function(){
+	var form = Ext.getCmp('changePasswordForm').getForm();
+	if ( this.onSubmit && Ext.isFunction( this.onSubmit ) ){
+	    this.onSubmit( form.getValues() );
+	}
+	this.close();
+    },
+
+    cancel: function(){
+	this.close();
+    },
+
+    generateRandomPassword: function(){
+	var form  = Ext.getCmp('changePasswordForm').getForm();
+	var field = form.findField( 'secondPass' );
+	var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+	var minLength = 30;
+	var maxLength = 60;
+	var length    = Math.floor(Math.random()*(maxLength-minLength+1)+minLength);
+	var password  = '';
+
+	for (var i=0; i<length; i++) {
+            var rnum = Math.floor(Math.random() * chars.length);
+            password += chars.substring(rnum,rnum+1);
+        }
+
+	field.setValue( password );
+    },
+
+    onSubmit: function(values){
+	this.el.mask(this.submitText);
+	Ext.Ajax.request({
+	    url: this.configUrl,
+	    method: this.submitMethod,
+	    jsonData: values,
+	    scope: this,
+	    disableCaching: true,
+	    success: function(response){
+		this.el.unmask();
+	    },
+	    failure: function(result){
+		this.el.unmask();
+		main.handleRestFailure(
+		    result, 
+		    null, 
+		    this.failedText
+		);
+	    }
+	});
+    },
+    
+    onLoad: function(el){
+	var tid = setTimeout( function(){ el.mask(this.loadingText); }, 100);
+	Ext.Ajax.request({
+	    url: this.configUrl,
+	    method: this.loadMethod,
+	    scope: this,
+	    disableCaching: true,
+	    success: function(response){
+		var obj = Ext.decode(response.responseText);
+		this.load(obj);
+		clearTimeout(tid);
+		el.unmask();
+	    },
+	    failure: function(result){
+		el.unmask();
+		clearTimeout(tid);
+		main.handleRestFailure(
+		    result, 
+		    null, 
+		    this.failedText
+		);
+	    }
+	});
+    }
+    
+});
+
+
+/* Add section to the navigationPanel */
+loginCallbacks.push(function(){
+  var navPanel = Ext.getCmp('navigationPanel');
+  var count = navPanel.count() - 1;
+  navPanel.insertSection(count, {
+    title: 'Alternative Password',
+    items: [{
+      label: 'Modify Password',
+      fn: function(){
+          new ChangeAlternativePasswordWindow().show();
+      }
+    }]
+  });
+});