Subversion Repositories oidplus

Rev

Rev 503 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. if (!defined('INSIDE_OIDPLUS')) die();
  21.  
  22. class OIDplusPagePublicAttachments extends OIDplusPagePluginPublic {
  23.  
  24.         public static function getUploadDir($id) {
  25.                 $path = realpath(OIDplus::localpath() . 'userdata/attachments/');
  26.  
  27.                 $obj = OIDplusObject::parse($id);
  28.                 if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id));
  29.                 if ($obj::ns() == 'oid') {
  30.                         $oid = $obj->nodeId(false);
  31.                 } else {
  32.                         $oid = null;
  33.                         $alt_ids = $obj->getAltIds();
  34.                         foreach ($alt_ids as $alt_id) {
  35.                                 if ($alt_id->getNamespace() == 'oid') {
  36.                                         $oid = $alt_id->getId();
  37.                                         break; // we prefer the first OID (for GUIDs, the first OID is the OIDplus-OID, and the second OID is the UUID OID)
  38.                                 }
  39.                         }
  40.                 }
  41.  
  42.                 if (!is_null($oid) && ($oid != '')) {
  43.                         // For OIDs, it is the OID, for other identifiers
  44.                         // it it the OID alt ID (generated using the SystemID)
  45.                         $path .= DIRECTORY_SEPARATOR . str_replace('.', '_', $oid);
  46.                 } else {
  47.                         // Can happen if you don't have a system ID (due to missing OpenSSL plugin)
  48.                         $path .= DIRECTORY_SEPARATOR . md5($obj->nodeId(true)); // we don't use $id, because $obj->nodeId(true) is possibly more canonical than $id
  49.                 }
  50.                
  51.                 return $path;
  52.         }
  53.  
  54.         private function raMayDelete() {
  55.                 return OIDplus::config()->getValue('attachments_allow_ra_delete', 0);
  56.         }
  57.  
  58.         private function raMayUpload() {
  59.                 return OIDplus::config()->getValue('attachments_allow_ra_upload', 0);
  60.         }
  61.  
  62.         public function action($actionID, $params) {
  63.  
  64.                 if ($actionID == 'deleteAttachment') {
  65.                         $id = $params['id'];
  66.                         $obj = OIDplusObject::parse($id);
  67.                         if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id));
  68.                         if (!$obj->userHasWriteRights()) throw new OIDplusException(_L('Authentication error. Please log in as admin, or as the RA of "%1" to upload an attachment.',$id));
  69.  
  70.                         if (!OIDplus::authUtils()::isAdminLoggedIn() && !$this->raMayDelete()) {
  71.                                 throw new OIDplusException(_L('The administrator has disabled deleting attachments by RAs.'));
  72.                         }
  73.  
  74.                         $req_filename = $params['filename'];
  75.                         if (strpos($req_filename, '/') !== false) throw new OIDplusException(_L('Illegal file name'));
  76.                         if (strpos($req_filename, '\\') !== false) throw new OIDplusException(_L('Illegal file name'));
  77.                         if (strpos($req_filename, '..') !== false) throw new OIDplusException(_L('Illegal file name'));
  78.                         if (strpos($req_filename, chr(0)) !== false) throw new OIDplusException(_L('Illegal file name'));
  79.  
  80.                         $uploaddir = self::getUploadDir($id);
  81.                         $uploadfile = $uploaddir . DIRECTORY_SEPARATOR . basename($req_filename);
  82.  
  83.                         if (!file_exists($uploadfile)) throw new OIDplusException(_L('File does not exist'));
  84.                         @unlink($uploadfile);
  85.                         if (file_exists($uploadfile)) {
  86.                                 OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Attachment file '".basename($uploadfile)."' could not be deleted from object '$id' (problem with permissions?)");
  87.                                 $msg = _L('Attachment file "%1" could not be deleted from object "%2" (problem with permissions?)',basename($uploadfile),$id);
  88.                                 if (OIDplus::authUtils()::isAdminLoggedIn()) {
  89.                                         throw new OIDplusException($msg);
  90.                                 } else {
  91.                                         throw new OIDplusException($msg.'. '._L('Please contact the system administrator.'));
  92.                                 }
  93.                         } else {
  94.                                 // If it was the last file, delete the empty directory
  95.                                 $ary = glob($uploaddir . DIRECTORY_SEPARATOR . '*');
  96.                                 if (count($ary) == 0) @rmdir($uploaddir);
  97.                         }
  98.  
  99.                         OIDplus::logger()->log("[OK]OID($id)+[?INFO/!OK]OIDRA($id)?/[?INFO/!OK]A?", "Deleted attachment '".basename($uploadfile)."' from object '$id'");
  100.  
  101.                         return array("status" => 0);
  102.  
  103.                 } else if ($actionID == 'uploadAttachment') {
  104.  
  105.                         $id = $params['id'];
  106.                         $obj = OIDplusObject::parse($id);
  107.                         if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id));
  108.                         if (!$obj->userHasWriteRights()) throw new OIDplusException(_L('Authentication error. Please log in as admin, or as the RA of "%1" to upload an attachment.',$id));
  109.  
  110.                         if (!OIDplus::authUtils()::isAdminLoggedIn() && !$this->raMayUpload()) {
  111.                                 throw new OIDplusException(_L('The administrator has disabled uploading attachments by RAs.'));
  112.                         }
  113.  
  114.                         if (!isset($_FILES['userfile'])) {
  115.                                 throw new OIDplusException(_L('Please choose a file.'));
  116.                         }
  117.  
  118.                         if (!OIDplus::authUtils()::isAdminLoggedIn()) {
  119.                                 $banned = explode(',', OIDplus::config()->getValue('attachments_block_extensions', ''));
  120.                                 foreach ($banned as $ext) {
  121.                                         $ext = trim($ext);
  122.                                         if ($ext == '') continue;
  123.                                         if (strtolower(substr(basename($_FILES['userfile']['name']), -strlen($ext)-1)) == strtolower('.'.$ext)) {
  124.                                                 throw new OIDplusException(_L('The file extension "%1" is banned by the administrator (it can be uploaded by the administrator though)',$ext));
  125.                                         }
  126.                                 }
  127.                         }
  128.  
  129.                         $req_filename = $_FILES['userfile']['name'];
  130.                         if (strpos($req_filename, '/') !== false) throw new OIDplusException(_L('Illegal file name'));
  131.                         if (strpos($req_filename, '\\') !== false) throw new OIDplusException(_L('Illegal file name'));
  132.                         if (strpos($req_filename, '..') !== false) throw new OIDplusException(_L('Illegal file name'));
  133.                         if (strpos($req_filename, chr(0)) !== false) throw new OIDplusException(_L('Illegal file name'));
  134.  
  135.                         $uploaddir = self::getUploadDir($id);
  136.                         $uploadfile = $uploaddir . DIRECTORY_SEPARATOR . basename($req_filename);
  137.  
  138.                         if (!is_dir($uploaddir)) {
  139.                                 @mkdir($uploaddir, 0777, true);
  140.                                 if (!is_dir($uploaddir)) {
  141.                                         OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Upload attachment '".basename($uploadfile)."' to object '$id' failed: Cannot create directory '".basename($uploaddir)."' (problem with permissions?)");
  142.                                         $msg = _L('Upload attachment "%1" to object "%2" failed',basename($uploadfile),$id).': '._L('Cannot create directory "%1" (problem with permissions?)',basename($uploaddir));
  143.                                         if (OIDplus::authUtils()::isAdminLoggedIn()) {
  144.                                                 throw new OIDplusException($msg);
  145.                                         } else {
  146.                                                 throw new OIDplusException($msg.'. '._L('Please contact the system administrator.'));
  147.                                         }
  148.                                 }
  149.                         }
  150.  
  151.                         if (!@move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
  152.                                 OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Upload attachment '".basename($uploadfile)."' to object '$id' failed: Cannot move uploaded file into directory (problem with permissions?)");
  153.                                 $msg = _L('Upload attachment "%1" to object "%2" failed',basename($uploadfile),$id).': '._L('Cannot move uploaded file into directory (problem with permissions?)');
  154.                                 if (OIDplus::authUtils()::isAdminLoggedIn()) {
  155.                                         throw new OIDplusException($msg);
  156.                                 } else {
  157.                                         throw new OIDplusException($msg.'. '._L('Please contact the system administrator.'));
  158.                                 }
  159.                         }
  160.  
  161.                         OIDplus::logger()->log("[OK]OID($id)+[?INFO/!OK]OIDRA($id)?/[?INFO/!OK]A?", "Uploaded attachment '".basename($uploadfile)."' to object '$id'");
  162.  
  163.                         return array("status" => 0);
  164.                 } else {
  165.                         throw new OIDplusException(_L('Unknown action ID'));
  166.                 }
  167.         }
  168.  
  169.         public function init($html=true) {
  170.                 OIDplus::config()->prepareConfigKey('attachments_block_extensions', 'Block file name extensions being used in file attachments (comma separated)', 'exe,scr,pif,bat,com,vbs,cmd', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  171.                 });
  172.                 OIDplus::config()->prepareConfigKey('attachments_allow_ra_delete', 'Allow that RAs delete file attachments? (0=no, 1=yes)', '0', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  173.                         if (!is_numeric($value) || ($value < 0) || ($value > 1)) {
  174.                                 throw new OIDplusException(_L('Please enter a valid value (0=no, 1=yes).'));
  175.                         }
  176.                 });
  177.                 OIDplus::config()->prepareConfigKey('attachments_allow_ra_upload', 'Allow that RAs upload file attachments? (0=no, 1=yes)', '0', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  178.                         if (!is_numeric($value) || ($value < 0) || ($value > 1)) {
  179.                                 throw new OIDplusException(_L('Please enter a valid value (0=no, 1=yes).'));
  180.                         }
  181.                 });
  182.         }
  183.  
  184.         public function gui($id, &$out, &$handled) {
  185.                 // Nothing
  186.         }
  187.  
  188.         public function publicSitemap(&$out) {
  189.                 // Nothing
  190.         }
  191.  
  192.         public function tree(&$json, $ra_email=null, $nonjs=false, $req_goto='') {
  193.                 return false;
  194.         }
  195.  
  196.         private static function convert_filesize($bytes, $decimals = 2){
  197.                 $size = array(_L('Bytes'),_L('KiB'),_L('MiB'),_L('GiB'),_L('TiB'),_L('PiB'),_L('EiB'),_L('ZiB'),_L('YiB'));
  198.                 $factor = floor((strlen($bytes) - 1) / 3);
  199.                 return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor];
  200.         }
  201.  
  202.         public function implementsFeature($id) {
  203.                 if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.2') return true; // modifyContent
  204.                 if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.3') return true; // beforeObject*, afterObject*
  205.                 if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.4') return true; // whois*Attributes
  206.                 return false;
  207.         }
  208.  
  209.         public function modifyContent($id, &$title, &$icon, &$text) {
  210.                 // Interface 1.3.6.1.4.1.37476.2.5.2.3.2
  211.  
  212.                 $files = glob(self::getUploadDir($id) . DIRECTORY_SEPARATOR . '*');
  213.                 $doshow = false;
  214.                 $output = '';
  215.                 $found_files = false;
  216.  
  217.                 $obj = OIDplusObject::parse($id);
  218.                 if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id));
  219.                 $can_upload = OIDplus::authUtils()::isAdminLoggedIn() || ($this->raMayUpload() && $obj->userHasWriteRights());
  220.                 $can_delete = OIDplus::authUtils()::isAdminLoggedIn() || ($this->raMayDelete() && $obj->userHasWriteRights());
  221.  
  222.                 $output .= '<h2>'._L('File attachments').'</h2>';
  223.                 $output .= '<div class="container box">';
  224.  
  225.                 if (OIDplus::authUtils()::isAdminLoggedIn()) {
  226.                         $output .= '<p>'._L('Admin info: The directory is %1','<b>'.htmlentities(self::getUploadDir($id)).'</b>').'</p>';
  227.                         $doshow = true;
  228.                 }
  229.  
  230.                 $output .= '<div id="fileattachments_table" class="table-responsive">';
  231.                 $output .= '<table class="table table-bordered table-striped">';
  232.                 $output .= '<tr>';
  233.                 $output .= '<th>'._L('Filename').'</th>';
  234.                 $output .= '<th>'._L('Size').'</th>';
  235.                 $output .= '<th>'._L('File type').'</th>';
  236.                 $output .= '<th>'._L('Download').'</th>';
  237.                 if ($can_delete) $output .= '<th>'._L('Delete').'</th>';
  238.                 $output .= '</tr>';
  239.                 foreach ($files as $file) {
  240.                         if (is_dir($file)) continue;
  241.  
  242.                         $output .= '<tr>';
  243.                         $output .= '<td>'.htmlentities(basename($file)).'</td>';
  244.                         $output .= '<td>'.htmlentities(self::convert_filesize(filesize($file), 0)).'</td>';
  245.                         $lookup_files = array(
  246.                                 OIDplus::localpath().'userdata/attachments/filetypes$'.OIDplus::getCurrentLang().'.conf',
  247.                                 OIDplus::localpath().'userdata/attachments/filetypes.conf',
  248.                                 OIDplus::localpath().'3p/vts_fileformats/filetypes$'.OIDplus::getCurrentLang().'.local', // not recommended
  249.                                 OIDplus::localpath().'3p/vts_fileformats/filetypes.local', // not recommended
  250.                                 OIDplus::localpath().'3p/vts_fileformats/filetypes$'.OIDplus::getCurrentLang().'.conf',
  251.                                 OIDplus::localpath().'3p/vts_fileformats/filetypes.conf'
  252.                         );
  253.                         $output .= '<td>'.htmlentities(VtsFileTypeDetect::getDescription($file, $lookup_files)).'</td>';
  254.  
  255.                         $output .= '     <td><button type="button" name="download_'.md5($file).'" id="download_'.md5($file).'" class="btn btn-success btn-xs download" onclick="downloadAttachment('.js_escape(OIDplus::webpath(__DIR__)).', current_node,'.js_escape(basename($file)).')">'._L('Download').'</button></td>';
  256.                         if ($can_delete) {
  257.                                 $output .= '     <td><button type="button" name="delete_'.md5($file).'" id="delete_'.md5($file).'" class="btn btn-danger btn-xs delete" onclick="deleteAttachment(current_node,'.js_escape(basename($file)).')">'._L('Delete').'</button></td>';
  258.                         }
  259.  
  260.                         $output .= '</tr>';
  261.                         $doshow = true;
  262.                         $found_files = true;
  263.                 }
  264.  
  265.                 if (!$found_files) $output .= '<tr><td colspan="'.($can_delete ? 5 : 4).'"><i>'._L('No attachments').'</i></td></tr>';
  266.  
  267.                 $output .= '</table></div>';
  268.  
  269.                 if ($can_upload) {
  270.                         $output .= '<form action="javascript:void(0);" onsubmit="return uploadAttachmentOnSubmit(this);" enctype="multipart/form-data" id="uploadAttachmentForm">';
  271.                         $output .= '<input type="hidden" name="id" value="'.htmlentities($id).'">';
  272.                         $output .= '<div>'._L('Add a file attachment').':<input type="file" name="userfile" value="" id="fileAttachment">';
  273.                         $output .= '<br><input type="submit" value="'._L('Upload').'"></div>';
  274.                         $output .= '</form>';
  275.                         $doshow = true;
  276.                 }
  277.  
  278.                 $output .= '</div>';
  279.  
  280.                 if ($doshow) $text .= $output;
  281.         }
  282.  
  283.         public function beforeObjectDelete($id) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3
  284.         public function afterObjectDelete($id) {
  285.                 // Interface 1.3.6.1.4.1.37476.2.5.2.3.3
  286.                 // Delete the attachment folder including all files in it (note: Subfolders are not possible)
  287.                 $uploaddir = self::getUploadDir($id);
  288.                 if ($uploaddir != '') {
  289.                         $ary = glob($uploaddir . DIRECTORY_SEPARATOR . '*');
  290.                         foreach ($ary as $a) @unlink($a);
  291.                         @rmdir($uploaddir);
  292.                         if (is_dir($uploaddir)) {
  293.                                 OIDplus::logger()->log("[WARN]OID($id)+[WARN]A!", "Attachment directory '$uploaddir' could not be deleted during the deletion of the OID");
  294.                         }
  295.                 }
  296.         }
  297.         public function beforeObjectUpdateSuperior($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3
  298.         public function afterObjectUpdateSuperior($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3
  299.         public function beforeObjectUpdateSelf($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3
  300.         public function afterObjectUpdateSelf($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3
  301.         public function beforeObjectInsert($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3
  302.         public function afterObjectInsert($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3
  303.  
  304.         public function tree_search($request) {
  305.                 return false;
  306.         }
  307.  
  308.         public function whoisObjectAttributes($id, &$out) {
  309.                 // Interface 1.3.6.1.4.1.37476.2.5.2.3.4
  310.  
  311.                 $files = glob(self::getUploadDir($id) . DIRECTORY_SEPARATOR . '*');
  312.                 foreach ($files as $file) {
  313.                         $out[] = 'attachment-name: '.basename($file);
  314.                         $out[] = 'attachment-url: '.OIDplus::webpath(__DIR__).'download.php?id='.urlencode($id).'&filename='.urlencode(basename($file));
  315.                 }
  316.  
  317.         }
  318.         public function whoisRaAttributes($email, &$out) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.4
  319. }
  320.