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