1  <?php
  2  /* Copyright (c) 2013, Geert Bergman (geert@scrivo.nl)
  3   * All rights reserved.
  4   *
  5   * Redistribution and use in source and binary forms, with or without
  6   * modification, are permitted provided that the following conditions are met:
  7   *
  8   * 1. Redistributions of source code must retain the above copyright notice,
  9   *    this list of conditions and the following disclaimer.
 10   * 2. Redistributions in binary form must reproduce the above copyright notice,
 11   *    this list of conditions and the following disclaimer in the documentation
 12   *    and/or other materials provided with the distribution.
 13   * 3. Neither the name of "Scrivo" nor the names of its contributors may be
 14   *    used to endorse or promote products derived from this software without
 15   *    specific prior written permission.
 16   *
 17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 18   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 19   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 20   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 21   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 22   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 23   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 24   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 25   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 26   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 27   * POSSIBILITY OF SUCH DAMAGE.
 28   *
 29   * $Id: LoginKey.php 866 2013-08-25 16:22:35Z geert $
 30   */
 31  
 32  /**
 33   * Implementation of the \Scrivo\LoginKey class.
 34   */
 35  
 36  namespace Scrivo;
 37  
 38  /**
 39   * Add a login_events record with a temporary key that can
 40   * be used to log in. The following fields should be populated:
 41   *
 42   *     instance_id           Instance id
 43   *     date_login   Time of the login event (for timeout)
 44   *     user_id           User id
 45   *     return_code       0: OK, 1: invalid pwd, 2: invalid ucode
 46   *     remote_address       Client IP-address
 47   *     access_key        Random key
 48   *
 49   * Login return codes:
 50   *     0: valid login credentials
 51   *     0: login failed, invalid key
 52   *
 53   * An alternative function can defined in the .htscrivo file
 54   * (LOGIN_PROVIDER refers to a php script that implements the
 55   * function 'login_key()'. This is usefull if you want to use
 56   * another user database to log in (LDAP). You still need to
 57   * match roles on both systems, but you can write a function
 58   * that does the authentication on the external system and if
 59   * authenticated simply insert a row in the login_events table.
 60   * Note that you still need a user in the Scrivo database,
 61   * but that is only functional: a user id is used to retreive
 62   * the roles. Simply insert a new user on login and link it
 63   * with the corresponding roles.
 64   */
 65  
 66  class LoginKey {
 67  
 68      /**
 69       * Value to indicate that login was unsuccesfull due to an invalid password.
 70       */
 71      const INVALID_PASSWORD 1;
 72  
 73      /**
 74       * Value to indicate that login was unsuccesfull due to an invalid usercode.
 75       */
 76      const INVALID_USERCODE 2;
 77  
 78      /**
 79       * Value to indicate that login was succesfull.
 80       */
 81      const LOGIN_SUCCESSFULL 3;
 82  
 83      /**
 84       * Value to indicate that login was unsuccesfull because login key
 85       * generation and verification action were initiated using a different ip
 86       * addresses.
 87       */
 88      const INVALID_IP 4;
 89  
 90      /**
 91       * Value to indicate that login was unsuccesfull because of a time out
 92       * when verifying the key.
 93       */
 94      const TIMEOUT 5;
 95  
 96      /**
 97       * A Scrivo context.
 98       * @var \Scrivo\Context
 99       */
100      protected $context null;
101  
102      /**
103       * Create a login object to either generate or verify a login key.
104       *
105       * @param \Scrivo\Context $context A valid Scrivo context.
106       */
107      public function __construct(\Scrivo\Context $context) {
108          $this->context $context;
109      }
110  
111      /**
112       * Generate a login key for a given usercode and password. Override this
113       * function if you want to verify the user against another autentication
114       * database.
115       *
116       * @param \Scrivo\String $usercode A user code.
117       * @param \Scrivo\String $password A password.
118       * @return \Scrivo\String The login key if usercode and password
119       *    verification was successfull, or NULL if not.
120       */
121      public function generate(
122              \Scrivo\String $usercode, \Scrivo\String $password) {
123  
124          $pwd false;
125          $usr false;
126  
127          $sth $this->context->connection->prepare(
128              "SELECT * FROM user where instance_id = :instId and user_code = :uCode");
129  
130          $this->context->connection->bindInstance($sth);
131          $sth->bindValue(":uCode"$usercode, \PDO::PARAM_STR);
132  
133          $sth->execute();
134  
135          if ($sth->rowCount() == 1) {
136              $usr true;
137              $user $sth->fetch(\PDO::FETCH_ASSOC);
138              $pwd =  $user["password"] == crypt($password$user["password"]);
139          }
140  
141          $lres 0;
142          if (!$usr) {
143              $lres self::INVALID_USERCODE;
144          } else if (!$pwd) {
145              $lres self::INVALID_PASSWORD;
146          }
147  
148          $k = new \Scrivo\String(md5(microtime()));
149  
150          $this->insert(
151              $lres == self::INVALID_USERCODE ? -intval($user["user_id"]),
152              $lres$k);
153  
154          return $lres == $k null;
155      }
156  
157      /**
158       * Insert an entry in the login_events table, this entry will be checked
159       * by LoginKey::verify().
160       *
161       * @param int $userId A user id if password verification was succesfull,
162       *   -1 if not.
163       * @param int $loginStatus The result of the password verification: either
164       *   \Scrivo\LoginKey::INVALID_USERCODE, \Scrivo\LoginKey::INVALID_PASSWORD
165       *   or 0 (no error).
166       * @param \Scrivo\String $key A generated random key.
167       */
168      public function insert($userId$loginStatus, \Scrivo\String $key) {
169  
170          $sth $this->context->connection->prepare(
171              "INSERT INTO login_events (
172                  instance_id, date_login, user_id, user_satus,
173                  return_code, remote_address, access_key
174              ) VALUES (
175                  :instId, NOW(), :userId, 0,
176                  :returnCode, :remoteAddr, :key
177              )");
178  
179          $this->context->connection->bindInstance($sth);
180          $sth->bindValue(":userId"$userId, \PDO::PARAM_INT);
181          $sth->bindValue(":returnCode"$loginStatus, \PDO::PARAM_INT);
182          $sth->bindValue(
183              ":remoteAddr"$_SERVER["REMOTE_ADDR"], \PDO::PARAM_STR);
184          $sth->bindValue(":key"$key, \PDO::PARAM_STR);
185  
186          $sth->execute();
187      }
188  
189      /**
190       * Verify the login key, return the user if succesfull, NULL if not. Also
191       * update the login_events table with the result of the verification.
192       *
193       * @param \Scrivo\String $key A key that was previously generated by
194       *     LoginKey::generate()
195       * @return \Scrivo\User|NULL A Scrivo user if verification was succesfull,
196       *     NULL if not.
197       */
198      public function verify(\Scrivo\String $key) {
199  
200          $addr $_SERVER["REMOTE_ADDR"];
201  
202          $sth $this->context->connection->prepare(
203              "SELECT
204                  UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(date_login) TP,
205                  user_id, user_satus, return_code, remote_address
206              FROM login_events
207              WHERE instance_id = :instId AND access_key = :key"
208          );
209  
210          $this->context->connection->bindInstance($sth);
211          $sth->bindValue(":key"$key, \PDO::PARAM_STR);
212  
213          $sth->execute();
214  
215          $status 0;
216  
217          if ($sth->rowCount() == 1) {
218  
219              $le $sth->fetch(\PDO::FETCH_ASSOC);
220  
221              $status self::LOGIN_SUCCESSFULL;
222              $r intval($le["return_code"]);
223  
224              if ($r == self::INVALID_USERCODE || $r == self::INVALID_PASSWORD) {
225                  $status $r;
226              } else if ($le["remote_address"] != $addr) {
227                  $status self::INVALID_IP;
228              } else if ($le["TP"] > 10) {
229                  $status self::TIMEOUT;
230              }
231  
232              if ($status == self::LOGIN_SUCCESSFULL ||
233                      $status == self::INVALID_IP || $status == self::TIMEOUT) {
234  
235                  $sth $this->context->connection->prepare(
236                      "UPDATE login_events SET return_code = :ret
237                          WHERE instance_id = :instId AND access_key = :key");
238  
239                  $this->context->connection->bindInstance($sth);
240                  $sth->bindValue(":ret"$status, \PDO::PARAM_INT);
241                  $sth->bindValue(":key"$key, \PDO::PARAM_STR);
242  
243                  $sth->execute();
244  
245              }
246  
247              if ($status == self::LOGIN_SUCCESSFULL) {
248  
249                  return \Scrivo\User::fetch($this->contextintval($le["user_id"]));
250  
251              }
252  
253          }
254  
255          return null;
256      }
257  
258  }
259  
260  ?>

Documentation generated by phpDocumentor 2.0.0a12 and ScrivoDocumentor on August 29, 2013