ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
MetaLoader.php
Go to the documentation of this file.
1<?php
7{
8 private $expire;
9 private $metadata;
11 private $stateFile;
12 private $changed;
13 private $state;
14 private $types = array(
15 'saml20-idp-remote',
16 'saml20-sp-remote',
17 'shib13-idp-remote',
18 'shib13-sp-remote',
19 'attributeauthority-remote'
20 );
21
22
30 public function __construct($expire = null, $stateFile = null, $oldMetadataSrc = null)
31 {
32 $this->expire = $expire;
33 $this->metadata = array();
34 $this->oldMetadataSrc = $oldMetadataSrc;
35 $this->stateFile = $stateFile;
36 $this->changed = false;
37
38 // Read file containing $state from disk
39 if (is_readable($stateFile)) {
40 include $stateFile;
41 }
42
43 $this->state = array();
44
45 }
46
47
53 public function getTypes()
54 {
55 return $this->types;
56 }
57
58
65 public function setTypes($types)
66 {
67 if (!is_array($types)) {
68 $types = array($types);
69 }
70 $this->types = $types;
71 }
72
73
79 public function loadSource($source)
80 {
81 if (preg_match('@^https?://@i', $source['src'])) {
82 // Build new HTTP context
84
85 // GET!
86 try {
87 list($data, $responseHeaders) = \SimpleSAML\Utils\HTTP::fetch($source['src'], $context, true);
88 } catch(Exception $e) {
89 SimpleSAML\Logger::warning('metarefresh: ' . $e->getMessage());
90 }
91
92 // We have response headers, so the request succeeded
93 if (!isset($responseHeaders)) {
94 // No response headers, this means the request failed in some way, so re-use old data
95 SimpleSAML\Logger::debug('No response from ' . $source['src'] . ' - attempting to re-use cached metadata');
97 return;
98 } elseif (preg_match('@^HTTP/1\.[01]\s304\s@', $responseHeaders[0])) {
99 // 304 response
100 SimpleSAML\Logger::debug('Received HTTP 304 (Not Modified) - attempting to re-use cached metadata');
102 return;
103 } elseif (!preg_match('@^HTTP/1\.[01]\s200\s@', $responseHeaders[0])) {
104 // Other error
105 SimpleSAML\Logger::debug('Error from ' . $source['src'] . ' - attempting to re-use cached metadata');
107 return;
108 }
109 } else {
110 // Local file.
111 $data = file_get_contents($source['src']);
112 $responseHeaders = null;
113 }
114
115 // Everything OK. Proceed.
116 if (isset($source['conditionalGET']) && $source['conditionalGET']) {
117 // Stale or no metadata, so a fresh copy
118 SimpleSAML\Logger::debug('Downloaded fresh copy');
119 }
120
121 try {
122 $entities = $this->loadXML($data, $source);
123 } catch(Exception $e) {
124 SimpleSAML\Logger::debug('XML parser error when parsing ' . $source['src'] . ' - attempting to re-use cached metadata');
125 SimpleSAML\Logger::debug('XML parser returned: ' . $e->getMessage());
127 return;
128 }
129
130 foreach ($entities as $entity) {
131
132 if (isset($source['blacklist'])) {
133 if (!empty($source['blacklist']) && in_array($entity->getEntityID(), $source['blacklist'], true)) {
134 SimpleSAML\Logger::info('Skipping "' . $entity->getEntityID() . '" - blacklisted.' . "\n");
135 continue;
136 }
137 }
138
139 if (isset($source['whitelist'])) {
140 if (!empty($source['whitelist']) && !in_array($entity->getEntityID(), $source['whitelist'], true)) {
141 SimpleSAML\Logger::info('Skipping "' . $entity->getEntityID() . '" - not in the whitelist.' . "\n");
142 continue;
143 }
144 }
145
146 if (array_key_exists('certificates', $source) && $source['certificates'] !== null) {
147 if (!$entity->validateSignature($source['certificates'])) {
148 SimpleSAML\Logger::info('Skipping "' . $entity->getEntityId() . '" - could not verify signature using certificate.' . "\n");
149 continue;
150 }
151 }
152
153 if (array_key_exists('validateFingerprint', $source) && $source['validateFingerprint'] !== null) {
154 if (!array_key_exists('certificates', $source) || $source['certificates'] == null) {
155 if (!$entity->validateFingerprint($source['validateFingerprint'])) {
156 SimpleSAML\Logger::info('Skipping "' . $entity->getEntityId() . '" - could not verify signature using fingerprint.' . "\n");
157 continue;
158 }
159 } else {
160 SimpleSAML\Logger::info('Skipping validation with fingerprint since option certificate is set.' . "\n");
161 }
162 }
163
164 $template = null;
165 if (array_key_exists('template', $source)) {
166 $template = $source['template'];
167 }
168
169 if (in_array('shib13-sp-remote', $this->types, true)) {
170 $this->addMetadata($source['src'], $entity->getMetadata1xSP(), 'shib13-sp-remote', $template);
171 }
172 if (in_array('shib13-idp-remote', $this->types, true)) {
173 $this->addMetadata($source['src'], $entity->getMetadata1xIdP(), 'shib13-idp-remote', $template);
174 }
175 if (in_array('saml20-sp-remote', $this->types, true)) {
176 $this->addMetadata($source['src'], $entity->getMetadata20SP(), 'saml20-sp-remote', $template);
177 }
178 if (in_array('saml20-idp-remote', $this->types, true)) {
179 $this->addMetadata($source['src'], $entity->getMetadata20IdP(), 'saml20-idp-remote', $template);
180 }
181 if (in_array('attributeauthority-remote', $this->types, true)) {
182 $attributeAuthorities = $entity->getAttributeAuthorities();
183 if (!empty($attributeAuthorities)) {
184 $this->addMetadata($source['src'], $attributeAuthorities[0], 'attributeauthority-remote', $template);
185 }
186 }
187 }
188
189 $this->saveState($source, $responseHeaders);
190 }
191
195 private function createContext($source)
196 {
198 $name = $config->getString('technicalcontact_name', null);
199 $mail = $config->getString('technicalcontact_email', null);
200
201 $rawheader = "User-Agent: SimpleSAMLphp metarefresh, run by $name <$mail>\r\n";
202
203 if (isset($source['conditionalGET']) && $source['conditionalGET']) {
204 if (array_key_exists($source['src'], $this->state)) {
205 $sourceState = $this->state[$source['src']];
206
207 if (isset($sourceState['last-modified'])) {
208 $rawheader .= 'If-Modified-Since: ' . $sourceState['last-modified'] . "\r\n";
209 }
210
211 if (isset($sourceState['etag'])) {
212 $rawheader .= 'If-None-Match: ' . $sourceState['etag'] . "\r\n";
213 }
214 }
215 }
216
217 return array('http' => array('header' => $rawheader));
218 }
219
220
221 private function addCachedMetadata($source)
222 {
223 if (isset($this->oldMetadataSrc)) {
224 foreach ($this->types as $type) {
225 foreach ($this->oldMetadataSrc->getMetadataSet($type) as $entity) {
226 if (array_key_exists('metarefresh:src', $entity)) {
227 if ($entity['metarefresh:src'] == $source['src']) {
228 $this->addMetadata($source['src'], $entity, $type);
229 }
230 }
231 }
232 }
233 }
234 }
235
236
240 private function saveState($source, $responseHeaders)
241 {
242 if (isset($source['conditionalGET']) && $source['conditionalGET']) {
243 // Headers section
244 if ($responseHeaders !== null) {
245 $candidates = array('last-modified', 'etag');
246
247 foreach ($candidates as $candidate) {
248 if (array_key_exists($candidate, $responseHeaders)) {
249 $this->state[$source['src']][$candidate] = $responseHeaders[$candidate];
250 }
251 }
252 }
253
254 if (!empty($this->state[$source['src']])) {
255 // Timestamp when this src was requested.
256 $this->state[$source['src']]['requested_at'] = $this->getTime();
257 $this->changed = true;
258 }
259 }
260 }
261
265 private function loadXML($data, $source)
266 {
267 try {
269 } catch (Exception $e) {
270 throw new Exception('Failed to read XML from ' . $source['src']);
271 }
272 if ($doc->documentElement === null) {
273 throw new Exception('Opened file is not an XML document: ' . $source['src']);
274 }
276 }
277
278
282 public function writeState()
283 {
284 if ($this->changed) {
285 SimpleSAML\Logger::debug('Writing: ' . $this->stateFile);
287 $this->stateFile,
288 "<?php\n/* This file was generated by the metarefresh module at ".$this->getTime() . ".\n".
289 " Do not update it manually as it will get overwritten. */\n".
290 '$state = ' . var_export($this->state, true) . ";\n?>\n",
291 0644
292 );
293 }
294 }
295
296
300 public function dumpMetadataStdOut()
301 {
302 foreach ($this->metadata as $category => $elements) {
303
304 echo '/* The following data should be added to metadata/' . $category . '.php. */' . "\n";
305
306 foreach ($elements as $m) {
307 $filename = $m['filename'];
308 $entityID = $m['metadata']['entityid'];
309
310 echo "\n";
311 echo '/* The following metadata was generated from ' . $filename . ' on ' . $this->getTime() . '. */' . "\n";
312 echo '$metadata[\'' . addslashes($entityID) . '\'] = ' . var_export($m['metadata'], true) . ';' . "\n";
313 }
314
315 echo "\n";
316 echo '/* End of data which should be added to metadata/' . $category . '.php. */' . "\n";
317 echo "\n";
318 }
319 }
320
321
330 private function addMetadata($filename, $metadata, $type, $template = null)
331 {
332 if ($metadata === null) {
333 return;
334 }
335
336 if (isset($template)) {
337 $metadata = array_merge($metadata, $template);
338 }
339
340 $metadata['metarefresh:src'] = $filename;
341 if (!array_key_exists($type, $this->metadata)) {
342 $this->metadata[$type] = array();
343 }
344
345 // If expire is defined in constructor...
346 if (!empty($this->expire)) {
347 // If expire is already in metadata
348 if (array_key_exists('expire', $metadata)) {
349 // Override metadata expire with more restrictive global config-
350 if ($this->expire < $metadata['expire']) {
351 $metadata['expire'] = $this->expire;
352 }
353
354 // If expire is not already in metadata use global config
355 } else {
356 $metadata['expire'] = $this->expire;
357 }
358 }
359 $this->metadata[$type][] = array('filename' => $filename, 'metadata' => $metadata);
360 }
361
362
366 public function writeARPfile($config)
367 {
368 assert($config instanceof SimpleSAML_Configuration);
369
370 $arpfile = $config->getValue('arpfile');
371 $types = array('saml20-sp-remote');
372
373 $md = array();
374 foreach ($this->metadata as $category => $elements) {
375 if (!in_array($category, $types, true)) {
376 continue;
377 }
378 $md = array_merge($md, $elements);
379 }
380
381 // $metadata, $attributemap, $prefix, $suffix
382 $arp = new sspmod_metarefresh_ARP($md,
383 $config->getValue('attributemap', ''),
384 $config->getValue('prefix', ''),
385 $config->getValue('suffix', '')
386 );
387
388
389 $arpxml = $arp->getXML();
390
391 SimpleSAML\Logger::info('Writing ARP file: ' . $arpfile . "\n");
392 file_put_contents($arpfile, $arpxml);
393 }
394
395
399 public function writeMetadataFiles($outputDir)
400 {
401 while (strlen($outputDir) > 0 && $outputDir[strlen($outputDir) - 1] === '/') {
402 $outputDir = substr($outputDir, 0, strlen($outputDir) - 1);
403 }
404
405 if (!file_exists($outputDir)) {
406 SimpleSAML\Logger::info('Creating directory: ' . $outputDir . "\n");
407 $res = @mkdir($outputDir, 0777, true);
408 if ($res === false) {
409 throw new Exception('Error creating directory: ' . $outputDir);
410 }
411 }
412
413 foreach ($this->types as $type) {
414 $filename = $outputDir . '/' . $type . '.php';
415
416 if (array_key_exists($type, $this->metadata)) {
417 $elements = $this->metadata[$type];
418 SimpleSAML\Logger::debug('Writing: ' . $filename);
419
420 $content = '<?php' . "\n" . '/* This file was generated by the metarefresh module at '. $this->getTime() . "\n";
421 $content .= ' Do not update it manually as it will get overwritten' . "\n" . '*/' . "\n";
422
423 foreach ($elements as $m) {
424 $entityID = $m['metadata']['entityid'];
425 $content .= "\n";
426 $content .= '$metadata[\'' . addslashes($entityID) . '\'] = ' . var_export($m['metadata'], true) . ';' . "\n";
427 }
428
429 $content .= "\n" . '?>';
430
431 SimpleSAML\Utils\System::writeFile($filename, $content, 0644);
432 } elseif (is_file($filename)) {
433 if (unlink($filename)) {
434 SimpleSAML\Logger::debug('Deleting stale metadata file: ' . $filename);
435 } else {
436 SimpleSAML\Logger::warning('Could not delete stale metadata file: ' . $filename);
437 }
438 }
439 }
440 }
441
442
448 public function writeMetadataSerialize($outputDir)
449 {
450 assert(is_string($outputDir));
451
452 $metaHandler = new SimpleSAML_Metadata_MetaDataStorageHandlerSerialize(array('directory' => $outputDir));
453
454 /* First we add all the metadata entries to the metadata handler. */
455 foreach ($this->metadata as $set => $elements) {
456 foreach ($elements as $m) {
457 $entityId = $m['metadata']['entityid'];
458
459 SimpleSAML\Logger::debug(
460 'metarefresh: Add metadata entry ' .
461 var_export($entityId, true) . ' in set ' . var_export($set, true) . '.'
462 );
463 $metaHandler->saveMetadata($entityId, $set, $m['metadata']);
464 }
465 }
466
467 /* Then we delete old entries which should no longer exist. */
468 $ct = time();
469 foreach ($metaHandler->getMetadataSets() as $set) {
470 foreach ($metaHandler->getMetadataSet($set) as $entityId => $metadata) {
471 if (!array_key_exists('expire', $metadata)) {
472 SimpleSAML\Logger::warning(
473 'metarefresh: Metadata entry without expire timestamp: ' . var_export($entityId, true) .
474 ' in set ' . var_export($set, true) . '.'
475 );
476 continue;
477 }
478 if ($metadata['expire'] > $ct) {
479 continue;
480 }
481 SimpleSAML\Logger::debug('metarefresh: ' . $entityId . ' expired ' . date('l jS \of F Y h:i:s A', $metadata['expire']));
482 SimpleSAML\Logger::debug(
483 'metarefresh: Delete expired metadata entry ' .
484 var_export($entityId, true) . ' in set ' . var_export($set, true) . '. (' . ($ct - $metadata['expire']) . ' sec)'
485 );
486 $metaHandler->deleteMetadata($entityId, $set);
487 }
488 }
489 }
490
491
492 private function getTime()
493 {
494 /* The current date, as a string. */
495 date_default_timezone_set('UTC');
496 return date('Y-m-d\\TH:i:s\\Z');
497 }
498}
getTime()
Definition: MetaLoader.php:492
$source
Definition: linkback.php:22
$filename
Definition: buildRTE.php:89
An exception for terminatinating execution or to throw for unit testing.
static info($string)
Definition: Logger.php:199
static warning($string)
Definition: Logger.php:177
static debug($string)
Definition: Logger.php:211
static fetch($url, $context=array(), $getHeaders=false)
Helper function to retrieve a file or URL with proxy support, also supporting proxy basic authorizati...
Definition: HTTP.php:408
static writeFile($filename, $data, $mode=0600)
Atomically write a file.
Definition: System.php:183
static getInstance($instancename='simplesaml')
Get a configuration file by its instance name.
static parseDescriptorsElement(DOMElement $element=null)
This function parses a DOMElement which represents either an EntityDescriptor element or an EntitiesD...
Definition: SAMLParser.php:341
loadSource($source)
This function processes a SAML metadata file.
Definition: MetaLoader.php:79
getTypes()
Get the types of entities that will be loaded.
Definition: MetaLoader.php:53
saveState($source, $responseHeaders)
Store caching state data for a source.
Definition: MetaLoader.php:240
createContext($source)
Create HTTP context, with any available caches taken into account.
Definition: MetaLoader.php:195
dumpMetadataStdOut()
This function writes the metadata to stdout.
Definition: MetaLoader.php:300
loadXML($data, $source)
Parse XML metadata and return entities.
Definition: MetaLoader.php:265
setTypes($types)
Set the types of entities that will be loaded.
Definition: MetaLoader.php:65
addMetadata($filename, $metadata, $type, $template=null)
This function adds metadata from the specified file to the list of metadata.
Definition: MetaLoader.php:330
writeState()
This function writes the state array back to disk.
Definition: MetaLoader.php:282
__construct($expire=null, $stateFile=null, $oldMetadataSrc=null)
Constructor.
Definition: MetaLoader.php:30
$template
$config
Definition: bootstrap.php:15
$type
$data
Definition: bench.php:6
$context
Definition: webdav.php:25