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