PluginProbe
ActivityPub / 2.0.0
ActivityPub v2.0.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.0.0, at includes/class-webfinger.php

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