PluginProbe
ActivityPub / 2.5.0
ActivityPub v2.5.0
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
activitypub / includes / class-webfinger.php

class-webfinger.php in ActivityPub 2.5.0, at includes/class-webfinger.php

252 lines 5.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Activitypub;
3
4 use WP_Error;
5 use Activitypub\Collection\Users;
6
7 /**
8 * ActivityPub WebFinger Class
9 *
10 * @author Matthias Pfefferle
11 *
12 * @see https://webfinger.net/
13 */
14 class Webfinger {
15 /**
16 * Returns a users WebFinger "resource"
17 *
18 * @param int $user_id The WordPress user id
19 *
20 * @return string The user-resource
21 */
22 public static function get_user_resource( $user_id ) {
23 $user = Users::get_by_id( $user_id );
24 if ( ! $user || is_wp_error( $user ) ) {
25 return '';
26 }
27
28 return $user->get_webfinger();
29 }
30
31 /**
32 * Resolve a WebFinger resource
33 *
34 * @param string $uri The WebFinger Resource
35 *
36 * @return string|WP_Error The URL or WP_Error
37 */
38 public static function resolve( $uri ) {
39 $data = self::get_data( $uri );
40
41 if ( \is_wp_error( $data ) ) {
42 return $data;
43 }
44
45 if ( ! is_array( $data ) || empty( $data['links'] ) ) {
46 return new WP_Error(
47 'webfinger_missing_links',
48 __( 'No valid Link elements found.', 'activitypub' ),
49 $data
50 );
51 }
52
53 foreach ( $data['links'] as $link ) {
54 if (
55 'self' === $link['rel'] &&
56 (
57 'application/activity+json' === $link['type'] ||
58 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' === $link['type']
59 )
60 ) {
61 return $link['href'];
62 }
63 }
64
65 return new WP_Error(
66 'webfinger_url_no_activitypub',
67 __( 'The Site supports WebFinger but not ActivityPub', 'activitypub' ),
68 $data
69 );
70 }
71
72 /**
73 * Transform a URI to an acct <identifier>@<host>
74 *
75 * @param string $uri The URI (acct:, mailto:, http:, https:)
76 *
77 * @return string|WP_Error Error or acct URI
78 */
79 public static function uri_to_acct( $uri ) {
80 $data = self::get_data( $uri );
81
82 if ( is_wp_error( $data ) ) {
83 return $data;
84 }
85
86 // check if subject is an acct URI
87 if (
88 isset( $data['subject'] ) &&
89 \str_starts_with( $data['subject'], 'acct:' )
90 ) {
91 return $data['subject'];
92 }
93
94 // search for an acct URI in the aliases
95 if ( isset( $data['aliases'] ) ) {
96 foreach ( $data['aliases'] as $alias ) {
97 if ( \str_starts_with( $alias, 'acct:' ) ) {
98 return $alias;
99 }
100 }
101 }
102
103 return new WP_Error(
104 'webfinger_url_no_acct',
105 __( 'No acct URI found.', 'activitypub' ),
106 $data
107 );
108 }
109
110 /**
111 * Convert a URI string to an identifier and its host.
112 * Automatically adds acct: if it's missing.
113 *
114 * @param string $url The URI (acct:, mailto:, http:, https:)
115 *
116 * @return WP_Error|array Error reaction or array with
117 * identifier and host as values
118 */
119 public static function get_identifier_and_host( $url ) {
120 // remove leading @
121 $url = ltrim( $url, '@' );
122
123 if ( ! preg_match( '/^([a-zA-Z+]+):/', $url, $match ) ) {
124 $identifier = 'acct:' . $url;
125 $scheme = 'acct';
126 } else {
127 $identifier = $url;
128 $scheme = $match[1];
129 }
130
131 $host = null;
132
133 switch ( $scheme ) {
134 case 'acct':
135 case 'mailto':
136 case 'xmpp':
137 if ( strpos( $identifier, '@' ) !== false ) {
138 $host = substr( $identifier, strpos( $identifier, '@' ) + 1 );
139 }
140 break;
141 default:
142 $host = wp_parse_url( $identifier, PHP_URL_HOST );
143 break;
144 }
145
146 if ( empty( $host ) ) {
147 return new WP_Error( 'webfinger_invalid_identifier', __( 'Invalid Identifier', 'activitypub' ) );
148 }
149
150 return array( $identifier, $host );
151 }
152
153 /**
154 * Get the WebFinger data for a given URI
155 *
156 * @param string $uri The Identifier: <identifier>@<host> or URI
157 *
158 * @return WP_Error|array Error reaction or array with
159 * identifier and host as values
160 */
161 public static function get_data( $uri ) {
162 $identifier_and_host = self::get_identifier_and_host( $uri );
163
164 if ( is_wp_error( $identifier_and_host ) ) {
165 return $identifier_and_host;
166 }
167
168 $transient_key = self::generate_cache_key( $uri );
169
170 list( $identifier, $host ) = $identifier_and_host;
171
172 $data = \get_transient( $transient_key );
173 if ( $data ) {
174 return $data;
175 }
176
177 $webfinger_url = 'https://' . $host . '/.well-known/webfinger?resource=' . rawurlencode( $identifier );
178
179 $response = wp_safe_remote_get(
180 $webfinger_url,
181 array(
182 'headers' => array( 'Accept' => 'application/jrd+json' ),
183 )
184 );
185
186 if ( is_wp_error( $response ) ) {
187 return new WP_Error(
188 'webfinger_url_not_accessible',
189 __( 'The WebFinger Resource is not accessible.', 'activitypub' ),
190 $webfinger_url
191 );
192 }
193
194 $body = wp_remote_retrieve_body( $response );
195 $data = json_decode( $body, true );
196
197 \set_transient( $transient_key, $data, WEEK_IN_SECONDS );
198
199 return $data;
200 }
201
202 /**
203 * Get the Remote-Follow endpoint for a given URI
204 *
205 * @return string|WP_Error Error or the Remote-Follow endpoint URI.
206 */
207 public static function get_remote_follow_endpoint( $uri ) {
208 $data = self::get_data( $uri );
209
210 if ( is_wp_error( $data ) ) {
211 return $data;
212 }
213
214 if ( empty( $data['links'] ) ) {
215 return new WP_Error(
216 'webfinger_missing_links',
217 __( 'No valid Link elements found.', 'activitypub' ),
218 $data
219 );
220 }
221
222 foreach ( $data['links'] as $link ) {
223 if ( 'http://ostatus.org/schema/1.0/subscribe' === $link['rel'] ) {
224 return $link['template'];
225 }
226 }
227
228 return new WP_Error(
229 'webfinger_missing_remote_follow_endpoint',
230 __( 'No valid Remote-Follow endpoint found.', 'activitypub' ),
231 $data
232 );
233 }
234
235 /**
236 * Generate a cache key for a given URI
237 *
238 * @param string $uri A WebFinger Resource URI
239 *
240 * @return string The cache key
241 */
242 public static function generate_cache_key( $uri ) {
243 $uri = ltrim( $uri, '@' );
244
245 if ( filter_var( $uri, FILTER_VALIDATE_EMAIL ) ) {
246 $uri = 'acct:' . $uri;
247 }
248
249 return 'webfinger_' . md5( $uri );
250 }
251 }
252