8.1 hermine@THW PHPConnector inspiriert von https://gitlab.com/aeberhardt/stashcat-api-client Beispiel: $hermine = new hermineConnect('mailadresse','Accountpassword','Verschlüsselungskennwort'); if($hermine->login(true) !== false){ $array = $hermine->get_companies_list(); print_r($array); $array = $hermine->get_conversations_list(); print_r($array); $array = $hermine->get_channels_list(); print_r($array); $file = file_get_contents('./ich.jpg'); $hermine->send_message_with_file_to_channel(165562,'jpg per php',$file,'test.jpg','image/jpeg',1080,2068); send_message_to_conversation(id,message); send_message_to_channel(id,message); }else{ //login fehlgeschlagen print_r($hermine->lasterror); } */ class UUID { public static function v3($namespace, $name) { if(!self::is_valid($namespace)) return false; // Get hexadecimal components of namespace $nhex = str_replace(array('-','{','}'), '', $namespace); // Binary Value $nstr = ''; // Convert Namespace UUID to bits for($i = 0; $i < strlen($nhex); $i+=2) { $nstr .= chr(hexdec($nhex[$i].$nhex[$i+1])); } // Calculate hash value $hash = md5($nstr . $name); return sprintf('%08s-%04s-%04x-%04x-%12s', // 32 bits for "time_low" substr($hash, 0, 8), // 16 bits for "time_mid" substr($hash, 8, 4), // 16 bits for "time_hi_and_version", // four most significant bits holds version number 3 (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x3000, // 16 bits, 8 bits for "clk_seq_hi_res", // 8 bits for "clk_seq_low", // two most significant bits holds zero and one for variant DCE1.1 (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000, // 48 bits for "node" substr($hash, 20, 12) ); } public static function v4() { return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', // 32 bits for "time_low" mt_rand(0, 0xffff), mt_rand(0, 0xffff), // 16 bits for "time_mid" mt_rand(0, 0xffff), // 16 bits for "time_hi_and_version", // four most significant bits holds version number 4 mt_rand(0, 0x0fff) | 0x4000, // 16 bits, 8 bits for "clk_seq_hi_res", // 8 bits for "clk_seq_low", // two most significant bits holds zero and one for variant DCE1.1 mt_rand(0, 0x3fff) | 0x8000, // 48 bits for "node" mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) ); } public static function v5($namespace, $name) { if(!self::is_valid($namespace)) return false; // Get hexadecimal components of namespace $nhex = str_replace(array('-','{','}'), '', $namespace); // Binary Value $nstr = ''; // Convert Namespace UUID to bits for($i = 0; $i < strlen($nhex); $i+=2) { $nstr .= chr(hexdec($nhex[$i].$nhex[$i+1])); } // Calculate hash value $hash = sha1($nstr . $name); return sprintf('%08s-%04s-%04x-%04x-%12s', // 32 bits for "time_low" substr($hash, 0, 8), // 16 bits for "time_mid" substr($hash, 8, 4), // 16 bits for "time_hi_and_version", // four most significant bits holds version number 5 (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x5000, // 16 bits, 8 bits for "clk_seq_hi_res", // 8 bits for "clk_seq_low", // two most significant bits holds zero and one for variant DCE1.1 (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000, // 48 bits for "node" substr($hash, 20, 12) ); } public static function is_valid($uuid) { return preg_match('/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?'. '[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', $uuid) === 1; } } class hermineConnect{ private $VERSION = '1.3'; private $hermineServer = 'https://api.thw-messenger.de'; //private $hermineServer = 'https://thw.n-tools.de'; private $connectorID = ""; private $user = ""; private $password = ""; private $passphrase = ""; private $cURL; private $client_key = ""; private $user_id = ""; private $private_key; private $key_cache_channels; private $key_cache_conversations; private $lasterror; private $companies; private $channels; private $conversations; //private $debug = true; function build_data_files($boundary, $fields, $files){ $data = ''; $eol = "\r\n"; $delimiter = '-------------' . $boundary; foreach ($fields as $name => $content) { $data .= "--" . $delimiter . $eol . 'Content-Disposition: form-data; name="' . $name . "\"".$eol.$eol . $content . $eol; } foreach ($files as $name => $content) { $data .= "--" . $delimiter . $eol . 'Content-Disposition: form-data; name="file"; filename="[object Object]"' . $eol . 'Content-Type: application/octet-stream'.$eol //. 'Content-Transfer-Encoding: binary'.$eol ; $data .= $eol; $data .= $content . $eol; } $data .= "--" . $delimiter . "--".$eol; return $data; } function __construct($user,$password,$passphrase) { function intArrayToString($ia){ $ret = ''; foreach($ia as $val){ $ret .= chr($val); } return $ret; } $cID = array_map('hexdec', str_split('deadbeef'.sha1($_SERVER['SERVER_NAME']), 2)); $cID = intArrayToString($cID); $this->connectorID = base64_encode($cID); $this->user = $user; $this->password = $password; $this->passphrase = $passphrase; $this->cURL = curl_init(); } function __destruct() { curl_close($this->cURL); } function request($_url,$_data,$_files=[]){ $_data['device_id'] = $this->connectorID; if($this->client_key != '') $_data['client_key'] = $this->client_key; $boundary = uniqid(); $delimiter = '-------------' . $boundary; $_data = $this->build_data_files($boundary, $_data, $_files); curl_setopt($this->cURL, CURLOPT_URL, $_url); curl_setopt($this->cURL, CURLOPT_VERBOSE, 0); curl_setopt($this->cURL, CURLOPT_SSL_VERIFYPEER, 1); curl_setopt($this->cURL, CURLOPT_RETURNTRANSFER, 1); curl_setopt($this->cURL, CURLOPT_POST, true); curl_setopt($this->cURL, CURLOPT_POSTFIELDS,$_data); curl_setopt($this->cURL, CURLOPT_HTTPHEADER, array("Accept: application/json, text/plain, */*", "Keep-Alive: timeout=5, max=100", "Connection: keep-alive", "Content-Type: multipart/form-data; boundary=" . $delimiter, "Content-Length: " . strlen($_data), 'Expect:' )); curl_setopt( $this->cURL, CURLOPT_COOKIESESSION, true ); curl_setopt( $this->cURL, CURLOPT_COOKIEJAR, './hermineConnectCookie.txt' ); curl_setopt( $this->cURL, CURLOPT_COOKIEFILE, './hermineConnectCookie.txt' ); curl_setopt( $this->cURL, CURLINFO_HEADER_OUT, true); if(isset($this->debug)) echo "\n\nsenddata->:\n".print_r($_data,true); $data = curl_exec($this->cURL); if(!curl_errno($this->cURL)){ if(isset($this->debug)){ echo "\n\nretdata->:\n".print_r($data,true); echo "\n\nSendheader->:\n".print_r(curl_getinfo($this->cURL, CURLINFO_HEADER_OUT),true); } $data = json_decode($data); if($data->status->value === "OK"){ $this->lasterror = ''; return $data->payload; }else{ $this->lasterror = $data; return false; } } else { $this->lasterror = 'Curl error: ' . curl_error($cURL); return false; } } function _open_private_key(){ $data = []; $response = $this->request($this->hermineServer."/security/get_private_key",$data); $private_key = json_decode($response->keys->private_key); $privkey_decoded = openssl_pkey_get_private($private_key->private, $this->passphrase); if($privkey_decoded !== false){ $this->private_key = $privkey_decoded; return true; }else{ return false; } } function _get_conversation_key($_target){ if($_target[0]=='conversation'){ if(!isset($this->key_cache_conversations[$_target[1]])){ $data = [ "conversation_id" => $_target[1] ]; $response = $this->request($this->hermineServer."/message/conversation",$data); $this->key_cache_conversations[$_target[1]] = $response->conversation->key; } if(openssl_private_decrypt(base64_decode($this->key_cache_conversations[$_target[1]]),$ret,$this->private_key,OPENSSL_PKCS1_OAEP_PADDING)) return $ret; else return false; }else if($_target[0]=='channel'){ if(!isset($this->key_cache_channels[$_target[1]])){ $data = [ "channel_id" => $_target[1], "without_members" => true ]; $response = $this->request($this->hermineServer."/channels/info",$data); $this->key_cache_channels[$_target[1]] = $response->channels->key; } if(openssl_private_decrypt(base64_decode($this->key_cache_channels[$_target[1]]),$ret,$this->private_key,OPENSSL_PKCS1_OAEP_PADDING)) return $ret; else return false; } } function _encrypt_aes($_plain, $_key, $_iv){ $ret = openssl_encrypt($_plain, "AES-256-CBC", $_key, 0, $_iv); print_r($ret); return $ret; } public function getLastError(){ return $this->lasterror; } public function getID(){ return $this->connectorID; } public function login($_saveLogin=false){ if($_saveLogin){ if(file_exists('./login.dat')){ $savelogin = json_decode(file_get_contents('./login.dat')); $this->client_key = $savelogin->ClientKey; $this->connectorID = $savelogin->DeviceId; $this->user_id = $savelogin->UserID; if($this->_open_private_key()){ $this->companies = $this->get_companies(); $this->channels = $this->get_channels($this->companies->companies[0]->id); $this->conversations = $this->get_conversations(); return true; } } } $data = [ "email" => $this->user, "password" => $this->password, "app_name" => 'hermine@thw-PHP:'.$this->VERSION, "encrypted" => true, "callable" => false ]; $response = $this->request($this->hermineServer."/auth/login",$data); if($response !== false){ $this->client_key = $response->client_key; $this->user_id = $response->userinfo->id; if($this->_open_private_key()){ $this->companies = $this->get_companies(); $this->channels = $this->get_channels($this->companies->companies[0]->id); $this->conversations = $this->get_conversations(); if($_saveLogin){ $savelogin['ClientKey'] = $this->client_key; $savelogin['DeviceId'] = $this->connectorID; $savelogin['UserID'] = $this->user_id; file_put_contents('./login.dat',json_encode($savelogin)); } return true; } } return false; } function get_companies(){ $data = [ "no_cache" => true ]; $response = $this->request($this->hermineServer."/company/member",$data); if($response !== false){ return $response; }else{ return false; } } function get_conversations($_limit = 99999, $_offset = 0){ $data = [ "limit" => $_limit, "offset" => true, "archive" => 0 ]; $response = $this->request($this->hermineServer."/message/conversations",$data); if($response !== false){ return $response; }else{ return false; } } function get_channels($_company_id){ $data = [ "no_cache" => true, "company" => $_company_id ]; $response = $this->request($this->hermineServer."/channels/subscripted",$data); if($response !== false){ return $response; }else{ return false; } } public function get_companies_list(){ $ret = []; foreach($this->companies->companies as $company){ $ret[$company->id] = $company->name; } return $ret; } public function get_conversations_list(){ $ret = []; foreach($this->conversations->conversations as $conversation){ $ret[$conversation->id] = $conversation->members[0]->last_name; } return $ret; } public function get_channels_list(){ $ret = []; foreach($this->channels->channels as $channels){ $ret[$channels->id] = $channels->name; } return $ret; } function sendmsg($_target,$_message,$_files=[],$_location=false,$_encrypted=true){ $iv = openssl_random_pseudo_bytes(16); $conversation_key = $this->_get_conversation_key($_target); $data = [ "target" => $_target[0], $_target[0]."_id" => $_target[1], "text" => bin2hex(base64_decode($this->_encrypt_aes($_message, $conversation_key, $iv))), "iv" => bin2hex($iv), "files" => json_encode($_files), //Nummern vom Upload! "url" => "[]", "type" => "text", "verification" => "", "encrypted" => true ]; if(!$_encrypted){ $data['encrypted'] = false; $data['text'] = $_message; unset($data[$iv]); } if($_location !== false){ if(!$_encrypted){ } } print_r($data); $response = $this->request($this->hermineServer."/message/send",$data); if($response !== false){ return $response; }else{ return false; } } //function uploadfile($_target,$_filerawdatat,$_filename,$_content_type="application/octet-stream",$_media_size=NULL,$_encrypted=false){ function uploadfile($_target,$_fileraw,$_filename,$_filetype,$_mediawidth=0,$_mediaheight=0){ $iv = openssl_random_pseudo_bytes(16); $file_key = openssl_random_pseudo_bytes(32); //$file = file_get_contents($_filewithpath); $file_uuid = UUID::v4(); $chunk_size = 5 * 1024 * 1024; $filesize = strlen($_fileraw); //$file = new \CURLStringFile($_fileraw, $_filename, $_filetype); $file_encryptet = base64_decode($this->_encrypt_aes($_fileraw, $file_key, $iv)); $file = ['dummy' => $file_encryptet]; $data = [ "resumableChunkNumber" => 0, "resumableChunkSize" => $chunk_size, "resumableCurrentChunkSize" => strlen($file_encryptet), "resumableTotalSize" => $filesize, "resumableType" => $_filetype, "resumableIdentifier" => $file_uuid, "resumableFilename" => $_filename, "resumableRelativePath" => $_filename, "resumableTotalChunks" => 1, "folder" => 0, "media_width" => $_mediawidth, "media_height" => $_mediaheight, "iv" => bin2hex($iv), "type" => $_target[0], "type_id" => $_target[1], "encrypted" => true ]; $response = $this->request($this->hermineServer."/file/upload",$data,$file); if($response !== false){ $iv = openssl_random_pseudo_bytes(16); $conversation_key = $this->_get_conversation_key($_target); $fileid = $response->file->id; $data = [ "file_id" => $fileid, "target" => $_target[0], "target_id" => $_target[1], "iv" => bin2hex($iv), "key" => bin2hex(base64_decode($this->_encrypt_aes($file_key, $conversation_key, $iv))) ]; $response = $this->request($this->hermineServer."/security/set_file_access_key",$data); return $fileid; }else{ return false; } } public function send_message_to_conversation($_conversation_id,$_message){ return $this->sendmsg(array('conversation',$_conversation_id),$_message); } public function send_message_with_file_to_conversation($_conversation_id,$_message,$_fileraw,$_filename,$_filetype,$_mediawidth=0,$_mediaheight=0){ $ret = $this->uploadfile(array('conversation',$_conversation_id),$_fileraw,$_filename,$_filetype,$_mediawidth,$_mediaheight); return $this->sendmsg(array('conversation',$_conversation_id),$_message,[$ret]); } public function send_message_to_channel($_channel_id,$_message){ return $this->sendmsg(array('channel',$_channel_id),$_message); } public function send_message_with_file_to_channel($_channel_id,$_message,$_fileraw,$_filename,$_filetype,$_mediawidth=0,$_mediaheight=0){ $ret = $this->uploadfile(array('channel',$_channel_id),$_fileraw,$_filename,$_filetype,$_mediawidth,$_mediaheight); return $this->sendmsg(array('channel',$_channel_id),$_message,[$ret]); } } ?>