001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.captcha.simplecaptcha;
016    
017    import com.liferay.portal.kernel.captcha.Captcha;
018    import com.liferay.portal.kernel.captcha.CaptchaException;
019    import com.liferay.portal.kernel.captcha.CaptchaMaxChallengesException;
020    import com.liferay.portal.kernel.captcha.CaptchaTextException;
021    import com.liferay.portal.kernel.log.Log;
022    import com.liferay.portal.kernel.log.LogFactoryUtil;
023    import com.liferay.portal.kernel.util.ContentTypes;
024    import com.liferay.portal.kernel.util.InstancePool;
025    import com.liferay.portal.kernel.util.ParamUtil;
026    import com.liferay.portal.kernel.util.Randomizer;
027    import com.liferay.portal.kernel.util.Validator;
028    import com.liferay.portal.util.PropsValues;
029    import com.liferay.portal.util.WebKeys;
030    
031    import java.io.IOException;
032    
033    import javax.portlet.PortletRequest;
034    import javax.portlet.PortletSession;
035    import javax.portlet.ResourceRequest;
036    import javax.portlet.ResourceResponse;
037    
038    import javax.servlet.http.HttpServletRequest;
039    import javax.servlet.http.HttpServletResponse;
040    import javax.servlet.http.HttpSession;
041    
042    import nl.captcha.backgrounds.BackgroundProducer;
043    import nl.captcha.gimpy.GimpyRenderer;
044    import nl.captcha.noise.NoiseProducer;
045    import nl.captcha.servlet.CaptchaServletUtil;
046    import nl.captcha.text.producer.TextProducer;
047    import nl.captcha.text.renderer.WordRenderer;
048    
049    /**
050     * @author Brian Wing Shun Chan
051     * @author Daniel Sanz
052     */
053    public class SimpleCaptchaImpl implements Captcha {
054    
055            public SimpleCaptchaImpl() {
056                    initBackgroundProducers();
057                    initGimpyRenderers();
058                    initNoiseProducers();
059                    initTextProducers();
060                    initWordRenderers();
061            }
062    
063            public void check(HttpServletRequest request) throws CaptchaException {
064                    if (!isEnabled(request)) {
065                            return;
066                    }
067    
068                    if (!validateChallenge(request)) {
069                            incrementCounter(request);
070    
071                            checkMaxChallenges(request);
072    
073                            throw new CaptchaTextException();
074                    }
075    
076                    if (_log.isDebugEnabled()) {
077                            _log.debug("CAPTCHA text is valid");
078                    }
079            }
080    
081            public void check(PortletRequest portletRequest) throws CaptchaException {
082                    if (!isEnabled(portletRequest)) {
083                            return;
084                    }
085    
086                    if (!validateChallenge(portletRequest)) {
087                            incrementCounter(portletRequest);
088    
089                            checkMaxChallenges(portletRequest);
090    
091                            throw new CaptchaTextException();
092                    }
093    
094                    if (_log.isDebugEnabled()) {
095                            _log.debug("CAPTCHA text is valid");
096                    }
097            }
098    
099            public String getTaglibPath() {
100                    return _TAGLIB_PATH;
101            }
102    
103            public boolean isEnabled(HttpServletRequest request)
104                    throws CaptchaException {
105    
106                    checkMaxChallenges(request);
107    
108                    if (PropsValues.CAPTCHA_MAX_CHALLENGES >= 0) {
109                            return true;
110                    }
111                    else {
112                            return false;
113                    }
114            }
115    
116            public boolean isEnabled(PortletRequest portletRequest)
117                    throws CaptchaException {
118    
119                    checkMaxChallenges(portletRequest);
120    
121                    if (PropsValues.CAPTCHA_MAX_CHALLENGES >= 0) {
122                            return true;
123                    }
124                    else {
125                            return false;
126                    }
127            }
128    
129            public void serveImage(
130                            HttpServletRequest request, HttpServletResponse response)
131                    throws IOException {
132    
133                    HttpSession session = request.getSession();
134    
135                    nl.captcha.Captcha simpleCaptcha = getSimpleCaptcha();
136    
137                    session.setAttribute(WebKeys.CAPTCHA_TEXT, simpleCaptcha.getAnswer());
138    
139                    response.setContentType(ContentTypes.IMAGE_JPEG);
140    
141                    CaptchaServletUtil.writeImage(
142                            response.getOutputStream(), simpleCaptcha.getImage());
143            }
144    
145            public void serveImage(
146                            ResourceRequest resourceRequest, ResourceResponse resourceResponse)
147                    throws IOException {
148    
149                    PortletSession portletSession = resourceRequest.getPortletSession();
150    
151                    nl.captcha.Captcha simpleCaptcha = getSimpleCaptcha();
152    
153                    portletSession.setAttribute(
154                            WebKeys.CAPTCHA_TEXT, simpleCaptcha.getAnswer());
155    
156                    CaptchaServletUtil.writeImage(
157                            resourceResponse.getPortletOutputStream(),
158                            simpleCaptcha.getImage());
159            }
160    
161            protected void checkMaxChallenges(HttpServletRequest request)
162                    throws CaptchaMaxChallengesException {
163    
164                    if (PropsValues.CAPTCHA_MAX_CHALLENGES > 0) {
165                            HttpSession session = request.getSession();
166    
167                            Integer count = (Integer)session.getAttribute(
168                                    WebKeys.CAPTCHA_COUNT);
169    
170                            checkMaxChallenges(count);
171                    }
172            }
173    
174            protected void checkMaxChallenges(Integer count)
175                    throws CaptchaMaxChallengesException {
176    
177                    if ((count != null) && (count > PropsValues.CAPTCHA_MAX_CHALLENGES)) {
178                            throw new CaptchaMaxChallengesException();
179                    }
180            }
181    
182            protected void checkMaxChallenges(PortletRequest portletRequest)
183                    throws CaptchaMaxChallengesException {
184    
185                    if (PropsValues.CAPTCHA_MAX_CHALLENGES > 0) {
186                            PortletSession portletSession = portletRequest.getPortletSession();
187    
188                            Integer count = (Integer)portletSession.getAttribute(
189                                    WebKeys.CAPTCHA_COUNT);
190    
191                            checkMaxChallenges(count);
192                    }
193            }
194    
195            protected BackgroundProducer getBackgroundProducer() {
196                    if (_backgroundProducers.length == 1) {
197                            return _backgroundProducers[0];
198                    }
199    
200                    Randomizer randomizer = Randomizer.getInstance();
201    
202                    int pos = randomizer.nextInt(_backgroundProducers.length);
203    
204                    return _backgroundProducers[pos];
205            }
206    
207            protected GimpyRenderer getGimpyRenderer() {
208                    if (_gimpyRenderers.length == 1) {
209                            return _gimpyRenderers[0];
210                    }
211    
212                    Randomizer randomizer = Randomizer.getInstance();
213    
214                    int pos = randomizer.nextInt(_gimpyRenderers.length);
215    
216                    return _gimpyRenderers[pos];
217            }
218    
219            protected int getHeight() {
220                    return PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_HEIGHT;
221            }
222    
223            protected NoiseProducer getNoiseProducer() {
224                    if (_noiseProducers.length == 1) {
225                            return _noiseProducers[0];
226                    }
227    
228                    Randomizer randomizer = Randomizer.getInstance();
229    
230                    int pos = randomizer.nextInt(_noiseProducers.length);
231    
232                    return _noiseProducers[pos];
233            }
234    
235            protected nl.captcha.Captcha getSimpleCaptcha() {
236                    nl.captcha.Captcha.Builder captchaBuilder =
237                            new nl.captcha.Captcha.Builder(getWidth(), getHeight());
238    
239                    captchaBuilder.addText(getTextProducer(), getWordRenderer());
240                    captchaBuilder.addBackground(getBackgroundProducer());
241                    captchaBuilder.gimp(getGimpyRenderer());
242                    captchaBuilder.addNoise(getNoiseProducer());
243                    captchaBuilder.addBorder();
244    
245                    return captchaBuilder.build();
246            }
247    
248            protected TextProducer getTextProducer() {
249                    if (_textProducers.length == 1) {
250                            return _textProducers[0];
251                    }
252    
253                    Randomizer randomizer = Randomizer.getInstance();
254    
255                    int pos = randomizer.nextInt(_textProducers.length);
256    
257                    return _textProducers[pos];
258            }
259    
260            protected int getWidth() {
261                    return PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_WIDTH;
262            }
263    
264            protected WordRenderer getWordRenderer() {
265                    if (_wordRenderers.length == 1) {
266                            return _wordRenderers[0];
267                    }
268    
269                    Randomizer randomizer = Randomizer.getInstance();
270    
271                    int pos = randomizer.nextInt(_wordRenderers.length);
272    
273                    return _wordRenderers[pos];
274            }
275    
276            protected void incrementCounter(HttpServletRequest request) {
277                    if ((PropsValues.CAPTCHA_MAX_CHALLENGES > 0) &&
278                            Validator.isNotNull(request.getRemoteUser())) {
279    
280                            HttpSession session = request.getSession();
281    
282                            Integer count = (Integer)session.getAttribute(
283                                    WebKeys.CAPTCHA_COUNT);
284    
285                            session.setAttribute(
286                                    WebKeys.CAPTCHA_COUNT, incrementCounter(count));
287                    }
288            }
289    
290            protected Integer incrementCounter(Integer count) {
291                    if (count == null) {
292                            count = new Integer(1);
293                    }
294                    else {
295                            count = new Integer(count.intValue() + 1);
296                    }
297    
298                    return count;
299            }
300    
301            protected void incrementCounter(PortletRequest portletRequest) {
302                    if ((PropsValues.CAPTCHA_MAX_CHALLENGES > 0) &&
303                            Validator.isNotNull(portletRequest.getRemoteUser())) {
304    
305                            PortletSession portletSession = portletRequest.getPortletSession();
306    
307                            Integer count = (Integer)portletSession.getAttribute(
308                                    WebKeys.CAPTCHA_COUNT);
309    
310                            portletSession.setAttribute(
311                                    WebKeys.CAPTCHA_COUNT, incrementCounter(count));
312                    }
313            }
314    
315            protected void initBackgroundProducers() {
316                    String[] backgroundProducerClassNames =
317                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_BACKGROUND_PRODUCERS;
318    
319                    _backgroundProducers = new BackgroundProducer[
320                            backgroundProducerClassNames.length];
321    
322                    for (int i = 0; i < backgroundProducerClassNames.length; i++) {
323                            String backgroundProducerClassName =
324                                    backgroundProducerClassNames[i];
325    
326                            _backgroundProducers[i] = (BackgroundProducer)InstancePool.get(
327                                    backgroundProducerClassName);
328                    }
329            }
330    
331            protected void initGimpyRenderers() {
332                    String[] gimpyRendererClassNames =
333                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_GIMPY_RENDERERS;
334    
335                    _gimpyRenderers = new GimpyRenderer[
336                            gimpyRendererClassNames.length];
337    
338                    for (int i = 0; i < gimpyRendererClassNames.length; i++) {
339                            String gimpyRendererClassName = gimpyRendererClassNames[i];
340    
341                            _gimpyRenderers[i] = (GimpyRenderer)InstancePool.get(
342                                    gimpyRendererClassName);
343                    }
344            }
345    
346            protected void initNoiseProducers() {
347                    String[] noiseProducerClassNames =
348                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_NOISE_PRODUCERS;
349    
350                    _noiseProducers = new NoiseProducer[noiseProducerClassNames.length];
351    
352                    for (int i = 0; i < noiseProducerClassNames.length; i++) {
353                            String noiseProducerClassName = noiseProducerClassNames[i];
354    
355                            _noiseProducers[i] = (NoiseProducer)InstancePool.get(
356                                    noiseProducerClassName);
357                    }
358            }
359    
360            protected void initTextProducers() {
361                    String[] textProducerClassNames =
362                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_TEXT_PRODUCERS;
363    
364                    _textProducers = new TextProducer[textProducerClassNames.length];
365    
366                    for (int i = 0; i < textProducerClassNames.length; i++) {
367                            String textProducerClassName = textProducerClassNames[i];
368    
369                            _textProducers[i] = (TextProducer)InstancePool.get(
370                                    textProducerClassName);
371                    }
372            }
373    
374            protected void initWordRenderers() {
375                    String[] wordRendererClassNames =
376                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_WORD_RENDERERS;
377    
378                    _wordRenderers = new WordRenderer[wordRendererClassNames.length];
379    
380                    for (int i = 0; i < wordRendererClassNames.length; i++) {
381                            String wordRendererClassName = wordRendererClassNames[i];
382    
383                            _wordRenderers[i] = (WordRenderer)InstancePool.get(
384                                    wordRendererClassName);
385                    }
386            }
387    
388            protected boolean validateChallenge(HttpServletRequest request)
389                    throws CaptchaException {
390    
391                    HttpSession session = request.getSession();
392    
393                    String captchaText = (String)session.getAttribute(WebKeys.CAPTCHA_TEXT);
394    
395                    if (captchaText == null) {
396                            _log.error(
397                                    "CAPTCHA text is null. User " + request.getRemoteUser() +
398                                            " may be trying to circumvent the CAPTCHA.");
399    
400                            throw new CaptchaTextException();
401                    }
402    
403                    boolean valid = captchaText.equals(
404                            ParamUtil.getString(request, "captchaText"));
405    
406                    if (valid) {
407                            session.removeAttribute(WebKeys.CAPTCHA_TEXT);
408                    }
409    
410                    return valid;
411            }
412    
413            protected boolean validateChallenge(PortletRequest portletRequest)
414                    throws CaptchaException {
415    
416                    PortletSession portletSession = portletRequest.getPortletSession();
417    
418                    String captchaText = (String)portletSession.getAttribute(
419                            WebKeys.CAPTCHA_TEXT);
420    
421                    if (captchaText == null) {
422                            _log.error(
423                                    "CAPTCHA text is null. User " + portletRequest.getRemoteUser() +
424                                            " may be trying to circumvent the CAPTCHA.");
425    
426                            throw new CaptchaTextException();
427                    }
428    
429                    boolean valid = captchaText.equals(
430                            ParamUtil.getString(portletRequest, "captchaText"));
431    
432                    if (valid) {
433                            portletSession.removeAttribute(WebKeys.CAPTCHA_TEXT);
434                    }
435    
436                    return valid;
437            }
438    
439            private static final String _TAGLIB_PATH =
440                    "/html/taglib/ui/captcha/simplecaptcha.jsp";
441    
442            private static Log _log = LogFactoryUtil.getLog(SimpleCaptchaImpl.class);
443    
444            private BackgroundProducer[] _backgroundProducers;
445            private GimpyRenderer[] _gimpyRenderers;
446            private NoiseProducer[] _noiseProducers;
447            private TextProducer[] _textProducers;
448            private WordRenderer[] _wordRenderers;
449    
450    }