001    /**
002     * Copyright (c) 2000-present 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.upload.UploadPortletRequest;
025    import com.liferay.portal.kernel.util.ContentTypes;
026    import com.liferay.portal.kernel.util.InstancePool;
027    import com.liferay.portal.kernel.util.ParamUtil;
028    import com.liferay.portal.kernel.util.Validator;
029    import com.liferay.portal.kernel.util.WebKeys;
030    import com.liferay.portal.util.PropsValues;
031    
032    import java.io.IOException;
033    
034    import javax.portlet.PortletRequest;
035    import javax.portlet.PortletSession;
036    import javax.portlet.ResourceRequest;
037    import javax.portlet.ResourceResponse;
038    
039    import javax.servlet.http.HttpServletRequest;
040    import javax.servlet.http.HttpServletResponse;
041    import javax.servlet.http.HttpSession;
042    
043    import nl.captcha.backgrounds.BackgroundProducer;
044    import nl.captcha.gimpy.GimpyRenderer;
045    import nl.captcha.noise.NoiseProducer;
046    import nl.captcha.servlet.CaptchaServletUtil;
047    import nl.captcha.text.producer.TextProducer;
048    import nl.captcha.text.renderer.WordRenderer;
049    
050    /**
051     * @author Brian Wing Shun Chan
052     * @author Daniel Sanz
053     */
054    public class SimpleCaptchaImpl implements Captcha {
055    
056            public SimpleCaptchaImpl() {
057                    initBackgroundProducers();
058                    initGimpyRenderers();
059                    initNoiseProducers();
060                    initTextProducers();
061                    initWordRenderers();
062            }
063    
064            @Override
065            public void check(HttpServletRequest request) throws CaptchaException {
066                    if (!isEnabled(request)) {
067                            return;
068                    }
069    
070                    if (!validateChallenge(request)) {
071                            incrementCounter(request);
072    
073                            checkMaxChallenges(request);
074    
075                            throw new CaptchaTextException();
076                    }
077    
078                    if (_log.isDebugEnabled()) {
079                            _log.debug("CAPTCHA text is valid");
080                    }
081            }
082    
083            @Override
084            public void check(PortletRequest portletRequest) throws CaptchaException {
085                    if (!isEnabled(portletRequest)) {
086                            return;
087                    }
088    
089                    if (!validateChallenge(portletRequest)) {
090                            incrementCounter(portletRequest);
091    
092                            checkMaxChallenges(portletRequest);
093    
094                            throw new CaptchaTextException();
095                    }
096    
097                    if (_log.isDebugEnabled()) {
098                            _log.debug("CAPTCHA text is valid");
099                    }
100            }
101    
102            @Override
103            public String getTaglibPath() {
104                    return _TAGLIB_PATH;
105            }
106    
107            @Override
108            public boolean isEnabled(HttpServletRequest request)
109                    throws CaptchaException {
110    
111                    checkMaxChallenges(request);
112    
113                    if (PropsValues.CAPTCHA_MAX_CHALLENGES >= 0) {
114                            return true;
115                    }
116                    else {
117                            return false;
118                    }
119            }
120    
121            @Override
122            public boolean isEnabled(PortletRequest portletRequest)
123                    throws CaptchaException {
124    
125                    checkMaxChallenges(portletRequest);
126    
127                    if (PropsValues.CAPTCHA_MAX_CHALLENGES >= 0) {
128                            return true;
129                    }
130                    else {
131                            return false;
132                    }
133            }
134    
135            @Override
136            public void serveImage(
137                            HttpServletRequest request, HttpServletResponse response)
138                    throws IOException {
139    
140                    HttpSession session = request.getSession();
141    
142                    nl.captcha.Captcha simpleCaptcha = getSimpleCaptcha();
143    
144                    session.setAttribute(WebKeys.CAPTCHA_TEXT, simpleCaptcha.getAnswer());
145    
146                    response.setContentType(ContentTypes.IMAGE_PNG);
147    
148                    CaptchaServletUtil.writeImage(
149                            response.getOutputStream(), simpleCaptcha.getImage());
150            }
151    
152            @Override
153            public void serveImage(
154                            ResourceRequest resourceRequest, ResourceResponse resourceResponse)
155                    throws IOException {
156    
157                    PortletSession portletSession = resourceRequest.getPortletSession();
158    
159                    nl.captcha.Captcha simpleCaptcha = getSimpleCaptcha();
160    
161                    portletSession.setAttribute(
162                            WebKeys.CAPTCHA_TEXT, simpleCaptcha.getAnswer());
163    
164                    resourceResponse.setContentType(ContentTypes.IMAGE_PNG);
165    
166                    CaptchaServletUtil.writeImage(
167                            resourceResponse.getPortletOutputStream(),
168                            simpleCaptcha.getImage());
169            }
170    
171            protected void checkMaxChallenges(HttpServletRequest request)
172                    throws CaptchaMaxChallengesException {
173    
174                    if (PropsValues.CAPTCHA_MAX_CHALLENGES > 0) {
175                            HttpSession session = request.getSession();
176    
177                            Integer count = (Integer)session.getAttribute(
178                                    WebKeys.CAPTCHA_COUNT);
179    
180                            checkMaxChallenges(count);
181                    }
182            }
183    
184            protected void checkMaxChallenges(Integer count)
185                    throws CaptchaMaxChallengesException {
186    
187                    if ((count != null) && (count > PropsValues.CAPTCHA_MAX_CHALLENGES)) {
188                            throw new CaptchaMaxChallengesException();
189                    }
190            }
191    
192            protected void checkMaxChallenges(PortletRequest portletRequest)
193                    throws CaptchaMaxChallengesException {
194    
195                    if (PropsValues.CAPTCHA_MAX_CHALLENGES > 0) {
196                            PortletSession portletSession = portletRequest.getPortletSession();
197    
198                            Integer count = (Integer)portletSession.getAttribute(
199                                    WebKeys.CAPTCHA_COUNT);
200    
201                            checkMaxChallenges(count);
202                    }
203            }
204    
205            protected BackgroundProducer getBackgroundProducer() {
206                    if (_backgroundProducers.length == 1) {
207                            return _backgroundProducers[0];
208                    }
209    
210                    int pos = RandomUtil.nextInt(_backgroundProducers.length);
211    
212                    return _backgroundProducers[pos];
213            }
214    
215            protected GimpyRenderer getGimpyRenderer() {
216                    if (_gimpyRenderers.length == 1) {
217                            return _gimpyRenderers[0];
218                    }
219    
220                    int pos = RandomUtil.nextInt(_gimpyRenderers.length);
221    
222                    return _gimpyRenderers[pos];
223            }
224    
225            protected int getHeight() {
226                    return PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_HEIGHT;
227            }
228    
229            protected NoiseProducer getNoiseProducer() {
230                    if (_noiseProducers.length == 1) {
231                            return _noiseProducers[0];
232                    }
233    
234                    int pos = RandomUtil.nextInt(_noiseProducers.length);
235    
236                    return _noiseProducers[pos];
237            }
238    
239            protected nl.captcha.Captcha getSimpleCaptcha() {
240                    nl.captcha.Captcha.Builder captchaBuilder =
241                            new nl.captcha.Captcha.Builder(getWidth(), getHeight());
242    
243                    captchaBuilder.addText(getTextProducer(), getWordRenderer());
244                    captchaBuilder.addBackground(getBackgroundProducer());
245                    captchaBuilder.gimp(getGimpyRenderer());
246                    captchaBuilder.addNoise(getNoiseProducer());
247                    captchaBuilder.addBorder();
248    
249                    return captchaBuilder.build();
250            }
251    
252            protected TextProducer getTextProducer() {
253                    if (_textProducers.length == 1) {
254                            return _textProducers[0];
255                    }
256    
257                    int pos = RandomUtil.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                    int pos = RandomUtil.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 = Integer.valueOf(1);
293                    }
294                    else {
295                            count = Integer.valueOf(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[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 (request instanceof UploadPortletRequest) {
395                            UploadPortletRequest uploadPortletRequest =
396                                    (UploadPortletRequest)request;
397    
398                            PortletRequest portletRequest =
399                                    uploadPortletRequest.getPortletRequest();
400    
401                            PortletSession portletSession = portletRequest.getPortletSession();
402    
403                            captchaText = (String)portletSession.getAttribute(
404                                    WebKeys.CAPTCHA_TEXT);
405                    }
406    
407                    if (captchaText == null) {
408                            _log.error(
409                                    "CAPTCHA text is null. User " + request.getRemoteUser() +
410                                            " may be trying to circumvent the CAPTCHA.");
411    
412                            throw new CaptchaTextException();
413                    }
414    
415                    boolean valid = captchaText.equals(
416                            ParamUtil.getString(request, "captchaText"));
417    
418                    if (valid) {
419                            if (request instanceof UploadPortletRequest) {
420                                    UploadPortletRequest uploadPortletRequest =
421                                            (UploadPortletRequest)request;
422    
423                                    PortletRequest portletRequest =
424                                            uploadPortletRequest.getPortletRequest();
425    
426                                    PortletSession portletSession =
427                                            portletRequest.getPortletSession();
428    
429                                    portletSession.removeAttribute(WebKeys.CAPTCHA_TEXT);
430                            }
431                            else {
432                                    session.removeAttribute(WebKeys.CAPTCHA_TEXT);
433                            }
434                    }
435    
436                    return valid;
437            }
438    
439            protected boolean validateChallenge(PortletRequest portletRequest)
440                    throws CaptchaException {
441    
442                    PortletSession portletSession = portletRequest.getPortletSession();
443    
444                    String captchaText = (String)portletSession.getAttribute(
445                            WebKeys.CAPTCHA_TEXT);
446    
447                    if (captchaText == null) {
448                            _log.error(
449                                    "CAPTCHA text is null. User " + portletRequest.getRemoteUser() +
450                                            " may be trying to circumvent the CAPTCHA.");
451    
452                            throw new CaptchaTextException();
453                    }
454    
455                    boolean valid = captchaText.equals(
456                            ParamUtil.getString(portletRequest, "captchaText"));
457    
458                    if (valid) {
459                            portletSession.removeAttribute(WebKeys.CAPTCHA_TEXT);
460                    }
461    
462                    return valid;
463            }
464    
465            private static final String _TAGLIB_PATH =
466                    "/html/taglib/ui/captcha/simplecaptcha.jsp";
467    
468            private static final Log _log = LogFactoryUtil.getLog(
469                    SimpleCaptchaImpl.class);
470    
471            private BackgroundProducer[] _backgroundProducers;
472            private GimpyRenderer[] _gimpyRenderers;
473            private NoiseProducer[] _noiseProducers;
474            private TextProducer[] _textProducers;
475            private WordRenderer[] _wordRenderers;
476    
477    }