001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.forms.formmodel;
018:
019: import java.security.NoSuchAlgorithmException;
020: import java.security.SecureRandom;
021: import java.util.Locale;
022: import java.util.Map;
023:
024: import org.apache.avalon.framework.CascadingRuntimeException;
025: import org.apache.avalon.framework.context.Context;
026: import org.apache.cocoon.components.ContextHelper;
027: import org.apache.cocoon.environment.ObjectModelHelper;
028: import org.apache.cocoon.environment.Session;
029: import org.apache.cocoon.forms.FormsConstants;
030: import org.apache.cocoon.xml.AttributesImpl;
031: import org.xml.sax.ContentHandler;
032: import org.xml.sax.SAXException;
033:
034: /**
035: * A {@link Field} for CAPTCHA validation. Upon generation, a secret random string is stored
036: * in a session attribute having a randomly generated name, for use by a
037: * {@link org.apache.cocoon.forms.validation.impl.CaptchaValidator}.
038: * <br>
039: * Usage sample:
040: * <pre>
041: <fd:captcha id="f1" required="true">
042: <fd:label>Copy the number shown into the input field</fd:label>
043: <fd:datatype base="string"/>
044: <fd:validation>
045: <fd:captcha/>
046: </fd:validation>
047: </fd:captcha>
048: * </pre>
049: *
050: * @see <a href="http://www.captcha.net/">captcha.net</a>
051: * @version $Id: CaptchaField.java 449149 2006-09-23 03:58:05Z crossley $
052: */
053: public class CaptchaField extends Field {
054:
055: public static final String SESSION_ATTR_PREFIX = "captcha-";
056:
057: private static final String IMAGE_EL = "captcha-image";
058: private static final String SECRET_CHARS = "abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
059: private static final int SESSION_ATTR_NAME_LENGTH = 6;
060:
061: private Context avalonContext;
062: private int length;
063:
064: /**
065: * Random number generator used to create session attribute name.
066: */
067: protected static final SecureRandom random;
068:
069: static {
070: SecureRandom sr = null;
071: try {
072: sr = SecureRandom.getInstance("SHA1PRNG");
073: } catch (java.security.NoSuchAlgorithmException nsae) {
074: // Maybe we are on IBM's SDK
075: try {
076: sr = SecureRandom.getInstance("IBMSecureRandom");
077: } catch (NoSuchAlgorithmException e) {
078: throw new CascadingRuntimeException(
079: "No random number generator available", e);
080: }
081: } finally {
082: random = sr;
083: }
084: random.setSeed(System.currentTimeMillis());
085: }
086:
087: public CaptchaField(CaptchaFieldDefinition fieldDefinition,
088: Context avalonContext) {
089: super (fieldDefinition);
090: this .avalonContext = avalonContext;
091: this .length = fieldDefinition.getLength();
092: }
093:
094: private String generateSecret() {
095: StringBuffer secret = new StringBuffer(length);
096: for (int n = 0; n < length; n++) {
097: int randomnumber = random.nextInt(SECRET_CHARS.length());
098: secret.append(SECRET_CHARS.charAt(randomnumber));
099: }
100: return secret.toString();
101: }
102:
103: public void generateItemSaxFragment(ContentHandler contentHandler,
104: Locale locale) throws SAXException {
105: super .generateItemSaxFragment(contentHandler, locale);
106: byte[] bytes = new byte[SESSION_ATTR_NAME_LENGTH];
107: char[] result = new char[bytes.length * 2];
108: random.nextBytes(bytes);
109: for (int i = 0; i < SESSION_ATTR_NAME_LENGTH; i++) {
110: byte ch = bytes[i];
111: result[2 * i] = Character.forDigit(Math.abs(ch >> 4), 16);
112: result[2 * i + 1] = Character.forDigit(Math.abs(ch & 0x0f),
113: 16);
114: }
115: String id = new String(result);
116: Map objectModel = ContextHelper
117: .getObjectModel(this .avalonContext);
118: Session session = ObjectModelHelper.getRequest(objectModel)
119: .getSession(true);
120: String secret = generateSecret();
121: session.setAttribute(SESSION_ATTR_PREFIX + id, secret);
122: this .setAttribute("secret", secret);
123: AttributesImpl attrs = new AttributesImpl();
124: attrs.addAttribute("", "id", "id", "PCDATA", id);
125: contentHandler.startElement(FormsConstants.INSTANCE_NS,
126: IMAGE_EL, FormsConstants.INSTANCE_PREFIX_COLON
127: + IMAGE_EL, attrs);
128: contentHandler.endElement(FormsConstants.INSTANCE_NS, IMAGE_EL,
129: FormsConstants.INSTANCE_PREFIX_COLON + IMAGE_EL);
130: }
131: }
|