001    /**
002     * Copyright (c) 2000-2013 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            @Override
064            public void check(HttpServletRequest request) throws CaptchaException {
065                    if (!isEnabled(request)) {
066                            return;
067                    }
068    
069                    if (!validateChallenge(request)) {
070                            incrementCounter(request);
071    
072                            checkMaxChallenges(request);
073    
074                            throw new CaptchaTextException();
075                    }
076    
077                    if (_log.isDebugEnabled()) {
078                            _log.debug("CAPTCHA text is valid");
079                    }
080            }
081    
082            @Override
083            public void check(PortletRequest portletRequest) throws CaptchaException {
084                    if (!isEnabled(portletRequest)) {
085                            return;
086                    }
087    
088                    if (!validateChallenge(portletRequest)) {
089                            incrementCounter(portletRequest);
090    
091                            checkMaxChallenges(portletRequest);
092    
093                            throw new CaptchaTextException();
094                    }
095    
096                    if (_log.isDebugEnabled()) {
097                            _log.debug("CAPTCHA text is valid");
098                    }
099            }
100    
101            @Override
102            public String getTaglibPath() {
103                    return _TAGLIB_PATH;
104            }
105    
106            @Override
107            public boolean isEnabled(HttpServletRequest request)
108                    throws CaptchaException {
109    
110                    checkMaxChallenges(request);
111    
112                    if (PropsValues.CAPTCHA_MAX_CHALLENGES >= 0) {
113                            return true;
114                    }
115                    else {
116                            return false;
117                    }
118            }
119    
120            @Override
121            public boolean isEnabled(PortletRequest portletRequest)
122                    throws CaptchaException {
123    
124                    checkMaxChallenges(portletRequest);
125    
126                    if (PropsValues.CAPTCHA_MAX_CHALLENGES >= 0) {
127                            return true;
128                    }
129                    else {
130                            return false;
131                    }
132            }
133    
134            @Override
135            public void serveImage(
136                            HttpServletRequest request, HttpServletResponse response)
137                    throws IOException {
138    
139                    HttpSession session = request.getSession();
140    
141                    nl.captcha.Captcha simpleCaptcha = getSimpleCaptcha();
142    
143                    session.setAttribute(WebKeys.CAPTCHA_TEXT, simpleCaptcha.getAnswer());
144    
145                    response.setContentType(ContentTypes.IMAGE_JPEG);
146    
147                    CaptchaServletUtil.writeImage(
148                            response.getOutputStream(), simpleCaptcha.getImage());
149            }
150    
151            @Override
152            public void serveImage(
153                            PortletRequest portletRequest, PortletResponse portletResponse)
154                    throws IOException {
155    
156                    PortletSession portletSession = portletRequest.getPortletSession();
157    
158                    nl.captcha.Captcha simpleCaptcha = getSimpleCaptcha();
159    
160                    portletSession.setAttribute(
161                            WebKeys.CAPTCHA_TEXT, simpleCaptcha.getAnswer());
162    
163                    HttpServletResponse response = PortalUtil.getHttpServletResponse(
164                            portletResponse);
165    
166                    response.setContentType(ContentTypes.IMAGE_PNG);
167    
168                    CaptchaServletUtil.writeImage(
169                            response.getOutputStream(), simpleCaptcha.getImage());
170            }
171    
172            protected void checkMaxChallenges(HttpServletRequest request)
173                    throws CaptchaMaxChallengesException {
174    
175                    if (PropsValues.CAPTCHA_MAX_CHALLENGES > 0) {
176                            HttpSession session = request.getSession();
177    
178                            Integer count = (Integer)session.getAttribute(
179                                    WebKeys.CAPTCHA_COUNT);
180    
181                            checkMaxChallenges(count);
182                    }
183            }
184    
185            protected void checkMaxChallenges(Integer count)
186                    throws CaptchaMaxChallengesException {
187    
188                    if ((count != null) && (count > PropsValues.CAPTCHA_MAX_CHALLENGES)) {
189                            throw new CaptchaMaxChallengesException();
190                    }
191            }
192    
193            protected void checkMaxChallenges(PortletRequest portletRequest)
194                    throws CaptchaMaxChallengesException {
195    
196                    if (PropsValues.CAPTCHA_MAX_CHALLENGES > 0) {
197                            PortletSession portletSession = portletRequest.getPortletSession();
198    
199                            Integer count = (Integer)portletSession.getAttribute(
200                                    WebKeys.CAPTCHA_COUNT);
201    
202                            checkMaxChallenges(count);
203                    }
204            }
205    
206            protected BackgroundProducer getBackgroundProducer() {
207                    if (_backgroundProducers.length == 1) {
208                            return _backgroundProducers[0];
209                    }
210    
211                    Randomizer randomizer = Randomizer.getInstance();
212    
213                    int pos = randomizer.nextInt(_backgroundProducers.length);
214    
215                    return _backgroundProducers[pos];
216            }
217    
218            protected GimpyRenderer getGimpyRenderer() {
219                    if (_gimpyRenderers.length == 1) {
220                            return _gimpyRenderers[0];
221                    }
222    
223                    Randomizer randomizer = Randomizer.getInstance();
224    
225                    int pos = randomizer.nextInt(_gimpyRenderers.length);
226    
227                    return _gimpyRenderers[pos];
228            }
229    
230            protected int getHeight() {
231                    return PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_HEIGHT;
232            }
233    
234            protected NoiseProducer getNoiseProducer() {
235                    if (_noiseProducers.length == 1) {
236                            return _noiseProducers[0];
237                    }
238    
239                    Randomizer randomizer = Randomizer.getInstance();
240    
241                    int pos = randomizer.nextInt(_noiseProducers.length);
242    
243                    return _noiseProducers[pos];
244            }
245    
246            protected nl.captcha.Captcha getSimpleCaptcha() {
247                    nl.captcha.Captcha.Builder captchaBuilder =
248                            new nl.captcha.Captcha.Builder(getWidth(), getHeight());
249    
250                    captchaBuilder.addText(getTextProducer(), getWordRenderer());
251                    captchaBuilder.addBackground(getBackgroundProducer());
252                    captchaBuilder.gimp(getGimpyRenderer());
253                    captchaBuilder.addNoise(getNoiseProducer());
254                    captchaBuilder.addBorder();
255    
256                    return captchaBuilder.build();
257            }
258    
259            protected TextProducer getTextProducer() {
260                    if (_textProducers.length == 1) {
261                            return _textProducers[0];
262                    }
263    
264                    Randomizer randomizer = Randomizer.getInstance();
265    
266                    int pos = randomizer.nextInt(_textProducers.length);
267    
268                    return _textProducers[pos];
269            }
270    
271            protected int getWidth() {
272                    return PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_WIDTH;
273            }
274    
275            protected WordRenderer getWordRenderer() {
276                    if (_wordRenderers.length == 1) {
277                            return _wordRenderers[0];
278                    }
279    
280                    Randomizer randomizer = Randomizer.getInstance();
281    
282                    int pos = randomizer.nextInt(_wordRenderers.length);
283    
284                    return _wordRenderers[pos];
285            }
286    
287            protected void incrementCounter(HttpServletRequest request) {
288                    if ((PropsValues.CAPTCHA_MAX_CHALLENGES > 0) &&
289                            Validator.isNotNull(request.getRemoteUser())) {
290    
291                            HttpSession session = request.getSession();
292    
293                            Integer count = (Integer)session.getAttribute(
294                                    WebKeys.CAPTCHA_COUNT);
295    
296                            session.setAttribute(
297                                    WebKeys.CAPTCHA_COUNT, incrementCounter(count));
298                    }
299            }
300    
301            protected Integer incrementCounter(Integer count) {
302                    if (count == null) {
303                            count = new Integer(1);
304                    }
305                    else {
306                            count = new Integer(count.intValue() + 1);
307                    }
308    
309                    return count;
310            }
311    
312            protected void incrementCounter(PortletRequest portletRequest) {
313                    if ((PropsValues.CAPTCHA_MAX_CHALLENGES > 0) &&
314                            Validator.isNotNull(portletRequest.getRemoteUser())) {
315    
316                            PortletSession portletSession = portletRequest.getPortletSession();
317    
318                            Integer count = (Integer)portletSession.getAttribute(
319                                    WebKeys.CAPTCHA_COUNT);
320    
321                            portletSession.setAttribute(
322                                    WebKeys.CAPTCHA_COUNT, incrementCounter(count));
323                    }
324            }
325    
326            protected void initBackgroundProducers() {
327                    String[] backgroundProducerClassNames =
328                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_BACKGROUND_PRODUCERS;
329    
330                    _backgroundProducers = new BackgroundProducer[
331                            backgroundProducerClassNames.length];
332    
333                    for (int i = 0; i < backgroundProducerClassNames.length; i++) {
334                            String backgroundProducerClassName =
335                                    backgroundProducerClassNames[i];
336    
337                            _backgroundProducers[i] = (BackgroundProducer)InstancePool.get(
338                                    backgroundProducerClassName);
339                    }
340            }
341    
342            protected void initGimpyRenderers() {
343                    String[] gimpyRendererClassNames =
344                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_GIMPY_RENDERERS;
345    
346                    _gimpyRenderers = new GimpyRenderer[
347                            gimpyRendererClassNames.length];
348    
349                    for (int i = 0; i < gimpyRendererClassNames.length; i++) {
350                            String gimpyRendererClassName = gimpyRendererClassNames[i];
351    
352                            _gimpyRenderers[i] = (GimpyRenderer)InstancePool.get(
353                                    gimpyRendererClassName);
354                    }
355            }
356    
357            protected void initNoiseProducers() {
358                    String[] noiseProducerClassNames =
359                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_NOISE_PRODUCERS;
360    
361                    _noiseProducers = new NoiseProducer[noiseProducerClassNames.length];
362    
363                    for (int i = 0; i < noiseProducerClassNames.length; i++) {
364                            String noiseProducerClassName = noiseProducerClassNames[i];
365    
366                            _noiseProducers[i] = (NoiseProducer)InstancePool.get(
367                                    noiseProducerClassName);
368                    }
369            }
370    
371            protected void initTextProducers() {
372                    String[] textProducerClassNames =
373                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_TEXT_PRODUCERS;
374    
375                    _textProducers = new TextProducer[textProducerClassNames.length];
376    
377                    for (int i = 0; i < textProducerClassNames.length; i++) {
378                            String textProducerClassName = textProducerClassNames[i];
379    
380                            _textProducers[i] = (TextProducer)InstancePool.get(
381                                    textProducerClassName);
382                    }
383            }
384    
385            protected void initWordRenderers() {
386                    String[] wordRendererClassNames =
387                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_WORD_RENDERERS;
388    
389                    _wordRenderers = new WordRenderer[wordRendererClassNames.length];
390    
391                    for (int i = 0; i < wordRendererClassNames.length; i++) {
392                            String wordRendererClassName = wordRendererClassNames[i];
393    
394                            _wordRenderers[i] = (WordRenderer)InstancePool.get(
395                                    wordRendererClassName);
396                    }
397            }
398    
399            protected boolean validateChallenge(HttpServletRequest request)
400                    throws CaptchaException {
401    
402                    HttpSession session = request.getSession();
403    
404                    String captchaText = (String)session.getAttribute(WebKeys.CAPTCHA_TEXT);
405    
406                    if (captchaText == null) {
407                            _log.error(
408                                    "CAPTCHA text is null. User " + request.getRemoteUser() +
409                                            " may be trying to circumvent the CAPTCHA.");
410    
411                            throw new CaptchaTextException();
412                    }
413    
414                    boolean valid = captchaText.equals(
415                            ParamUtil.getString(request, "captchaText"));
416    
417                    if (valid) {
418                            session.removeAttribute(WebKeys.CAPTCHA_TEXT);
419                    }
420    
421                    return valid;
422            }
423    
424            protected boolean validateChallenge(PortletRequest portletRequest)
425                    throws CaptchaException {
426    
427                    PortletSession portletSession = portletRequest.getPortletSession();
428    
429                    String captchaText = (String)portletSession.getAttribute(
430                            WebKeys.CAPTCHA_TEXT);
431    
432                    if (captchaText == null) {
433                            _log.error(
434                                    "CAPTCHA text is null. User " + portletRequest.getRemoteUser() +
435                                            " may be trying to circumvent the CAPTCHA.");
436    
437                            throw new CaptchaTextException();
438                    }
439    
440                    boolean valid = captchaText.equals(
441                            ParamUtil.getString(portletRequest, "captchaText"));
442    
443                    if (valid) {
444                            portletSession.removeAttribute(WebKeys.CAPTCHA_TEXT);
445                    }
446    
447                    return valid;
448            }
449    
450            private static final String _TAGLIB_PATH =
451                    "/html/taglib/ui/captcha/simplecaptcha.jsp";
452    
453            private static Log _log = LogFactoryUtil.getLog(SimpleCaptchaImpl.class);
454    
455            private BackgroundProducer[] _backgroundProducers;
456            private GimpyRenderer[] _gimpyRenderers;
457            private NoiseProducer[] _noiseProducers;
458            private TextProducer[] _textProducers;
459            private WordRenderer[] _wordRenderers;
460    
461    }