ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Negotiate.php
Go to the documentation of this file.
1 <?php
2 
3 
11 {
12 
13  // Constants used in the module
14  const STAGEID = 'sspmod_negotiate_Auth_Source_Negotiate.StageId';
15 
16  protected $ldap = null;
17  protected $backend = '';
18  protected $hostname = '';
19  protected $port = 389;
20  protected $referrals = true;
21  protected $enableTLS = false;
22  protected $debugLDAP = false;
23  protected $timeout = 30;
24  protected $keytab = '';
25  protected $base = array();
26  protected $attr = 'uid';
27  protected $subnet = null;
28  protected $admin_user = null;
29  protected $admin_pw = null;
30  protected $attributes = null;
31 
32 
41  public function __construct($info, $config)
42  {
43  assert(is_array($info));
44  assert(is_array($config));
45 
46  if (!extension_loaded('krb5')) {
47  throw new Exception('KRB5 Extension not installed');
48  }
49 
50  // call the parent constructor first, as required by the interface
51  parent::__construct($info, $config);
52 
54 
55  $this->backend = $config->getString('fallback');
56  $this->hostname = $config->getString('hostname');
57  $this->port = $config->getInteger('port', 389);
58  $this->referrals = $config->getBoolean('referrals', true);
59  $this->enableTLS = $config->getBoolean('enable_tls', false);
60  $this->debugLDAP = $config->getBoolean('debugLDAP', false);
61  $this->timeout = $config->getInteger('timeout', 30);
62  $this->keytab = $config->getString('keytab');
63  $this->base = $config->getArrayizeString('base');
64  $this->attr = $config->getString('attr', 'uid');
65  $this->subnet = $config->getArray('subnet', null);
66  $this->admin_user = $config->getString('adminUser', null);
67  $this->admin_pw = $config->getString('adminPassword', null);
68  $this->attributes = $config->getArray('attributes', null);
69  }
70 
71 
83  public function authenticate(&$state)
84  {
85  assert(is_array($state));
86 
87  // set the default backend to config
88  $state['LogoutState'] = array(
89  'negotiate:backend' => $this->backend,
90  );
91  $state['negotiate:authId'] = $this->authId;
92 
93 
94  // check for disabled SPs. The disable flag is store in the SP metadata
95  if (array_key_exists('SPMetadata', $state) && $this->spDisabledInMetadata($state['SPMetadata'])) {
96  $this->fallBack($state);
97  }
98  /* Go straight to fallback if Negotiate is disabled or if you are sent back to the IdP directly from the SP
99  after having logged out. */
101  $disabled = $session->getData('negotiate:disable', 'session');
102 
103  if ($disabled ||
104  (!empty($_COOKIE['NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT']) &&
105  $_COOKIE['NEGOTIATE_AUTOLOGIN_DISABLE_PERMANENT'] == 'True')
106  ) {
107  SimpleSAML\Logger::debug('Negotiate - session disabled. falling back');
108  $this->fallBack($state);
109  // never executed
110  assert(false);
111  }
112  $mask = $this->checkMask();
113  if (!$mask) {
114  $this->fallBack($state);
115  // never executed
116  assert(false);
117  }
118 
119  SimpleSAML\Logger::debug('Negotiate - authenticate(): looking for Negotiate');
120  if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
121  SimpleSAML\Logger::debug('Negotiate - authenticate(): Negotiate found');
122  $this->ldap = new SimpleSAML_Auth_LDAP(
123  $this->hostname,
124  $this->enableTLS,
125  $this->debugLDAP,
126  $this->timeout,
127  $this->port,
128  $this->referrals
129  );
130 
131  list($mech,) = explode(' ', $_SERVER['HTTP_AUTHORIZATION'], 2);
132  if (strtolower($mech) == 'basic') {
133  SimpleSAML\Logger::debug('Negotiate - authenticate(): Basic found. Skipping.');
134  } else {
135  if (strtolower($mech) != 'negotiate') {
136  SimpleSAML\Logger::debug('Negotiate - authenticate(): No "Negotiate" found. Skipping.');
137  }
138  }
139 
140  $auth = new KRB5NegotiateAuth($this->keytab);
141  // attempt Kerberos authentication
142  try {
143  $reply = $auth->doAuthentication();
144  } catch (Exception $e) {
145  SimpleSAML\Logger::error('Negotiate - authenticate(): doAuthentication() exception: '.$e->getMessage());
146  $reply = null;
147  }
148 
149  if ($reply) {
150  // success! krb TGS received
151  $user = $auth->getAuthenticatedUser();
152  SimpleSAML\Logger::info('Negotiate - authenticate(): '.$user.' authenticated.');
153  $lookup = $this->lookupUserData($user);
154  if ($lookup !== null) {
155  $state['Attributes'] = $lookup;
156  // Override the backend so logout will know what to look for
157  $state['LogoutState'] = array(
158  'negotiate:backend' => null,
159  );
160  SimpleSAML\Logger::info('Negotiate - authenticate(): '.$user.' authorized.');
162  // Never reached.
163  assert(false);
164  }
165  } else {
166  // Some error in the received ticket. Expired?
167  SimpleSAML\Logger::info('Negotiate - authenticate(): Kerberos authN failed. Skipping.');
168  }
169  } else {
170  // No auth token. Send it.
171  SimpleSAML\Logger::debug('Negotiate - authenticate(): Sending Negotiate.');
172  // Save the $state array, so that we can restore if after a redirect
173  SimpleSAML\Logger::debug('Negotiate - fallback: '.$state['LogoutState']['negotiate:backend']);
174  $id = SimpleSAML_Auth_State::saveState($state, self::STAGEID);
175  $params = array('AuthState' => $id);
176 
177  $this->sendNegotiate($params);
178  exit;
179  }
180 
181  SimpleSAML\Logger::info('Negotiate - authenticate(): Client failed Negotiate. Falling back');
182  $this->fallBack($state);
183  /* The previous function never returns, so this code is never
184  executed */
185  assert(false);
186  }
187 
188 
190  {
191  if (array_key_exists('negotiate:disable', $spMetadata)) {
192  if ($spMetadata['negotiate:disable'] == true) {
193  SimpleSAML\Logger::debug('Negotiate - SP disabled. falling back');
194  return true;
195  } else {
196  SimpleSAML\Logger::debug('Negotiate - SP disable flag found but set to FALSE');
197  }
198  } else {
199  SimpleSAML\Logger::debug('Negotiate - SP disable flag not found');
200  }
201  return false;
202  }
203 
204 
213  public function checkMask()
214  {
215  // No subnet means all clients are accepted.
216  if ($this->subnet === null) {
217  return true;
218  }
219  $ip = $_SERVER['REMOTE_ADDR'];
220  foreach ($this->subnet as $cidr) {
222  if ($ret) {
223  SimpleSAML\Logger::debug('Negotiate: Client "'.$ip.'" matched subnet.');
224  return true;
225  }
226  }
227  SimpleSAML\Logger::debug('Negotiate: Client "'.$ip.'" did not match subnet.');
228  return false;
229  }
230 
231 
238  protected function sendNegotiate($params)
239  {
240  $url = htmlspecialchars(SimpleSAML\Module::getModuleURL('negotiate/backend.php', $params));
241  $json_url = json_encode($url);
242 
243  header('HTTP/1.1 401 Unauthorized');
244  header('WWW-Authenticate: Negotiate', false);
245  echo <<<EOF
246 <html>
247  <head>
248  <script type="text/javascript">window.location = $json_url</script>
249  <title>Redirect to login</title>
250  </head>
251 <body>
252  <p>Your browser seems to have Javascript disabled. Please click <a href="$url">here</a>.</p>
253 </body>
254 </html>
255 EOF;
256  }
257 
258 
268  public static function fallBack(&$state)
269  {
270  $authId = $state['LogoutState']['negotiate:backend'];
271 
272  if ($authId === null) {
273  throw new SimpleSAML_Error_Error(array(500, "Unable to determine auth source."));
274  }
276 
277  try {
278  $source->authenticate($state);
279  } catch (SimpleSAML_Error_Exception $e) {
281  } catch (Exception $e) {
284  }
285  // fallBack never returns after loginCompleted()
286  SimpleSAML\Logger::debug('Negotiate: backend returned');
287  self::loginCompleted($state);
288  }
289 
290 
299  protected function lookupUserData($user)
300  {
301  // Kerberos user names include realm. Strip that away.
302  $pos = strpos($user, '@');
303  if ($pos === false) {
304  return null;
305  }
306  $uid = substr($user, 0, $pos);
307 
308  $this->adminBind();
309  try {
310  $dn = $this->ldap->searchfordn($this->base, $this->attr, $uid);
311  return $this->ldap->getAttributes($dn, $this->attributes);
312  } catch (SimpleSAML_Error_Exception $e) {
313  SimpleSAML\Logger::debug('Negotiate - ldap lookup failed: '.$e);
314  return null;
315  }
316  }
317 
318 
323  protected function adminBind()
324  {
325  if ($this->admin_user === null) {
326  // no admin user
327  return;
328  }
330  'Negotiate - authenticate(): Binding as system user '.var_export($this->admin_user, true)
331  );
332 
333  if (!$this->ldap->bind($this->admin_user, $this->admin_pw)) {
334  $msg = 'Unable to authenticate system user (LDAP_INVALID_CREDENTIALS) '.var_export($this->admin_user, true);
335  SimpleSAML\Logger::error('Negotiate - authenticate(): '.$msg);
336  throw new SimpleSAML_Error_AuthSource('negotiate', $msg);
337  }
338  }
339 
340 
349  public function logout(&$state)
350  {
351  assert(is_array($state));
352  // get the source that was used to authenticate
353  $authId = $state['negotiate:backend'];
354  SimpleSAML\Logger::debug('Negotiate - logout has the following authId: "'.$authId.'"');
355 
356  if ($authId === null) {
358  $session->setData('negotiate:disable', 'session', true, 24 * 60 * 60);
359  parent::logout($state);
360  } else {
362  $source->logout($state);
363  }
364  }
365 }
$_COOKIE['client_id']
Definition: server.php:9
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
__construct($info, $config)
Constructor for this authentication source.
Definition: Negotiate.php:41
$config
Definition: bootstrap.php:15
static throwException($state, SimpleSAML_Error_Exception $exception)
Throw exception to the state exception handler.
Definition: State.php:343
static debug($string)
Definition: Logger.php:211
$session
if(!array_key_exists('StateId', $_REQUEST)) $id
base()
Definition: base.php:2
$spMetadata
$auth
Definition: fileserver.php:48
sendNegotiate($params)
Send the actual headers and body of the 401.
Definition: Negotiate.php:238
lookupUserData($user)
Strips away the realm of the Kerberos identifier, looks up what attributes to fetch from SP metadata ...
Definition: Negotiate.php:299
if(!array_key_exists('stateid', $_REQUEST)) $state
Handle linkback() response from LinkedIn.
Definition: linkback.php:10
Attribute-related utility methods.
static info($string)
Definition: Logger.php:199
static ipCIDRcheck($cidr, $ip=null)
Check whether an IP address is part of a CIDR.
Definition: Net.php:26
$mask
Definition: example_042.php:90
logout(&$state)
Log out from this authentication source.
Definition: Negotiate.php:349
static error($string)
Definition: Logger.php:166
$user
Definition: migrateto20.php:57
adminBind()
Elevates the LDAP connection to allow restricted lookups if so configured.
Definition: Negotiate.php:323
exit
Definition: backend.php:16
$ret
Definition: parser.php:6
html()
$url
static completeAuth(&$state)
Complete authentication.
Definition: Source.php:136
$source
Definition: linkback.php:22
static getById($authId, $type=null)
Retrieve authentication source.
Definition: Source.php:340
$info
Definition: index.php:5
checkMask()
checkMask() looks up the subnet config option and verifies that the client is within that range...
Definition: Negotiate.php:213
static getSessionFromRequest()
Retrieves the current session.
Definition: Session.php:241
static loadFromArray($config, $location='[ARRAY]', $instance=null)
Loads a configuration from the given array.
authenticate(&$state)
The inner workings of the module.
Definition: Negotiate.php:83
static saveState(&$state, $stage, $rawId=false)
Save the state.
Definition: State.php:194
static fallBack(&$state)
Passes control of the login process to a different module.
Definition: Negotiate.php:268
const EOF
How fgetc() reports an End Of File.
Definition: JSMin_lib.php:92