PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / trunk
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin vtrunk
1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 0.8.6 All 33 releases
desktop-mode / includes / network / registry.php

registry.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin trunk, at includes/network/registry.php

210 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — Network registry: the installs that belong with this
4 * one, as this one sees them.
5 *
6 * WordPress has one notion of a site in a network, a row it serves
7 * itself. A member of an OpenStation network lives elsewhere, so it is
8 * kept here instead: its URL, its name, its shell screen, and the public
9 * key pinned when it was added. Pinned, not refreshed: a key that later
10 * differs is flagged and refused, never silently accepted, because a
11 * changed key is either a reinstall the admin should confirm or someone
12 * else answering at that address.
13 *
14 * @package OpenStation
15 */
16
17 defined( 'ABSPATH' ) || exit;
18
19 /** The registry option: members keyed by id. */
20 const OPENSTATION_NETWORK_MEMBERS_OPTION = 'openstation_network_members';
21
22 /**
23 * A member's id: derived from its canonical URL, so the same install
24 * cannot be added twice under two spellings.
25 *
26 * @param string $url The install's URL.
27 * @return string
28 */
29 function openstation_network_member_id( $url ) {
30 $parts = wp_parse_url( strtolower( trim( (string) $url ) ) );
31 $host = isset( $parts['host'] ) ? $parts['host'] : '';
32 $port = isset( $parts['port'] ) ? ':' . $parts['port'] : '';
33 $path = isset( $parts['path'] ) ? untrailingslashit( $parts['path'] ) : '';
34 return substr( md5( $host . $port . $path ), 0, 12 );
35 }
36
37 /**
38 * Whether a URL names this very install (the hub adding itself).
39 *
40 * @param string $url URL.
41 * @return bool
42 */
43 function openstation_network_url_is_self( $url ) {
44 $self = is_multisite() ? network_home_url( '/' ) : home_url( '/' );
45 return openstation_network_member_id( $url ) === openstation_network_member_id( $self );
46 }
47
48 /**
49 * Every member, keyed by id, in the order added. Entries missing what a
50 * member needs are dropped on read.
51 *
52 * @return array<string,array<string,mixed>>
53 */
54 function openstation_network_members() {
55 $stored = openstation_network_option_get( OPENSTATION_NETWORK_MEMBERS_OPTION, array() );
56 $members = array();
57 if ( ! is_array( $stored ) ) {
58 return $members;
59 }
60 foreach ( $stored as $member ) {
61 if ( ! is_array( $member ) || empty( $member['id'] ) || empty( $member['url'] ) || empty( $member['publicKey'] ) ) {
62 continue;
63 }
64 $members[ (string) $member['id'] ] = array(
65 'id' => (string) $member['id'],
66 'url' => (string) $member['url'],
67 'name' => isset( $member['name'] ) ? (string) $member['name'] : (string) $member['url'],
68 'shellUrl' => isset( $member['shellUrl'] ) ? (string) $member['shellUrl'] : '',
69 'publicKey' => (string) $member['publicKey'],
70 'status' => isset( $member['status'] ) ? (string) $member['status'] : 'paired',
71 'error' => isset( $member['error'] ) ? (string) $member['error'] : '',
72 'added' => isset( $member['added'] ) ? (int) $member['added'] : 0,
73 'checked' => isset( $member['checked'] ) ? (int) $member['checked'] : 0,
74 );
75 }
76 return $members;
77 }
78
79 /**
80 * Persist the registry.
81 *
82 * @param array<string,array<string,mixed>> $members Members keyed by id.
83 * @return bool
84 */
85 function openstation_network_save_members( array $members ) {
86 return openstation_network_option_set( OPENSTATION_NETWORK_MEMBERS_OPTION, array_values( $members ) );
87 }
88
89 /**
90 * Add an install to the network: fetch its identity, pin its key.
91 *
92 * @param string $url The install's URL, as typed.
93 * @return array<string,mixed>|WP_Error The member, or why not.
94 */
95 function openstation_network_add_member( $url ) {
96 $url = esc_url_raw( trim( (string) $url ) );
97 if ( '' === $url || ! wp_parse_url( $url, PHP_URL_HOST ) ) {
98 return new WP_Error( 'openstation_network_bad_url', __( 'Enter the site\'s address, starting with https://.', 'desktop-mode' ) );
99 }
100 if ( openstation_network_url_is_self( $url ) ) {
101 return new WP_Error( 'openstation_network_self', __( 'That is this network itself.', 'desktop-mode' ) );
102 }
103 $identity = openstation_network_fetch_identity( $url );
104 if ( is_wp_error( $identity ) ) {
105 return $identity;
106 }
107 if ( $identity['multisite'] ) {
108 return new WP_Error( 'openstation_network_is_network', __( 'That site is a network of its own; a network cannot join another yet.', 'desktop-mode' ) );
109 }
110 $members = openstation_network_members();
111 $id = openstation_network_member_id( $identity['url'] );
112 if ( isset( $members[ $id ] ) ) {
113 return new WP_Error( 'openstation_network_exists', __( 'That site is already in the network.', 'desktop-mode' ) );
114 }
115 $members[ $id ] = array(
116 'id' => $id,
117 'url' => $identity['url'],
118 'name' => $identity['name'],
119 'shellUrl' => $identity['shellUrl'],
120 'publicKey' => $identity['publicKey'],
121 'status' => 'paired',
122 'error' => '',
123 'added' => time(),
124 'checked' => time(),
125 );
126 openstation_network_save_members( $members );
127 return $members[ $id ];
128 }
129
130 /**
131 * Remove a member.
132 *
133 * @param string $id Member id.
134 * @return bool Whether there was one to remove.
135 */
136 function openstation_network_remove_member( $id ) {
137 $members = openstation_network_members();
138 if ( ! isset( $members[ $id ] ) ) {
139 return false;
140 }
141 unset( $members[ $id ] );
142 openstation_network_save_members( $members );
143 return true;
144 }
145
146 /**
147 * Re-check one member against its live identity: the name and shell
148 * follow it, the key is only compared. Unreachable and key-changed are
149 * recorded as statuses the registry shows, not as removals.
150 *
151 * @param string $id Member id.
152 * @return array<string,mixed>|null The member as re-checked, or null when unknown.
153 */
154 function openstation_network_check_member( $id ) {
155 $members = openstation_network_members();
156 if ( ! isset( $members[ $id ] ) ) {
157 return null;
158 }
159 $member = $members[ $id ];
160 $identity = openstation_network_fetch_identity( $member['url'] );
161 if ( is_wp_error( $identity ) ) {
162 $member['status'] = 'unreachable';
163 $member['error'] = $identity->get_error_message();
164 } elseif ( $identity['publicKey'] !== $member['publicKey'] ) {
165 $member['status'] = 'key-changed';
166 $member['error'] = __( 'The site now publishes a different key. Remove it and add it again if that is expected.', 'desktop-mode' );
167 } else {
168 $member['status'] = 'paired';
169 $member['error'] = '';
170 $member['name'] = $identity['name'];
171 $member['shellUrl'] = $identity['shellUrl'];
172 }
173 $member['checked'] = time();
174 $members[ $id ] = $member;
175 openstation_network_save_members( $members );
176 return $member;
177 }
178
179 /**
180 * Re-check every member.
181 *
182 * @return array<string,array<string,mixed>>
183 */
184 function openstation_network_check_members() {
185 foreach ( array_keys( openstation_network_members() ) as $id ) {
186 openstation_network_check_member( $id );
187 }
188 return openstation_network_members();
189 }
190
191 /**
192 * The member whose pinned key is this one, or null. Only the pinned key
193 * counts: a member whose key changed still signs with the old one until
194 * an admin re-pairs it, and nothing signed with the new one is trusted.
195 *
196 * @param string $public_key Base64 public key.
197 * @return array<string,mixed>|null
198 */
199 function openstation_network_member_by_key( $public_key ) {
200 if ( '' === (string) $public_key ) {
201 return null;
202 }
203 foreach ( openstation_network_members() as $member ) {
204 if ( hash_equals( $member['publicKey'], (string) $public_key ) ) {
205 return $member;
206 }
207 }
208 return null;
209 }
210