PluginProbe
ActivityPub / 3.2.4
ActivityPub v3.2.4
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
← All changes | includes/class-webfinger.php +84 -155 8.2.13.2.4 View file →
@@ -1,19 +1,12 @@
1 1 <?php
2 -/**
3 - * WebFinger class file.
4 - *
5 - * @package Activitypub
6 - */
7 -
8 2 namespace Activitypub;
9 3
10 -use Activitypub\Activity\Actor;
11 -use Activitypub\Collection\Actors;
12 -use Activitypub\Collection\Remote_Actors;
4 +use WP_Error;
5 +use Activitypub\Collection\Users;
13 6
14 7 /**
15 - * ActivityPub WebFinger Class.
8 + * ActivityPub WebFinger Class
16 9 *
17 10 * @author Matthias Pfefferle
18 11 *
19 12 * @see https://webfinger.net/
@@ -19,16 +12,16 @@
19 12 * @see https://webfinger.net/
20 13 */
21 14 class Webfinger {
22 15 /**
23 - * Returns a users WebFinger "resource".
16 + * Returns a users WebFinger "resource"
24 17 *
25 - * @param int $user_id The WordPress user id.
18 + * @param int $user_id The WordPress user id
26 19 *
27 - * @return string The user-resource.
20 + * @return string The user-resource
28 21 */
29 22 public static function get_user_resource( $user_id ) {
30 - $user = Actors::get_by_id( $user_id );
23 + $user = Users::get_by_id( $user_id );
31 24 if ( ! $user || is_wp_error( $user ) ) {
32 25 return '';
33 26 }
34 27
@@ -35,13 +28,13 @@
35 28 return $user->get_webfinger();
36 29 }
37 30
38 31 /**
39 - * Resolve a WebFinger resource.
32 + * Resolve a WebFinger resource
40 33 *
41 - * @param string $uri The WebFinger Resource.
34 + * @param string $uri The WebFinger Resource
42 35 *
43 - * @return string|\WP_Error The URL or WP_Error.
36 + * @return string|WP_Error The URL or WP_Error
44 37 */
45 38 public static function resolve( $uri ) {
46 39 $data = self::get_data( $uri );
47 40
@@ -49,9 +42,9 @@
49 42 return $data;
50 43 }
51 44
52 45 if ( ! is_array( $data ) || empty( $data['links'] ) ) {
53 - return new \WP_Error(
46 + return new WP_Error(
54 47 'webfinger_missing_links',
55 48 __( 'No valid Link elements found.', 'activitypub' ),
56 49 array(
57 50 'status' => 400,
@@ -62,9 +55,8 @@
62 55
63 56 foreach ( $data['links'] as $link ) {
64 57 if (
65 58 'self' === $link['rel'] &&
66 - isset( $link['type'] ) &&
67 59 (
68 60 'application/activity+json' === $link['type'] ||
69 61 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' === $link['type']
70 62 )
@@ -72,9 +64,9 @@
72 64 return $link['href'];
73 65 }
74 66 }
75 67
76 - return new \WP_Error(
68 + return new WP_Error(
77 69 'webfinger_url_no_activitypub',
78 70 __( 'The Site supports WebFinger but not ActivityPub', 'activitypub' ),
79 71 array(
80 72 'status' => 400,
@@ -83,15 +75,13 @@
83 75 );
84 76 }
85 77
86 78 /**
87 - * Transform a URI to an acct <identifier>@<host>.
79 + * Transform a URI to an acct <identifier>@<host>
88 80 *
89 - * @see https://swicg.github.io/activitypub-webfinger/#reverse-discovery
81 + * @param string $uri The URI (acct:, mailto:, http:, https:)
90 82 *
91 - * @param string $uri The URI (acct:, mailto:, http:, https:).
92 - *
93 - * @return string|\WP_Error Error or acct URI.
83 + * @return string|WP_Error Error or acct URI
94 84 */
95 85 public static function uri_to_acct( $uri ) {
96 86 $data = self::get_data( $uri );
97 87
@@ -98,9 +88,9 @@
98 88 if ( is_wp_error( $data ) ) {
99 89 return $data;
100 90 }
101 91
102 - // Check if subject is an acct URI.
92 + // check if subject is an acct URI
103 93 if (
104 94 isset( $data['subject'] ) &&
105 95 \str_starts_with( $data['subject'], 'acct:' )
106 96 ) {
@@ -106,9 +96,9 @@
106 96 ) {
107 97 return $data['subject'];
108 98 }
109 99
110 - // Search for an acct URI in the aliases.
100 + // search for an acct URI in the aliases
111 101 if ( isset( $data['aliases'] ) ) {
112 102 foreach ( $data['aliases'] as $alias ) {
113 103 if ( \str_starts_with( $alias, 'acct:' ) ) {
114 104 return $alias;
@@ -115,9 +105,9 @@
115 105 }
116 106 }
117 107 }
118 108
119 - return new \WP_Error(
109 + return new WP_Error(
120 110 'webfinger_url_no_acct',
121 111 __( 'No acct URI found.', 'activitypub' ),
122 112 array(
123 113 'status' => 400,
@@ -129,15 +119,16 @@
129 119 /**
130 120 * Convert a URI string to an identifier and its host.
131 121 * Automatically adds acct: if it's missing.
132 122 *
133 - * @param string $url The URI (acct:, mailto:, http:, https:).
123 + * @param string $url The URI (acct:, mailto:, http:, https:)
134 124 *
135 - * @return \WP_Error|array Error reaction or array with identifier and host as values.
125 + * @return WP_Error|array Error reaction or array with
126 + * identifier and host as values
136 127 */
137 128 public static function get_identifier_and_host( $url ) {
138 129 if ( ! $url ) {
139 - return new \WP_Error(
130 + return new WP_Error(
140 131 'webfinger_invalid_identifier',
141 132 __( 'Invalid Identifier', 'activitypub' ),
142 133 array(
143 134 'status' => 400,
@@ -145,17 +136,17 @@
145 136 )
146 137 );
147 138 }
148 139
149 - // Remove leading @.
140 + // remove leading @
150 141 $url = ltrim( $url, '@' );
151 142
152 143 if ( ! preg_match( '/^([a-zA-Z+]+):/', $url, $match ) ) {
153 144 $identifier = 'acct:' . $url;
154 - $scheme = 'acct';
145 + $scheme = 'acct';
155 146 } else {
156 147 $identifier = $url;
157 - $scheme = $match[1];
148 + $scheme = $match[1];
158 149 }
159 150
160 151 $host = null;
161 152
@@ -172,9 +163,9 @@
172 163 break;
173 164 }
174 165
175 166 if ( empty( $host ) ) {
176 - return new \WP_Error(
167 + return new WP_Error(
177 168 'webfinger_invalid_identifier',
178 169 __( 'Invalid Identifier', 'activitypub' ),
179 170 array(
180 171 'status' => 400,
@@ -186,13 +177,14 @@
186 177 return array( $identifier, $host );
187 178 }
188 179
189 180 /**
190 - * Get the WebFinger data for a given URI.
181 + * Get the WebFinger data for a given URI
191 182 *
192 - * @param string $uri The Identifier: <identifier>@<host> or URI.
183 + * @param string $uri The Identifier: <identifier>@<host> or URI
193 184 *
194 - * @return \WP_Error|array Error reaction or array with identifier and host as values.
185 + * @return WP_Error|array Error reaction or array with
186 + * identifier and host as values
195 187 */
196 188 public static function get_data( $uri ) {
197 189 $identifier_and_host = self::get_identifier_and_host( $uri );
198 190
@@ -199,109 +191,65 @@
199 191 if ( is_wp_error( $identifier_and_host ) ) {
200 192 return $identifier_and_host;
201 193 }
202 194
195 + $transient_key = self::generate_cache_key( $uri );
196 +
203 197 list( $identifier, $host ) = $identifier_and_host;
204 198
199 + $data = \get_transient( $transient_key );
200 + if ( $data ) {
201 + return $data;
202 + }
203 +
205 204 $webfinger_url = sprintf(
206 205 'https://%s/.well-known/webfinger?resource=%s',
207 206 $host,
208 - \rawurlencode( $identifier )
207 + rawurlencode( $identifier )
209 208 );
210 209
211 - // Use Http::get() which handles all caching (success and errors).
212 - $response = Http::get(
210 + $response = wp_safe_remote_get(
213 211 $webfinger_url,
214 - array( 'headers' => array( 'Accept' => 'application/jrd+json' ) ),
215 - WEEK_IN_SECONDS
212 + array(
213 + 'headers' => array( 'Accept' => 'application/jrd+json' ),
214 + )
216 215 );
217 216
218 - if ( \is_wp_error( $response ) ) {
219 - return $response;
217 + if ( is_wp_error( $response ) ) {
218 + return new WP_Error(
219 + 'webfinger_url_not_accessible',
220 + __( 'The WebFinger Resource is not accessible.', 'activitypub' ),
221 + array(
222 + 'status' => 400,
223 + 'data' => $webfinger_url,
224 + )
225 + );
220 226 }
221 227
222 - $body = \wp_remote_retrieve_body( $response );
228 + $body = wp_remote_retrieve_body( $response );
229 + $data = json_decode( $body, true );
223 230
224 - return \json_decode( $body, true );
231 + \set_transient( $transient_key, $data, WEEK_IN_SECONDS );
232 +
233 + return $data;
225 234 }
226 235
227 236 /**
228 - * Get the Remote-Follow endpoint for a given URI.
237 + * Get the Remote-Follow endpoint for a given URI
229 238 *
230 - * @param string $uri The WebFinger Resource URI.
231 - *
232 - * @return string|\WP_Error Error or the Remote-Follow endpoint URI.
239 + * @return string|WP_Error Error or the Remote-Follow endpoint URI.
233 240 */
234 241 public static function get_remote_follow_endpoint( $uri ) {
235 - return self::get_intent_endpoint( $uri, 'http://ostatus.org/schema/1.0/subscribe' );
236 - }
237 -
238 - /**
239 - * Generate a cache key for a given URI.
240 - *
241 - * @param string $uri A WebFinger Resource URI.
242 - *
243 - * @return string The cache key.
244 - */
245 - public static function generate_cache_key( $uri ) {
246 - $uri = ltrim( $uri, '@' );
247 -
248 - if ( filter_var( $uri, FILTER_VALIDATE_EMAIL ) ) {
249 - $uri = 'acct:' . $uri;
250 - }
251 -
252 - return 'webfinger_' . md5( $uri );
253 - }
254 -
255 - /**
256 - * Infer a shortname from the Actor ID or URL. Used only for fallbacks,
257 - * we will try to use what's supplied.
258 - *
259 - * @param Actor|string $actor_or_uri The Actor or URI.
260 - *
261 - * @return string Hopefully the name of the Follower.
262 - */
263 - public static function guess( $actor_or_uri ) {
264 - if ( ! $actor_or_uri instanceof Actor ) {
265 - $actor = Remote_Actors::fetch_by_uri( $actor_or_uri );
266 - if ( \is_wp_error( $actor ) ) {
267 - return extract_name_from_uri( $actor_or_uri ) . '@' . \wp_parse_url( $actor_or_uri, PHP_URL_HOST );
268 - }
269 -
270 - $actor_or_uri = $actor;
271 - }
272 -
273 - if ( $actor_or_uri->get_preferred_username() ) {
274 - return $actor_or_uri->get_preferred_username() . '@' . \wp_parse_url( $actor_or_uri->get_id(), PHP_URL_HOST );
275 - }
276 -
277 - return extract_name_from_uri( $actor_or_uri->get_id() ) . '@' . \wp_parse_url( $actor_or_uri->get_id(), PHP_URL_HOST );
278 - }
279 -
280 - /**
281 - * Get the Intent endpoint for a given URI and intent.
282 - *
283 - * @since 8.0.0
284 - *
285 - * @see https://codeberg.org/fediverse/fep/src/branch/main/fep/3b86/fep-3b86.md
286 - *
287 - * @param string $uri The WebFinger Resource URI.
288 - * @param string $intent The intent to look for.
289 - * @param bool $fallback Whether to fallback to the Remote-Follow endpoint.
290 - *
291 - * @return string|\WP_Error Error or the Intent endpoint URI (may contain `{uri}` placeholder).
292 - */
293 - public static function get_intent_endpoint( $uri, $intent, $fallback = false ) {
294 242 $data = self::get_data( $uri );
295 243
296 - if ( \is_wp_error( $data ) ) {
244 + if ( is_wp_error( $data ) ) {
297 245 return $data;
298 246 }
299 247
300 248 if ( empty( $data['links'] ) ) {
301 - return new \WP_Error(
249 + return new WP_Error(
302 250 'webfinger_missing_links',
303 - \__( 'No valid Link elements found.', 'activitypub' ),
251 + __( 'No valid Link elements found.', 'activitypub' ),
304 252 array(
305 253 'status' => 400,
306 254 'data' => $data,
307 255 )
@@ -307,56 +255,37 @@
307 255 )
308 256 );
309 257 }
310 258
311 - // Normalize the links with $rel as key.
312 - $links = array();
313 -
314 259 foreach ( $data['links'] as $link ) {
315 - if ( isset( $link['rel'] ) && isset( $link['template'] ) ) {
316 - $links[ \strtolower( $link['rel'] ) ] = $link['template'];
260 + if ( 'http://ostatus.org/schema/1.0/subscribe' === $link['rel'] ) {
261 + return $link['template'];
317 262 }
318 263 }
319 264
320 - $intent = \sanitize_text_field( $intent );
321 - $intent = \strtolower( $intent );
265 + return new WP_Error(
266 + 'webfinger_missing_remote_follow_endpoint',
267 + __( 'No valid Remote-Follow endpoint found.', 'activitypub' ),
268 + array(
269 + 'status' => 400,
270 + 'data' => $data,
271 + )
272 + );
273 + }
322 274
323 - if ( ! \filter_var( $intent, FILTER_VALIDATE_URL ) ) {
324 - $intent = 'https://w3id.org/fep/3b86/' . $intent;
325 - }
275 + /**
276 + * Generate a cache key for a given URI
277 + *
278 + * @param string $uri A WebFinger Resource URI
279 + *
280 + * @return string The cache key
281 + */
282 + public static function generate_cache_key( $uri ) {
283 + $uri = ltrim( $uri, '@' );
326 284
327 - if ( isset( $links[ $intent ] ) ) {
328 - return $links[ $intent ];
285 + if ( filter_var( $uri, FILTER_VALIDATE_EMAIL ) ) {
286 + $uri = 'acct:' . $uri;
329 287 }
330 288
331 - if ( ! $fallback ) {
332 - return new \WP_Error(
333 - 'webfinger_missing_intent_endpoint',
334 - \__( 'No valid Intent endpoint found.', 'activitypub' ),
335 - array(
336 - 'status' => 400,
337 - 'data' => $data,
338 - )
339 - );
340 - }
341 -
342 - if ( isset( $links['http://ostatus.org/schema/1.0/subscribe'] ) ) {
343 - return $links['http://ostatus.org/schema/1.0/subscribe'];
344 - }
345 -
346 - // Last-resort: construct a Mastodon-compatible authorize_interaction URL.
347 - $identifier_and_host = self::get_identifier_and_host( $uri );
348 -
349 - if ( \is_wp_error( $identifier_and_host ) ) {
350 - return new \WP_Error(
351 - 'webfinger_missing_intent_endpoint',
352 - \__( 'No valid Intent endpoint found.', 'activitypub' ),
353 - array(
354 - 'status' => 400,
355 - 'data' => $data,
356 - )
357 - );
358 - }
359 -
360 - return 'https://' . $identifier_and_host[1] . '/authorize_interaction?uri={uri}';
289 + return 'webfinger_' . md5( $uri );
361 290 }
362 291 }