Commit | Line | Data |
---|---|---|
b4454468 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
4 | | Copyright CiviCRM LLC. All rights reserved. | | |
5 | | | | |
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 | +--------------------------------------------------------------------+ | |
10 | */ | |
11 | ||
12 | namespace Civi\Pipe; | |
13 | ||
5e13f388 TO |
14 | use Civi\Authx\AuthxException; |
15 | ||
b4454468 TO |
16 | /** |
17 | * Collection of methods to expose to the pipe session. Any public method will be accessible. | |
18 | */ | |
19 | class PublicMethods { | |
20 | ||
f1ab7a2e TO |
21 | /** |
22 | * How should API errors be reported? | |
23 | * | |
24 | * @var string | |
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. | |
27 | */ | |
870ffb26 | 28 | protected $apiError = 'exception'; |
f1ab7a2e | 29 | |
5e13f388 TO |
30 | /** |
31 | * Should API calls use permission checks? | |
32 | * | |
33 | * Note: This property is only consulted on trusted connections. It is ignored on untrusted connections. | |
34 | * | |
35 | * @var bool | |
36 | */ | |
37 | protected $apiCheckPermissions = TRUE; | |
38 | ||
b4454468 TO |
39 | /** |
40 | * Send a request to APIv3. | |
41 | * | |
25804d7a | 42 | * @param \Civi\Pipe\PipeSession $session |
b4454468 TO |
43 | * @param array $request |
44 | * Tuple: [$entity, $action, $params] | |
45 | * @return array|\Civi\Api4\Generic\Result|int | |
46 | */ | |
e98a5c1f | 47 | public function api3(PipeSession $session, array $request) { |
5e13f388 TO |
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. | |
f1ab7a2e TO |
51 | switch ($this->apiError) { |
52 | case 'array': | |
53 | return civicrm_api(...$request); | |
54 | ||
55 | case 'exception': | |
56 | return civicrm_api3(...$request); | |
57 | ||
58 | default: | |
59 | throw new \CRM_Core_Exception("Invalid API error-handling mode: $this->apiError"); | |
60 | } | |
b4454468 TO |
61 | } |
62 | ||
63 | /** | |
64 | * Send a request to APIv4. | |
65 | * | |
25804d7a | 66 | * @param \Civi\Pipe\PipeSession $session |
b4454468 TO |
67 | * @param array $request |
68 | * Tuple: [$entity, $action, $params] | |
69 | * @return array|\Civi\Api4\Generic\Result|int | |
70 | */ | |
e98a5c1f | 71 | public function api4(PipeSession $session, array $request) { |
5e13f388 TO |
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. | |
f1ab7a2e TO |
75 | switch ($this->apiError) { |
76 | case 'array': | |
77 | return civicrm_api(...$request); | |
78 | ||
79 | case 'exception': | |
80 | return civicrm_api4(...$request); | |
81 | ||
82 | default: | |
83 | throw new \CRM_Core_Exception("Invalid API error-handling mode: $this->apiError"); | |
84 | } | |
b4454468 TO |
85 | } |
86 | ||
87 | /** | |
88 | * Simple test; send/receive a fragment of data. | |
89 | * | |
25804d7a | 90 | * @param \Civi\Pipe\PipeSession $session |
e98a5c1f TO |
91 | * @param array $request |
92 | * @return array | |
b4454468 | 93 | */ |
e98a5c1f | 94 | public function echo(PipeSession $session, array $request) { |
b4454468 TO |
95 | return $request; |
96 | } | |
97 | ||
98 | /** | |
99 | * Set active user. | |
100 | * | |
25804d7a | 101 | * @param \Civi\Pipe\PipeSession $session |
02dc5c62 | 102 | * @param array{contactId: int, userId: int, user: string, cred: string} $request |
b4454468 TO |
103 | * @return array|\Civi\Api4\Generic\Result|int |
104 | */ | |
e98a5c1f | 105 | public function login(PipeSession $session, array $request) { |
b4454468 | 106 | if (!function_exists('authx_login')) { |
eaa0d7ac | 107 | throw new \CRM_Core_Exception('Cannot authenticate. Authx is not configured.'); |
b4454468 | 108 | } |
5e13f388 TO |
109 | |
110 | $redact = function(?array $authx) { | |
111 | return $authx ? \CRM_Utils_Array::subset($authx, ['contactId', 'userId']) : FALSE; | |
112 | }; | |
113 | ||
114 | $principal = \CRM_Utils_Array::subset($request, ['contactId', 'userId', 'user']); | |
115 | if ($principal && $session->isTrusted()) { | |
02dc5c62 | 116 | return $redact(authx_login(['flow' => 'script', 'principal' => $principal])); |
5e13f388 TO |
117 | } |
118 | elseif ($principal && !$session->isTrusted()) { | |
eaa0d7ac | 119 | throw new AuthxException('Session is not trusted.'); |
5e13f388 TO |
120 | } |
121 | elseif (isset($request['cred'])) { | |
122 | $authn = new \Civi\Authx\Authenticator(); | |
123 | $authn->setRejectMode('exception'); | |
02dc5c62 | 124 | if ($authn->auth(NULL, ['flow' => 'pipe', 'cred' => $request['cred']])) { |
eaa0d7ac | 125 | return $redact(\CRM_Core_Session::singleton()->get('authx')); |
5e13f388 TO |
126 | } |
127 | } | |
128 | ||
eaa0d7ac | 129 | throw new AuthxException('Cannot authenticate. Must specify principal/credentials.'); |
b4454468 TO |
130 | } |
131 | ||
132 | /** | |
133 | * Set ephemeral session options. | |
134 | * | |
25804d7a | 135 | * @param \Civi\Pipe\PipeSession $session |
9aa53a5d | 136 | * @param array{bufferSize: int, responsePrefix: int} $request |
b4454468 | 137 | * Any updates to perform. May be empty/omitted. |
9aa53a5d | 138 | * @return array{bufferSize: int, responsePrefix: int} |
b4454468 TO |
139 | * List of updated options. |
140 | * If the list of updates was empty, then return all options. | |
141 | */ | |
e98a5c1f | 142 | public function options(PipeSession $session, array $request) { |
f1ab7a2e | 143 | $storageMap = [ |
5e13f388 | 144 | 'apiCheckPermissions' => $this, |
f1ab7a2e | 145 | 'apiError' => $this, |
9aa53a5d | 146 | 'bufferSize' => $session, |
f1ab7a2e | 147 | 'responsePrefix' => $session, |
b4454468 TO |
148 | ]; |
149 | ||
5e13f388 TO |
150 | if (!$session->isTrusted() && array_key_exists('apiCheckPermissions', $request)) { |
151 | unset($request['apiCheckPermissions']); | |
152 | } | |
153 | ||
f1ab7a2e TO |
154 | $get = function($storage, $name) { |
155 | if (method_exists($storage, 'get' . ucfirst($name))) { | |
156 | return $storage->{'get' . ucfirst($name)}(); | |
157 | } | |
158 | else { | |
159 | return $storage->{$name}; | |
160 | } | |
161 | }; | |
162 | ||
163 | $set = function($storage, $name, $value) use ($get) { | |
164 | if (method_exists($storage, 'set' . ucfirst($name))) { | |
165 | $storage->{'set' . ucfirst($name)}($value); | |
166 | } | |
167 | else { | |
168 | $storage->{$name} = $value; | |
169 | } | |
170 | return $get($storage, $name); | |
171 | }; | |
172 | ||
b4454468 TO |
173 | $result = []; |
174 | if (!empty($request)) { | |
f1ab7a2e TO |
175 | foreach ($request as $name => $value) { |
176 | if (isset($storageMap[$name])) { | |
177 | $result[$name] = $set($storageMap[$name], $name, $value); | |
b4454468 TO |
178 | } |
179 | } | |
180 | } | |
181 | else { | |
f1ab7a2e TO |
182 | foreach ($storageMap as $name => $storage) { |
183 | $result[$name] = $get($storage, $name); | |
b4454468 TO |
184 | } |
185 | } | |
186 | return $result; | |
187 | } | |
188 | ||
5e13f388 TO |
189 | private function isCheckPermissions(array $params, string $field) { |
190 | return isset($params[$field]) ? $params[$field] : $this->apiCheckPermissions; | |
191 | } | |
192 | ||
b4454468 | 193 | } |