commit 323d538e4244ecc6940154e990220e46e253ee7e Author: Houssem NOUIRA Date: Tue Apr 23 22:56:20 2024 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd7d9df --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..86941e3 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +# SamApiLib +*PHP library to ease requesting SAM via his REST API* + +## installation +Copy the `SamApiLib` folder into your autoload library older. +Then you can simply use it with : + + use SamApiLib\SamApi; + +## usage + use SamApiLib\Archive; + + $sam_api = new SamApi("base_url", "api_token"); + $sam_archive_api = new Archive($sam_api); + $archives = $sam_archive_api->read(); + var_dump($archives); + +## result + array(89) { + [0]=> + array(50) { + ["description"]=> + string(2) "{}" + ["text"]=> + string(39) "BulletinDePaye_123456789012345_2022-03 " + ["archiveId"]=> + string(22) "SAM_rak4z8-1xxw-7v88fo" + ["archiverArchiveId"]=> + string(20) "FR094080_2022_AE0001" + ["originatorArchiveId"]=> + NULL + ["depositorArchiveId"]=> + NULL + ["archiveName"]=> + string(38) "BulletinDePaye_123456789012345_2022-03" + ["filePlanPosition"]=> + NULL + ["originatingDate"]=> + NULL + ["archivalProfileReference"]=> + NULL + ["serviceLevelReference"]=> + string(16) "serviceLevel_002" + ["archivalAgreementReference"]=> + NULL + ["retentionRuleCode"]=> + NULL + ["retentionStartDate"]=> + string(10) "2022-04-19" + ["retentionDuration"]=> + NULL + ["finalDisposition"]=> + NULL + ["disposalDate"]=> + NULL + ["retentionRuleStatus"]=> + NULL + ["accessRuleCode"]=> + NULL + ["accessRuleDuration"]=> + NULL + ["accessRuleStartDate"]=> + NULL + ["accessRuleComDate"]=> + NULL + ["classificationRuleCode"]=> + NULL + ["classificationRuleDuration"]=> + NULL + ["classificationRuleStartDate"]=> + NULL + ["classificationEndDate"]=> + NULL + ["classificationLevel"]=> + NULL + ["classificationOwner"]=> + NULL + ["originatorOrgRegNumber"]=> + string(2) "rh" + ["originatorOwnerOrgId"]=> + string(4) "ACME" + ["originatorOwnerOrgRegNumber"]=> + string(4) "ACME" + ["depositorOrgRegNumber"]=> + NULL + ["archiverOrgRegNumber"]=> + string(3) "GIC" + ["userOrgRegNumbers"]=> + NULL + ["depositDate"]=> + string(25) "2022-04-18T22:54:44+00:00" + ["lastCheckDate"]=> + string(25) "2022-09-29T14:45:07+00:00" + ["lastDeliveryDate"]=> + NULL + ["lastModificationDate"]=> + NULL + ["lifeCycleEvent"]=> + NULL + ["status"]=> + string(9) "preserved" + ["fullTextIndexation"]=> + string(4) "none" + ["descriptionClass"]=> + NULL + ["descriptionObject"]=> + array(0) { + } + ["fileplanLevel"]=> + NULL + ["storagePath"]=> + string(73) "/SAM/ACME/rh/_archivalProfileReference_/2022/04/19/SAM_rak4z8-1xxw-7v88fo" + ["processingStatus"]=> + NULL + ["parentArchiveId"]=> + NULL + ["contents"]=> + NULL + ["digitalResources"]=> + NULL + ["archiveRelationship"]=> + NULL + } + [1]=> + ... \ No newline at end of file diff --git a/SamApiLib/Archive.php b/SamApiLib/Archive.php new file mode 100644 index 0000000..6a6b8d0 --- /dev/null +++ b/SamApiLib/Archive.php @@ -0,0 +1,300 @@ +sam_api = $sam_api; + } + + + /** + * list all archives + * http://sam2_dev.localhost/openapi.html#operation/recordsManagement/archives/read + * @return array[]|mixed[] + */ + public function read () + { + $ws_route = "get /recordsManagement/archives"; + $url_params = []; + $query_params = [ + "archiveId" => "", + "profileReference" => "", + "status" => "", + "archiveName" => "", + "agreementReference" => "", + "archiveExpired" => "", + "finalDisposition" => "", + "originatorOrgRegNumber" => "", + "originatorOwnerOrgId" => "", + "originatorArchiveId" => "", + "originatingDate" => "", + "filePlanPosition" => "", + "hasParent" => "", + "description" => "", + "partialRetentionRule" => "", + "retentionRuleCode" => "", + "depositStartDate" => "", + "depositEndDate" => "", + "originatingStartDate" => "", + "originatingEndDate" => "", + "archiverArchiveId" => "", + "maxResults" => "", + ]; + $body_params = []; + + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + if(!empty($body)) + return $body; + else + null; + } + + + /** + * list archives only inside an organization / folder + * @param string $registrationNumber + * @param string $folderId + * @return array[]|mixed[] + */ + public function readList ($registrationNumber = "", $folderId = "") + { + $ws_route = "get /recordsManagement/archives/List"; + $url_params = []; + $query_params = [ + "originatorOrgRegNumber" => $registrationNumber, // registrationNumber : m183129110301321 + "filePlanPosition" => $folderId, // folderId : SAM_rak426-6xon-f8ywrz + "archiveUnit" => "" + ]; + $body_params = []; + + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + if(!empty($body)) + return $body; + else + null; + } + + + /** + * get a particular archive + * @param $id + * @return array[]|mixed[] + */ + public function getArchiveById ($id) + { + $ws_route = " get /recordsManagement/archiveDescription/{archiveId}"; + $url_params = [ + "archiveId" => $id, + ]; + $query_params = [ + "isCommunication" => "", + ]; + $body_params = []; + + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + if(!empty($body)) + return $body; + else + null; + } + + + /** + * get archive metadata + * @param $archive_id + * @return array[]|mixed[] + */ + public function getMetadata ($archive_id) + { + $ws_route = " get /recordsManagement/archive/Metadata/{archiveId}"; + $url_params = [ + "archiveId" => $archive_id, + ]; + $query_params = []; + $body_params = []; + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + return $body; + } + + + /** + * get a resoure's content base 64 encoded + * @param $archive_id + * @param $resource_id + * @return array[]|mixed[] + */ + public function getResourceContentBase64 ($archive_id, $resource_id) + { + $ws_route = " get /recordsManagement/archive/Consultation/{archiveId}/Digitalresource/{resId}"; + $url_params = [ + "archiveId" => $archive_id, + "resId" => $resource_id, + ]; + $query_params = [ + "isCommunication" => "", + "embedded" => "", + ]; + $body_params = []; + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + return $body; // use ["attachment"]["data"] for direct access to base64 content + } + + + /** + * create an archive + * @param $archive_name + * @param $originatorOrgRegNumber + * @param $file_plan_position + * @return array[]|mixed[] + */ + public function createArchive ($archive_name, $originatorOrgRegNumber, $file_plan_position) + { + $ws_route = " post /recordsManagement/archive"; + $url_params = []; + $query_params = []; + $body_params = [ + "archive" => [ + "archiveName" => $archive_name, + "filePlanPosition" => $file_plan_position, + ], + "zipContainer" => false, + ]; + $body_params = [ + "archive" => [ + "archiveName" => $archive_name, + "originatorOrgRegNumber" => $originatorOrgRegNumber,// organization registration number (registrationNumber) + "filePlanPosition" => $file_plan_position, // folder id (folderId), or null if root of an organization + ], + "zipContainer" => false, + ]; + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + return $body; + } + + /** + * create an archive + * @param $archive_name + * @param $originatorOrgRegNumber + * @param $file_plan_position + * @return array[]|mixed[] + */ + public function createArchiveWithResource ($archiveId, $originatorOwnerOrgRegNumber, $originatorOrgRegNumber, $content, $file_name, $hash, $hashAlgorithm) + { + $ws_route = " post /digitalSafe/digitalSafe/$originatorOwnerOrgRegNumber/$originatorOrgRegNumber"; + $url_params = []; + $query_params = []; + $body_params = [ + "originatorArchiveId" => $archiveId, + "digitalResources" => [ + [ + "handler" => base64_encode($content), + "fileName" => $file_name, + "hash" => $hash, + "hashAlgorithm" => $hashAlgorithm + ] + ] + ]; + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + return $body; + } + + + /** + * add resource content (base 64 encoded) to an archive + * @param $archive_id + * @param $content + * @param $file_name + * @return array[]|mixed[] + */ + public function addResourceBase64ToArchive ($archive_id, $content, $file_name) + { + $ws_route = " post /recordsManagement/archive/{archiveId}/Digitalresource"; + $url_params = [ + "archiveId" => $archive_id, + ]; + $query_params = []; + $body_params = [ + "contents" => $content, + "filename" => $file_name, + ]; + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + return $body; + } + + /** + * add resource content (raw) to an archive + * @param $archive_id + * @param $content + * @param $file_name + * @return array|mixed[] + */ + public function addResourceToArchive ($archive_id, $content, $file_name) + { + return $this->addResourceBase64ToArchive($archive_id, base64_encode($content), $file_name); + } + + + /** + * count all archives + * @return array[]|mixed[] + */ + public function count () + { + $ws_route = "get /recordsManagement/archives/Count"; + $url_params = []; + $query_params = [ + "originatorOrgRegNumber" => "", + "filePlanPosition" => "", + ]; + $body_params = []; + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + return $body; + } + + + /** + * ask for an archive destruction + * @param $originatorOwnerOrgRegNumber + * @param $originatorOrgRegNumber + * @param $archiveId + * @return array[]|mixed[] + */ + public function destruct ($originatorOwnerOrgRegNumber, $originatorOrgRegNumber, $archiveId) + { + $ws_route = " delete /digitalSafe/digitalSafe/{originatorOwnerOrgRegNumber}/{originatorOrgRegNumber}/{archiveId}"; + $url_params = [ + "originatorOwnerOrgRegNumber" => $originatorOwnerOrgRegNumber, + "originatorOrgRegNumber" => $originatorOrgRegNumber, + "archiveId" => $archiveId, + ]; + $query_params = []; + $body_params = []; + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + return $body; + } + + + /** + * ask for an archive integrity check + * @param $archiveId + * @return array[]|mixed[] + */ + public function checkIntegrity ($archiveId) + { + $ws_route = " get /recordsManagement/archives/Integritycheck"; + $url_params = []; + $query_params = [ + "archiveIds" => [$archiveId], + ]; + $body_params = []; + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + return $body; + } + +} diff --git a/SamApiLib/Auth.php b/SamApiLib/Auth.php new file mode 100644 index 0000000..f941668 --- /dev/null +++ b/SamApiLib/Auth.php @@ -0,0 +1,56 @@ +sam_api = $sam_api; + } + + + /** + * get all service accounts + * @return array[]|mixed[] + */ + public function serviceAccountList () + { + $ws_route = "get /auth/serviceAccount/Index"; + $url_params = []; + $query_params = []; + $body_params = []; + + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + if(!empty($body)) + return $body; + else + null; + } + + + /** + * get detail about a service account + * @param $serviceAccountId + * @return array[]|mixed[] + */ + public function serviceAccountDetail ($serviceAccountId) + { + $ws_route = "get /auth/serviceAccount/{serviceAccountId}"; + $url_params = [ + "serviceAccountId" => $serviceAccountId, // "SAM_rak3yb-dih2-lp6sua" + ]; + $query_params = []; + $body_params = []; + + list($headers, $body) = $this->sam_api->query($ws_route, $url_params, $query_params, $body_params); + if(!empty($body)) + return $body; + else + null; + } + +} diff --git a/SamApiLib/HcpApi.php b/SamApiLib/HcpApi.php new file mode 100644 index 0000000..65da8e3 --- /dev/null +++ b/SamApiLib/HcpApi.php @@ -0,0 +1,224 @@ +base_url = $base_url; + $this->auth_token = base64_encode($login) . ":" . md5($password); + } + + public static function create($protocol, $repository_url, $tenant, $namespace, $login, $password) + { + $base_url = "$protocol://$namespace.$tenant.$repository_url"; + return new self($base_url, $login, $password); + } + + + /** + * REST API query helper + * @param $ws_route + * @param $url_params + * @param $query_params + * @param $body_params + * @return array + */ + protected function query($ws_method, $ws_url, $url_params, $query_params, $body_params) + { + $query_string = http_build_query($query_params); + foreach ($url_params as $url_param => $value) + { + $ws_url = str_replace('{'.$url_param.'}', $value, $ws_url); + } + $url = $this->base_url . $ws_url . "?" . $query_string; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_HEADER, true); + + $ws_method = strtoupper($ws_method); + if($ws_method !== "GET") + { + if($ws_method === "POST") + { + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, (json_encode($body_params))); + } + elseif ($ws_method === "PUT") + { + curl_setopt($ch, CURLOPT_PUT, true); + if(file_exists($body_params) && is_readable($body_params)) + { + curl_setopt($ch, CURLOPT_INFILE, fopen($body_params, "r")); + curl_setopt($ch, CURLOPT_INFILESIZE, filesize($body_params)); + } + else + { + die("file open error"); + } + } + elseif ($ws_method === "HEAD") + { + curl_setopt($ch, CURLOPT_NOBODY, true); + } + else + { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $ws_method); + } + } + + curl_setopt($ch, CURLOPT_COOKIE, self::cookie_name . "=" . $this->auth_token); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $res = curl_exec($ch); + if ($res === false) + { + echo "curl error #" . curl_errno($ch) . " : " . curl_error($ch); die; + } + curl_close($ch); + +// var_dump($res); die; + list($headers, $body) = explode("\r\n\r\n", $res); // PHP_EOL.PHP_EOL + $headers = explode("\r\n", $headers); + + // sometimes headers are in the body + if(count($headers) === 1 && !empty($body)) + { + $headers = explode("\r\n", $body); + $body = ""; + } + else + { + $body2 = json_decode($body, true); + if(json_last_error() === JSON_ERROR_NONE) + { // content was really json + $body = $body2; // use decoded content + } + } + + $status_row = array_shift($headers); + preg_match("|HTTP/1.1 (\d+) (.+)|", $status_row, $matches); + $status = $matches[1]; + + $headers2 = []; + foreach ($headers as $header) + { + list($key, $value) = \explode(": ", $header); + $headers2 [$key] = $value; + } + + return [$status, $headers2, $body]; + } + + + public function put_file ($filename, $directory) + { + $basename = basename($filename); + list($status, $headers, $body) = $this->query("put", "/rest/$directory/$basename", [], [], $filename); + + if($status === "201") + { + // check the hash + $keyword = "X-HCP-Hash"; + $algo = ""; + $hash = ""; + $test = array_filter($headers, function($header) use ($keyword, &$algo, &$hash) + { + if(preg_match("|$keyword: (.+) (.+)|", $header, $matches) === 1) + { + list(,$algo, $hash) = $matches; + return true; + } + else + { + return false; + } + }); + if($algo === "SHA-256") + { + $hash_calculated = strtoupper(hash("sha256", file_get_contents($filename))); + if($hash !== $hash_calculated) + { + die("hash mismatch"); + } + } + } + + //TODO handle 409 ... + return $status; + } + + + public function get_file ($filename) + { + list($status, $headers, $body) = $this->query("get", "/rest/$filename", [], [], []); + + //TODO handle 404 ... + if($status == 200) + { + return $body; + } + else + { + return false; + } + } + + + public function delete_file ($filename) + { + list($status, $headers, $body) = $this->query("delete", "/rest/$filename", [], [], []); + + //TODO handle 403 404 ... + return $status; + } + + + public function head_file ($filename) + { + list($status, $headers, $body) = $this->query("head", "/rest/$filename", [], [], []); + + if($status == 200) + { + return $headers; + } + elseif($status == 404) + { + return false; + } + } + + + public function list_files ($directory) + { + list($status, $headers, $body) = $this->query("get", "/rest/$directory", [], [/*"type" => "directory"*/], []); + + //TODO handle 404 ... + if($status == 200) + { +// echo $body . PHP_EOL; + $res = []; + $xml = \simplexml_load_string($body); + foreach ($xml->entry as $entry) + { + $elem = ((array)$entry->attributes())['@attributes']; + $res [$elem["urlName"]] = $elem; + } + + return $res; + } + else + { + return false; + } + } + +} diff --git a/SamApiLib/SamApi.php b/SamApiLib/SamApi.php new file mode 100644 index 0000000..2cf12f6 --- /dev/null +++ b/SamApiLib/SamApi.php @@ -0,0 +1,181 @@ +base_url = $base_url; + $this->api_token = $api_token; + } + + + /** + * REST API query helper + * @param $ws_route + * @param $url_params + * @param $query_params + * @param $body_params + * @return array + */ + public function query($ws_route, $url_params, $query_params, $body_params) + { + list($ws_method, $ws_url) = explode(" ", trim($ws_route)); + + $query_string = http_build_query($query_params); + foreach ($url_params as $url_param => $value) + { + $ws_url = str_replace('{'.$url_param.'}', $value, $ws_url); + } + $url = $this->base_url . $ws_url . "?" . $query_string; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: ".$this::ws_accept, "Content-type: ".$this::ws_content_type]); + curl_setopt($ch, CURLOPT_USERAGENT, $this::ws_user_agent); + + $ws_method = strtoupper($ws_method); + if($ws_method !== "GET") + { + if($ws_method === "POST") + { + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, (json_encode($body_params))); + } + else + { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $ws_method); + } + } + + curl_setopt($ch, CURLOPT_COOKIE, "LAABS-AUTH=".urlencode($this->api_token)); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $res = curl_exec($ch); + if ($res === false) + { + echo "curl error #" . curl_errno($ch) . " : " . curl_error($ch); die; + } + curl_close($ch); + + list($headers, $body) = explode("\r\n\r\n", $res); // PHP_EOL.PHP_EOL + $headers = explode(PHP_EOL, $headers); + $body2 = json_decode($body, true); + if(json_last_error() === JSON_ERROR_NONE) + { // content was really json + $body = $body2; // use decoded content + } + return [$headers, $body]; + } + + + /** + * get the file plan (organization structure) + * @return array + */ + public function getFilePlan () + { + $ws_route = " get /filePlan/filePlan/Tree"; + $url_params = []; + $query_params = []; + $body_params = []; + + list($headers, $body) = $this->query($ws_route, $url_params, $query_params, $body_params); + return $body; + } + + + /** + * get a user by its login + * @param $login + * @return array + */ + public function getUsersByLogin ($login) + { + $ws_route = " get /auth/userAccount/Search"; + $url_params = []; + $query_params = [ + "query" => "accountName = '$login'", + ]; + $body_params = []; + + list($headers, $body) = $this->query($ws_route, $url_params, $query_params, $body_params); + if(count($body) > 0) + return $body[0]; + else + null; + } + + + /** + * get the Lifecycle journal + * @param string $eventType + * @param string $objectClass + * @param string $objectId + * @param string $minDate + * @param string $maxDate + * @param string $org + * @param number $maxResults + * @return array + */ + public function getLifeCycleJournals ($eventType="", $objectClass="", $objectId="", $minDate="", $maxDate="", $org="", $maxResults=50) + { + $ws_route = "get /lifeCycle/event/Search"; + $url_params = []; + $query_params = [ + "eventType" => $eventType, + "objectClass" => $objectClass, + "objectId" => $objectId, + "minDate" => $minDate, + "maxDate" => $maxDate, + "org" => $org, + "maxResults" => $maxResults, + ]; + $body_params = []; + + list($headers, $body) = $this->query($ws_route, $url_params, $query_params, $body_params); + return $body; + } + + + /** + * get the application journal + * @param string $fromDate + * @param string $toDate + * @param string $event + * @param string $accountId + * @param string $status + * @param string $term + * @param number $maxResults + * @return array + */ + public function getApplicationJournals ($fromDate="", $toDate="", $event="", $accountId="", $status="", $term="", $maxResults=50) + { + $ws_route = "get /audit/event/Search"; + $url_params = []; + $query_params = [ + "fromDate" => $fromDate, + "toDate" => $toDate, + "event" => $event, + "accountId" => $accountId, + "status" => $status, + "term" => $term, + "maxResults" => $maxResults, + ]; + $body_params = []; + + list($headers, $body) = $this->query($ws_route, $url_params, $query_params, $body_params); + return $body; + } + +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6b6901d --- /dev/null +++ b/composer.json @@ -0,0 +1,10 @@ +{ + "name" : "archives-factory/sam-api-lib", + "description" : "SAM API library", + "type" : "library", + "autoload" : { + "psr-4" : { + "SamApiLib\\" : "SamApiLib" + } + } +} \ No newline at end of file diff --git a/test_hcp/test_delete.php b/test_hcp/test_delete.php new file mode 100644 index 0000000..2274b46 --- /dev/null +++ b/test_hcp/test_delete.php @@ -0,0 +1,18 @@ +delete_file($filename); diff --git a/test_hcp/test_get.php b/test_hcp/test_get.php new file mode 100644 index 0000000..dfee52c --- /dev/null +++ b/test_hcp/test_get.php @@ -0,0 +1,24 @@ +get_file($filename); +if($res === false) +{ + die("ERROR"); +} + +echo $res; diff --git a/test_hcp/test_head.php b/test_hcp/test_head.php new file mode 100644 index 0000000..6216c8a --- /dev/null +++ b/test_hcp/test_head.php @@ -0,0 +1,24 @@ +head_file($filename); +if($res === false) +{ + die("file does not exist"); +} + +var_dump($res); diff --git a/test_hcp/test_list.php b/test_hcp/test_list.php new file mode 100644 index 0000000..a7f2aa9 --- /dev/null +++ b/test_hcp/test_list.php @@ -0,0 +1,24 @@ +list_files($filename); +if($res === false) +{ + die("ERROR"); +} + +var_dump($res); diff --git a/test_hcp/test_put.php b/test_hcp/test_put.php new file mode 100644 index 0000000..5c148ed --- /dev/null +++ b/test_hcp/test_put.php @@ -0,0 +1,19 @@ +put_file($filename, $directory);