PluginProbe
ActivityPub / 2.1.0
ActivityPub v2.1.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.1.0, at includes/class-webfinger.php

249 lines 5.4 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 'application/activity+json' === $link['type']
57 ) {
58 return $link['href'];
59 }
60 }
61
62 return new WP_Error(
63 'webfinger_url_no_activitypub',
64 __( 'The Site supports WebFinger but not ActivityPub', 'activitypub' ),
65 $data
66 );
67 }
68
69 /**
70 * Transform a URI to an acct <identifier>@<host>
71 *
72 * @param string $uri The URI (acct:, mailto:, http:, https:)
73 *
74 * @return string|WP_Error Error or acct URI
75 */
76 public static function uri_to_acct( $uri ) {
77 $data = self::get_data( $uri );
78
79 if ( is_wp_error( $data ) ) {
80 return $data;
81 }
82
83 // check if subject is an acct URI
84 if (
85 isset( $data['subject'] ) &&
86 \str_starts_with( $data['subject'], 'acct:' )
87 ) {
88 return $data['subject'];
89 }
90
91 // search for an acct URI in the aliases
92 if ( isset( $data['aliases'] ) ) {
93 foreach ( $data['aliases'] as $alias ) {
94 if ( \str_starts_with( $alias, 'acct:' ) ) {
95 return $alias;
96 }
97 }
98 }
99
100 return new WP_Error(
101 'webfinger_url_no_acct',
102 __( 'No acct URI found.', 'activitypub' ),
103 $data
104 );
105 }
106
107 /**
108 * Convert a URI string to an identifier and its host.
109 * Automatically adds acct: if it's missing.
110 *
111 * @param string $url The URI (acct:, mailto:, http:, https:)
112 *
113 * @return WP_Error|array Error reaction or array with
114 * identifier and host as values
115 */
116 public static function get_identifier_and_host( $url ) {
117 // remove leading @
118 $url = ltrim( $url, '@' );
119
120 if ( ! preg_match( '/^([a-zA-Z+]+):/', $url, $match ) ) {
121 $identifier = 'acct:' . $url;
122 $scheme = 'acct';
123 } else {
124 $identifier = $url;
125 $scheme = $match[1];
126 }
127
128 $host = null;
129
130 switch ( $scheme ) {
131 case 'acct':
132 case 'mailto':
133 case 'xmpp':
134 if ( strpos( $identifier, '@' ) !== false ) {
135 $host = substr( $identifier, strpos( $identifier, '@' ) + 1 );
136 }
137 break;
138 default:
139 $host = wp_parse_url( $identifier, PHP_URL_HOST );
140 break;
141 }
142
143 if ( empty( $host ) ) {
144 return new WP_Error( 'webfinger_invalid_identifier', __( 'Invalid Identifier', 'activitypub' ) );
145 }
146
147 return array( $identifier, $host );
148 }
149
150 /**
151 * Get the WebFinger data for a given URI
152 *
153 * @param string $uri The Identifier: <identifier>@<host> or URI
154 *
155 * @return WP_Error|array Error reaction or array with
156 * identifier and host as values
157 */
158 public static function get_data( $uri ) {
159 $identifier_and_host = self::get_identifier_and_host( $uri );
160
161 if ( is_wp_error( $identifier_and_host ) ) {
162 return $identifier_and_host;
163 }
164
165 $transient_key = self::generate_cache_key( $uri );
166
167 list( $identifier, $host ) = $identifier_and_host;
168
169 $data = \get_transient( $transient_key );
170 if ( $data ) {
171 return $data;
172 }
173
174 $webfinger_url = 'https://' . $host . '/.well-known/webfinger?resource=' . rawurlencode( $identifier );
175
176 $response = wp_safe_remote_get(
177 $webfinger_url,
178 array(
179 'headers' => array( 'Accept' => 'application/jrd+json' ),
180 )
181 );
182
183 if ( is_wp_error( $response ) ) {
184 return new WP_Error(
185 'webfinger_url_not_accessible',
186 __( 'The WebFinger Resource is not accessible.', 'activitypub' ),
187 $webfinger_url
188 );
189 }
190
191 $body = wp_remote_retrieve_body( $response );
192 $data = json_decode( $body, true );
193
194 \set_transient( $transient_key, $data, WEEK_IN_SECONDS );
195
196 return $data;
197 }
198
199 /**
200 * Get the Remote-Follow endpoint for a given URI
201 *
202 * @return string|WP_Error Error or the Remote-Follow endpoint URI.
203 */
204 public static function get_remote_follow_endpoint( $uri ) {
205 $data = self::get_data( $uri );
206
207 if ( is_wp_error( $data ) ) {
208 return $data;
209 }
210
211 if ( empty( $data['links'] ) ) {
212 return new WP_Error(
213 'webfinger_missing_links',
214 __( 'No valid Link elements found.', 'activitypub' ),
215 $data
216 );
217 }
218
219 foreach ( $data['links'] as $link ) {
220 if ( 'http://ostatus.org/schema/1.0/subscribe' === $link['rel'] ) {
221 return $link['template'];
222 }
223 }
224
225 return new WP_Error(
226 'webfinger_missing_remote_follow_endpoint',
227 __( 'No valid Remote-Follow endpoint found.', 'activitypub' ),
228 $data
229 );
230 }
231
232 /**
233 * Generate a cache key for a given URI
234 *
235 * @param string $uri A WebFinger Resource URI
236 *
237 * @return string The cache key
238 */
239 public static function generate_cache_key( $uri ) {
240 $uri = ltrim( $uri, '@' );
241
242 if ( filter_var( $uri, FILTER_VALIDATE_EMAIL ) ) {
243 $uri = 'acct:' . $uri;
244 }
245
246 return 'webfinger_' . md5( $uri );
247 }
248 }
249