Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 505d87a556
Fetching contributors…

Cannot retrieve contributors at this time

file 197 lines (157 sloc) 6.232 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class digestauth
{
// !ivars
var $realm;
var $required_user; // String or array
var $user; // Details of the current user (as returned by -> auth()) or false if not logged in

var $tbl_name; // name of the table that user data is stored in. Defaults to 'users'
protected $ci; // local CI instance

// !constructor
public function __construct($config=null)
{
// Get a copy of CI so we can use the DB classes
$this -> ci =& get_instance();

// Set tbl_name (defaults to 'users')
$this -> tbl_name = (empty($config['tbl_name'])) ? 'users' : $config['tbl_name'];

// No-one's logged in already
$this -> user = false;
}

// !methods
// Setters/getters
public function set_realm($realm)
{
// TODO: Any cleaning required?
$this -> realm = $realm;
}

public function require_user($user)
{
$this -> required_user = $user;
}

// Utility Methods

/**
* Stolen from http://www.php.net/manual/en/features.http-auth.php -- thanks!
*/
protected function http_digest_parse($txt)
{
// protect against missing data
$needed_parts = array('nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1);
$data = array();
$keys = implode('|', array_keys($needed_parts));

preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);

foreach ($matches as $m) {
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}

return $needed_parts ? false : $data;
}

// User management

/**
* TODO
* Multipurpose function. Either creates or alters a user/realm combination.
* E.G. user 'barnabywalters' in two realms 'realm1' and 'realm2' requires a DB record for each -- identical usernames, but different realm and hash values.
* @return bool success
*/
public function create_user($username, $realm, $password, $extra=null)
{
// Validate data
$username = trim($username);

// Compute Hash
$hash = md5($username . ':' . $realm . ':' . $password);

// Check current state of db

// Add to DB

// Return
}

// Auth methods
public function authenticate($realm = null, $username = null)
{
// If args are valid, overwrite current required user and realm.
if (isset($realm)) $this -> realm = $realm;
if (isset($username)) $this -> required_user = $username;

// Check for valid ivars
if (empty($this -> realm))
{
// No valid realm, cannot perform auth
show_error('No realm provided for auth!');
return (object) array('status' => 'invalid');
}

// Check for auth headers, send if they're not there
// Get auth headers
$auth_headers = ''; // Default to empty string
if (!empty($_SERVER['PHP_AUTH_DIGEST']))
{
// headers sent AND running as an apache module
$auth_headers = $_SERVER['PHP_AUTH_DIGEST'];
}
else if (!empty($_ENV['REDIRECT_HTTP_AUTHORIZATION']))
{
// headers sent AND running under CGI

// Should probably check for digest vs basic here

$auth_headers = $_ENV['REDIRECT_HTTP_AUTHORIZATION'];
}

if (empty($auth_headers) OR $this -> ci -> session -> flashdata('auth_status') == 'invalid')
{
$this -> ci -> output -> set_header('HTTP/1.1 401 Unauthorized');
$this -> ci -> output -> set_header('WWW-Authenticate: Digest realm="' . $this -> realm.
'",qop="auth",nonce="' . uniqid() . '",opaque="' . md5($this -> realm) . '"');

return (object) array('status' => 'initial');
}

// Parse auth headers
if (!($data = $this -> http_digest_parse($auth_headers))) return (object) array('status' => 'invalid', 'message' => 'invalid_http_data'); // Bad credentials. TODO: What sort of meaningful error can we give here?

// Check to see if the client user matches any required_user given
if (!empty($this -> required_user))
{
// We're looking to authenticate a particular user or set of users.

// Is required_user a string or array? Make array regardless
if (is_string($this -> required_user))
{
$this -> required_user = array($this -> required_user); // weird line of code...
}

// Check $data['username'] against $this -> required_user
if (!in_array($data['username'], $this -> required_user))
{
$this -> ci -> session -> set_flashdata('auth_status', 'invalid');
return (object) array('status' => 'invalid', 'message' => 'user_not_permitted');
}
}

// Check user data against db
$query = $this -> ci -> db -> from($this -> tbl_name)
-> where('username', $data['username'])
-> where('realm', $this -> realm) -> get();

if ($query -> num_rows() !== 1)
{
$this -> ci -> session -> set_flashdata('auth_status', 'invalid');
return (object) array('status' => 'invalid', 'message' => 'invalid_user');
}

// Get hash
$user_hash = $query -> row() -> password;

// Compute valid response (code stolen from http://www.php.net/manual/en/features.http-auth.php -- thanks!
$A2 = md5($_SERVER['REQUEST_METHOD'] . ':' .$data['uri']);
$valid_response = md5($user_hash . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $A2);

// Correct?
if ($data['response'] !== $valid_response)
{
$this -> ci -> session -> set_flashdata('auth_status', 'invalid');
return (object) array('status' => 'invalid', 'message' => 'invalid_password');
}

// Store current user details
$this -> user = $query -> row();

// Result
return (object) array('status' => 'logged_in');
}

/**
* (In theory) logs out the current user.
* Sends HTTP headers so must be called before any are sent by the rest of the script
*/
public function logout()
{
// Remove current user details
$this -> user = false;

// Send 401 header to clear auth chache (in most cases)
$this -> ci -> session -> set_flashdata('auth_status', 'invalid');
$this -> ci -> output -> set_header('HTTP/1.1 401 Unauthorized');
$this -> ci -> output -> set_header('WWW-Authenticate: Digest realm="' . $this -> realm.
'",qop="auth",nonce="' . uniqid() . '",opaque="' . md5($this -> realm) . '"');
}

}

/* EOF digestauth.php */
Something went wrong with that request. Please try again.