001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
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.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.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 = new Integer(1);
293                    }
294                    else {
295                            count = new Integer(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[
336                            gimpyRendererClassNames.length];
337    
338                    for (int i = 0; i < gimpyRendererClassNames.length; i++) {
339                            String gimpyRendererClassName = gimpyRendererClassNames[i];
340    
341                            _gimpyRenderers[i] = (GimpyRenderer)InstancePool.get(
342                                    gimpyRendererClassName);
343                    }
344            }
345    
346            protected void initNoiseProducers() {
347                    String[] noiseProducerClassNames =
348                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_NOISE_PRODUCERS;
349    
350                    _noiseProducers = new NoiseProducer[noiseProducerClassNames.length];
351    
352                    for (int i = 0; i < noiseProducerClassNames.length; i++) {
353                            String noiseProducerClassName = noiseProducerClassNames[i];
354    
355                            _noiseProducers[i] = (NoiseProducer)InstancePool.get(
356                                    noiseProducerClassName);
357                    }
358            }
359    
360            protected void initTextProducers() {
361                    String[] textProducerClassNames =
362                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_TEXT_PRODUCERS;
363    
364                    _textProducers = new TextProducer[textProducerClassNames.length];
365    
366                    for (int i = 0; i < textProducerClassNames.length; i++) {
367                            String textProducerClassName = textProducerClassNames[i];
368    
369                            _textProducers[i] = (TextProducer)InstancePool.get(
370                                    textProducerClassName);
371                    }
372            }
373    
374            protected void initWordRenderers() {
375                    String[] wordRendererClassNames =
376                            PropsValues.CAPTCHA_ENGINE_SIMPLECAPTCHA_WORD_RENDERERS;
377    
378                    _wordRenderers = new WordRenderer[wordRendererClassNames.length];
379    
380                    for (int i = 0; i < wordRendererClassNames.length; i++) {
381                            String wordRendererClassName = wordRendererClassNames[i];
382    
383                            _wordRenderers[i] = (WordRenderer)InstancePool.get(
384                                    wordRendererClassName);
385                    }
386            }
387    
388            protected boolean validateChallenge(HttpServletRequest request)
389                    throws CaptchaException {
390    
391                    HttpSession session = request.getSession();
392    
393                    String captchaText = (String)session.getAttribute(WebKeys.CAPTCHA_TEXT);
394    
395                    if (request instanceof UploadPortletRequest) {
396                            UploadPortletRequest uploadPortletRequest =
397                                    (UploadPortletRequest)request;
398    
399                            PortletRequest portletRequest =
400                                    uploadPortletRequest.getPortletRequest();
401    
402                            PortletSession portletSession = portletRequest.getPortletSession();
403    
404                            captchaText = (String)portletSession.getAttribute(
405                                    WebKeys.CAPTCHA_TEXT);
406                    }
407    
408                    if (captchaText == null) {
409                            _log.error(
410                                    "CAPTCHA text is null. User " + request.getRemoteUser() +
411                                            " may be trying to circumvent the CAPTCHA.");
412    
413                            throw new CaptchaTextException();
414                    }
415    
416                    boolean valid = captchaText.equals(
417                            ParamUtil.getString(request, "captchaText"));
418    
419                    if (valid) {
420                            if (request instanceof UploadPortletRequest) {
421                                    UploadPortletRequest uploadPortletRequest =
422                                            (UploadPortletRequest)request;
423    
424                                    PortletRequest portletRequest =
425                                            uploadPortletRequest.getPortletRequest();
426    
427                                    PortletSession portletSession =
428                                            portletRequest.getPortletSession();
429    
430                                    portletSession.removeAttribute(WebKeys.CAPTCHA_TEXT);
431                            }
432                            else {
433                                    session.removeAttribute(WebKeys.CAPTCHA_TEXT);
434                            }
435                    }
436    
437                    return valid;
438            }
439    
440            protected boolean validateChallenge(PortletRequest portletRequest)
441                    throws CaptchaException {
442    
443                    PortletSession portletSession = portletRequest.getPortletSession();
444    
445                    String captchaText = (String)portletSession.getAttribute(
446                            WebKeys.CAPTCHA_TEXT);
447    
448                    if (captchaText == null) {
449                            _log.error(
450                                    "CAPTCHA text is null. User " + portletRequest.getRemoteUser() +
451                                            " may be trying to circumvent the CAPTCHA.");
452    
453                            throw new CaptchaTextException();
454                    }
455    
456                    boolean valid = captchaText.equals(
457                            ParamUtil.getString(portletRequest, "captchaText"));
458    
459                    if (valid) {
460                            portletSession.removeAttribute(WebKeys.CAPTCHA_TEXT);
461                    }
462    
463                    return valid;
464            }
465    
466            private static final String _TAGLIB_PATH =
467                    "/html/taglib/ui/captcha/simplecaptcha.jsp";
468    
469            private static Log _log = LogFactoryUtil.getLog(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    }