summaryrefslogblamecommitdiffstats
path: root/extras/import-idp.php
blob: a16ed927b0edd6d82b20f710935bb956ff389fbf (plain) (tree)
1
2
3
4
5
6
7
8
9



                       




                                  


                                                      






                               


                                                      







                                                                                          
                   

                        
                                                               
                                                                  
                   


                                     



                                                                                                                                                                  
                            
 
















                                                              



























































                                                                                                                           







                                                                                   
                            


                                                                                                                          
                                                                                      






                                                            
                                                                                                                                                         







                                                                          
                     
                                                                                       




                                                                                                                                              
                                                                      
                                            


                                                                                                
                         














                                                                                                                                                                               

                                                   

                                                                                                  


                                                                                                       
                                                                                                                                 


                 











































                                                                                                                                          
 
<?php

error_reporting(E_ALL);

function pdebug($text)
{
	if (DEBUG) echo "$text\n";
}

if ($argc < 2)
	die("Too few arguments. Pass config file!\n");

if ($argv[1] === '--debug') {
	$argc--;
	array_shift($argv);
	define('DEBUG', true);
} else {
	define('DEBUG', false);
}
if ($argc < 2)
	die("Too few arguments. Pass config file!\n");

$handle = fopen($argv[1], 'r') or die("Cannot open mysql config given on command line\n");
$settings = array();
while (($line = fgets($handle)) !== false) {
	if (!preg_match('/^\s*(.*?)\s*=\s*(.*?)\s*$/', $line, $out)) continue;
	$settings[$out[1]] = $out[2];
}
fclose($handle);

// Default/Fallback
$suffixMappings = false;
$localFile = false;
$requiredAttribute = 'http://aai.dfn.de/category/bwidm-member';
$url = 'https://www.aai.dfn.de/metadata/dfn-aai-idp-metadata.xml';
$requireEcp = true;
$f = __DIR__ . '/shib.conf.php';
if (is_readable($f)) require_once $f;

if (empty($settings['host']) || empty($settings['user']) || empty($settings['password']) || empty($settings['db'])) die("Missing fields in given mysql config\n");

$db = new mysqli($settings['host'], $settings['user'], $settings['password'], $settings['db']);
if ($db->connect_errno) die("Could not connect to db: " . $db->connect_error . "\n");
$db->set_charset("utf8mb4");

if ($localFile !== false && file_exists($localFile)) {
	$data = file_get_contents($localFile);
} else {
	pdebug("Downloading...");
	$ch = curl_init();
	if ($ch === false) die("Could not init curl\n");

	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_TIMEOUT, 10);
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
	curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	$data = curl_exec($ch);
	if ($data === false) {
		die("Could not download DFN-AAI meta data\n");
	}
}

function getAttributes($array, $path)
{
	if (!is_array($path)) {
		// Convert '/path/syntax/foo/wanteditem' to array for further processing and recursive calls
		$path = explode('/', $path);
	}
	do {
		// Get next element from array, loop to ignore empty elements (so double slashes in the path are allowed)
		$element = array_shift($path);
	} while (empty($element) && !empty($path));
	if (!isset($array[$element])) {
		// Current path element does not exist - error
		return array();
	}
	if (empty($path)) {
		// Path is now empty which means we're at 'wanteditem' from out example above now
		if (!is_array($array[$element]) || !isset($array[$element][0])) {
			// If it's a leaf node of the array, wrap it in plain array, so the function will
			// always return an array on success
			return array($array[$element]);
		}
		// 'wanteditem' is not a unique leaf node, return as is
		// This means it's either a plain array, in case there are multiple 'wanteditem' elements on the same level
		// or it's an associative array if 'wanteditem' has any sub-nodes
		return $array[$element];
	}
	// Recurse
	if (!is_array($array[$element])) {
		// We're in the middle of the requested path, but the current element is already a leaf node with no
		// children - error
		return array();
	}
	if (isset($array[$element][0])) {
		// The currently handled element of the path exists multiple times on the current level, so it is
		// wrapped in a plain array - recurse into each one of them and merge the results
		$return = array();
		foreach ($array[$element] as $item) {
			$return = array_merge($return, getAttributes($item, $path));
		}
		return $return;
	}
	// Unique non-leaf node - simple recursion
	return getAttributes($array[$element], $path);
}

// Safety check: If XML module is missing, don't wipe DB
@simplexml_load_string('<?xml version="1.0" encoding="utf-8" standalone="yes" ?'.'><test></test>');

// Also, only wipe old URLs if we found at least one valid URL
function wipeDb() {
	global $db;
	static $wiped = false;
	if ($wiped)
		return;
	$wiped = true;
	$db->query("LOCK TABLES organization WRITE, organization_suffix WRITE");
	$db->query("UPDATE organization SET authmethod = '' WHERE authmethod LIKE 'http%'");
}

// Regular ECP auth for suite

$data = preg_replace('#<(/?)[a-zA-Z0-9_-]+:#', '<\1', $data);
$data = preg_replace('# ([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)=#', ' \1_\2=', $data);
$count = preg_match_all('#<EntityDescriptor.*?</EntityDescriptor>#s', $data, $out);
unset($data);
pdebug("Found $count EntityDescriptors");

foreach ($out[0] as $data) {
	$xml = json_decode(json_encode(simplexml_load_string('<?xml version="1.0" encoding="utf-8" standalone="yes" ?'.'>'
		. $data
	)), true);
	$name = getAttributes($xml, "IDPSSODescriptor/Extensions/UIInfo/DisplayName");
	if (is_array($name) && !empty($name)) {
		$name = $name[0];
		pdebug(" *** Found $name");
	} else {
		pdebug(" *** Entry without DisplayName...");
		continue;
	}
	if ($requiredAttribute !== false && !in_array($requiredAttribute, getAttributes($xml, 'Extensions/EntityAttributes/Attribute/AttributeValue'))) {
		pdebug("Not bwIDM member...");
		continue;
	}
	$scope = getAttributes($xml, "IDPSSODescriptor/Extensions/Scope");
	if (empty($scope)) {
		pdebug("No list of scopes...");
		continue;
	}
	$ecp = false;
	foreach (getAttributes($xml, "IDPSSODescriptor/SingleSignOnService") as $sso) {
		if (isset($sso['@attributes']['Binding']) && $sso['@attributes']['Binding'] === 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP') {
			$ecp = $sso['@attributes']['Location'];
			break;
		}
	}
	// Now usable: $scope, $name, $ecp (if known, false otherwise)
	if ($requireEcp && $ecp === false) {
		pdebug("No ECP end-point...");
	} else {
		pdebug("Adding/Updating with ECP URL $ecp, suffixes: " . implode(', ', $scope));
		wipeDb();
		$orgid = false;
		// Try to use any existing organization ID based on the suffixes. This is to avoid adding the same organzation twice, should the first entry in the list change
		foreach ($scope as $alias) {
			$ealias = $db->escape_string($alias);
			$res = $db->query("SELECT organizationid FROM organization_suffix WHERE suffix = '$ealias' LIMIT 1");
			if ($row = $res->fetch_assoc()) {
				$orgid = $row['organizationid'];
				break;
			}
		}
		if ($orgid === false) {
			// Not known yet, use first
			$orgid = $scope[0];
		}
		$eid = $db->escape_string($orgid);
		$ename = $db->escape_string($name);
		$eecp = $db->escape_string($ecp);
		$db->query("INSERT INTO organization (organizationid, name, authmethod, publickey)
			VALUES ('$eid', '$ename', '$eecp', '')
			ON DUPLICATE KEY UPDATE authmethod = VALUES(authmethod), name = VALUES(name)");
		foreach ($scope as $alias) {
			$ealias = $db->escape_string($alias);
			$db->query("INSERT IGNORE INTO organization_suffix (organizationid, suffix) VALUES ('$eid', '$ealias')");
		}
	}
}
$db->query("UNLOCK TABLES");

// Mapping of suffix to idp (and back)

if (is_array($suffixMappings)) {
	$db->query("LOCK TABLES suffix2idp WRITE");
	$db->query("TRUNCATE TABLE suffix2idp");
	foreach ($suffixMappings as $file) {
		$data = file_get_contents($file);
		if (empty($data))
			continue;
		$data = preg_replace('#<(/?)[a-zA-Z0-9_-]+:#', '<\1', $data);
		$data = preg_replace('# ([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)=#', ' \1_\2=', $data);
		$count = preg_match_all('#<EntityDescriptor.*?</EntityDescriptor>#s', $data, $out);
		unset($data);
		pdebug("Found $count EntityDescriptors");
		foreach ($out[0] as $data) {
			$xml = json_decode(json_encode(simplexml_load_string('<?xml version="1.0" encoding="utf-8" standalone="yes" ?'.'>'
				. $data
			)), true);
			$scope = getAttributes($xml, "IDPSSODescriptor/Extensions/Scope");
			if (empty($scope)) {
				pdebug("No list of scopes...");
				continue;
			}
			$id = getAttributes($xml, "@attributes/entityID");
			if (is_array($id) && !empty($id)) {
				$id = $id[0];
			}
			$ereg = '';
			$reg = getAttributes($xml, "Extensions/RegistrationInfo/@attributes/registrationAuthority");
			if (is_array($reg) && !empty($reg)) {
				$ereg = $db->escape_string($reg[0]);
			}
			$eid = $db->escape_string($id);
			foreach ($scope as $alias) {
				$ealias = $db->escape_string($alias);
				$db->query("INSERT IGNORE INTO suffix2idp (idpurl, suffix, regauth)"
						. " VALUES ('$eid', '$ealias', '$ereg')");
			}
		}
	}
	$db->query("UNLOCK TABLES");
}