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.PortalUtil;
029    import com.liferay.portal.util.PropsValues;
030    import com.liferay.portal.util.WebKeys;
031    
032    import java.io.IOException;
033    
034    import javax.portlet.PortletRequest;
035    import javax.portlet.PortletResponse;
036    import javax.portlet.PortletSession;
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                            PortletRequest portletRequest, PortletResponse portletResponse)
147                    throws IOException {
148    
149                    PortletSession portletSession = portletRequest.getPortletSession();
150    
151                    nl.captcha.Captcha simpleCaptcha = getSimpleCaptcha();
152    
153                    portletSession.setAttribute(
154                            WebKeys.CAPTCHA_TEXT, simpleCaptcha.getAnswer());
155    
156                    HttpServletResponse response = PortalUtil.getHttpServletResponse(
157                            portletResponse);
158    
159                    CaptchaServletUtil.writeImage(
160                            response.getOutputStream(), simpleCaptcha.getImage());
161            }
162    
163            protected void checkMaxChallenges(HttpServletRequest request)
164                    throws CaptchaMaxChallengesException {
165    
166                    if (PropsValues.CAPTCHA_MAX_CHALLENGES > 0) {
167                            HttpSession session = request.getSession();
168    
169                            Integer count = (Integer)session.getAttribute(
170                                    WebKeys.CAPTCHA_COUNT);
171    
172                            checkMaxChallenges(count);
173                    }
174            }
175    
176            protected void checkMaxChallenges(Integer count)
177                    throws CaptchaMaxChallengesException {
178    
179                    if ((count != null) && (count > PropsValues.CAPTCHA_MAX_CHALLENGES)) {
180                            throw new CaptchaMaxChallengesException();
181                    }
182            }
183    
184            protected void checkMaxChallenges(PortletRequest portletRequest)
185                    throws CaptchaMaxChallengesException {
186    
187                    if (PropsValues.CAPTCHA_MAX_CHALLENGES > 0) {
188                            PortletSession portletSession = portletRequest.getPortletSession();
189    
190                            Integer count = (Integer)portletSession.getAttribute(
191                                    WebKeys.CAPTCHA_COUNT);
192    
193                            checkMaxChallenges(count);
194                    }
195            }
196    
197            protected BackgroundProducer getBackgroundProducer() {
198                    if (_backgroundProducers.length == 1) {
199                            return _backgroundProducers[0];
200                    }
201    
202                    Randomizer randomizer = Randomizer.getInstance();
203    
204                    int pos = randomizer.nextInt(_backgroundProducers.length);
205    
206                    return _backgroundProducers[pos];
207            }
208    
209            protected GimpyRenderer getGimpyRenderer() {
210                    if (_gimpyRenderers.length == 1) {
211                            return _gimpyRenderers[0];
212                    }
213    
214                    Randomizer randomizer = Randomizer.getInstance();
215    
216                    int pos = randomizer.nextInt(_gimpyRenderers.length);
217    
218                    return _gimpyRenderers[pos];
219            }
220    
221            protected int getHeight() {
222                    return PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_HEIGHT;
223            }
224    
225            protected NoiseProducer getNoiseProducer() {
226                    if (_noiseProducers.length == 1) {
227                            return _noiseProducers[0];
228                    }
229    
230                    Randomizer randomizer = Randomizer.getInstance();
231    
232                    int pos = randomizer.nextInt(_noiseProducers.length);
233    
234                    return _noiseProducers[pos];
235            }
236    
237            protected nl.captcha.Captcha getSimpleCaptcha() {
238                    nl.captcha.Captcha.Builder captchaBuilder =
239                            new nl.captcha.Captcha.Builder(getWidth(), getHeight());
240    
241                    captchaBuilder.addText(getTextProducer(), getWordRenderer());
242                    captchaBuilder.addBackground(getBackgroundProducer());
243                    captchaBuilder.gimp(getGimpyRenderer());
244                    captchaBuilder.addNoise(getNoiseProducer());
245                    captchaBuilder.addBorder();
246    
247                    return captchaBuilder.build();
248            }
249    
250            protected TextProducer getTextProducer() {
251                    if (_textProducers.length == 1) {
252                            return _textProducers[0];
253                    }
254    
255                    Randomizer randomizer = Randomizer.getInstance();
256    
257                    int pos = randomizer.nextInt(_textProducers.length);
258    
259                    return _textProducers[pos];
260            }
261    
262            protected int getWidth() {
263                    return PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_WIDTH;
264            }
265    
266            protected WordRenderer getWordRenderer() {
267                    if (_wordRenderers.length == 1) {
268                            return _wordRenderers[0];
269                    }
270    
271                    Randomizer randomizer = Randomizer.getInstance();
272    
273                    int pos = randomizer.nextInt(_wordRenderers.length);
274    
275                    return _wordRenderers[pos];
276            }
277    
278            protected void incrementCounter(HttpServletRequest request) {
279                    if ((PropsValues.CAPTCHA_MAX_CHALLENGES > 0) &&
280                            (Validator.isNotNull(request.getRemoteUser()))) {
281    
282                            HttpSession session = request.getSession();
283    
284                            Integer count = (Integer)session.getAttribute(
285                                    WebKeys.CAPTCHA_COUNT);
286    
287                            session.setAttribute(WebKeys.CAPTCHA_COUNT,
288                                    incrementCounter(count));
289                    }
290            }
291    
292            protected Integer incrementCounter(Integer count) {
293                    if (count == null) {
294                            count = new Integer(1);
295                    }
296                    else {
297                            count = new Integer(count.intValue() + 1);
298                    }
299    
300                    return count;
301            }
302    
303            protected void incrementCounter(PortletRequest portletRequest) {
304                    if ((PropsValues.CAPTCHA_MAX_CHALLENGES > 0) &&
305                            (Validator.isNotNull(portletRequest.getRemoteUser()))) {
306    
307                            PortletSession portletSession = portletRequest.getPortletSession();
308    
309                            Integer count = (Integer)portletSession.getAttribute(
310                                    WebKeys.CAPTCHA_COUNT);
311    
312                            portletSession.setAttribute(WebKeys.CAPTCHA_COUNT,
313                                    incrementCounter(count));
314                    }
315            }
316    
317            protected void initBackgroundProducers() {
318                    String[] backgroundProducerClassNames =
319                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_BACKGROUND_PRODUCERS;
320    
321                    _backgroundProducers = new BackgroundProducer[
322                            backgroundProducerClassNames.length];
323    
324                    for (int i = 0; i < backgroundProducerClassNames.length; i++) {
325                            String backgroundProducerClassName =
326                                    backgroundProducerClassNames[i];
327    
328                            _backgroundProducers[i] = (BackgroundProducer)InstancePool.get(
329                                    backgroundProducerClassName);
330                    }
331            }
332    
333            protected void initGimpyRenderers() {
334                    String[] gimpyRendererClassNames =
335                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_GIMPY_RENDERERS;
336    
337                    _gimpyRenderers = new GimpyRenderer[
338                            gimpyRendererClassNames.length];
339    
340                    for (int i = 0; i < gimpyRendererClassNames.length; i++) {
341                            String gimpyRendererClassName = gimpyRendererClassNames[i];
342    
343                            _gimpyRenderers[i] = (GimpyRenderer)InstancePool.get(
344                                    gimpyRendererClassName);
345                    }
346            }
347    
348            protected void initNoiseProducers() {
349                    String[] noiseProducerClassNames =
350                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_NOISE_PRODUCERS;
351    
352                    _noiseProducers = new NoiseProducer[noiseProducerClassNames.length];
353    
354                    for (int i = 0; i < noiseProducerClassNames.length; i++) {
355                            String noiseProducerClassName = noiseProducerClassNames[i];
356    
357                            _noiseProducers[i] = (NoiseProducer)InstancePool.get(
358                                    noiseProducerClassName);
359                    }
360            }
361    
362            protected void initTextProducers() {
363                    String[] textProducerClassNames =
364                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_TEXT_PRODUCERS;
365    
366                    _textProducers = new TextProducer[textProducerClassNames.length];
367    
368                    for (int i = 0; i < textProducerClassNames.length; i++) {
369                            String textProducerClassName = textProducerClassNames[i];
370    
371                            _textProducers[i] = (TextProducer)InstancePool.get(
372                                    textProducerClassName);
373                    }
374            }
375    
376            protected void initWordRenderers() {
377                    String[] wordRendererClassNames =
378                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_WORD_RENDERERS;
379    
380                    _wordRenderers = new WordRenderer[wordRendererClassNames.length];
381    
382                    for (int i = 0; i < wordRendererClassNames.length; i++) {
383                            String wordRendererClassName = wordRendererClassNames[i];
384    
385                            _wordRenderers[i] = (WordRenderer)InstancePool.get(
386                                    wordRendererClassName);
387                    }
388            }
389    
390            protected boolean validateChallenge(HttpServletRequest request)
391                    throws CaptchaException {
392    
393                    HttpSession session = request.getSession();
394    
395                    String captchaText = (String)session.getAttribute(WebKeys.CAPTCHA_TEXT);
396    
397                    if (captchaText == null) {
398                            _log.error(
399                                    "Captcha text is null. User " + request.getRemoteUser() +
400                                            " may be trying to circumvent the captcha.");
401    
402                            throw new CaptchaTextException();
403                    }
404    
405                    boolean valid = captchaText.equals(
406                            ParamUtil.getString(request, "captchaText"));
407    
408                    if (valid) {
409                            session.removeAttribute(WebKeys.CAPTCHA_TEXT);
410                    }
411    
412                    return valid;
413            }
414    
415            protected boolean validateChallenge(PortletRequest portletRequest)
416                    throws CaptchaException {
417    
418                    PortletSession portletSession = portletRequest.getPortletSession();
419    
420                    String captchaText = (String)portletSession.getAttribute(
421                            WebKeys.CAPTCHA_TEXT);
422    
423                    if (captchaText == null) {
424                            _log.error(
425                                    "Captcha text is null. User " + portletRequest.getRemoteUser() +
426                                            " may be trying to circumvent the captcha.");
427    
428                            throw new CaptchaTextException();
429                    }
430    
431                    boolean valid = captchaText.equals(
432                            ParamUtil.getString(portletRequest, "captchaText"));
433    
434                    if (valid) {
435                            portletSession.removeAttribute(WebKeys.CAPTCHA_TEXT);
436                    }
437    
438                    return valid;
439            }
440    
441            private static final String _TAGLIB_PATH =
442                    "/html/taglib/ui/captcha/simplecaptcha.jsp";
443    
444            private static Log _log = LogFactoryUtil.getLog(SimpleCaptchaImpl.class);
445    
446            private BackgroundProducer[] _backgroundProducers;
447            private GimpyRenderer[] _gimpyRenderers;
448            private NoiseProducer[] _noiseProducers;
449            private TextProducer[] _textProducers;
450            private WordRenderer[] _wordRenderers;
451    
452    }