ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
MDQ.php
Go to the documentation of this file.
1 <?php
2 
4 
7 
17 {
18 
24  private $server;
25 
33 
39  private $cacheDir;
40 
41 
47  private $cacheLength;
48 
49 
66  protected function __construct($config)
67  {
68  assert(is_array($config));
69 
70  if (!array_key_exists('server', $config)) {
71  throw new \Exception(__CLASS__.": the 'server' configuration option is not set.");
72  } else {
73  $this->server = $config['server'];
74  }
75 
76  if (array_key_exists('validateFingerprint', $config)) {
77  $this->validateFingerprint = $config['validateFingerprint'];
78  } else {
79  $this->validateFingerprint = null;
80  }
81 
82  if (array_key_exists('cachedir', $config)) {
84  $this->cacheDir = $globalConfig->resolvePath($config['cachedir']);
85  } else {
86  $this->cacheDir = null;
87  }
88 
89  if (array_key_exists('cachelength', $config)) {
90  $this->cacheLength = $config['cachelength'];
91  } else {
92  $this->cacheLength = 86400;
93  }
94  }
95 
96 
104  public function getMetadataSet($set)
105  {
106  // we don't have this metadata set
107  return array();
108  }
109 
110 
119  private function getCacheFilename($set, $entityId)
120  {
121  assert(is_string($set));
122  assert(is_string($entityId));
123 
124  $cachekey = sha1($entityId);
125  return $this->cacheDir.'/'.$set.'-'.$cachekey.'.cached.xml';
126  }
127 
128 
139  private function getFromCache($set, $entityId)
140  {
141  assert(is_string($set));
142  assert(is_string($entityId));
143 
144  if (empty($this->cacheDir)) {
145  return null;
146  }
147 
148  $cachefilename = $this->getCacheFilename($set, $entityId);
149  if (!file_exists($cachefilename)) {
150  return null;
151  }
152  if (!is_readable($cachefilename)) {
153  throw new \Exception(__CLASS__.': could not read cache file for entity ['.$cachefilename.']');
154  }
155  Logger::debug(__CLASS__.': reading cache ['.$entityId.'] => ['.$cachefilename.']');
156 
157  /* Ensure that this metadata isn't older that the cachelength option allows. This
158  * must be verified based on the file, since this option may be changed after the
159  * file is written.
160  */
161  $stat = stat($cachefilename);
162  if ($stat['mtime'] + $this->cacheLength <= time()) {
163  Logger::debug(__CLASS__.': cache file older that the cachelength option allows.');
164  return null;
165  }
166 
167  $rawData = file_get_contents($cachefilename);
168  if (empty($rawData)) {
169  $error = error_get_last();
170  throw new \Exception(
171  __CLASS__.': error reading metadata from cache file "'.$cachefilename.'": '.$error['message']
172  );
173  }
174 
175  $data = unserialize($rawData);
176  if ($data === false) {
177  throw new \Exception(__CLASS__.': error unserializing cached data from file "'.$cachefilename.'".');
178  }
179 
180  if (!is_array($data)) {
181  throw new \Exception(__CLASS__.': Cached metadata from "'.$cachefilename.'" wasn\'t an array.');
182  }
183 
184  return $data;
185  }
186 
187 
197  private function writeToCache($set, $entityId, $data)
198  {
199  assert(is_string($set));
200  assert(is_string($entityId));
201  assert(is_array($data));
202 
203  if (empty($this->cacheDir)) {
204  return;
205  }
206 
207  $cachefilename = $this->getCacheFilename($set, $entityId);
208  if (!is_writable(dirname($cachefilename))) {
209  throw new \Exception(__CLASS__.': could not write cache file for entity ['.$cachefilename.']');
210  }
211  Logger::debug(__CLASS__.': Writing cache ['.$entityId.'] => ['.$cachefilename.']');
212  file_put_contents($cachefilename, serialize($data));
213  }
214 
215 
225  private static function getParsedSet(\SimpleSAML_Metadata_SAMLParser $entity, $set)
226  {
227  assert(is_string($set));
228 
229  switch ($set) {
230  case 'saml20-idp-remote':
231  return $entity->getMetadata20IdP();
232  case 'saml20-sp-remote':
233  return $entity->getMetadata20SP();
234  case 'shib13-idp-remote':
235  return $entity->getMetadata1xIdP();
236  case 'shib13-sp-remote':
237  return $entity->getMetadata1xSP();
238  case 'attributeauthority-remote':
239  $ret = $entity->getAttributeAuthorities();
240  return $ret[0];
241 
242  default:
243  Logger::warning(__CLASS__.': unknown metadata set: \''.$set.'\'.');
244  }
245 
246  return null;
247  }
248 
249 
268  public function getMetaData($index, $set)
269  {
270  assert(is_string($index));
271  assert(is_string($set));
272 
273  Logger::info(__CLASS__.': loading metadata entity ['.$index.'] from ['.$set.']');
274 
275  // read from cache if possible
276  try {
277  $data = $this->getFromCache($set, $index);
278  } catch (\Exception $e) {
279  Logger::error($e->getMessage());
280  // proceed with fetching metadata even if the cache is broken
281  $data = null;
282  }
283 
284  if ($data !== null && array_key_exists('expires', $data) && $data['expires'] < time()) {
285  // metadata has expired
286  $data = null;
287  }
288 
289  if (isset($data)) {
290  // metadata found in cache and not expired
291  Logger::debug(__CLASS__.': using cached metadata for: '.$index.'.');
292  return $data;
293  }
294 
295  // look at Metadata Query Protocol: https://github.com/iay/md-query/blob/master/draft-young-md-query.txt
296  $mdq_url = $this->server.'/entities/'.urlencode($index);
297 
298  Logger::debug(__CLASS__.': downloading metadata for "'.$index.'" from ['.$mdq_url.']');
299  try {
300  $xmldata = HTTP::fetch($mdq_url);
301  } catch (\Exception $e) {
302  // Avoid propagating the exception, make sure we can handle the error later
303  $xmldata = false;
304  }
305 
306  if (empty($xmldata)) {
307  $error = error_get_last();
308  Logger::info('Unable to fetch metadata for "'.$index.'" from '.$mdq_url.': '.
309  (is_array($error) ? $error['message'] : 'no error available'));
310  return null;
311  }
312 
314  $entity = \SimpleSAML_Metadata_SAMLParser::parseString($xmldata);
315  Logger::debug(__CLASS__.': completed parsing of ['.$mdq_url.']');
316 
317  if ($this->validateFingerprint !== null) {
318  if (!$entity->validateFingerprint($this->validateFingerprint)) {
319  throw new \Exception(__CLASS__.': error, could not verify signature for entity: '.$index.'".');
320  }
321  }
322 
323  $data = self::getParsedSet($entity, $set);
324  if ($data === null) {
325  throw new \Exception(__CLASS__.': no metadata for set "'.$set.'" available from "'.$index.'".');
326  }
327 
328  try {
329  $this->writeToCache($set, $index, $data);
330  } catch (\Exception $e) {
331  // Proceed without writing to cache
332  Logger::error('Error writing MDQ result to cache: '.$e->getMessage());
333  }
334 
335  return $data;
336  }
337 }
writeToCache($set, $entityId, $data)
Save a entity to the cache.
Definition: MDQ.php:197
getMetadata1xSP()
This function returns the metadata for SAML 1.x SPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:520
$config
Definition: bootstrap.php:15
static getParsedSet(\SimpleSAML_Metadata_SAMLParser $entity, $set)
Retrieve metadata for the correct set from a SAML2Parser.
Definition: MDQ.php:225
getFromCache($set, $entityId)
Load a entity from the cache.
Definition: MDQ.php:139
__construct($config)
This function initializes the dynamic XML metadata source.
Definition: MDQ.php:66
getMetadataSet($set)
This function is not implemented.
Definition: MDQ.php:104
static debug($string)
Definition: Logger.php:211
getAttributeAuthorities()
Retrieve AttributeAuthorities from the metadata.
Definition: SAMLParser.php:811
getMetadata1xIdP()
This function returns the metadata for SAML 1.x IdPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:594
static warning($string)
Definition: Logger.php:177
getCacheFilename($set, $entityId)
Find the cache file name for an entity,.
Definition: MDQ.php:119
getMetadata20IdP()
This function returns the metadata for SAML 2.0 IdPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:753
if($source===null) if(!($source instanceof sspmod_saml_Auth_Source_SP)) $entityId
Definition: metadata.php:22
$globalConfig
This is class for parsing of SAML 1.x and SAML 2.0 metadata.
Definition: SAMLParser.php:15
$ret
Definition: parser.php:6
getMetadata20SP()
This function returns the metadata for SAML 2.0 SPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:650
static getInstance($instancename='simplesaml')
Get a configuration file by its instance name.
$data
Definition: bench.php:6