From 17d9d49eda293644c27692af4caff61c7e6bcc2b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20=C3=85kre=20Solberg?= <andreas.solberg@uninett.no>
Date: Thu, 21 May 2009 18:56:08 +0000
Subject: [PATCH] minimalistic metadata registry with flatfile support. To be
 used with Feide OpenIdP. Ask before use. Wayf is working on a registry
 module, here is my idea of how to make UI pluggable etc...

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@1507 44740490-163a-0410-bde0-09ae8108e29a
---
 .../config-template/module_metaedit.php       |  12 ++
 modules/metaedit/default-disable              |   0
 modules/metaedit/hooks/hook_frontpage.php     |  18 +++
 modules/metaedit/lib/MetaEditor.php           | 148 ++++++++++++++++++
 modules/metaedit/templates/formedit.php       |  21 +++
 modules/metaedit/templates/metalist.php       |  55 +++++++
 modules/metaedit/templates/saved.php          |  15 ++
 modules/metaedit/templates/xmlimport.tpl.php  |  26 +++
 modules/metaedit/www/edit.php                 |  80 ++++++++++
 modules/metaedit/www/index.php                |  54 +++++++
 modules/metaedit/www/resources/style.css      |  37 +++++
 modules/metaedit/www/xmlimport.php            |   9 ++
 12 files changed, 475 insertions(+)
 create mode 100644 modules/metaedit/config-template/module_metaedit.php
 create mode 100644 modules/metaedit/default-disable
 create mode 100644 modules/metaedit/hooks/hook_frontpage.php
 create mode 100644 modules/metaedit/lib/MetaEditor.php
 create mode 100644 modules/metaedit/templates/formedit.php
 create mode 100644 modules/metaedit/templates/metalist.php
 create mode 100644 modules/metaedit/templates/saved.php
 create mode 100644 modules/metaedit/templates/xmlimport.tpl.php
 create mode 100644 modules/metaedit/www/edit.php
 create mode 100644 modules/metaedit/www/index.php
 create mode 100644 modules/metaedit/www/resources/style.css
 create mode 100644 modules/metaedit/www/xmlimport.php

diff --git a/modules/metaedit/config-template/module_metaedit.php b/modules/metaedit/config-template/module_metaedit.php
new file mode 100644
index 000000000..f482280e6
--- /dev/null
+++ b/modules/metaedit/config-template/module_metaedit.php
@@ -0,0 +1,12 @@
+<?php
+/* 
+ * The configuration of simpleSAMLphp statistics package
+ */
+
+$config = array (
+	'admins' => array('andreas@rnd.feide.no'),
+	'metahandlerConfig' => array('directory' => 'metadata/metaedit'),
+	'auth' => 'saml2',
+	'useridattr' => 'eduPersonPrincipalName',
+);
+
diff --git a/modules/metaedit/default-disable b/modules/metaedit/default-disable
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/metaedit/hooks/hook_frontpage.php b/modules/metaedit/hooks/hook_frontpage.php
new file mode 100644
index 000000000..21ca8be7c
--- /dev/null
+++ b/modules/metaedit/hooks/hook_frontpage.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * Hook to add the modinfo module to the frontpage.
+ *
+ * @param array &$links  The links on the frontpage, split into sections.
+ */
+function metaedit_hook_frontpage(&$links) {
+	assert('is_array($links)');
+	assert('array_key_exists("links", $links)');
+
+	$links['links']['metaedit'] = array(
+		'href' => SimpleSAML_Module::getModuleURL('metaedit/index.php'),
+		'text' => array('en' => 'Metadata registry', 'no' => 'Metadata registrering'),
+		'shorttext' => array('en' => 'Metadata registry', 'no' => 'Metadata registrering'),
+	);
+
+}
+?>
\ No newline at end of file
diff --git a/modules/metaedit/lib/MetaEditor.php b/modules/metaedit/lib/MetaEditor.php
new file mode 100644
index 000000000..ac83b235d
--- /dev/null
+++ b/modules/metaedit/lib/MetaEditor.php
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * Editor for metadata
+ *
+ * @author Andreas Ă…kre Solberg <andreas@uninett.no>, UNINETT AS.
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class sspmod_metaedit_MetaEditor {
+
+
+	protected function getStandardField($request, &$metadata, $key) {
+		if (array_key_exists('field_' . $key, $request)) {
+			$metadata[$key] = $request['field_' . $key];
+		} else {
+			if (isset($metadata[$key])) unset($metadata[$key]);
+		}
+	}
+
+	public function formToMeta($request, $metadata = array(), $override = NULL) {
+		$this->getStandardField($request, $metadata, 'entityid');
+		$this->getStandardField($request, $metadata, 'name');
+		$this->getStandardField($request, $metadata, 'description');
+		$this->getStandardField($request, $metadata, 'AssertionConsumerService');
+		$this->getStandardField($request, $metadata, 'SingleLogoutService');
+		// $this->getStandardField($request, $metadata, 'certFingerprint');
+		$metadata['updated'] = time();
+		
+		if ($override) {
+			foreach($override AS $key => $value) {
+				$metadata[$key] = $value;
+			}
+		}
+		
+		return $metadata;
+	}
+
+	protected function requireStandardField($request, $key) {
+		if (!array_key_exists('field_' . $key, $request))
+			throw new Exception('Required field [' . $key . '] was missing.');
+		if (empty($request['field_' . $key]))
+			throw new Exception('Required field [' . $key . '] was empty.');
+	}
+
+	public function checkForm($request) {
+		$this->requireStandardField($request, 'entityid');
+		$this->requireStandardField($request, 'name');
+	}
+	
+
+	protected function header($name) {
+		return '<tr ><td>&nbsp;</td><td class="header">' . $name . '</td></tr>';
+		
+	}
+	
+	protected function readonlyDateField($metadata, $key, $name) {
+		$value = '<span style="color: #aaa">Not set</a>';
+		if (array_key_exists($key, $metadata))
+			$value = date('j. F Y, G:i', $metadata[$key]);
+		return '<tr>
+			<td class="name">' . $name . '</td>
+			<td class="data">' . $value . '</td></tr>';
+
+	}
+	
+	protected function readonlyField($metadata, $key, $name) {
+		$value = '';
+		if (array_key_exists($key, $metadata))
+			$value = $metadata[$key];
+		return '<tr>
+			<td class="name">' . $name . '</td>
+			<td class="data">' . htmlspecialchars($value) . '</td></tr>';
+
+	}
+	
+	protected function hiddenField($key, $value) {
+		return '<input type="hidden" name="' . $key . '" value="' . htmlspecialchars($value) . '" />';
+	}
+	
+	protected function flattenLanguageField(&$metadata, $key) {
+		if (array_key_exists($key, $metadata)) {
+			if (is_array($metadata[$key])) {
+				if (isset($metadata[$key]['en'])) {
+					$metadata[$key] = $metadata[$key]['en'];
+				} else {
+					unset($metadata[$key]);
+				}
+			}
+		}
+	}
+	
+	protected function standardField($metadata, $key, $name, $textarea = FALSE) {
+		$value = '';
+		if (array_key_exists($key, $metadata)) {
+			$value = htmlspecialchars($metadata[$key]);
+		}
+		
+		if ($textarea) {
+			return '<tr><td class="name">' . $name . '</td><td class="data">
+			<textarea name="field_' . $key . '" rows="5" cols="50">' . $value . '</textarea></td></tr>';
+			
+		} else {
+			return '<tr><td class="name">' . $name . '</td><td class="data">
+			<input type="text" size="60" name="field_' . $key . '" value="' . $value . '" /></td></tr>';
+			
+		}
+	}
+
+	public function metaToForm($metadata) {
+		$this->flattenLanguageField($metadata, 'name');
+		$this->flattenLanguageField($metadata, 'description');
+		return '<form action="edit.php" method="post">' .
+		
+			(array_key_exists('entityid', $metadata) ? 
+				$this->hiddenField('was-entityid', $metadata['entityid']) :
+				'') .
+		
+			'<div id="tabdiv">' .
+			'<ul>' .
+			'<li><a href="#basic">Name and descrition</a></li>' . 
+			'<li><a href="#saml">SAML 2.0</a></li>' . 
+			// '<li><a href="#attributes">Attributes</a></li>' . 
+			// '<li><a href="#orgs">Organizations</a></li>' . 
+			// '<li><a href="#contacts">Contacts</a></li>' . 
+			'</ul>' .
+			'<div id="basic"><table class="formtable">' .
+				$this->standardField($metadata, 'entityid', 'EntityID') .
+				$this->standardField($metadata, 'name', 'Name of service') .
+				$this->standardField($metadata, 'description', 'Description of service', TRUE) .
+				$this->readonlyField($metadata, 'owner', 'Owner') .
+				$this->readonlyDateField($metadata, 'updated', 'Last updated') .
+				$this->readonlyDateField($metadata, 'expire', 'Expire') .
+
+			'</table></div><div id="saml"><table class="formtable">' .
+				$this->standardField($metadata, 'AssertionConsumerService', 'AssertionConsumerService endpoint') .
+				$this->standardField($metadata, 'SingleLogoutService', 'SingleLogoutService endpoint') .
+				// $this->standardField($metadata, 'certFingerprint', 'Certificate Fingerprint') .			
+				
+			'</table></div>' .
+			'</div>' .
+			'<input type="submit" name="submit" value="Save" style="margin-top: 5px" />' .
+		'</form>';
+	}
+	
+}
+
+
diff --git a/modules/metaedit/templates/formedit.php b/modules/metaedit/templates/formedit.php
new file mode 100644
index 000000000..a90a92451
--- /dev/null
+++ b/modules/metaedit/templates/formedit.php
@@ -0,0 +1,21 @@
+<?php
+
+$this->data['jquery'] = array('version' => '1.6', 'core' => TRUE, 'ui' => TRUE, 'css' => TRUE);
+$this->data['head']  = '<link rel="stylesheet" type="text/css" href="/' . $this->data['baseurlpath'] . 'module.php/metaedit/resources/style.css" />' . "\n";
+$this->data['head'] .= '<script type="text/javascript">
+$(document).ready(function() {
+	$("#tabdiv").tabs();
+});
+</script>';
+
+$this->includeAtTemplateBase('includes/header.php');
+
+
+echo('<h1>Metadata Editor</h1>');
+
+echo($this->data['form']);
+
+echo('<p style="float: right"><a href="index.php">Return to entity listing <strong>without saving...</strong></a></p>');
+
+$this->includeAtTemplateBase('includes/footer.php');
+
diff --git a/modules/metaedit/templates/metalist.php b/modules/metaedit/templates/metalist.php
new file mode 100644
index 000000000..af75ad9f8
--- /dev/null
+++ b/modules/metaedit/templates/metalist.php
@@ -0,0 +1,55 @@
+<?php
+
+$this->data['jquery'] = array('version' => '1.6', 'core' => TRUE, 'ui' => TRUE, 'css' => TRUE);
+$this->data['head']  = '<link rel="stylesheet" type="text/css" href="/' . $this->data['baseurlpath'] . 'module.php/metaedit/resources/style.css" />' . "\n";
+// $this->data['head'] .= '<script type="text/javascript">
+// $(document).ready(function() {
+// 	$("#tabdiv").tabs();
+// });
+// </script>';
+
+$this->includeAtTemplateBase('includes/header.php');
+
+
+echo('<h1>Metadata Registry</h1>');
+
+echo('<p>Here you can register new SAML entities. You are successfully logged in as ' . $this->data['userid'] . '</p>');
+
+echo('<h2>Your entries</h2>');
+echo('<table class="metalist" style="width: 100%">');
+$i = 0; $rows = array('odd', 'even');
+foreach($this->data['metadata']['mine'] AS $md ) {
+	$i++; 
+	echo('<tr class="' . $rows[$i % 2] . '">
+		<td>' . $md['name'] . '</td>
+		<td><tt>' . $md['entityid'] . '</tt></td>
+		<td>
+			<a href="edit.php?entityid=' . urlencode($md['entityid']) . '">edit</a>
+			<a href="index.php?delete=' . urlencode($md['entityid']) . '">delete</a>
+		</td></tr>');
+}
+if ($i == 0) {
+	echo('<tr><td colspan="3">No entries registered</td></tr>');
+}
+echo('</table>');
+
+echo('<p><a href="edit.php">Add new entity</a> | <a href="xmlimport.php">Add from SAML 2.0 XML metadata</a></p>');
+
+echo('<h2>Other entries</h2>');
+echo('<table class="metalist" style="width: 100%">');
+$i = 0; $rows = array('odd', 'even');
+foreach($this->data['metadata']['others'] AS $md ) {
+	$i++; 
+	echo('<tr class="' . $rows[$i % 2] . '">
+		<td>' . $md['name'] . '</td>
+		<td><tt>' . $md['entityid'] . '</tt></td>
+		<td>' . (isset($md['owner']) ? $md['owner'] : 'No owner') . '
+		</td></tr>');
+}
+if ($i == 0) {
+	echo('<tr><td colspan="3">No entries registered</td></tr>');
+}
+echo('</table>');
+
+$this->includeAtTemplateBase('includes/footer.php');
+
diff --git a/modules/metaedit/templates/saved.php b/modules/metaedit/templates/saved.php
new file mode 100644
index 000000000..f9b10ba81
--- /dev/null
+++ b/modules/metaedit/templates/saved.php
@@ -0,0 +1,15 @@
+<?php
+
+
+
+$this->includeAtTemplateBase('includes/header.php');
+
+
+echo('<h1>Metadata successfully saved</h1>');
+
+echo('<p><a href="index.php">Go back to metadata registry listing</a></p>');
+
+
+
+$this->includeAtTemplateBase('includes/footer.php');
+
diff --git a/modules/metaedit/templates/xmlimport.tpl.php b/modules/metaedit/templates/xmlimport.tpl.php
new file mode 100644
index 000000000..6ade0113b
--- /dev/null
+++ b/modules/metaedit/templates/xmlimport.tpl.php
@@ -0,0 +1,26 @@
+<?php
+
+// $this->data['jquery'] = array('version' => '1.6', 'core' => TRUE, 'ui' => TRUE, 'css' => TRUE);
+// $this->data['head']  = '<link rel="stylesheet" type="text/css" href="/' . $this->data['baseurlpath'] . 'module.php/metaedit/resources/style.css" />' . "\n";
+// $this->data['head'] .= '<script type="text/javascript">
+// $(document).ready(function() {
+// 	$("#tabdiv").tabs();
+// });
+// </script>';
+
+$this->includeAtTemplateBase('includes/header.php');
+
+
+echo('<h1>Import SAML 2.0 XML Metadata</h1>');
+
+echo('<form method="get" action="edit.php">');
+echo('<p>Paste in SAML 2.0 XML Metadata for the entity that you would like to add.</p>');
+echo('<textarea style="height: 200px; width: 90%; border: 1px solid #aaa;" cols="50" rows="5" name="xmlmetadata"></textarea>');
+echo('<input type="submit" style="margin-top: .5em" name="metasubmit" value="Import metadata" />');
+echo('</form>');
+
+
+echo('<p style="float: right"><a href="index.php">Return to entity listing</a></p>');
+
+$this->includeAtTemplateBase('includes/footer.php');
+
diff --git a/modules/metaedit/www/edit.php b/modules/metaedit/www/edit.php
new file mode 100644
index 000000000..5beb272af
--- /dev/null
+++ b/modules/metaedit/www/edit.php
@@ -0,0 +1,80 @@
+<?php
+
+/* Load simpleSAMLphp, configuration and metadata */
+$config = SimpleSAML_Configuration::getInstance();
+$session = SimpleSAML_Session::getInstance();
+$metaconfig = SimpleSAML_Configuration::getConfig('module_metaedit.php');
+
+$mdh = new SimpleSAML_Metadata_MetaDataStorageHandlerSerialize($metaconfig->getValue('metahandlerConfig', NULL));
+
+$authsource = $metaconfig->getValue('auth', 'login-admin');
+$useridattr = $metaconfig->getValue('useridattr', 'eduPersonPrincipalName');
+
+if ($session->isValid($authsource)) {
+	$attributes = $session->getAttributes();
+	// Check if userid exists
+	if (!isset($attributes[$useridattr])) 
+		throw new Exception('User ID is missing');
+	$userid = $attributes[$useridattr][0];
+} else {
+	SimpleSAML_Auth_Default::initLogin($authsource, SimpleSAML_Utilities::selfURL());
+}
+
+function requireOwnership($metadata, $userid) {
+	if (!isset($metadata['owner']))
+		throw new Exception('Metadata has no owner. Which means no one is granted access, not even you.');
+	if ($metadata['owner'] !== $userid) 
+		throw new Exception('Metadata has an owner that is not equal to your userid, hence you are not granted access.');
+}
+
+
+if (array_key_exists('entityid', $_REQUEST)) {
+	$metadata = $mdh->getMetadata($_REQUEST['entityid'], 'saml20-sp-remote');	
+	requireOwnership($metadata, $userid);
+} elseif(array_key_exists('xmlmetadata', $_REQUEST)) {
+
+	$xmldata = $_REQUEST['xmlmetadata'];
+	SimpleSAML_Utilities::validateXMLDocument($xmldata, 'saml-meta');
+	$entities = SimpleSAML_Metadata_SAMLParser::parseDescriptorsString($xmldata);
+	$entity = array_pop($entities);
+	$metadata =  $entity->getMetadata20SP();
+
+} else {
+	$metadata = array(
+		'owner' => $userid,
+	);
+}
+
+
+$editor = new sspmod_metaedit_MetaEditor();
+
+
+if (isset($_POST['submit'])) {
+	$editor->checkForm($_POST);
+	$metadata = $editor->formToMeta($_POST, array(), array('owner' => $userid));
+	
+	if (isset($_REQUEST['was-entityid']) && $_REQUEST['was-entityid'] !== $metadata['entityid']) {
+		$premetadata = $mdh->getMetadata($_REQUEST['was-entityid'], 'saml20-sp-remote');	
+		requireOwnership($premetadata, $userid);
+		$mdh->deleteMetadata($_REQUEST['was-entityid'], 'saml20-sp-remote');
+	}
+	
+	$testmetadata = NULL;
+	try {
+		$testmetadata = $mdh->getMetadata($metadata['entityid'], 'saml20-sp-remote');
+	} catch(Exception $e) {}
+	if ($testmetadata) requireOwnership($testmetadata, $userid);
+	
+	$mdh->saveMetadata($metadata['entityid'], 'saml20-sp-remote', $metadata);
+	
+	$template = new SimpleSAML_XHTML_Template($config, 'metaedit:saved.php');
+	$template->show();
+	exit;
+}
+
+$form = $editor->metaToForm($metadata);
+
+$template = new SimpleSAML_XHTML_Template($config, 'metaedit:formedit.php');
+$template->data['form'] = $form;
+$template->show();
+
diff --git a/modules/metaedit/www/index.php b/modules/metaedit/www/index.php
new file mode 100644
index 000000000..bd2fb2827
--- /dev/null
+++ b/modules/metaedit/www/index.php
@@ -0,0 +1,54 @@
+<?php
+
+/* Load simpleSAMLphp, configuration and metadata */
+$config = SimpleSAML_Configuration::getInstance();
+$session = SimpleSAML_Session::getInstance();
+$metaconfig = SimpleSAML_Configuration::getConfig('module_metaedit.php');
+
+$mdh = new SimpleSAML_Metadata_MetaDataStorageHandlerSerialize($metaconfig->getValue('metahandlerConfig', NULL));
+
+$authsource = $metaconfig->getValue('auth', 'login-admin');
+$useridattr = $metaconfig->getValue('useridattr', 'eduPersonPrincipalName');
+
+if ($session->isValid($authsource)) {
+	$attributes = $session->getAttributes();
+	// Check if userid exists
+	if (!isset($attributes[$useridattr])) 
+		throw new Exception('User ID is missing');
+	$userid = $attributes[$useridattr][0];
+} else {
+	SimpleSAML_Auth_Default::initLogin($authsource, SimpleSAML_Utilities::selfURL());
+}
+
+function requireOwnership($metadata, $userid) {
+	if (!isset($metadata['owner']))
+		throw new Exception('Metadata has no owner. Which means no one is granted access, not even you.');
+	if ($metadata['owner'] !== $userid) 
+		throw new Exception('Metadata has an owner that is not equal to your userid, hence you are not granted access.');
+}
+
+
+if (isset($_REQUEST['delete'])) {
+	$premetadata = $mdh->getMetadata($_REQUEST['delete'], 'saml20-sp-remote');	
+	requireOwnership($premetadata, $userid);
+	$mdh->deleteMetadata($_REQUEST['delete'], 'saml20-sp-remote');
+}
+
+
+$list = $mdh->getMetadataSet('saml20-sp-remote');
+
+$slist = array('mine' => array(), 'others' => array());
+foreach($list AS $listitem) {
+	if (array_key_exists('owner', $listitem)) {
+		if ($listitem['owner'] === $userid) {
+			$slist['mine'][] = $listitem; continue;
+		}
+	}
+	$slist['others'][] = $listitem;
+}
+
+
+$template = new SimpleSAML_XHTML_Template($config, 'metaedit:metalist.php');
+$template->data['metadata'] = $slist;
+$template->data['userid'] = $userid;
+$template->show();
diff --git a/modules/metaedit/www/resources/style.css b/modules/metaedit/www/resources/style.css
new file mode 100644
index 000000000..1240db065
--- /dev/null
+++ b/modules/metaedit/www/resources/style.css
@@ -0,0 +1,37 @@
+table.formtable {
+	width: 100%;
+}
+table.formtable tr td.name {
+	text-align: right;
+	vertical-align: top;
+	padding-right: .6em;
+}
+table.formtable tr td.value {
+	text-align: left;
+	padding: 0px;
+}
+table.formtable tr td.header {
+	padding-left: 5px;
+	padding-top: 8px;
+	font-weight: bold;
+	font-size: 110%;
+}
+
+table.formtable tr td input,table.formtable tr td textarea {
+	width: 90%;
+	border: 1px solid #bbb;
+	margin: 2px 5px;
+	padding: 2px 4px;
+}
+
+
+table.metalist {
+	border: 1px solid #aaa;
+	border-collapse: collapse;
+}
+table.metalist tr td {
+	padding: 2px 5px; 
+}
+table.metalist tr.even td {
+	background: #e5e5e5;
+}
\ No newline at end of file
diff --git a/modules/metaedit/www/xmlimport.php b/modules/metaedit/www/xmlimport.php
new file mode 100644
index 000000000..12f607211
--- /dev/null
+++ b/modules/metaedit/www/xmlimport.php
@@ -0,0 +1,9 @@
+<?php
+
+
+/* Load simpleSAMLphp, configuration and metadata */
+$config = SimpleSAML_Configuration::getInstance();
+$session = SimpleSAML_Session::getInstance();
+
+$template = new SimpleSAML_XHTML_Template($config, 'metaedit:xmlimport.tpl.php');
+$template->show();
-- 
GitLab