diff options
| author | Rasmus Fuhse <fuhse@data-quest.de> | 2021-12-13 15:46:41 +0000 |
|---|---|---|
| committer | Rasmus Fuhse <fuhse@data-quest.de> | 2021-12-13 15:46:41 +0000 |
| commit | c5419123d53da2656e6cec6d7bc3c77f936b73d8 (patch) | |
| tree | 4d37722352a4427777ea9f2fabf499bb9ed8f6a9 /vendor | |
| parent | 9ccf095bd2c597eef1ffb2c0557b1ca7e1d6b60c (diff) | |
Resolve "StEP00358 Schnittstelle zum OER Portal Niedersachsen"
Diffstat (limited to 'vendor')
| -rw-r--r-- | vendor/edu-sharing-plugin/edu-sharing-auth-helper.php | 101 | ||||
| -rw-r--r-- | vendor/edu-sharing-plugin/edu-sharing-helper-abstract.php | 43 | ||||
| -rw-r--r-- | vendor/edu-sharing-plugin/edu-sharing-helper-base.php | 42 | ||||
| -rw-r--r-- | vendor/edu-sharing-plugin/edu-sharing-helper.php | 47 | ||||
| -rw-r--r-- | vendor/edu-sharing-plugin/edu-sharing-node-helper.php | 175 | ||||
| -rw-r--r-- | vendor/edu-sharing-plugin/example/example-api.php | 59 | ||||
| -rw-r--r-- | vendor/edu-sharing-plugin/example/example.php | 49 | ||||
| -rw-r--r-- | vendor/edu-sharing-plugin/example/index.html | 120 | ||||
| -rw-r--r-- | vendor/edu-sharing-plugin/readme.md | 1 |
9 files changed, 637 insertions, 0 deletions
diff --git a/vendor/edu-sharing-plugin/edu-sharing-auth-helper.php b/vendor/edu-sharing-plugin/edu-sharing-auth-helper.php new file mode 100644 index 0000000..071c0c6 --- /dev/null +++ b/vendor/edu-sharing-plugin/edu-sharing-auth-helper.php @@ -0,0 +1,101 @@ +<?php +require_once "edu-sharing-helper-abstract.php"; + +class EduSharingAuthHelper extends EduSharingHelperAbstract { + + /** + * Gets detailed information about a ticket + * Will throw an exception if the given ticket is not valid anymore + * @param string $ticket + * The ticket, obtained by @getTicketForUser + * @return array + * Detailed information about the current session + * @throws Exception + * Thrown if the ticket is not valid anymore + */ + public function getTicketAuthenticationInfo(string $ticket) { + $curl = curl_init($this->base->baseUrl . '/rest/authentication/v1/validateSession'); + curl_setopt_array($curl, [ + CURLOPT_HTTPHEADER => [ + $this->getRESTAuthenticationHeader($ticket), + 'Accept: application/json', + 'Content-Type: application/json', + ], + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_TIMEOUT => 5 + ]); + $data = json_decode(curl_exec($curl), true); + curl_close($curl); + if ( is_null( $data ) ) { + throw new Exception( 'No answer from repository. Possibly a timeout while trying to connect' ); + } + if($data['statusCode'] !== 'OK') { + throw new Exception('The given ticket is not valid anymore'); + } + return $data; + } + + /** + * Fetches the edu-sharing ticket for a given username + * @param string $username + * The username you want to generate a ticket for + * @return string + * The ticket, which you can use as an authentication header, see @getRESTAuthenticationHeader + * @throws Exception + */ + public function getTicketForUser(string $username, $bodyparams = null) { + if ($bodyparams === null) { + $bodyparams = [ + "primaryAffiliation" => "employee", + "skills" => [ + "string" + ], + "types" => [ + "string" + ], + "extendedAttributes" => [ + 'affiliation' => ["employee"] + ], + "vcard" => "string", + "firstName" => User::findCurrent()->vorname, + "lastName" => User::findCurrent()->nachname, + "email" => User::findCurrent()->email, + "avatar" => "string", + "about" => "string" + ]; + } + $curl = curl_init($this->base->baseUrl . '/rest/authentication/v1/appauth/' . rawurlencode($username)); + curl_setopt_array($curl, [ + CURLOPT_POST => 1, + CURLOPT_FAILONERROR => false, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_HTTPHEADER => $this->getSignatureHeaders($username), + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_TIMEOUT => 5 + ]); + curl_setopt( + $curl, + CURLOPT_POSTFIELDS, + is_array($bodyparams) ? json_encode($bodyparams) : (string) $bodyparams + ); + + $output = curl_exec($curl); + $data = json_decode($output, true); + + $err = curl_errno( $curl ); + $info = curl_getinfo($curl); + curl_close($curl); + if ($err === 0 && $info["http_code"] === 200 && $data['userId'] === $username) { + return $data['ticket']; + } else { + if ( is_null( $data ) ) { + $data = ['error' => $output]; + } + throw new Exception( + 'edu-sharing ticket could not be retrieved: HTTP-Code ' . + $info["http_code"] . ': ' . $data['error'] + ); + } + } +} diff --git a/vendor/edu-sharing-plugin/edu-sharing-helper-abstract.php b/vendor/edu-sharing-plugin/edu-sharing-helper-abstract.php new file mode 100644 index 0000000..d531eec --- /dev/null +++ b/vendor/edu-sharing-plugin/edu-sharing-helper-abstract.php @@ -0,0 +1,43 @@ +<?php + +abstract class EduSharingHelperAbstract { + protected $base; + public function __construct( + EduSharingHelperBase $base + ) { + $this->base = $base; + } + + /** + * Generates the header to use for a given ticket to authenticate with any edu-sharing api endpoint + * @param string $ticket + * The ticket, obtained by @getTicketForUser + * @return string + */ + public function getRESTAuthenticationHeader(string $ticket) { + return 'Authorization: EDU-TICKET ' . $ticket; + } + + + protected function getSignatureHeaders( + string $signString, + $accept = 'application/json', + $contentType = 'application/json' + ) { + $ts = time() * 1000; + $toSign = $this->base->appId . $signString . $ts; + $signature = $this->sign($toSign); + return [ + 'Accept: ' . $accept, + 'Content-Type: ' . $contentType, + 'X-Edu-App-Id: ' . $this->base->appId, + 'X-Edu-App-Signed: ' . $toSign, + 'X-Edu-App-Sig: ' . $signature, + 'X-Edu-App-Ts: ' . $ts, + ]; + } + + protected function sign(string $toSign) { + return $this->base->sign($toSign); + } +}
\ No newline at end of file diff --git a/vendor/edu-sharing-plugin/edu-sharing-helper-base.php b/vendor/edu-sharing-plugin/edu-sharing-helper-base.php new file mode 100644 index 0000000..ac3e2f6 --- /dev/null +++ b/vendor/edu-sharing-plugin/edu-sharing-helper-base.php @@ -0,0 +1,42 @@ +<?php + +class EduSharingHelperBase { + public $baseUrl; + public $privateKey; + public $appId; + public $language = 'de'; + + /** + * @param string $baseUrl + * The base url to your repository in the format "http://<host>/edu-sharing" + * @param string $privateKey + * Your app's private key. This must match the public key registered in the repo + * @param string $appId + * Your app id name (as registered in the edu-sharing repository) + */ + public function __construct( + string $baseUrl, + string $privateKey, + string $appId + ) { + if(!preg_match('/^([a-z]|[A-Z]|[0-9]|[-_])+$/', $appId)) { + throw new Exception('The given app id contains invalid characters or symbols'); + } + $this->baseUrl=$baseUrl; + $this->privateKey=$privateKey; + $this->appId=$appId; + } + + public function setLanguage(string $language) { + $this->language = $language; + } + + function sign(string $toSign) { + $pkeyid = openssl_get_privatekey($this->privateKey); + openssl_sign($toSign, $signature, $pkeyid); + $signature = base64_encode($signature); + openssl_free_key($pkeyid); + return $signature; + } + +}
\ No newline at end of file diff --git a/vendor/edu-sharing-plugin/edu-sharing-helper.php b/vendor/edu-sharing-plugin/edu-sharing-helper.php new file mode 100644 index 0000000..3c9d884 --- /dev/null +++ b/vendor/edu-sharing-plugin/edu-sharing-helper.php @@ -0,0 +1,47 @@ +<?php + +class EduSharingHelper { + /** + * generate a new key pair (private + public) to be registered in the edu-sharing repository + * Store the data somewhere in your application, e.g. database + * use the public key returned to register the application in edu-sharing + * NOTE: This function will fail on windows-based systems! + * @throws Exception + */ + public static function generateKeyPair( + ) + { + $res = openssl_pkey_new(); + if(!$res) { + throw new Exception("No result from openssl_pkey_new. Please check your php installation"); + } + openssl_pkey_export($res, $privatekey); + $publickey = openssl_pkey_get_details($res); + $publickey = $publickey["key"]; + return [ + "privatekey" => $privatekey, + "publickey" => $publickey + ]; + } + + /** + * Generates an edu-sharing compatible xml file for registering the application + * This is a very basic function and is only intended for demonstration or manual use. Data is not escaped! + */ + public static function generateEduAppXMLData(string $appId, string $publickey, string $type = 'LMS', string $publicIP = '*') { + return '<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> + <properties> + <entry key="appid">' . $appId . '</entry> + <entry key="public_key">' . $publickey . '</entry> + <entry key="type">' . $type . '</entry> + <entry key="domain"></entry> + <!-- in case of wildcard host: Replace this, if possible, with the public ip from your service --> + <entry key ="host">' . $publicIP . '</entry> + <!-- must be true --> + <entry key="trustedclient">true</entry> + </properties> + '; + } + +}
\ No newline at end of file diff --git a/vendor/edu-sharing-plugin/edu-sharing-node-helper.php b/vendor/edu-sharing-plugin/edu-sharing-node-helper.php new file mode 100644 index 0000000..112d7d8 --- /dev/null +++ b/vendor/edu-sharing-plugin/edu-sharing-node-helper.php @@ -0,0 +1,175 @@ +<?php +require_once "edu-sharing-helper-abstract.php"; + +class DisplayMode { + const Inline = 'inline'; + const Embed = 'embed'; + const Dynamic = 'dynamic'; +} +class Usage { + public $nodeId; + public $nodeVersion; + public $containerId; + public $resourceId; + public $usageId; + + public function __construct($nodeId, $nodeVersion, $containerId, $resourceId, $usageId) + { + $this->nodeId = $nodeId; + $this->nodeVersion = $nodeVersion; + $this->containerId = $containerId; + $this->resourceId = $resourceId; + $this->usageId = $usageId; + } + +} +class EduSharingNodeHelper extends EduSharingHelperAbstract { + /** + * creates a usage for a given node + * The given usage can later be used to fetch this node REGARDLESS of the actual user + * The usage gives permanent access to this node and acts similar to a license + * In order to be able to create an usage for a node, the current user (provided via the ticket) + * MUST have CC_PUBLISH permissions on the given node id + * @param string $ticket + * A ticket with the user session who is creating this usage + * @param string $containerId + * A unique page / course id this usage refers to inside your system (e.g. a database id of the page you include the usage) + * @param string $resourceId + * The individual resource id on the current page or course this object refers to + * (you may enumerate or use unique UUID's) + * @param string $nodeId + * The edu-sharing node id the usage shall be created for + * @param string|null $nodeVersion + * Optional: The fixed version this usage should refer to + * If you leave it empty, the usage will always refer to the latest version of the node + * @return Usage + * An usage element you can use with @getNodeByUsage + * Keep all data of this object stored inside your system! + */ + public function createUsage( + string $ticket, + string $containerId, + string $resourceId, + string $nodeId, + string $nodeVersion = null + ) { + $curl = curl_init($this->base->baseUrl . '/rest/usage/v1/usages/repository/-home-'); + $headers = $this->getSignatureHeaders($ticket); + $headers[] = $this->getRESTAuthenticationHeader($ticket); + curl_setopt_array($curl, [ + CURLOPT_FAILONERROR => false, + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode([ + 'appId' => $this->base->appId, + 'courseId' => $containerId, + 'resourceId' => $resourceId, + 'nodeId' => $nodeId, + 'nodeVersion' => $nodeVersion, + ]), + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_HTTPHEADER => $headers + ]); + $data = json_decode(curl_exec($curl), true); + $err = curl_errno( $curl ); + $info = curl_getinfo($curl); + curl_close($curl); + if ($err === 0 && $info["http_code"] === 200) { + return new Usage( + $data['parentNodeId'], + $nodeVersion, + $containerId, + $resourceId, + $data['nodeId'] + ); + } else { + throw new Exception('creating usage failed ' . + $info["http_code"] . ': ' . $data['error'] . ' ' . $data['message']); + } + + } + + /** + * Loads the edu-sharing node refered by a given usage + * @param Usage $usage + * The usage, as previously returned by @createUsage + * @param string $displayMode + * The displayMode + * This will ONLY change the content representation inside the "detailsSnippet" return value + * @param array $renderingParams + * @return mixed + * Returns an object containing a "detailsSnippet" repesentation + * as well as the full node as provided by the REST API + * Please refer to the edu-sharing REST documentation for more details + * @throws Exception + */ + public function getNodeByUsage( + Usage $usage, + $displayMode = DisplayMode::Inline, + array $renderingParams = null + ) + { + $url = $this->base->baseUrl . '/rest/rendering/v1/details/-home-/' . rawurlencode($usage->nodeId); + $url .= '?displayMode=' . rawurlencode($displayMode); + if($usage->nodeVersion) { + $url .= '&version=' . rawurlencode($usage->nodeVersion); + } + $curl = curl_init($url); + + $headers = $this->getSignatureHeaders($usage->usageId); + $headers[] = 'X-Edu-Usage-Node-Id: ' . $usage->nodeId; + $headers[] = 'X-Edu-Usage-Course-Id: ' . $usage->containerId; + $headers[] = 'X-Edu-Usage-Resource-Id: ' . $usage->resourceId; + + curl_setopt_array($curl, [ + CURLOPT_FAILONERROR => false, + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($renderingParams), + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_HTTPHEADER => $headers + ]); + $data = json_decode(curl_exec($curl), true); + $err = curl_errno( $curl ); + $info = curl_getinfo($curl); + if ($err === 0 && $info["http_code"] === 200) { + return $data; + } else { + throw new Exception('fetching node by usage failed ' . + $info["http_code"] . ': ' . $data['error'] . ' ' . $data['message']); + } + } + + /** + * Deletes the given usage + * We trust that you've validated if the current user in your context is allowed to do so + * There is no restriction in deleting usages even from foreign users, as long as they were generated by your app + * Thus, this endpoint does not require any user ticket + * @param string $nodeId + * The edu-sharing node id this usage belongs to + * @param string $usageId + * The usage id + */ + public function deleteUsage( + string $nodeId, + string $usageId + ) { + $curl = curl_init($this->base->baseUrl . '/rest/usage/v1/usages/node/' . rawurlencode($nodeId) . '/' . rawurlencode($usageId)); + $headers = $this->getSignatureHeaders($nodeId.$usageId); + curl_setopt_array($curl, [ + CURLOPT_FAILONERROR => false, + CURLOPT_CUSTOMREQUEST => 'DELETE', + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_HTTPHEADER => $headers + ]); + $data = json_decode(curl_exec($curl), true); + $err = curl_errno( $curl ); + $info = curl_getinfo($curl); + curl_close($curl); + if ($err === 0 && $info["http_code"] === 200) { + + } else { + throw new Exception('deleting usage failed ' . + $info["http_code"] . ': ' . $data['error'] . ' ' . $data['message']); + } + + } +}
\ No newline at end of file diff --git a/vendor/edu-sharing-plugin/example/example-api.php b/vendor/edu-sharing-plugin/example/example-api.php new file mode 100644 index 0000000..a961a7d --- /dev/null +++ b/vendor/edu-sharing-plugin/example/example-api.php @@ -0,0 +1,59 @@ +<?php +define('APP_ID', 'data-quest Test'); +define('BASE_URL', 'http://localhost:8080/edu-sharing'); +define('USERNAME', 'root@studip'); + + +header('Accept: application/json'); +header('Content-Type: application/json'); + +require_once "../edu-sharing-helper.php"; +require_once "../edu-sharing-helper-base.php"; +require_once "../edu-sharing-auth-helper.php"; +require_once "../edu-sharing-node-helper.php"; + +$privatekey = @file_get_contents('private.key'); +if(!$privatekey) { + die('no private key'); +} else { + $key["privatekey"] = $privatekey; +} +// init the base class instance we use for all helpers +$base = new EduSharingHelperBase(BASE_URL, $key["privatekey"], APP_ID); +$postData = json_decode(file_get_contents('php://input')); +$action = $postData->action; +$result = null; +if ($action === 'BASE_URL') { + $result = BASE_URL; +} else if ($action === 'GET_NODE') { + $nodeHelper = new EduSharingNodeHelper($base); + $result = $nodeHelper->getNodeByUsage( + new Usage( + $postData->nodeId, + $postData->nodeVersion, + $postData->containerId, + $postData->resourceId, + $postData->usageId + ) + ); +} else if ($action === 'CREATE_USAGE') { + $nodeHelper = new EduSharingNodeHelper($base); + $result = $nodeHelper->createUsage( + $postData->ticket, + $postData->containerId, + $postData->resourceId, + $postData->nodeId + ); +} else if ($action === 'DELETE_USAGE') { + $nodeHelper = new EduSharingNodeHelper($base); + $nodeHelper->deleteUsage( + $postData->nodeId, + $postData->usageId + ); +} else if ($action === 'TICKET') { + $authHelper = new EduSharingAuthHelper($base); + $ticket = $authHelper->getTicketForUser(USERNAME); + $result = $ticket; +} + +echo json_encode($result); diff --git a/vendor/edu-sharing-plugin/example/example.php b/vendor/edu-sharing-plugin/example/example.php new file mode 100644 index 0000000..e959d59 --- /dev/null +++ b/vendor/edu-sharing-plugin/example/example.php @@ -0,0 +1,49 @@ +<?php +/** + * This is a sample file on how to use the edu-sharing remote library + * Run this script for the first time to create a private/public keypair + * On first run, a properties.xml file will be created + * Upload this file to your target edu-sharing (Admin-Tools -> Remote Systems -> Choose XML-File) + */ + +define('APP_ID', 'sample-app'); +define('USERNAME', 'tester'); +require_once "../edu-sharing-helper.php"; +require_once "../edu-sharing-helper-base.php"; +require_once "../edu-sharing-auth-helper.php"; +require_once "../edu-sharing-node-helper.php"; + +$privatekey = @file_get_contents('private.key'); +if(!$privatekey) { + $key = EduSharingHelper::generateKeyPair(); + // store the $key data inside your application, e.g. your database or plugin config + file_put_contents(APP_ID . '.properties.xml', EduSharingHelper::generateEduAppXMLData(APP_ID, $key['publickey'])); + file_put_contents('private.key', $key['privatekey']); + die('Wrote ' . APP_ID . '.properties.xml file. Upload it to edu-sharing, then run this script again'); +} else { + $key["privatekey"] = $privatekey; +} +if(count($argv) < 2) { + die('This script should be called as follow: "example.php http://localhost:8080/edu-sharing [<node-id>]"'); +} +// init the base class instance we use for all helpers +$base = new EduSharingHelperBase($argv[1], $key["privatekey"], APP_ID); +$base->setLanguage('de'); + +// authenticating (getting a ticket) and validating the given ticket +$authHelper = new EduSharingAuthHelper($base); +$ticket = $authHelper->getTicketForUser(USERNAME); +echo "Ticket validation result:\n"; +print_r($authHelper->getTicketAuthenticationInfo($ticket)); + +if(count($argv) !== 3) { + die("No node id given. Add a 3rd parameter to test create + fetching of nodes by usage"); +} +$nodeHelper = new EduSharingNodeHelper($base); +$usage = $nodeHelper->createUsage($ticket, '1', '1', $argv[2]); +echo "Usage create result:\n"; +print_r($usage); + +$node = $nodeHelper->getNodeByUsage($usage); +echo "Get node by usage:\n"; +print_r($node["node"]["name"]);
\ No newline at end of file diff --git a/vendor/edu-sharing-plugin/example/index.html b/vendor/edu-sharing-plugin/example/index.html new file mode 100644 index 0000000..9be18ce --- /dev/null +++ b/vendor/edu-sharing-plugin/example/index.html @@ -0,0 +1,120 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Edu Sharing Usage Example</title> + <style> + body > div { + padding: 20px 10px; + } + pre { + border: 1px solid #eee; + padding: 10px; + margin: 10px; + } + </style> + <script> + let ticket; + let baseUrl = null; + let esWindow = null; + function openEduSharing() { + esWindow = window.open( + baseUrl + '/components/search?ticket=' + encodeURIComponent(ticket) + + '&reurl=IFRAME' + ); + } + window.addEventListener('message', receiveMessage, false); + async function receiveMessage(event){ + if(event.data.event === 'APPLY_NODE'){ // Event Name hier festlegen + esWindow.close(); + console.log(event.data.data); + usage = await createUsage(event.data.data.nodeId); + // in a real application, the usage is stored in your backend system + localStorage.setItem('usage', JSON.stringify(usage)); + await renderUsage(); + + } + } + async function renderUsage() { + if(localStorage.getItem('usage')) { + const usage = JSON.parse(localStorage.getItem('usage')); + document.querySelector('#usage').style.display = null; + document.querySelector('#delete-usage').style.display = null; + document.querySelector('#usage').innerHTML = JSON.stringify(usage, null, 4); + usage.action = 'GET_NODE'; + const render = await fetchAPI(usage) + document.querySelector('#render').innerHTML = render.detailsSnippet; + } + + } + async function createUsage(nodeId) { + return await fetchAPI({ + action: 'CREATE_USAGE', + ticket, + nodeId, + containerId: 'my_sample_page_1', + resourceId: Math.random() + }); + } + async function deleteUsage(nodeId) { + const usage = JSON.parse(localStorage.getItem('usage')); + await fetchAPI({ + action: 'DELETE_USAGE', + nodeId: usage.nodeId, + usageId: usage.usageId + }); + localStorage.removeItem('usage'); + document.querySelector('#render').style.display = 'none'; + document.querySelector('#usage').style.display = 'none'; + document.querySelector('#delete-usage').style.display = 'none'; + } + async function fetchAPI(data) { + return new Promise((resolve, reject) => { + var xhr = new XMLHttpRequest(); + xhr.open("POST", "example-api.php", true); + xhr.onload = () => { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + resolve(JSON.parse(xhr.response)); + } else { + alert(xhr.statusText); + reject(xhr.statusText); + } + } + }; + xhr.onerror = function (e) { + alert(xhr.statusText); + }; + xhr.send(JSON.stringify(data)); + }); + } + async function getTicket() { + ticket = await fetchAPI({action: 'TICKET'}); + document.querySelector('#ticket').innerText = ticket; + document.querySelector('#edu-select').style.display = null; + + } + window.addEventListener('load', async () => { + baseUrl = await fetchAPI({action: 'BASE_URL'}); + await renderUsage(); + + await getTicket(); + }); + </script> +</head> +<body> +<div> + <button onclick="getTicket()">Re-Fetch ticket</button> + <span id="ticket">No ticket</span> +</div> +<div id="edu-select" style="display:none;"> + <button onclick="openEduSharing()">Open edu-sharing & select node</button> +</div> +<pre id="usage" style="display: none"></pre> +<div id="delete-usage" style="display: none"> + <button onclick="deleteUsage()">Delete current Usage</button> +</div> +<div id="render"></div> + +</body> +</html>
\ No newline at end of file diff --git a/vendor/edu-sharing-plugin/readme.md b/vendor/edu-sharing-plugin/readme.md new file mode 100644 index 0000000..ab1f332 --- /dev/null +++ b/vendor/edu-sharing-plugin/readme.md @@ -0,0 +1 @@ +This library is a changed version of https://github.com/edu-sharing/php-auth-plugin |
