3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
14 use Civi\Authx\AuthxException
;
17 * Collection of methods to expose to the pipe session. Any public method will be accessible.
22 * How should API errors be reported?
25 * - 'array': Traditional array format from civicrm_api(). Maximizes consistency of error data.
26 * - 'exception': Converted to an exception. Somewhat lossy. Improves out-of-box DX on stricter JSON-RPC clients.
28 protected $apiError = 'exception';
31 * Should API calls use permission checks?
33 * Note: This property is only consulted on trusted connections. It is ignored on untrusted connections.
37 protected $apiCheckPermissions = TRUE;
40 * Send a request to APIv3.
42 * @param \Civi\Pipe\PipeSession $session
43 * @param array $request
44 * Tuple: [$entity, $action, $params]
45 * @return array|\Civi\Api4\Generic\Result|int
47 public function api3(PipeSession
$session, array $request) {
48 $request[2] = array_merge($request[2] ??
[], ['version' => 3]);
49 $request[2]['check_permissions'] = !$session->isTrusted() ||
$this->isCheckPermissions($request[2], 'check_permissions');
50 // ^^ Untrusted sessions MUST check perms. All sessions DEFAULT to checking perms. Trusted sessions MAY disable perms.
51 switch ($this->apiError
) {
53 return civicrm_api(...$request);
56 return civicrm_api3(...$request);
59 throw new \
CRM_Core_Exception("Invalid API error-handling mode: $this->apiError");
64 * Send a request to APIv4.
66 * @param \Civi\Pipe\PipeSession $session
67 * @param array $request
68 * Tuple: [$entity, $action, $params]
69 * @return array|\Civi\Api4\Generic\Result|int
71 public function api4(PipeSession
$session, array $request) {
72 $request[2] = array_merge($request[2] ??
[], ['version' => 4]);
73 $request[2]['checkPermissions'] = !$session->isTrusted() ||
$this->isCheckPermissions($request[2], 'checkPermissions');
74 // ^^ Untrusted sessions MUST check perms. All sessions DEFAULT to checking perms. Trusted sessions MAY disable perms.
75 switch ($this->apiError
) {
77 return civicrm_api(...$request);
80 return civicrm_api4(...$request);
83 throw new \
CRM_Core_Exception("Invalid API error-handling mode: $this->apiError");
88 * Simple test; send/receive a fragment of data.
90 * @param \Civi\Pipe\PipeSession $session
91 * @param array $request
94 public function echo(PipeSession
$session, array $request) {
101 * @param \Civi\Pipe\PipeSession $session
102 * @param array{contactId: int, userId: int, user: string, cred: string} $request
103 * @return array|\Civi\Api4\Generic\Result|int
105 public function login(PipeSession
$session, array $request) {
106 if (!function_exists('authx_login')) {
107 throw new \
CRM_Core_Exception('Cannot authenticate. Authx is not configured.');
110 $redact = function(?
array $authx) {
111 return $authx ? \CRM_Utils_Array
::subset($authx, ['contactId', 'userId']) : FALSE;
114 $principal = \CRM_Utils_Array
::subset($request, ['contactId', 'userId', 'user']);
115 if ($principal && $session->isTrusted()) {
116 return $redact(authx_login(['flow' => 'script', 'principal' => $principal]));
118 elseif ($principal && !$session->isTrusted()) {
119 throw new AuthxException('Session is not trusted.');
121 elseif (isset($request['cred'])) {
122 $authn = new \Civi\Authx\
Authenticator();
123 $authn->setRejectMode('exception');
124 if ($authn->auth(NULL, ['flow' => 'pipe', 'cred' => $request['cred']])) {
125 return $redact(\CRM_Core_Session
::singleton()->get('authx'));
129 throw new AuthxException('Cannot authenticate. Must specify principal/credentials.');
133 * Set ephemeral session options.
135 * @param \Civi\Pipe\PipeSession $session
136 * @param array{bufferSize: int, responsePrefix: int} $request
137 * Any updates to perform. May be empty/omitted.
138 * @return array{bufferSize: int, responsePrefix: int}
139 * List of updated options.
140 * If the list of updates was empty, then return all options.
142 public function options(PipeSession
$session, array $request) {
144 'apiCheckPermissions' => $this,
146 'bufferSize' => $session,
147 'responsePrefix' => $session,
150 if (!$session->isTrusted() && array_key_exists('apiCheckPermissions', $request)) {
151 unset($request['apiCheckPermissions']);
154 $get = function($storage, $name) {
155 if (method_exists($storage, 'get' . ucfirst($name))) {
156 return $storage->{'get' . ucfirst($name)}();
159 return $storage->{$name};
163 $set = function($storage, $name, $value) use ($get) {
164 if (method_exists($storage, 'set' . ucfirst($name))) {
165 $storage->{'set' . ucfirst($name)}($value);
168 $storage->{$name} = $value;
170 return $get($storage, $name);
174 if (!empty($request)) {
175 foreach ($request as $name => $value) {
176 if (isset($storageMap[$name])) {
177 $result[$name] = $set($storageMap[$name], $name, $value);
182 foreach ($storageMap as $name => $storage) {
183 $result[$name] = $get($storage, $name);
189 private function isCheckPermissions(array $params, string $field) {
190 return isset($params[$field]) ?
$params[$field] : $this->apiCheckPermissions
;