Mantis#0008484 Expresso#5295
authorAntonio Carlos da Silva <antonio-carlos.silva@serpro.gov.br>
Wed, 5 Jun 2013 16:52:23 +0000 (13:52 -0300)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 3 Jul 2013 12:53:18 +0000 (14:53 +0200)
Allow use "captcha" in login

Change-Id: Ib1279408671ebff91aac5f2015e2b8a8177843ac
Reviewed-on: https://gerrit.tine20.org/tine20/2100
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tine20/Tinebase/Controller.php
tine20/Tinebase/Frontend/Json.php
tine20/Tinebase/js/LoginPanel.js
tine20/Tinebase/views/jsclient.php
tine20/config.inc.php.dist
tine20/fonts/Milonga-Regular.ttf [new file with mode: 0644]
tine20/fonts/OFL.txt [new file with mode: 0644]

index 1f9257e..03ef8ba 100644 (file)
@@ -59,7 +59,7 @@ class Tinebase_Controller extends Tinebase_Controller_Event
         
         return self::$_instance;
     }
-    
+  
     /**
      * create new user session
      *
@@ -67,9 +67,10 @@ class Tinebase_Controller extends Tinebase_Controller_Event
      * @param   string $_password
      * @param   string $_ipAddress
      * @param   string $_clientIdString
+     * @param   string $securitycode the security code(captcha)
      * @return  bool
      */
-    public function login($_loginname, $_password, $_ipAddress, $_clientIdString = NULL)
+    public function login($_loginname, $_password, $_ipAddress, $_clientIdString = NULL, $securitycode = NULL)
     {
         $authResult = Tinebase_Auth::getInstance()->authenticate($_loginname, $_password);
         $authResultCode = $authResult->getCode();
@@ -266,6 +267,70 @@ class Tinebase_Controller extends Tinebase_Controller_Event
         sleep(mt_rand(2,5));
     }
     
+     /**
+     * renders and send to browser one captcha image
+     *
+     * @return void
+     */
+    public function makeCaptcha()
+    {
+        return $this->_makeImage();
+    }
+
+    /**
+     * renders and send to browser one captcha image
+     *
+     * @return void
+     */
+    protected function _makeImage()
+    {
+        $result = array();
+        $width='170';
+        $height='40';
+        $characters= mt_rand(5,7);
+        $possible = '123456789aAbBcCdDeEfFgGhHIijJKLmMnNpPqQrRstTuUvVwWxXyYZz';
+        $code = '';
+        $i = 0;
+        while ($i < $characters) {
+            $code .= substr($possible, mt_rand(0, strlen($possible)-1), 1);
+            $i++;
+        }
+        $font = './fonts/Milonga-Regular.ttf';
+        /* font size will be 70% of the image height */
+        $font_size = $height * 0.67;
+        try {
+            $image = @imagecreate($width, $height);
+            /* set the colours */
+            $background_color = imagecolorallocate($image, 255, 255, 255);
+            $text_color = imagecolorallocate($image, 20, 40, 100);
+            $noise_color = imagecolorallocate($image, 100, 120, 180);
+            /* generate random dots in background */
+            for( $i=0; $i<($width*$height)/3; $i++ ) {
+                imagefilledellipse($image, mt_rand(0,$width), mt_rand(0,$height), 1, 1, $noise_color);
+            }
+            /* generate random lines in background */
+            for( $i=0; $i<($width*$height)/150; $i++ ) {
+                imageline($image, mt_rand(0,$width), mt_rand(0,$height), mt_rand(0,$width), mt_rand(0,$height), $noise_color);
+            }
+            /* create textbox and add text */
+            $textbox = imagettfbbox($font_size, 0, $font, $code);
+            $x = ($width - $textbox[4])/2;
+            $y = ($height - $textbox[5])/2;
+            imagettftext($image, $font_size, 0, $x, $y, $text_color, $font , $code);
+            ob_start();
+            imagejpeg($image);
+            $image_code = ob_get_contents ();
+            ob_end_clean();
+            imagedestroy($image);
+            $result = array();
+            $result['1'] = base64_encode($image_code);
+            $_SESSION['captcha']['code'] = $code;
+        } catch (Exception $e) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
+        }
+        return $result;
+    }
+
     /**
      * authenticate user but don't log in
      *
index c5b23dd..9c1dad8 100644 (file)
@@ -434,15 +434,33 @@ class Tinebase_Frontend_Json extends Tinebase_Frontend_Json_Abstract
      *
      * @param  string $username the username
      * @param  string $password the password
+     * @param  string $securitycode the security code(captcha)
      * @return array
      */
-    public function login($username, $password)
+    public function login($username, $password, $securitycode=NULL)
     {
         Tinebase_Core::startSession('tinebase');
-
+        
+        $captcha = (isset(Tinebase_Core::getConfig()->captcha->count) && Tinebase_Core::getConfig()->captcha->count != 0) ? TRUE : FALSE; 
+        if ($captcha) {
+            $config_count = Tinebase_Core::getConfig()->captcha->count;
+            $count = (isset($_SESSION['captcha']['count']) ? $_SESSION['captcha']['count'] : 1);
+            if ($count >= $config_count) {
+                $aux = isset($_SESSION['captcha']['code']) ? $_SESSION['captcha']['code'] : NULL;
+                if ($aux != $securitycode) {
+                    $rets = Tinebase_Controller::getInstance()->makeCaptcha(); 
+                    $response = array(
+                        'success'      => FALSE,
+                        'errorMessage' => "Wrong username or password!",
+                        'c1' => $rets['1']
+                    );
+                    return $response;
+                   }
+            }
+        }
         // try to login user
-        $success = (Tinebase_Controller::getInstance()->login($username, $password, $_SERVER['REMOTE_ADDR'], 'TineJson') === TRUE);
-
+        $success = (Tinebase_Controller::getInstance()->login($username, $password, $_SERVER['REMOTE_ADDR'], 'TineJson', $securitycode) === TRUE);
+        
         if ($success == true) {
             $response = array(
                 'success'        => TRUE,
@@ -451,7 +469,7 @@ class Tinebase_Frontend_Json extends Tinebase_Frontend_Json_Abstract
                 'welcomeMessage' => "Welcome to Tine 2.0!"
             );
              
-            if(Tinebase_Config::getInstance()->get(Tinebase_Config::REUSEUSERNAME_SAVEUSERNAME, 0)){
+            if (Tinebase_Config::getInstance()->get(Tinebase_Config::REUSEUSERNAME_SAVEUSERNAME, 0)) {
                 // save in cookie (expires in 2 weeks)
                 setcookie('TINE20LASTUSERID', $username, time()+60*60*24*14);
             } else {
@@ -461,14 +479,30 @@ class Tinebase_Frontend_Json extends Tinebase_Frontend_Json_Abstract
             $this->_setCredentialCacheCookie();
             
         } else {
-            Zend_Session::destroy();
-            
-            Tinebase_Auth_CredentialCache::getInstance()->getCacheAdapter()->resetCache();
-
             $response = array(
                 'success'      => FALSE,
-                'errorMessage' => "Wrong username or password!"
+                'errorMessage' => "Wrong username or password!",
             );
+            Tinebase_Auth_CredentialCache::getInstance()->getCacheAdapter()->resetCache();
+            if ($captcha) {
+                $config_count = Tinebase_Core::getConfig()->captcha->count;
+                if (!isset($_SESSION['captcha']['count'])) {
+                    $_SESSION['captcha']['count'] = 1;
+                } else {
+                    $_SESSION['captcha']['count'] = $_SESSION['captcha']['count'] + 1;
+                }
+                if ($_SESSION['captcha']['count'] >= $config_count) {
+                    $rets = Tinebase_Controller::getInstance()->makeCaptcha(); 
+                    $response = array(
+                        'success'      => FALSE,
+                        'errorMessage' => "Wrong username or password!",
+                        'c1' => $rets['1']
+                    );
+                }
+            } else {
+                Zend_Session::destroy(false,true);
+            }
+            
         }
 
         return $response;
index b9fd8c8..a1daacf 100644 (file)
@@ -64,11 +64,11 @@ Tine.Tinebase.LoginPanel = Ext.extend(Ext.Panel, {
     getLoginPanel: function () {
 
         var modSsl = Tine.Tinebase.registry.get('modSsl');
-        
+
         if (! this.loginPanel) {
             this.loginPanel = new Ext.FormPanel({
                 width: 460,
-                height: 250,
+                height: 290,
                 frame: true,
                 labelWidth: 90,
                 cls: 'tb-login-panel',
@@ -118,6 +118,7 @@ Tine.Tinebase.LoginPanel = Ext.extend(Ext.Panel, {
                     listeners: {
                         render: this.setLastLoginUser.createDelegate(this) 
                     }
+                    
                 }, {
                     xtype: 'displayfield',
                     style: {
@@ -126,7 +127,31 @@ Tine.Tinebase.LoginPanel = Ext.extend(Ext.Panel, {
                     },
                     value: _('Certificate detected. Please, press Login button to proceed.'),
                     hidden: modSsl ? false : true
-                }],
+                }, {
+                    xtype: 'container',
+                    id:'contImgCaptcha',
+                    layout: 'form',
+                    style: { visibility:'hidden' },
+                    items:[{
+                       xtype: 'textfield',
+                       width: 170,
+                       labelSeparator: '',
+                       id: 'security_code',
+                       value: null,
+                       name: 'securitycode'
+                    }, {
+                       fieldLabel:(' '),
+                       labelSeparator: '',
+                       items:[
+                           new Ext.Component({
+                               autoEl: { 
+                                   tag: 'img',
+                                   id: 'imgCaptcha'
+                               }
+                           })]
+                    }]
+                  },
+                ],
                 buttonAlign: 'right',
                 buttons: [{
                     xtype: 'button',
@@ -413,7 +438,8 @@ Tine.Tinebase.LoginPanel = Ext.extend(Ext.Panel, {
                 params : {
                     method: this.loginMethod,
                     username: values.username,
-                    password: values.password
+                    password: values.password,
+                    securitycode: values.securitycode
                 },
                 timeout: 60000, // 1 minute
                 callback: function (request, httpStatus, response) {
@@ -440,6 +466,12 @@ Tine.Tinebase.LoginPanel = Ext.extend(Ext.Panel, {
                                 icon: Ext.MessageBox.ERROR,
                                 fn: function () {
                                     this.getLoginPanel().getForm().findField('password').focus(true);
+                                    if(document.getElementById('useCaptcha')) {
+                                        if(typeof responseData.c1 != 'undefined') {
+                                            document.getElementById('imgCaptcha').src = 'data:image/png;base64,' + responseData.c1;
+                                            document.getElementById('contImgCaptcha').style.visibility = 'visible';  
+                                        }
+                                    }                                    
                                 }.createDelegate(this)
                             });
                         }
index b886d94..b506768 100644 (file)
 <body>
     <!-- Loading Indicator -->
     <div class="tine-viewport-waitcycle" style="position: absolute; top: 50%; left: 50%; background-image: url(data:image/gif;base64,R0lGODlhEAAQALMMAKqooJGOhp2bk7e1rZ2bkre1rJCPhqqon8PBudDOxXd1bISCef///wAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFAAAMACwAAAAAEAAQAAAET5DJyYyhmAZ7sxQEs1nMsmACGJKmSaVEOLXnK1PuBADepCiMg/DQ+/2GRI8RKOxJfpTCIJNIYArS6aRajWYZCASDa41Ow+Fx2YMWOyfpTAQAIfkEBQAADAAsAAAAABAAEAAABE6QyckEoZgKe7MEQMUxhoEd6FFdQWlOqTq15SlT9VQM3rQsjMKO5/n9hANixgjc9SQ/CgKRUSgw0ynFapVmGYkEg3v1gsPibg8tfk7CnggAIfkEBQAADAAsAAAAABAAEAAABE2QycnOoZjaA/IsRWV1goCBoMiUJTW8A0XMBPZmM4Ug3hQEjN2uZygahDyP0RBMEpmTRCKzWGCkUkq1SsFOFQrG1tr9gsPc3jnco4A9EQAh+QQFAAAMACwAAAAAEAAQAAAETpDJyUqhmFqbJ0LMIA7McWDfF5LmAVApOLUvLFMmlSTdJAiM3a73+wl5HYKSEET2lBSFIhMIYKRSimFriGIZiwWD2/WCw+Jt7xxeU9qZCAAh+QQFAAAMACwAAAAAEAAQAAAETZDJyRCimFqbZ0rVxgwF9n3hSJbeSQ2rCWIkpSjddBzMfee7nQ/XCfJ+OQYAQFksMgQBxumkEKLSCfVpMDCugqyW2w18xZmuwZycdDsRACH5BAUAAAwALAAAAAAQABAAAARNkMnJUqKYWpunUtXGIAj2feFIlt5JrWybkdSydNNQMLaND7pC79YBFnY+HENHMRgyhwPGaQhQotGm00oQMLBSLYPQ9QIASrLAq5x0OxEAIfkEBQAADAAsAAAAABAAEAAABE2QycmUopham+da1cYkCfZ94UiW3kmtbJuRlGF0E4Iwto3rut6tA9wFAjiJjkIgZAYDTLNJgUIpgqyAcTgwCuACJssAdL3gpLmbpLAzEQA7); width: 16px; height: 16px">&#160;</div><div class="tine-viewport-poweredby" style="position: absolute; bottom: 10px; right: 10px; font:normal 12px arial, helvetica,tahoma,sans-serif;">Powered by: <a target="_blank" href="http://www.tine20.com/" title="online open source groupware and crm">Tine 2.0</a></div>
-    
+    <?php
+    if(isset(Tinebase_Core::getConfig()->captcha->count) && Tinebase_Core::getConfig()->captcha->count != 0)
+    {
+        echo "\n".' <span id="useCaptcha" />'."\n";
+    }
+    ?>    
     <!-- EXT JS -->
 
     <?php 
index f37ed18..882ab54 100644 (file)
@@ -6,6 +6,8 @@
 
 // minimal configuration
 return array(
+    // set 'count' equal zero to disable captcha, or set to number of invalid logins before request captcha.
+    'captcha' => array('count'=>0), 
     'database' => array(
         'host'          => 'ENTER DATABASE HOSTNAME',
         'dbname'           => 'ENTER DATABASE NAME',
diff --git a/tine20/fonts/Milonga-Regular.ttf b/tine20/fonts/Milonga-Regular.ttf
new file mode 100644 (file)
index 0000000..e01cb25
Binary files /dev/null and b/tine20/fonts/Milonga-Regular.ttf differ
diff --git a/tine20/fonts/OFL.txt b/tine20/fonts/OFL.txt
new file mode 100644 (file)
index 0000000..ce635c4
--- /dev/null
@@ -0,0 +1,92 @@
+Copyright (c) 2010, Kimberly Geswein (kimberlygeswein.com)\r
+This Font Software is licensed under the SIL Open Font License, Version 1.1.\r
+This license is copied below, and is also available with a FAQ at:\r
+http://scripts.sil.org/OFL\r
+\r
+\r
+-----------------------------------------------------------\r
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\r
+-----------------------------------------------------------\r
+\r
+PREAMBLE\r
+The goals of the Open Font License (OFL) are to stimulate worldwide\r
+development of collaborative font projects, to support the font creation\r
+efforts of academic and linguistic communities, and to provide a free and\r
+open framework in which fonts may be shared and improved in partnership\r
+with others.\r
+\r
+The OFL allows the licensed fonts to be used, studied, modified and\r
+redistributed freely as long as they are not sold by themselves. The\r
+fonts, including any derivative works, can be bundled, embedded, \r
+redistributed and/or sold with any software provided that any reserved\r
+names are not used by derivative works. The fonts and derivatives,\r
+however, cannot be released under any other type of license. The\r
+requirement for fonts to remain under this license does not apply\r
+to any document created using the fonts or their derivatives.\r
+\r
+DEFINITIONS\r
+"Font Software" refers to the set of files released by the Copyright\r
+Holder(s) under this license and clearly marked as such. This may\r
+include source files, build scripts and documentation.\r
+\r
+"Reserved Font Name" refers to any names specified as such after the\r
+copyright statement(s).\r
+\r
+"Original Version" refers to the collection of Font Software components as\r
+distributed by the Copyright Holder(s).\r
+\r
+"Modified Version" refers to any derivative made by adding to, deleting,\r
+or substituting -- in part or in whole -- any of the components of the\r
+Original Version, by changing formats or by porting the Font Software to a\r
+new environment.\r
+\r
+"Author" refers to any designer, engineer, programmer, technical\r
+writer or other person who contributed to the Font Software.\r
+\r
+PERMISSION & CONDITIONS\r
+Permission is hereby granted, free of charge, to any person obtaining\r
+a copy of the Font Software, to use, study, copy, merge, embed, modify,\r
+redistribute, and sell modified and unmodified copies of the Font\r
+Software, subject to the following conditions:\r
+\r
+1) Neither the Font Software nor any of its individual components,\r
+in Original or Modified Versions, may be sold by itself.\r
+\r
+2) Original or Modified Versions of the Font Software may be bundled,\r
+redistributed and/or sold with any software, provided that each copy\r
+contains the above copyright notice and this license. These can be\r
+included either as stand-alone text files, human-readable headers or\r
+in the appropriate machine-readable metadata fields within text or\r
+binary files as long as those fields can be easily viewed by the user.\r
+\r
+3) No Modified Version of the Font Software may use the Reserved Font\r
+Name(s) unless explicit written permission is granted by the corresponding\r
+Copyright Holder. This restriction only applies to the primary font name as\r
+presented to the users.\r
+\r
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\r
+Software shall not be used to promote, endorse or advertise any\r
+Modified Version, except to acknowledge the contribution(s) of the\r
+Copyright Holder(s) and the Author(s) or with their explicit written\r
+permission.\r
+\r
+5) The Font Software, modified or unmodified, in part or in whole,\r
+must be distributed entirely under this license, and must not be\r
+distributed under any other license. The requirement for fonts to\r
+remain under this license does not apply to any document created\r
+using the Font Software.\r
+\r
+TERMINATION\r
+This license becomes null and void if any of the above conditions are\r
+not met.\r
+\r
+DISCLAIMER\r
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\r
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\r
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\r
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\r
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\r
+OTHER DEALINGS IN THE FONT SOFTWARE.\r