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