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