Make sure Piwik session is started in Auth::authenticate
[piwik-CASLogin.git] / Auth.php
1 <?php
2 /**
3  * Piwik - Open source web analytics
4  * 
5  * @link http://piwik.org
6  * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
7  * @version $Id:$
8  * 
9  * @category Piwik_Plugins
10  * @package CASLogin
11  */
12
13 namespace Piwik\Plugins\CASLogin;
14
15 use Piwik\AuthResult;
16 use Piwik\Common;
17 use Piwik\Config;
18 use Piwik\Db;
19 use Piwik\Piwik;
20 use Piwik\Plugins\UsersManager\API;
21 use Piwik\Session;
22
23 /**
24  * Class that implements an authentication mechanism via CAS (Central Authentication Services)
25  *
26  * @package Piwik_CASLogin
27  */
28 class Auth implements \Piwik\Auth
29 {
30         protected $login = null;
31         protected $token_auth = null;
32         private static $phpcas_client_called = false;
33
34         public function getName()
35         {
36                 return 'CASLogin';
37         }
38
39         public function initSession($login, $md5Password, $rememberMe) {}
40
41         public function authenticate()
42         {
43                 // Make sure Piwik session is started.
44                 Session::start();
45
46                 $user = '';
47
48                 require_once PIWIK_INCLUDE_PATH . '/plugins/CASLogin/CAS/CAS.php';
49
50                 // initialize phpCAS
51
52                 // What happens here: in some piwik functionality, some additional API-style calls are
53                 // made from a controller action, where the authenticate() method will be called *again*.
54                 // This happens for instance when an admin changes some permissions in Settings->Users.
55                 // The first authenticate() is from the page, and the second is due to an API call.
56                 // This checks if there was already a phpcas instance already initialized, otherwize
57                 // phpCAS::client() would fail.
58                 if (!self::$phpcas_client_called) {
59                         \phpCAS::client(
60                                 constant( Config::getInstance()->caslogin['protocol'] ),
61                                 Config::getInstance()->caslogin['host'],
62                                 (integer) Config::getInstance()->caslogin['port'],
63                 '',
64                 false
65                         );
66                         self::$phpcas_client_called = true;
67                 }
68
69                 // no SSL validation for the CAS server
70                 \phpCAS::setNoCasServerValidation();
71
72                 // Handle single signout requests from CAS server
73                 \phpCAS::handleLogoutRequests();
74
75                 // force CAS authentication only if it has been requested by action argument
76                 $action = Piwik::getAction();
77                 
78                 $auth = \phpCAS::checkAuthentication();
79                 if(!$auth) {
80                         if($action == 'redirectToCAS') {
81                                 phpCAS::forceAuthentication();
82                         }
83
84                         if($action != 'login' && Piwik::getModule() != 'CoreUpdater') {
85                                 Piwik::redirectToModule('CASLogin', 'login');
86                                 return;
87                         } elseif($action == 'redirectToCAS') {
88                                 phpCAS::forceAuthentication();
89                         } else {
90                                 return new AuthResult( AuthResult::FAILURE, $user, NULL );
91                         }
92                 }
93
94                 // Additional Attributes
95                 // For future retrieval of attributes; they _might_ be of some use, but are highly
96                 // dependable on a specific installation. CAS|piwik hackers can do some magic
97                 // here with SAML attributes etc.
98                 /*
99                 foreach (\phpCAS::getAttributes() as $key => $value) {
100                         // syslog(LOG_DEBUG, "attribute: $key - ". print_r($value, true));
101                 }
102                  */
103
104                 if (isset($_SESSION['phpCAS']) && isset($_SESSION['phpCAS']['user'])) {
105                         $user = $_SESSION['phpCAS']['user'];
106                 }
107
108                 if($user) {
109                         $db_user = Db::fetchRow('SELECT login, superuser_access FROM '.Common::prefixTable('user').' WHERE login = ?',
110                                         array($user)
111                         );
112                         if($db_user === null) {
113                                 // ***User Autocreate***
114                                 // We can either add the authenticated but not-yet-authorized user to the piwik users
115                                 // database, or ignore that.
116                                 // TODO: make this a config option
117                                 $this->_populateDb($user);
118                                 $login = $user;
119                                 $superuser = false;
120                         }
121                         else {
122                                 $login = $db_user['login'];
123                                 $superuser = $db_user['superuser_access'];
124                         }
125                         if($login == $user)
126                         {
127                                 if ($superuser)
128                                         $code = AuthResult::SUCCESS_SUPERUSER_AUTH_CODE;
129                                 else $code = AuthResult::SUCCESS; 
130                                 return new AuthResult($code, $login, NULL );
131                         }
132                 }
133
134                 return new AuthResult( AuthResult::FAILURE, $user, NULL );
135         }
136
137         public function setLogin($login)
138         {
139                 $this->login = $login;
140         }
141     public function getLogin() {
142         return $this->login;
143     }
144         
145     public function setTokenAuth($token_auth)
146         {
147                 $this->token_auth = $token_auth;
148         }
149     public function getTokenAuthSecret() {
150         return $this->token_auth;
151     }
152
153         /**
154          * This method is used to inject user into Piwik's tables.
155          * @todo Alias could be the 'cn' returned from CAS attributes.
156          */
157         private function _populateDb($user)
158         {
159                 $result = null;
160                 $dummy = md5('abcd1234');
161                 if ($this->_helper_userExists($user)) {
162                         $this->_helper_updateUser($user, $dummy, '', $user);
163                 } else {
164                         $this->_helper_addUser($user, $dummy, '', $user);
165                 }
166         }
167
168
169         ///// The following methods are taken from Piwik's UserManager, but in order to inject data into piwik's user and access tables, we need
170         ///// to make sure we don't wreck things. The UserManager API uses authenticate() to check if we're eligable to look this up,
171         ///// soi we can't use it - we need superuser permissions anyway.
172         //
173         ///// Warning - these methods are of course under Piwik's license.
174         private function _helper_userExists($name)
175         {
176                 $count = Db::fetchOne("SELECT count(*)
177                                                                         FROM ".Common::prefixTable("user"). "
178                                                                         WHERE login = ?", $name);
179                 return $count > 0;
180         }
181
182         private function _helper_updateUser( $userLogin, $password = false, $email = false, $alias = false ) 
183         {
184                 $token_auth = API::getTokenAuth($userLogin, $password);
185
186                 Db::get()->update( Common::prefixTable("user"),
187                                         array(
188                                                 'password' => $password,
189                                                 'alias' => $alias,
190                                                 'email' => $email,
191                                                 'token_auth' => $token_auth,
192                                                 ),
193                                         "login = '$userLogin'"
194                         );
195         }
196
197         private function _helper_addUser( $userLogin, $password, $email, $alias = false )
198         {               
199                 $token_auth = API::getTokenAuth($userLogin, $password);
200
201                 Db::get()->insert( Common::prefixTable("user"), array(
202                                                                         'login' => $userLogin,
203                                                                         'password' => $password,
204                                                                         'alias' => $alias,
205                                                                         'email' => $email,
206                                                                         'token_auth' => $token_auth,
207                                                                         )
208                 );
209         }
210
211     // Those methods are required by Piwik API.
212     public function setPassword($password) {}
213     public function setPasswordHash($passwordHash) {}
214 }
215