$file) { if (!$config->debug && !self::checkToken($post['crm_attachment_token'])) { require_once 'api/v3/utils.php'; $results[$key] = civicrm_api3_create_error("SECURITY ALERT: Attaching files via AJAX requires a recent, valid token.", [ 'IP' => $server['REMOTE_ADDR'], 'level' => 'security', 'referer' => $server['HTTP_REFERER'], 'reason' => 'CSRF suspected', ] ); } elseif ($file['error']) { $results[$key] = civicrm_api3_create_error("Upload failed (code=" . $file['error'] . ")"); } else { CRM_Core_Transaction::create(TRUE) ->run(function (CRM_Core_Transaction $tx) use ($key, $file, $post, &$results) { // We want check_permissions=1 while creating the DB record and check_permissions=0 while moving upload, // so split the work across two api calls. $params = []; if (isset($file['name'])) { $params['name'] = $file['name']; } if (isset($file['type'])) { $params['mime_type'] = $file['type']; } foreach (['entity_table', 'entity_id', 'description'] as $field) { if (isset($post[$field])) { $params[$field] = $post[$field]; } } $params['version'] = 3; $params['check_permissions'] = 1; $params['content'] = ''; $results[$key] = civicrm_api('Attachment', 'create', $params); if (!$results[$key]['is_error']) { $moveParams = [ 'id' => $results[$key]['id'], 'version' => 3, 'options.move-file' => $file['tmp_name'], // note: in this second call, check_permissions==false ]; $moveResult = civicrm_api('Attachment', 'create', $moveParams); if ($moveResult['is_error']) { $results[$key] = $moveResult; $tx->rollback(); } } }); } } return $results; } /** * @param array $result * List of API responses, keyed by file. */ public static function sendResponse($result) { $isError = FALSE; foreach ($result as $item) { $isError = $isError || $item['is_error']; } if ($isError) { $sapi_type = php_sapi_name(); if (substr($sapi_type, 0, 3) == 'cgi') { CRM_Utils_System::setHttpHeader("Status", "500 Internal Server Error"); } else { header("HTTP/1.1 500 Internal Server Error"); } } CRM_Utils_JSON::output(array_merge($result)); } /** * @return array */ public static function angularSettings() { return [ 'token' => self::createToken(), ]; } /** * @return string */ public static function createToken() { $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), ['for', 'ts']); $ts = CRM_Utils_Time::getTimeRaw(); return $signer->sign([ 'for' => 'crmAttachment', 'ts' => $ts, ]) . ';;;' . $ts; } /** * @param string $token * A token supplied by the user. * @return bool * TRUE if the token is valid for submitting attachments * @throws Exception */ public static function checkToken($token) { list ($signature, $ts) = explode(';;;', $token); $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), ['for', 'ts']); if (!is_numeric($ts) || CRM_Utils_Time::getTimeRaw() > $ts + self::ATTACHMENT_TOKEN_TTL) { return FALSE; } return $signer->validate($signature, [ 'for' => 'crmAttachment', 'ts' => $ts, ]); } }