You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jetspeed-dev@portals.apache.org by ta...@apache.org on 2008/09/09 01:31:35 UTC

svn commit: r693316 [2/3] - in /portals/jetspeed-2/applications/mfa: ./ WebContent/ WebContent/META-INF/ WebContent/WEB-INF/ WebContent/WEB-INF/lib/ WebContent/WEB-INF/view/ WebContent/captchas/ WebContent/images/ src/ src/org/ src/org/apache/ src/org/...

Added: portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/CaptchaImageResource.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/CaptchaImageResource.java?rev=693316&view=auto
==============================================================================
--- portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/CaptchaImageResource.java (added)
+++ portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/CaptchaImageResource.java Mon Sep  8 16:31:33 2008
@@ -0,0 +1,561 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jetspeed.security.mfa.impl;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.WritableRaster;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.lang.ref.SoftReference;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Random;
+import java.util.TimeZone;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriter;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.jetspeed.security.mfa.MFA;
+import org.apache.jetspeed.security.mfa.MultiFacetedAuthentication;
+
+import com.sun.image.codec.jpeg.JPEGCodec;
+import com.sun.image.codec.jpeg.JPEGImageDecoder;
+
+/**
+ * TODO: try to find a javax.imageio equivalent and not use Sun classes
+ * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
+ * @version $Id: $
+ */
+public final class CaptchaImageResource
+{
+    private static final long serialVersionUID = 1L;
+
+    private String challengeId;
+    private  List charAttsList;
+    private int height = 0;
+    private int width = 0;
+    private byte[] background = null;
+    private BufferedImage image = null;
+    private CaptchaConfiguration config;
+
+    /** Transient image data so that image only needs to be generated once per VM */
+    private transient SoftReference imageData;
+
+    /**
+     * Construct.
+     */
+    public CaptchaImageResource(CaptchaConfiguration config)
+    {        
+        this(config, null);
+    }
+
+    /**
+     * Construct.
+     * 
+     * @param challengeId
+     *            The id of the challenge
+     */
+    public CaptchaImageResource(CaptchaConfiguration config, String challengeId)
+    {
+        if (challengeId == null)
+            this.challengeId = randomString(config.getTextMinlength(), config.getTextMaxlength());
+        else
+            this.challengeId = challengeId;
+        this.config = config;
+        this.background = null;
+    }
+
+    public void setBackgroundImage(byte[] background)
+    {
+        this.background = background;
+    }
+    
+    /**
+     * Gets the id for the challenge.
+     * 
+     * @return The the id for the challenge
+     */
+    public final String getChallengeId()
+    {
+        return challengeId;
+    }
+
+    /**
+     * Causes the image to be redrawn the next time its requested.
+     * 
+     * @see wicket.Resource#invalidate()
+     */
+    public final void invalidate()
+    {
+        imageData = null;
+    }
+
+    /**
+     * 
+     */
+    public void saveTo(OutputStream target) throws IOException
+    {
+        byte[] data = getImageData();
+        target.write(data);
+    }
+
+    public byte[] getImageBytes()
+    {
+        try
+        {
+            return getImageData();
+        } catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * @throws IOException
+     * @see wicket.markup.html.image.resource.DynamicImageResource#getImageData()
+     */
+    protected final byte[] getImageData() throws IOException
+    {
+        // get image data is always called in sync block
+        byte[] data = null;
+        if (imageData != null)
+        {
+            data = (byte[]) imageData.get();
+        }
+        if (data == null)
+        {
+            data = render();
+            imageData = new SoftReference(data);
+        }
+        return data;
+    }
+
+    private Font getFont(String fontName)
+    {
+        return new Font(fontName, config.getFontStyle(), config.getFontSize());
+    }
+
+    public void init() 
+    {
+        boolean emptyBackground = true;
+        if (config.isUseImageBackground() && background != null)
+        {
+            ByteArrayInputStream is = new ByteArrayInputStream(background);
+            JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(is);
+            try
+            {
+                this.image = decoder.decodeAsBufferedImage();
+                this.width = image.getWidth();
+                this.height = image.getHeight();
+                emptyBackground = false;
+            }
+            catch (Exception e)
+            {
+                emptyBackground = true;
+            }
+        }
+        if (emptyBackground)
+        {
+            this.width = config.getTextMarginLeft() * 2;
+            this.height = config.getTextMarginBottom() * 6;
+        }
+        char[] chars = challengeId.toCharArray();
+        charAttsList = new ArrayList();
+        TextLayout text = null;
+        AffineTransform textAt = null;
+        String []fontNames = config.getFontNames();
+        for (int i = 0; i < chars.length; i++)
+        {
+            // font name
+            String fontName = (fontNames.length == 1) ? fontNames[0] : fontNames[randomInt(0, fontNames.length)];
+                        
+            // rise
+            int rise = config.getTextRiseRange();
+            if (rise > 0)
+            {
+                rise = randomInt(config.getTextMarginBottom(), config.getTextMarginBottom() + config.getTextRiseRange());
+            }
+
+            if (config.getTextShear() > 0.0 || config.getTextRotation() > 0)
+            {
+                // rotation
+                double dRotation = 0.0;
+                if (config.getTextRotation() > 0)
+                {
+                    dRotation = Math.toRadians(randomInt(-(config.getTextRotation()), config.getTextRotation()));
+                }
+                
+                // shear
+                double shearX = 0.0;
+                double shearY = 0.0;
+                if (config.getTextShear() > 0.0)
+                {
+                    Random ran = new Random();
+                    shearX = ran.nextDouble() * config.getTextShear();
+                    shearY = ran.nextDouble() * config.getTextShear();
+                }
+                CharAttributes cf = new CharAttributes(chars[i], fontName, dRotation, rise, shearX, shearY);
+                charAttsList.add(cf);
+                text = new TextLayout(chars[i] + "", getFont(fontName),
+                        new FontRenderContext(null, config.isFontAntialiasing(), false));
+                textAt = new AffineTransform();
+                if (config.getTextRotation() > 0)
+                    textAt.rotate(dRotation);
+                if (config.getTextShear() > 0.0)
+                    textAt.shear(shearX, shearY);                
+            }
+            else
+            {
+                CharAttributes cf = new CharAttributes(chars[i], fontName, 0, rise, 0.0, 0.0);
+                charAttsList.add(cf);                
+            }
+            if (emptyBackground)
+            {
+                Shape shape = text.getOutline(textAt);
+//                this.width += text.getBounds().getWidth();
+                this.width += (int) shape.getBounds2D().getWidth();
+                this.width += config.getTextSpacing() + 1;
+                if (this.height < (int) shape.getBounds2D().getHeight() + rise)
+                {
+                    this.height = (int) shape.getBounds2D().getHeight() + rise;
+                }
+            }
+        }
+        if (emptyBackground)
+        {
+            this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+            Graphics2D gfx = (Graphics2D) this.image.getGraphics();
+            gfx.setBackground(Color.WHITE);
+            gfx.clearRect(0, 0, width, height);            
+        }
+    }
+    
+    /**
+     * Renders this image
+     * 
+     * @return The image data
+     */
+    private final byte[] render() throws IOException
+    {       
+        Graphics2D gfx = (Graphics2D) this.image.getGraphics();
+        if (config.isFontAntialiasing())
+            gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+        int curWidth = config.getTextMarginLeft();
+        FontRenderContext ctx = new FontRenderContext(null, config.isFontAntialiasing(), false);        
+        for (int i = 0; i < charAttsList.size(); i++)
+        {
+            CharAttributes cf = (CharAttributes) charAttsList.get(i);
+            TextLayout text = new TextLayout(cf.getChar() + "", getFont(cf.getName()), ctx); //gfx.getFontRenderContext());
+            AffineTransform textAt = new AffineTransform();
+            textAt.translate(curWidth, this.height - cf.getRise());
+            if (cf.getRotation() != 0)
+            {
+                textAt.rotate(cf.getRotation());
+            }
+            if (cf.getShearX() > 0.0)
+                textAt.shear(cf.getShearX(), cf.getShearY());
+            Shape shape = text.getOutline(textAt);
+            curWidth += shape.getBounds().getWidth() + config.getTextSpacing();
+            if (config.isUseImageBackground())
+                gfx.setColor(Color.BLACK);
+            else
+                gfx.setXORMode(Color.BLACK);
+            gfx.fill(shape);
+        }
+        if (config.isEffectsNoise())
+        {
+            noiseEffects(gfx, image);
+        }
+        if (config.isUseTimestamp())
+        {
+            if (config.isEffectsNoise())
+                gfx.setColor(Color.WHITE);
+            else
+                gfx.setColor(Color.BLACK);
+
+            TimeZone tz = TimeZone.getTimeZone(config.getTimestampTZ());
+            Calendar cal = new GregorianCalendar(tz);            
+            SimpleDateFormat formatter;
+            if (config.isUseTimestamp24hr())
+                formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss z");            
+            else
+                formatter = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a, z");
+            formatter.setTimeZone (tz);
+            Font font = gfx.getFont();
+            Font newFont = new Font(font.getName(), font.getStyle(), config.getTimestampFontSize());
+            gfx.setFont(newFont);
+            gfx.drawString(formatter.format(cal.getTime()), config.getTextMarginLeft() * 4, this.height - 1);
+        }
+        
+        return toImageData(image);
+    }
+
+    protected void noiseEffects(Graphics2D gfx, BufferedImage image)
+    {
+        // XOR circle
+        int dx = randomInt(width, 2 * width);
+        int dy = randomInt(width, 2 * height);
+        int x = randomInt(0, width / 2);
+        int y = randomInt(0, height / 2);
+
+        gfx.setXORMode(Color.GRAY);
+        if (config.isFontSizeRandom())
+            gfx.setStroke(new BasicStroke(randomInt(config.getFontSize() / 8, config.getFontSize() / 2)));
+        else
+            gfx.setStroke(new BasicStroke(config.getFontSize()));
+            
+        gfx.drawOval(x, y, dx, dy);
+
+        WritableRaster rstr = image.getRaster();
+        int[] vColor = new int[3];
+        int[] oldColor = new int[3];
+        Random vRandom = new Random(System.currentTimeMillis());
+        // noise
+        for (x = 0; x < width; x++)
+        {
+            for (y = 0; y < height; y++)
+            {
+                rstr.getPixel(x, y, oldColor);
+
+                // hard noise
+                vColor[0] = 0 + (int) (Math.floor(vRandom.nextFloat() * 1.03) * 255);
+                // soft noise
+                vColor[0] = vColor[0]
+                        ^ (170 + (int) (vRandom.nextFloat() * 80));
+                // xor to image
+                vColor[0] = vColor[0] ^ oldColor[0];
+                vColor[1] = vColor[0];
+                vColor[2] = vColor[0];
+
+                rstr.setPixel(x, y, vColor);
+            }
+        }
+    }
+
+    /**
+     * @param image
+     *            The image to turn into data
+     * @return The image data for this dynamic image
+     */
+    protected byte[] toImageData(final BufferedImage image) throws IOException
+    {
+        // Create output stream
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        String format = config.getImageFormat().substring(1);
+        // Get image writer for format
+        // FIXME: config.getImageFormat()
+        final ImageWriter writer = (ImageWriter) ImageIO
+                .getImageWritersByFormatName(format).next();
+
+        // Write out image
+        writer.setOutput(ImageIO.createImageOutputStream(out));
+        writer.write(image);
+
+        // Return the image data
+        return out.toByteArray();
+    }
+
+    /**
+     * This class is used to encapsulate all the filters that a character will
+     * get when rendered. The changes are kept so that the size of the shapes
+     * can be properly recorded and reproduced later, since it dynamically
+     * generates the size of the captcha image. The reason I did it this way is
+     * because none of the JFC graphics classes are serializable, so they cannot
+     * be instance variables here. If anyone knows a better way to do this,
+     * please let me know.
+     */
+    private static final class CharAttributes implements Serializable
+    {
+
+        private static final long serialVersionUID = 1L;
+
+        private char c;
+
+        private String name;
+
+        private int rise;
+
+        private double rotation;
+
+        private double shearX;
+
+        private double shearY;
+
+        CharAttributes(char c, String name, double rotation, int rise,
+                double shearX, double shearY)
+        {
+            this.c = c;
+            this.name = name;
+            this.rotation = rotation;
+            this.rise = rise;
+            this.shearX = shearX;
+            this.shearY = shearY;
+        }
+
+        char getChar()
+        {
+            return c;
+        }
+
+        String getName()
+        {
+            return name;
+        }
+
+        int getRise()
+        {
+            return rise;
+        }
+
+        double getRotation()
+        {
+            return rotation;
+        }
+
+        double getShearX()
+        {
+            return shearX;
+        }
+
+        double getShearY()
+        {
+            return shearY;
+        }
+    }
+
+    private static int randomInt(int min, int max)
+    {
+        return (int) (Math.random() * (max - min) + min);
+    }
+
+    public static String randomString(int min, int max)
+    {
+        int num = randomInt(min, max);
+        byte b[] = new byte[num];
+        for (int i = 0; i < num; i++)
+            b[i] = (byte) randomInt('a', 'z');
+        return new String(b);
+    }
+
+    private static String randomWord()
+    {
+        final String words[] =
+        { "Albert", "Barber", "Charlie", "Daniel", "Edward", "Flower",
+                "Georgia", "Lawrence", "Michael", "Piper", "Stanley"};
+
+        return words[randomInt(0, words.length)];
+    }
+ 
+    public static void main(String args[])
+    {
+        String configLocation = "./WebContent/WEB-INF/mfa.properties";
+        String ttsLocation = "./WebContent/WEB-INF/tts.properties";
+        
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        PropertiesConfiguration tconfig = new PropertiesConfiguration();
+        
+        try
+        {
+            InputStream is = new FileInputStream(configLocation);
+            config.load(is);
+            is.close();
+            InputStream tis = new FileInputStream(ttsLocation);
+            tconfig.load(tis);
+            tis.close();            
+            MultiFacetedAuthentication mfa = new MultiFacetedAuthenticationImpl(config, tconfig);
+            MFA.setInstance(mfa);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            System.exit(1);
+        }
+        
+        CaptchaConfiguration captchaConfig = new CaptchaConfiguration(config);
+        CaptchaImageResource captcha = new CaptchaImageResource(captchaConfig);
+        
+        InputStream is = null;
+        try
+        {
+            is = new FileInputStream("./WebContent/images/jetspeedlogo98.jpg");
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            MultiFacetedAuthenticationImpl.drain(is, bytes);
+            byte[] background = bytes.toByteArray();
+            captcha.setBackgroundImage(background);            
+        }
+        catch (IOException e)
+        {
+            captchaConfig.setUseImageBackground(false);
+        }
+        finally
+        {
+            if (is != null)
+            {
+                try
+                {
+                    is.close();
+                }
+                catch (IOException ee) 
+                {}
+            }
+        }        
+        
+        captcha.init();
+        FileOutputStream fs = null;
+        try
+        {
+            fs = new FileOutputStream("/data/result.jpg");
+            byte[] data = captcha.getImageBytes();
+            fs.write(data);
+        } catch (IOException e)
+        {
+            e.printStackTrace(System.err);
+        } finally
+        {
+            try
+            {
+                if (fs != null) fs.close();
+            } catch (IOException e)
+            {
+            }
+        }
+
+    }
+    
+}

Added: portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/MFAServletListener.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/MFAServletListener.java?rev=693316&view=auto
==============================================================================
--- portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/MFAServletListener.java (added)
+++ portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/MFAServletListener.java Mon Sep  8 16:31:33 2008
@@ -0,0 +1,73 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jetspeed.security.mfa.impl;
+
+import java.io.InputStream;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.jetspeed.security.mfa.MFA;
+import org.apache.jetspeed.security.mfa.MultiFacetedAuthentication;
+
+/**
+ * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
+ * @version $Id: $
+ */
+public class MFAServletListener implements ServletContextListener
+{
+	// TODO: Re-read the properties files as few times as possible.
+
+    public void contextDestroyed(ServletContextEvent arg0)
+    {
+        MultiFacetedAuthentication mfa = MFA.getInstance();
+        if (mfa != null)
+            mfa.destroy();
+    }
+
+    public void contextInitialized(ServletContextEvent event)
+    {
+        String configLocation = "/WEB-INF/mfa.properties";
+        String ttsConfigLocation = "/WEB-INF/tts.properties";
+        
+        try
+        {
+            InputStream is = event.getServletContext().getResourceAsStream(configLocation);
+            PropertiesConfiguration config = new PropertiesConfiguration();
+            config.load(is);
+            is.close();
+
+            InputStream tis = event.getServletContext().getResourceAsStream(ttsConfigLocation);            
+            PropertiesConfiguration ttsConfig = new PropertiesConfiguration();
+            ttsConfig.load(tis);
+            tis.close();
+            
+            String rootPath = event.getServletContext().getRealPath("/");
+            MultiFacetedAuthentication mfa = new MultiFacetedAuthenticationImpl(config, ttsConfig, rootPath);
+            MFA.setInstance(mfa);
+        }
+        catch (Exception e)
+        {
+            PropertiesConfiguration config = new PropertiesConfiguration();
+            PropertiesConfiguration ttsConfig = new PropertiesConfiguration();
+            MultiFacetedAuthentication mfa = new MultiFacetedAuthenticationImpl(config, ttsConfig);
+            MFA.setInstance(mfa);            
+        }
+    }
+    
+}
\ No newline at end of file

Added: portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/MultiFacetedAuthenticationImpl.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/MultiFacetedAuthenticationImpl.java?rev=693316&view=auto
==============================================================================
--- portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/MultiFacetedAuthenticationImpl.java (added)
+++ portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/MultiFacetedAuthenticationImpl.java Mon Sep  8 16:31:33 2008
@@ -0,0 +1,301 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jetspeed.security.mfa.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jetspeed.security.mfa.CaptchaBean;
+import org.apache.jetspeed.security.mfa.MultiFacetedAuthentication;
+import org.apache.jetspeed.security.mfa.TextToSpeechBean;
+import org.apache.jetspeed.security.mfa.util.ServerData;
+
+/* license not compatible? 
+import javax.sound.sampled.AudioFileFormat;
+import com.sun.speech.freetts.Voice;
+import com.sun.speech.freetts.VoiceManager;
+import com.sun.speech.freetts.audio.AudioPlayer;
+import com.sun.speech.freetts.audio.SingleFileAudioPlayer;
+*/
+
+/**
+ * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
+ * @version $Id: $
+ */
+public class MultiFacetedAuthenticationImpl implements MultiFacetedAuthentication
+{
+    protected final static Log log = LogFactory.getLog(MultiFacetedAuthenticationImpl.class);
+        
+    private String captchasRealPath;
+    private String ttsRealPath;
+    private String backgroundRealPath;
+    private String rootPath;
+    
+    private CaptchaConfiguration captchaConfig;
+    private TTSConfiguration ttsConfig;
+    private ResourceRemovalCache removalCache;
+    private byte[] background = null;
+    
+    public MultiFacetedAuthenticationImpl(Configuration cc, Configuration tc)
+    {
+        this(cc, tc, null);
+    }
+    
+    public MultiFacetedAuthenticationImpl(Configuration cc, Configuration tc, String rootPath)
+    {
+        // captcha configuration
+        this.captchaConfig = new CaptchaConfiguration(cc);        
+        // text-to-speech configuration
+        this.ttsConfig = new TTSConfiguration(tc);
+
+        this.rootPath = rootPath;
+        captchasRealPath = concatenatePaths(rootPath, captchaConfig.getDirectory());
+        ttsRealPath = concatenatePaths(rootPath, ttsConfig.getDirectory());
+        backgroundRealPath = concatenatePaths(rootPath, captchaConfig.getImageBackground());
+        if (this.captchaConfig.isUseImageBackground())
+        {
+            loadBackground();
+        }
+        
+        // image removal scanner
+        this.removalCache = new ResourceRemovalCache(captchaConfig.getScanRateSeconds(), captchaConfig.getTimetoliveSeconds());
+        this.removalCache.setDaemon(true);
+        try
+        {
+            this.removalCache.start();
+        }
+        catch (java.lang.IllegalThreadStateException e)
+        {
+            log.error("Exception starting scanner", e);
+        }        
+    }
+
+    private void loadBackground()
+    {
+        InputStream is = null;
+        try
+        {
+            is = new FileInputStream(backgroundRealPath);
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            drain(is, bytes);
+            background = bytes.toByteArray();
+        }
+        catch (IOException e)
+        {
+            this.captchaConfig.setUseImageBackground(false);
+        }
+        finally
+        {
+            if (is != null)
+            {
+                try
+                {
+                    is.close();
+                }
+                catch (IOException ee) 
+                {}
+            }
+        }        
+    }
+        
+    public void destroy()
+    {
+        this.removalCache.shutdown();
+    }
+    
+    public CaptchaBean createCaptcha(HttpServletRequest request)
+    {
+    	return createCaptcha(request, null);
+    }
+    
+    public CaptchaBean createCaptcha(HttpServletRequest request, String text)
+    {
+    	CaptchaBean captcha;
+    	if ( text == null )
+    		captcha = new CaptchaBeanImpl(captchaConfig);       
+    	else
+    		captcha = new CaptchaBeanImpl(captchaConfig, text);
+
+        if (captchaConfig.isUseImageBackground())
+        {
+            captcha.setBackgroundImage(this.background);
+        }        
+        FileOutputStream fs = null;
+        ServerData url = new ServerData(request);
+        String imageUrl =  url.getBasePath() + url.getContextPath() + captchaConfig.getDirectory() + "/" + captcha.getImageId() + captchaConfig.getImageFormat();        
+        captcha.setImageURL(imageUrl);
+        String imagePath = this.captchasRealPath + "/" + captcha.getImageId() + captchaConfig.getImageFormat();
+        
+        captcha.init();
+        
+        try
+        {
+            fs = new FileOutputStream(imagePath);
+            byte[] data = captcha.getImageBytes();
+            fs.write(data);
+            this.removalCache.insert(new RemovableResource(captcha.getImageId(), imagePath));
+        } 
+        catch (IOException e)
+        {
+            e.printStackTrace(System.err);
+            imageUrl = "";
+        } 
+        finally
+        {
+            try
+            {
+                if (fs != null)
+                    fs.close();
+            } catch (IOException e)
+            {}
+        }
+        
+        return captcha;
+    }
+    
+    public TextToSpeechBean createTextToSpeech(HttpServletRequest request, String text)
+    {
+        TextToSpeechBean tts = new TextToSpeechBeanImpl(text);
+        ServerData url = new ServerData(request);
+        String audioUrl =  url.getBasePath() + url.getContextPath() + ttsConfig.getDirectory() + "/" + tts.getAudioId() + ttsConfig.getImageFormat();        
+        tts.setAudioURL(audioUrl);
+        play(tts);
+        return tts;
+    }
+    
+    public void play(TextToSpeechBean ttsBean)
+    {
+/* // TODO: LICENSE        
+        boolean phonetic = true;
+        String imageText = ttsBean.getText();
+        if (imageText == null || imageText.trim().length() == 0)
+        {
+            log.error(this.getClass().getName() + " no text to write "
+                    + imageText);
+            return;
+        }
+        // build the full text string
+        imageText = imageText.trim();
+        StringBuffer buf = new StringBuffer(ttsConfig.getConfiguration().getString("YOUR_SECURE_IMAGE_TEXT_IS"));
+        for (int i = 0; i < imageText.length(); i++)
+        {
+            char c = imageText.charAt(i);
+            if (Character.isUpperCase(c))
+            {
+                buf.append(ttsConfig.getConfiguration().getString("capital"));
+            } 
+            else if (Character.isDigit(c))
+            {
+                buf.append(ttsConfig.getConfiguration().getString("number"));
+            }
+            buf.append("'" + c + "' ");
+            if (phonetic)
+            {
+                String phon = TTSHelper.getPhonetic(c, ttsConfig.getConfiguration());
+                if (phon != null)
+                {
+                    // problems with a as in
+                    buf.append(ttsConfig.getConfiguration().getString("AS_IN") + phon);
+                }
+            }
+            buf.append(" ,, ");
+        }
+        String textToSave = buf.toString();
+        // create voice
+        Voice voice = VoiceManager.getInstance().getVoice(ttsConfig.getVoiceName());
+        if (voice == null)
+        {
+            log.error(this.getClass().getName() + " Cannot load a voice named " + ttsConfig.getVoiceName());
+            return;
+        }
+        // Allocate the resources for the voice.
+        voice.setVerbose(ttsConfig.isDebug());
+        voice.setPitch(ttsConfig.getPitch());
+        voice.setRate(ttsConfig.getRate());
+        voice.allocate();
+
+        // Create output file
+        String audioPath = this.ttsRealPath + "/" + ttsBean.getAudioId() + ttsConfig.getImageFormat();               
+        AudioFileFormat.Type type = TTSHelper.getAudioType(audioPath); 
+        AudioPlayer audioPlayer = new SingleFileAudioPlayer(TTSHelper.getBaseName(audioPath), type);
+        voice.setAudioPlayer(audioPlayer);
+
+         // Synthesize speech based on whether or not build is successful
+        voice.startBatch();
+        if (!voice.speak(textToSave))
+        {
+            log.error(this.getClass().getName()
+                    + " Cannot save text to speach ");
+        }
+        voice.endBatch();
+
+        // cleanup. Be a good citizen.
+        audioPlayer.close();
+
+        voice.deallocate();
+        */
+    }
+
+    static final int BLOCK_SIZE = 4096;
+
+    public static void drain(InputStream r, OutputStream w) throws IOException
+    {
+        byte[] bytes = new byte[BLOCK_SIZE];
+        try
+        {
+            int length = r.read(bytes);
+            while (length != -1)
+            {
+                if (length != 0)
+                {
+                    w.write(bytes, 0, length);
+                }
+                length = r.read(bytes);
+            }
+        } finally
+        {
+            bytes = null;
+        }
+    }
+    
+    private static String concatenatePaths(String root, String rel)
+    {
+        if (root == null)
+        {
+            return rel;
+        }        
+        if (rel.startsWith("/"))
+        {
+            return root + rel.substring(1);
+        }
+        else
+        {
+            return root + rel;
+        }        
+    }
+    
+    
+}
\ No newline at end of file

Added: portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/RemovableResource.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/RemovableResource.java?rev=693316&view=auto
==============================================================================
--- portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/RemovableResource.java (added)
+++ portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/RemovableResource.java Mon Sep  8 16:31:33 2008
@@ -0,0 +1,51 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jetspeed.security.mfa.impl;
+
+/**
+ * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
+ * @version $Id: $
+ */
+public class RemovableResource
+{
+    private String resource;
+    private String key;
+    private long insertedTime;
+    
+    public RemovableResource(String key, String resource)
+    {
+        this.resource = resource;
+        this.key = key;
+        this.insertedTime = System.currentTimeMillis();
+    }
+
+    
+    public long getInsertedTime()
+    {
+        return insertedTime;
+    }
+
+    public String getKey()
+    {
+        return key;
+    }
+    
+    public String getResource()
+    {
+        return resource;
+    }
+}
\ No newline at end of file

Added: portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/ResourceRemovalCache.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/ResourceRemovalCache.java?rev=693316&view=auto
==============================================================================
--- portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/ResourceRemovalCache.java (added)
+++ portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/ResourceRemovalCache.java Mon Sep  8 16:31:33 2008
@@ -0,0 +1,151 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jetspeed.security.mfa.impl;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
+ * @version $Id: $
+ */
+public class ResourceRemovalCache extends Thread
+{
+    protected final static Log log = LogFactory.getLog(ResourceRemovalCache.class);
+    
+    private boolean stopping = false;
+    private long scanRateSeconds = 60;
+    private long msTimeToLive = 120 * 1000;
+    private Map resources = new HashMap();
+    
+    public ResourceRemovalCache(long scanRateSeconds, long timeToLiveSeconds)
+    {
+        this.scanRateSeconds = scanRateSeconds;
+        this.msTimeToLive = timeToLiveSeconds * 1000;
+    }
+    public void setStopping(boolean flag)
+    {
+        this.stopping = flag;
+    }
+
+    public void insert(RemovableResource r)
+    {
+        synchronized (resources)
+        {
+            resources.put(r.getKey(), r);
+        }
+    }
+    
+   public void shutdown()
+   {
+       traverse(true);
+   }
+   
+    /**
+     * Run the file scanner thread
+     *
+     */
+    public void run()
+    {
+        traverse(false);
+    }
+    
+    protected void traverse(boolean deleteAll)
+    {
+        boolean done = false;
+        try
+        {
+            while(!done)
+            {
+                try
+                {
+                    if (deleteAll)
+                    {
+                        done = true;
+                    }
+                    int count = 0;
+                                        
+                    List deleted = new LinkedList();
+                    Iterator it = resources.entrySet().iterator();
+                    while (it.hasNext())
+                    {
+                        Map.Entry e = (Map.Entry)it.next();
+                        RemovableResource r = (RemovableResource)e.getValue();
+                        long expired = System.currentTimeMillis() - r.getInsertedTime();
+                        //System.out.println("expired = " + expired);
+                        if (deleteAll || expired > this.msTimeToLive)
+                        {
+                            //System.out.println("*** resource Expired: " + r.getKey());
+                            deleted.add(r);
+                        }
+                        //    System.out.println("*** resource not expired: " + r.getKey());
+                    }
+                    
+                    it = deleted.iterator();
+                    while (it.hasNext())
+                    {
+                        RemovableResource r = (RemovableResource)it.next();
+                            try
+                            {
+                                // remove from file system
+                                File f = new File(r.getResource());
+                                if (f.exists())
+                                {
+                                    f.delete();
+                                    //System.out.println("*** deleted : " + r.getKey());                                        
+                                }
+                                synchronized(resources)
+                                {                            
+                                    resources.remove(r.getKey());
+                                }                                
+                            }
+                            catch (Exception e1)
+                            {
+                                log.error("Could not delete " + r.getResource());
+                            }
+                        }                        
+                }
+                catch (Exception e)
+                {
+                    e.printStackTrace();
+                    log.error("FileCache Scanner: Error in iteration...", e);                    
+                }
+
+                if (!done)
+                    sleep(scanRateSeconds * 1000);                
+
+                if (this.stopping)
+                {
+                    this.stopping = false;
+                    done = true;
+                }
+            }
+        }
+        catch (InterruptedException e)
+        {
+            log.error("FileCacheScanner: recieved interruption, exiting.", e);
+        }
+    }
+    
+}
\ No newline at end of file

Added: portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/TTSConfiguration.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/TTSConfiguration.java?rev=693316&view=auto
==============================================================================
--- portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/TTSConfiguration.java (added)
+++ portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/TTSConfiguration.java Mon Sep  8 16:31:33 2008
@@ -0,0 +1,119 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jetspeed.security.mfa.impl;
+
+import org.apache.commons.configuration.Configuration;
+
+/**
+ * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
+ * @version $Id: $
+ */
+public final class TTSConfiguration
+{
+    public final String DIRECTORY = "tts.directory";
+    public final String IMAGE_FORMAT = "tts.image.format";
+    public final String VOICE_NAME = "tts.voice.name";
+    public final String DEBUG = "debug";
+    public final String PITCH = "pitch";
+    public final String RATE = "rate";
+    
+    // Text-To-Speech properties
+    private String directory = "/tts";
+    private String imageFormat = ".wav";
+    private String voiceName = "kevin16";
+    private boolean debug = false;
+    private int pitch = 150;
+    private int rate = 110;
+    private Configuration config;
+       
+    public TTSConfiguration(Configuration c)
+    {
+        this.config = c;
+        this.setDebug(config.getBoolean(DEBUG));
+        this.setDirectory(config.getString(DIRECTORY));
+        this.setVoiceName(config.getString(VOICE_NAME));
+        this.setImageFormat(config.getString(IMAGE_FORMAT));
+        this.setPitch(config.getInt(PITCH));
+        this.setRate(config.getInt(RATE));
+    }
+    
+    public boolean isDebug()
+    {
+        return this.debug;
+    }
+    
+    public void setDebug(boolean debug)
+    {
+        this.debug = debug;
+    }
+    
+    public String getDirectory()
+    {
+        return directory;
+    }
+    
+    public void setDirectory(String ttsDir)
+    {
+        this.directory = ttsDir;
+    }
+    
+    public String getImageFormat()
+    {
+        return imageFormat;
+    }
+    
+    public void setImageFormat(String ttsImageFormat)
+    {
+        this.imageFormat = ttsImageFormat;
+    }
+    
+    public int getPitch()
+    {
+        return pitch;
+    }
+    
+    public void setPitch(int ttsPitch)
+    {
+        this.pitch = ttsPitch;
+    }
+    
+    public int getRate()
+    {
+        return rate;
+    }
+    
+    public void setRate(int ttsRate)
+    {
+        this.rate = ttsRate;
+    }
+    
+    public String getVoiceName()
+    {
+        return voiceName;
+    }
+    
+    public void setVoiceName(String ttsVoiceName)
+    {
+        this.voiceName = ttsVoiceName;
+    }
+    
+    public Configuration getConfiguration()
+    {
+        return config;
+    }
+    
+}
\ No newline at end of file

Added: portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/TextToSpeechBeanImpl.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/TextToSpeechBeanImpl.java?rev=693316&view=auto
==============================================================================
--- portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/TextToSpeechBeanImpl.java (added)
+++ portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/impl/TextToSpeechBeanImpl.java Mon Sep  8 16:31:33 2008
@@ -0,0 +1,63 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jetspeed.security.mfa.impl;
+
+import org.apache.jetspeed.security.mfa.TextToSpeechBean;
+
+/**
+ * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
+ * @version $Id: $
+ */
+public class TextToSpeechBeanImpl implements TextToSpeechBean
+{
+    private CaptchaImageResource cis;
+    private String audioId;
+    private String text;
+    private String audioURL;
+    
+    public TextToSpeechBeanImpl(String text)
+    {
+        this.text = text;
+        this.audioId = CaptchaImageResource.randomString(7, 9);        
+    }
+    
+    public String getText()
+    {
+        return this.text;
+    }
+    
+    public String getAudioId()
+    {
+        return audioId;
+    }    
+        
+    public byte[] getAudioBytes()
+    {
+        return cis.getImageBytes();
+    }
+    
+    public String getAudioURL()
+    {
+        return audioURL;
+    }
+    
+    public void setAudioURL(String url)
+    {
+        this.audioURL = url;
+    }
+   
+}
\ No newline at end of file

Added: portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/portlets/MFALogin.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/portlets/MFALogin.java?rev=693316&view=auto
==============================================================================
--- portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/portlets/MFALogin.java (added)
+++ portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/portlets/MFALogin.java Mon Sep  8 16:31:33 2008
@@ -0,0 +1,681 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jetspeed.security.mfa.portlets;
+
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.util.Random;
+import java.util.prefs.Preferences;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.ActionResponse;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletException;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletSession;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.jetspeed.CommonPortletServices;
+import org.apache.jetspeed.audit.AuditActivity;
+import org.apache.jetspeed.login.LoginConstants;
+import org.apache.jetspeed.request.RequestContext;
+import org.apache.jetspeed.security.PasswordCredential;
+import org.apache.jetspeed.security.User;
+import org.apache.jetspeed.security.UserManager;
+import org.apache.jetspeed.security.mfa.impl.CaptchaImageResource;
+import org.apache.jetspeed.security.mfa.util.QuestionFactory;
+import org.apache.jetspeed.security.mfa.util.SecurityHelper;
+import org.apache.portals.bridges.common.GenericServletPortlet;
+import org.apache.portals.messaging.PortletMessaging;
+
+/**
+ * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
+ * @version $Id: $
+ */
+public class MFALogin extends GenericServletPortlet
+{
+    private UserManager userManager;
+    private AuditActivity audit;
+    private Random rand = new Random();
+	
+	private int cookieLifetime = 172800;	// 48 hours
+    private int maxNumberOfAuthenticationFailures = 3;
+    
+	private QuestionFactory questionFactory;
+
+    public static final String RETRYCOUNT = "mfaRetryCount";
+    public static final String ERRORCODE = "mfaErrorCode";
+    public static final String QUESTION_FACTORY = "mfaQuestionFactory";
+    public static final String LOGIN_ENROLL_ACTIVITY = "login-enroll";
+    
+    public void init(PortletConfig config)
+    throws PortletException 
+    {
+        super.init(config);
+        userManager = (UserManager) getPortletContext().getAttribute(CommonPortletServices.CPS_USER_MANAGER_COMPONENT);
+        if (null == userManager)
+        {
+            throw new PortletException("Failed to find the User Manager on portlet initialization");
+        }
+        audit = (AuditActivity)getPortletContext().getAttribute(CommonPortletServices.CPS_AUDIT_ACTIVITY);
+        if (null == audit)
+        {
+            throw new PortletException("Failed to find the Audit Activity on portlet initialization");            
+        }
+        
+        // Read maximum lifetime for authentication cookies.
+        String cookie = getInitParameter("cookieLifetime");
+        String max = getInitParameter("maxNumberOfAuthenticationFailures");
+        if ( cookie != null )
+        {
+        	try
+        	{
+        		cookieLifetime = Integer.parseInt(cookie);
+                this.maxNumberOfAuthenticationFailures = Integer.parseInt(max);
+        		//System.out.println("Config file specified cookie lifetime of " + cookieLifetime + " seconds.");
+        	}
+        	catch (NumberFormatException e)
+        	{
+        		//System.out.println("Warning: cookie lifetime " + cookie + " is not a valid integer.");
+        	}
+        }
+        else
+        {
+        	//System.out.println("Warning: cookie lifetime not specified; defaulting to 48 hours");
+        }
+        
+        // Read random questions.
+        questionFactory = new QuestionFactory( getInitParameter("randomQuestions") );
+        
+        // Read whether or not to refuse login to misconfigured users.
+        // (i.e. users who have not set up challenge questions.)
+        //String failOnMisconfig = getInitParameter("failOnMisconfiguredUser");
+        //failOnMisconfiguredUser = (!failOnMisconfig.equals("false"));
+        //stealthOnMisconfiguredUser = (failOnMisconfig.equals("strict"));
+    }
+    
+    public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException
+    {
+        response.setContentType("text/html");
+        String  view = (String)PortletMessaging.receive(request, VIEW);
+        if (request.getUserPrincipal() != null)
+        {
+            // user is logged on, force them to logged on view
+            view = this.setView(request, "loggedon", SUCCESS1);
+            request.setAttribute(PARAM_VIEW_PAGE, view);            
+        }
+        else
+        {
+            if (view != null)
+            {
+                Integer ecode = (Integer)((RequestContext)request.getAttribute(RequestContext.REQUEST_PORTALENV)).getSessionAttribute(LoginConstants.ERRORCODE);
+                if (ecode != null && (ecode.equals(LoginConstants.ERROR_USER_DISABLED) || ecode.equals(LoginConstants.ERROR_CREDENTIAL_DISABLED)))
+                {
+                    view = this.setView(request, "three", FAILURE2);
+                }
+                request.setAttribute(PARAM_VIEW_PAGE, view);
+            }
+            else
+            {            
+                request.setAttribute(PARAM_VIEW_PAGE, this.getDefaultViewPage());            
+            }
+        }
+        StatusMessage message = (StatusMessage) PortletMessaging.consume(request, STATUS_MESSAGE);
+        if (message != null)
+        {
+            request.setAttribute(STATUS_MESSAGE, message);
+        }
+        if (view == null || view.equals("one"))
+        {
+            clearLoginMessages(request);
+        }
+        request.setAttribute(QUESTION_FACTORY, this.questionFactory);
+        super.doView(request, response);
+    }
+    
+    protected String[][] SUCCESS1_MAP =
+    {
+            { "one", "/WEB-INF/view/login3.jsp" },  // success and a valid cookie, move on to password
+            { "two", "/WEB-INF/view/login3.jsp" },  // success answer to personal question
+            { "three", "/WEB-INF/view/login3.jsp" }, // stay on the same page in case of errors
+            { "enroll", "/WEB-INF/view/enroll-login.jsp" },
+            { "enroll-login", "/WEB-INF/view/enroll.jsp"},
+            { "loggedon", "/WEB-INF/view/loggedon.jsp" }, 
+            { "restart", "/WEB-INF/view/login1.jsp" }
+    };
+    protected String[][] SUCCESS2_MAP =
+    {
+            { "one", "/WEB-INF/view/login2.jsp" },  // success but no valid cookie, move on to asking question
+    };
+    protected String[][] SUCCESS3_MAP =
+    {
+            { "one", "/WEB-INF/view/enroll-login.jsp" },  // success but no preferences configured yet, move on to enrollment mode
+    };        
+    protected String[][] FAILURE1_MAP =
+    {
+            { "one", "/WEB-INF/view/login1.jsp" },  // return back, display message
+            { "two", "/WEB-INF/view/login1.jsp" },   // stay on same page
+            { "three", "/WEB-INF/view/login3.jsp" },
+            { "enroll", "/WEB-INF/view/enroll.jsp" },  // validation error
+            { "enroll-login", "/WEB-INF/view/enroll-login.jsp"},            
+    };
+    protected String[][] FAILURE2_MAP =
+    {
+            { "one", "/WEB-INF/view/login2.jsp" }, // send them on like they succeeded, but its a trap
+            { "two", "/WEB-INF/view/login1.jsp" }, // user disabled, reset
+            { "enroll", "/WEB-INF/view/login1.jsp" },  // reset
+            { "enroll-login", "/WEB-INF/view/login4.jsp"},    
+            { "three", "/WEB-INF/view/login4.jsp" }
+    };
+
+    protected String[][][] TRANSITIONS =
+    {
+            SUCCESS1_MAP,
+            SUCCESS2_MAP,
+            SUCCESS3_MAP,
+            FAILURE1_MAP,
+            FAILURE2_MAP
+    };
+    
+    public static final String VIEW = "mfa.view";
+    public static final String USERBEAN = "userBean";
+    public static final String STATUS_MESSAGE = "statusMsg";
+    protected static final int SUCCESS1 = 0;
+    protected static final int SUCCESS2 = 1;
+    protected static final int SUCCESS3 = 2;    
+    protected static final int FAILURE1 = 3;
+    protected static final int FAILURE2 = 4;
+   
+    
+    protected String setView(PortletRequest request, String phase, int state)
+    throws PortletException
+    {
+        String[][] views = TRANSITIONS[state];
+        String result = "/WEB-INF/view/login1.jsp";
+        for (int ix = 0; ix < views.length; ix++)
+        {
+            if (views[ix][0].equals(phase))
+            {
+                try
+                {
+                    PortletMessaging.publish(request, VIEW, views[ix][1]);
+                    result = views[ix][1];
+                }
+                catch (Exception e)
+                {
+                    throw new PortletException(e);
+                }
+                break;
+            }
+        }
+        return result;
+    }
+    
+    public void processAction(ActionRequest actionRequest, ActionResponse actionResponse) throws PortletException,
+    IOException
+    {
+        String phase = actionRequest.getParameter("phase");
+        UserBean userBean = (UserBean)actionRequest.getPortletSession().getAttribute(USERBEAN, PortletSession.APPLICATION_SCOPE);            
+        if (userBean != null && phase != null)
+        {
+            if (phase.equals("one"))
+            {
+                // process captcha validation, and verify username
+                String captcha = actionRequest.getParameter("captcha");
+                String username = actionRequest.getParameter("username");
+                
+                if (SecurityHelper.isEmpty(captcha) || !userBean.getCaptcha().equals(captcha))
+                {
+                    StatusMessage msg = new StatusMessage("The text entered does not match the displayed text.", StatusMessage.ERROR);
+                    PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg);
+                    setView(actionRequest, phase, FAILURE1);                                                
+                    return;
+                }                
+                
+                if (userManager.userExists(username))
+                {                    
+                    User user = null;
+                    try
+                    {
+                        user = userManager.getUser(username);
+                    }
+                    catch (Exception e)
+                    {
+                        StatusMessage msg = new StatusMessage("User not accessible.", StatusMessage.ERROR);
+                        PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg);
+                        userBean.setInvalidUser(true);
+                        userBean.setQuestion( questionFactory.getRandomQuestion() );                        
+                        setView(actionRequest, phase, SUCCESS2); // act like nothing happening
+                        return;
+                    }
+                    userBean.setUsername(username);
+                    userBean.setUser(user);                    
+                    PasswordCredential credential = SecurityHelper.getCredential(user);
+                    if ( credential != null )
+                    {
+                        if (credential.isEnabled() == false)
+                        {
+                            userBean.setInvalidUser(true);
+                            setView(actionRequest, phase, SUCCESS2);
+                            userBean.setQuestion( questionFactory.getRandomQuestion() );                            
+                            StatusMessage msg = new StatusMessage("The account has been disabled.", StatusMessage.ERROR);
+                            PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg);                            
+                            return;
+                        }
+                    }
+                    userBean.setUser(user);
+                    userBean.setUsername(username);
+                    // see if we have a valid MFA Cookie
+                    Cookie mfaCookie = SecurityHelper.getMFACookie(actionRequest, username);
+                    if (mfaCookie == null)
+                    {
+                        if (generateQuestionAndAnswer(userBean))
+                        {
+                            setView(actionRequest, phase, SUCCESS2);
+                        }
+                        else
+                        {
+                            // go into enrollment mode
+                            setView(actionRequest, phase, SUCCESS3);                                
+                        }                            
+                    }
+                    else
+                    {
+                    	Preferences userAttributes = getUserAttributes(userBean);
+                    	String cookie = userAttributes.get("user.cookie", username);                        	
+                        if (mfaCookie.getValue().equals(cookie))
+                        {                        
+                            userBean.setHasCookie(true);
+                        	userBean.setPassPhrase( userAttributes.get("user.passphrase", "") );
+                            setView(actionRequest, phase, SUCCESS1);
+                        }
+                        else
+                        {
+                            userBean.setHasCookie(false);
+                            if (generateQuestionAndAnswer(userBean))
+                            {
+                                setView(actionRequest, phase, SUCCESS2);
+                            }
+                            else
+                            {
+                                // go into enrollment mode
+                                setView(actionRequest, phase, SUCCESS3);
+                            }                                                                
+                        }
+                    }
+                }
+                else
+                {
+                    // Proceed on but mark the User Bean as invalid user to prevent user harvesting
+                	// Also need to supply a random challenge question.
+                    userBean.setInvalidUser(true);
+                	userBean.setQuestion( questionFactory.getRandomQuestion() );
+                    StatusMessage msg = new StatusMessage("The text entered does not match the displayed text.", StatusMessage.ERROR);
+                    PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg);                    
+                    setView(actionRequest, phase, SUCCESS2);
+                }                
+            }            
+            else if (phase.equals("two"))
+            {
+                if (userBean.isInvalidUser())
+                {
+                    // prevent harvesting
+                    StatusMessage msg = new StatusMessage("Invalid User.", StatusMessage.ERROR);
+                    PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg);                    
+                    setView(actionRequest, phase, FAILURE1);
+                }
+                else
+                {
+                    if (userBean.getUser() == null)
+                    {
+                        StatusMessage msg = new StatusMessage("User not accessible.", StatusMessage.ERROR);
+                        PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg);
+                        setView(actionRequest, phase, FAILURE1);                                                                                                            
+                        return;
+                    }
+                    String typedAnswer = actionRequest.getParameter("answer");
+                    String publicTerminal = actionRequest.getParameter("publicTerminal");
+                    userBean.setPublicTerminal(publicTerminal != null);
+                    Preferences userAttributes = getUserAttributes(userBean);                       
+                    int failures = Integer.parseInt(userAttributes.get("user.question.failures", "0"));                    
+                    if (SecurityHelper.isEmpty(typedAnswer) || !typedAnswer.equalsIgnoreCase(userBean.getAnswer()))
+                    {
+                        int count = failures + 1;
+                        if (count >= this.maxNumberOfAuthenticationFailures)
+                        {
+                            try
+                            {
+                                userManager.setPasswordEnabled(userBean.getUsername(), false);
+                                User user = userManager.getUser(userBean.getUsername());                        
+                                userBean.setUser(user);
+                                userAttributes = getUserAttributes(userBean);                                                       
+                                userAttributes.put("user.question.failures", "0");                                                                
+                                audit.logUserActivity(userBean.getUsername(), 
+                                        ((RequestContext)actionRequest.getAttribute(RequestContext.REQUEST_PORTALENV)).getRequest().getRemoteAddr(), 
+                                        AuditActivity.USER_DISABLE, "Failed question and answer limit reached");                                
+                            }
+                            catch (Exception e)
+                            {
+                            }
+                            StatusMessage msg = new StatusMessage("Disabling user after too many failed questions.", StatusMessage.ERROR);
+                            PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg); 
+                            setView(actionRequest, phase, FAILURE2);                            
+                        }
+                        else
+                        {
+                            userAttributes.put("user.question.failures", Integer.toString(count));                            
+                            StatusMessage msg = new StatusMessage("Invalid answer to question.", StatusMessage.ERROR);
+                            PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg); 
+                            setView(actionRequest, phase, FAILURE1);
+                        }
+                    }
+                    else
+                    {
+                    	userBean.setPassPhrase( userAttributes.get("user.passphrase", "") );
+                        userAttributes.put("user.question.failures", "0");
+                        setView(actionRequest, phase, SUCCESS1);                                
+                    }
+                }
+            }
+            else if (phase.equals("enroll"))
+            {
+                boolean success = false;
+                String password = userBean.getPassword();
+                User user = userBean.getUser();
+                if (user != null && password != null)
+                {
+                    boolean authenticated = userManager.authenticate(userBean.getUsername(), password);                    
+                    if (authenticated)
+                    {
+                        // validate request parameers, if valid update user preferences
+                        String question1 = actionRequest.getParameter("question1");
+                        String question2 = actionRequest.getParameter("question2");
+                        String question3 = actionRequest.getParameter("question3");
+                        String answer1 = actionRequest.getParameter("answer1");
+                        String answer2 = actionRequest.getParameter("answer2");
+                        String answer3 = actionRequest.getParameter("answer3");
+                        String passPhrase = actionRequest.getParameter("passphrase");
+                        
+                        // validation (SecurityHelper.isEmpty, unique questions)
+                        if (SecurityHelper.isEmpty(answer1) || SecurityHelper.isEmpty(answer2) || SecurityHelper.isEmpty(answer3))
+                        {
+                            StatusMessage msg = new StatusMessage("Please enter a valid answer for all 3 questions.", StatusMessage.ERROR);
+                            PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg);
+                            setView(actionRequest, phase, FAILURE1);                                                                    
+                            return;
+                        }
+                        if (SecurityHelper.isEmpty(passPhrase))
+                        {
+                            StatusMessage msg = new StatusMessage("Please enter a valid pass phrase.", StatusMessage.ERROR);
+                            PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg);
+                            setView(actionRequest, phase, FAILURE1);                                                                    
+                            return;
+                        }
+                        if (question1.equals(question2) || question1.equals(question3) || question2.equals(question3))
+                        {
+                            StatusMessage msg = new StatusMessage("Please select a unique question in all cases.", StatusMessage.ERROR);
+                            PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg);
+                            setView(actionRequest, phase, FAILURE1);                                                                    
+                            return;                    
+                        }
+        
+                        // update the prefs db (we are not logged in yet
+                        user = userBean.getUser();
+                        user.getUserAttributes().put("user.question.1", question1);
+                        user.getUserAttributes().put("user.question.2", question2);
+                        user.getUserAttributes().put("user.question.3", question3);
+                        user.getUserAttributes().put("user.answer.1", answer1);
+                        user.getUserAttributes().put("user.answer.2", answer2);
+                        user.getUserAttributes().put("user.answer.3", answer3);      
+                        user.getUserAttributes().put("user.passphrase", passPhrase); 
+                        user.getUserAttributes().put("user.cookie", CaptchaImageResource.randomString(8, 16) );
+        
+                        String username = userBean.getUsername();
+                        String redirect = actionRequest.getParameter("redirect");
+                        audit.logUserActivity(username, ((RequestContext)actionRequest.getAttribute(RequestContext.REQUEST_PORTALENV)).getRequest().getRemoteAddr(), 
+                                LOGIN_ENROLL_ACTIVITY, "enrolling user with questions and passphrase");
+                        redirect(actionRequest, actionResponse, redirect, username, password);
+                        success = true;
+                    }
+                }
+                if (success == false)
+                {
+                    RequestContext rc = (RequestContext)actionRequest.getAttribute(RequestContext.REQUEST_PORTALENV);                    
+                    audit.logUserActivity(userBean.getUsername(), rc.getRequest().getRemoteAddr(), AuditActivity.AUTHENTICATION_FAILURE, "Unauthorized Attribute Modification Attempt.");
+                    setView(actionRequest, phase, FAILURE2);                    
+                }
+            }
+            else if (phase.equals("enroll-login"))
+            {                
+                String username = userBean.getUsername();
+                String password = actionRequest.getParameter(LoginConstants.PASSWORD);
+                if (SecurityHelper.isEmpty(password))
+                {
+                    ((RequestContext)actionRequest).setSessionAttribute(MFALogin.ERRORCODE, LoginConstants.ERROR_INVALID_PASSWORD);                    
+                    setView(actionRequest, phase, FAILURE1);
+                    return;
+                }                
+                // are we in the enrollment phase?
+                if (SecurityHelper.isEmpty(userBean.getPassPhrase()))
+                {
+                    boolean authenticated = userManager.authenticate(username, password);
+                    if (authenticated)
+                    {
+                        userBean.setPassword(password);
+                        setView(actionRequest, phase, SUCCESS1);
+                        clearLoginMessages(actionRequest);
+                    }
+                    else
+                    {
+                        failedLoginProcessing(actionRequest, phase, username, userBean);                        
+                    }                    
+                }
+            }
+            else if (phase.equals("three"))
+            {
+                String redirect = actionRequest.getParameter("redirect");
+
+                String username = userBean.getUsername();
+                String password = actionRequest.getParameter(LoginConstants.PASSWORD);
+                if (SecurityHelper.isEmpty(password) || SecurityHelper.isEmpty(redirect))
+                {
+                    ((RequestContext)actionRequest).setSessionAttribute(MFALogin.ERRORCODE, LoginConstants.ERROR_INVALID_PASSWORD);                                        
+                    setView(actionRequest, phase, FAILURE1);
+                    return;
+                }                
+                // process authentication
+                boolean authenticated = userManager.authenticate(username, password);
+                if (authenticated)
+                {
+                    userBean.setPassword(password);
+                    setView(actionRequest, phase, SUCCESS1);
+                    clearLoginMessages(actionRequest);
+                    if (!userBean.isHasCookie() && !userBean.isPublicTerminal())
+                    {
+                        Preferences userAttributes = getUserAttributes(userBean);
+                        String cookie = userAttributes.get("user.cookie", username);
+                        
+                        SecurityHelper.addMFACookie(actionRequest, username, cookie, cookieLifetime);
+                        userBean.setHasCookie(true);
+                    }                               
+                    // set cookie
+                    setView(actionRequest, phase, SUCCESS1);
+                    redirect(actionRequest, actionResponse, redirect, username, password);                    
+                }
+                else
+                {
+                    failedLoginProcessing(actionRequest, phase, username, userBean);                        
+                }                    
+            }
+            else if (phase.equals("restart"))
+            {
+                clearLoginMessages(actionRequest);                
+                setView(actionRequest, phase,  SUCCESS1);                                                                    
+            }                
+        }
+    }
+
+    private void failedLoginProcessing(ActionRequest actionRequest, String phase, String username, UserBean userBean) throws NotSerializableException, PortletException
+    {
+        int nextView = FAILURE1;
+        User user = null;
+        try
+        {
+            user = userManager.getUser(username);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return;
+        }
+        PasswordCredential pwdCredential = SecurityHelper.getCredential(user);
+        userBean.setUser(user);
+        // Failed login processing
+        RequestContext rc = (RequestContext)actionRequest.getAttribute(RequestContext.REQUEST_PORTALENV);
+        HttpSession session = rc.getRequest().getSession(true);
+        Integer retryCount = (Integer) session.getAttribute(MFALogin.RETRYCOUNT);
+        if (retryCount == null)
+            retryCount = new Integer(1);
+        else
+            retryCount = new Integer(retryCount.intValue() + 1);
+        session.setAttribute(MFALogin.RETRYCOUNT, retryCount);
+        if ( pwdCredential == null || !pwdCredential.isEnabled() )
+        {
+            rc.setSessionAttribute(MFALogin.ERRORCODE, LoginConstants.ERROR_CREDENTIAL_DISABLED);
+            nextView = FAILURE2;
+        }
+        else if ( pwdCredential.isExpired() )
+        {
+            rc.setSessionAttribute(MFALogin.ERRORCODE, LoginConstants.ERROR_CREDENTIAL_EXPIRED);
+        }
+        else if ( maxNumberOfAuthenticationFailures > 1 && pwdCredential.getAuthenticationFailures() == maxNumberOfAuthenticationFailures -1  )
+        {
+            rc.setSessionAttribute(MFALogin.ERRORCODE, LoginConstants.ERROR_FINAL_LOGIN_ATTEMPT);
+        }
+        else
+        {
+            rc.setSessionAttribute(MFALogin.ERRORCODE, LoginConstants.ERROR_INVALID_PASSWORD);
+        }
+        audit.logUserActivity(username, rc.getRequest().getRemoteAddr(), AuditActivity.AUTHENTICATION_FAILURE, "MFA");
+        //StatusMessage msg = new StatusMessage("invalid password.", StatusMessage.ERROR);
+        //PortletMessaging.publish(actionRequest, STATUS_MESSAGE, msg);
+        setView(actionRequest, phase, nextView);
+    }
+
+    private void clearLoginMessages(PortletRequest actionRequest)
+    {
+        RequestContext rc = (RequestContext)actionRequest.getAttribute(RequestContext.REQUEST_PORTALENV);
+        HttpSession session = rc.getRequest().getSession(true);
+        session.removeAttribute(MFALogin.RETRYCOUNT);
+        session.removeAttribute(MFALogin.ERRORCODE);
+        session.removeAttribute(LoginConstants.RETRYCOUNT);
+        session.removeAttribute(LoginConstants.ERRORCODE);                
+    }
+    
+    private void redirect(ActionRequest actionRequest, ActionResponse actionResponse, String redirect, String username, String password) throws IOException
+    {
+        StringBuffer s = new StringBuffer();
+        s.append(redirect);
+        if (!redirect.endsWith("/"))
+            s.append("/");
+        s.append("login/proxy");
+        /*
+        s.append("?");
+        s.append(LoginConstants.USERNAME);
+        s.append("=");
+        s.append(username);
+        s.append("&");
+        s.append(LoginConstants.PASSWORD);
+        s.append("=");
+        s.append(password);
+        */
+        //System.out.println("Redirect: " + s.toString());
+        RequestContext rc = (RequestContext)actionRequest.getAttribute(RequestContext.REQUEST_PORTALENV);
+        HttpServletRequest request = rc.getRequest();
+        HttpSession session = request.getSession(true);
+        session.setAttribute(LoginConstants.USERNAME, username);
+        session.setAttribute(LoginConstants.PASSWORD, password);
+        actionResponse.sendRedirect(s.toString());
+    }
+            
+    public boolean generateQuestionAndAnswer(UserBean userBean)
+    {
+        User user = userBean.getUser();
+        if (user == null)
+        {
+            if (userBean.getUsername() == null)
+            {
+                // hard out of luck
+                return false;
+            }
+            else
+            {
+                try
+                {
+                    user = userManager.getUser(userBean.getUsername());
+                }
+                catch (Exception e)
+                {
+                   	// not a valid user; present a random question.
+                	userBean.setQuestion( questionFactory.getRandomQuestion() );
+                    return false;
+                }
+            }
+        }
+        Preferences ua = user.getUserAttributes();
+        String[] questions = new String[3];
+        String[] answers = new String[3];
+        int max = 3;
+        
+        questions[0] = ua.get("user.question.1", null);
+        answers[0] = ua.get("user.answer.1", null);
+        if (SecurityHelper.isEmpty(questions[0]) || SecurityHelper.isEmpty(answers[0]))
+        {
+            return false;
+        }
+        questions[1] = ua.get("user.question.2", null);
+        answers[1] = ua.get("user.answer.2", null);
+        if (SecurityHelper.isEmpty(questions[1]) || SecurityHelper.isEmpty(answers[1]))
+        {
+            // work with what we got
+            userBean.setQuestion(questions[0]);
+            userBean.setAnswer(answers[0]);
+            return true;
+        }
+        questions[2] = ua.get("user.question.3", null);
+        answers[2] = ua.get("user.answer.3", null);
+        if (SecurityHelper.isEmpty(questions[2]) || SecurityHelper.isEmpty(answers[2]))
+        {
+            // work with what we got
+            max = 2;
+        }
+        
+        int index = rand.nextInt(max);
+        userBean.setQuestion(questions[index]);
+        userBean.setAnswer(answers[index]);
+        return true;        
+    }
+
+    private Preferences getUserAttributes(UserBean user)
+    {
+    	return user.getUser().getUserAttributes();
+    }
+}

Added: portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/portlets/StatusMessage.java
URL: http://svn.apache.org/viewvc/portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/portlets/StatusMessage.java?rev=693316&view=auto
==============================================================================
--- portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/portlets/StatusMessage.java (added)
+++ portals/jetspeed-2/applications/mfa/src/org/apache/jetspeed/security/mfa/portlets/StatusMessage.java Mon Sep  8 16:31:33 2008
@@ -0,0 +1,78 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jetspeed.security.mfa.portlets;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
+ * @version $Id: $
+ */
+public class StatusMessage implements Serializable
+{    
+    private static final long serialVersionUID = 1;    
+    private String text;
+    private String type;
+        
+    public static final String INFO  = "portlet-msg-info";
+    public static final String ERROR = "portlet-msg-error";
+    public static final String ALERT = "portlet-msg-alert";
+    public static final String SUCCESS = "portlet-msg-success";
+    
+    public StatusMessage(String text, String type)
+    {
+        this.text = new String(text);
+        this.type = type;
+    }
+
+    public StatusMessage(String text)
+    {
+        this.text = new String(text);
+        this.type = INFO;
+    }
+    
+    
+    
+    /**
+     * @return Returns the text.
+     */
+    public String getText()
+    {
+        return text;
+    }
+    /**
+     * @param text The text to set.
+     */
+    public void setText(String text)
+    {
+        this.text = text;
+    }
+    /**
+     * @return Returns the type.
+     */
+    public String getType()
+    {
+        return type;
+    }
+    /**
+     * @param type The type to set.
+     */
+    public void setType(String type)
+    {
+        this.type = type;
+    }
+}
\ No newline at end of file



---------------------------------------------------------------------
To unsubscribe, e-mail: jetspeed-dev-unsubscribe@portals.apache.org
For additional commands, e-mail: jetspeed-dev-help@portals.apache.org