mirror of
https://github.com/Heretek-AI/heretek-control-core.git
synced 2026-10-12 02:03:55 +00:00
feat(oauth): show every client registration, and let an admin remove one
Prompted by a support ticket where a client kept failing with "the return address this app asked for does not match the one it registered". The server was right to refuse (RFC 8252 relaxes the PORT for a loopback redirect, never the path), but the site owner had no way to see the stale registration, let alone clear it, and nothing on the client side would drop it either. Connected apps listed only clients holding a live token, because list_authorized_clients() INNER JOINs the token table. Its own copy admitted it: "An app that has registered but not finished signing in is not listed." And Revoke deleted tokens while leaving the registration, which then survived gc() forever because authorized_at was set. - list_clients() LEFT JOINs, so a registration with no live token appears. Connected rows sort first so the useful ones stay on page 1. - client_state() names the three cases: connected, signed out (authorized once, can sign back in), never signed in. - delete_client() removes the registration and its tokens together. Tokens first: bearer validation reads the token table alone, so one outliving its client row would keep working. - The panel shows each registration's callback under its name, which is the field someone comes here to compare against a refused authorization, plus status and registration date. Sign out (when connected) and Remove (confirm-gated), both manage_options with a per-client nonce. Paged at 20. Also fixes a silent action: revoke redirected with oauth_revoked=1 but nothing rendered it, so signing an app out appeared to do nothing. All three outcomes now show a notice. Live-verified on msrplugins.test with three seeded registrations: cancelling the confirm changed nothing, accepting removed the row, and Sign out flipped a connected app to Signed out while keeping its registration. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b7cfe45d58
commit
bee16487dd
@@ -4826,3 +4826,31 @@ body[data-emcp-auth="app-password"] [data-authfor="oauth"] { display: none; }
|
||||
font-size: 12px;
|
||||
color: #787c82;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* OAuth client registrations (Connection tab > Connected apps)
|
||||
*
|
||||
* The callback address is the field people come to this table to compare
|
||||
* against a refused authorization, so it gets its own line under the name
|
||||
* rather than a title attribute.
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
.emcp-oauth-clients__uri {
|
||||
display: block;
|
||||
max-width: 440px;
|
||||
margin-top: 4px;
|
||||
padding: 2px 6px;
|
||||
font-size: 11px;
|
||||
color: #50575e;
|
||||
background: #f6f7f7;
|
||||
border-radius: 4px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.emcp-oauth-clients__actions {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.emcp-oauth-clients__actions .button {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
@@ -307,6 +307,7 @@ class EMCP_Tools_Admin {
|
||||
add_action( 'admin_post_' . self::ACTION_DELETE_CHANGE, array( $this, 'handle_delete_change' ) );
|
||||
add_action( 'admin_post_' . self::ACTION_CLEAR_CHANGES, array( $this, 'handle_clear_changes' ) );
|
||||
add_action( 'admin_post_' . self::ACTION_REVOKE_OAUTH, array( $this, 'handle_revoke_oauth_client' ) );
|
||||
add_action( 'admin_post_' . self::ACTION_DELETE_OAUTH_CLIENT, array( $this, 'handle_delete_oauth_client' ) );
|
||||
add_action( 'admin_post_' . self::ACTION_EXPORT_ARTIFACT, array( $this, 'handle_export_artifact' ) );
|
||||
add_action( 'admin_post_' . self::ACTION_IMPORT_ARTIFACT, array( $this, 'handle_import_artifact' ) );
|
||||
add_action( 'admin_post_emcp_tools_settings_push', array( $this, 'handle_settings_push' ) );
|
||||
@@ -348,6 +349,18 @@ class EMCP_Tools_Admin {
|
||||
*/
|
||||
const ACTION_REVOKE_OAUTH = 'emcp_tools_revoke_oauth_client';
|
||||
|
||||
/**
|
||||
* Delete an OAuth client registration outright (tokens included).
|
||||
*
|
||||
* Distinct from ACTION_REVOKE_OAUTH, which signs an app out but keeps the
|
||||
* registration so it can sign back in. This one is for a registration that
|
||||
* can never be used again, typically because the app now asks for a
|
||||
* different callback than the one it registered.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*/
|
||||
const ACTION_DELETE_OAUTH_CLIENT = 'emcp_tools_delete_oauth_client';
|
||||
|
||||
/**
|
||||
* Nonce-protected URL that rolls back one change-ledger entry.
|
||||
*
|
||||
@@ -1167,7 +1180,43 @@ class EMCP_Tools_Admin {
|
||||
EMCP_Tools_OAuth_Store::revoke_client( $client_id );
|
||||
}
|
||||
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG . '-connection&oauth_revoked=1#emcp-conn-main' ) );
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG . '-connection&oauth_revoked=1#emcp-conn-manage-apps' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an OAuth client registration and every token issued to it.
|
||||
*
|
||||
* The recovery path for a registration an app can no longer use: it asks to
|
||||
* come back at a different callback than the one it registered, so every
|
||||
* authorization attempt is refused and nothing on the client side clears it.
|
||||
* Removing the row here means the next connection attempt registers afresh.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*/
|
||||
public function handle_delete_oauth_client(): void {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to do that.', 'emcp-tools' ), '', array( 'response' => 403 ) );
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce verified just below against the per-client action.
|
||||
$client_id = isset( $_GET['client'] ) ? sanitize_text_field( wp_unslash( $_GET['client'] ) ) : '';
|
||||
check_admin_referer( self::ACTION_DELETE_OAUTH_CLIENT . '_' . $client_id );
|
||||
|
||||
if ( '' !== $client_id && class_exists( 'EMCP_Tools_Gateway_Credential' ) ) {
|
||||
// Same ordering as the revoke path: the teardown wants to see the
|
||||
// token count before the tokens go.
|
||||
EMCP_Tools_Gateway_Credential::handle_client_revoked( $client_id );
|
||||
}
|
||||
|
||||
$removed = ( '' !== $client_id && class_exists( 'EMCP_Tools_OAuth_Store' ) )
|
||||
? EMCP_Tools_OAuth_Store::delete_client( $client_id )
|
||||
: false;
|
||||
|
||||
wp_safe_redirect(
|
||||
admin_url(
|
||||
'admin.php?page=' . self::PAGE_SLUG . '-connection&oauth_removed=' . ( $removed ? '1' : '0' ) . '#emcp-conn-manage-apps'
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -312,54 +312,163 @@ $emcp_tools_server_enabled = class_exists( 'EMCP_Tools_Plugin' )
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php // ===== Connected apps (authorized OAuth clients) ===== ?>
|
||||
<?php // ===== Connected apps (every OAuth client registration) ===== ?>
|
||||
<?php
|
||||
$emcp_oauth_clients = ( $emcp_oauth_ok && class_exists( 'EMCP_Tools_OAuth_Store' ) ) ? EMCP_Tools_OAuth_Store::list_authorized_clients() : array();
|
||||
// Rendered whenever OAuth is available, even with nothing connected yet.
|
||||
// The "Manage connected apps" link above targets this element, so hiding
|
||||
// it on an empty list made that link silently do nothing, which is exactly
|
||||
// when someone is most likely to click it: while a connection is failing.
|
||||
// Registrations, not just live connections. A client that registered and
|
||||
// never finished signing in is precisely the row someone needs when a
|
||||
// connection keeps failing, and the old list (live tokens only) could
|
||||
// never show it.
|
||||
$emcp_oauth_store = $emcp_oauth_ok && class_exists( 'EMCP_Tools_OAuth_Store' );
|
||||
$emcp_oauth_per = 20;
|
||||
$emcp_oauth_total = $emcp_oauth_store ? EMCP_Tools_OAuth_Store::count_clients() : 0;
|
||||
$emcp_oauth_page = class_exists( 'EMCP_Tools_Admin_Pager' ) ? EMCP_Tools_Admin_Pager::current( 'oauth_page' ) : 1;
|
||||
$emcp_oauth_pages = max( 1, (int) ceil( $emcp_oauth_total / $emcp_oauth_per ) );
|
||||
$emcp_oauth_page = min( $emcp_oauth_page, $emcp_oauth_pages );
|
||||
$emcp_oauth_clients = $emcp_oauth_store
|
||||
? EMCP_Tools_OAuth_Store::list_clients( $emcp_oauth_per, ( $emcp_oauth_page - 1 ) * $emcp_oauth_per )
|
||||
: array();
|
||||
|
||||
$emcp_oauth_href = static function ( $emcp_n ) {
|
||||
$args = array(
|
||||
'page' => EMCP_Tools_Admin::PAGE_SLUG . '-connection',
|
||||
);
|
||||
if ( $emcp_n > 1 ) {
|
||||
$args['oauth_page'] = (int) $emcp_n;
|
||||
}
|
||||
return add_query_arg( $args, admin_url( 'admin.php' ) ) . '#emcp-conn-manage-apps';
|
||||
};
|
||||
|
||||
$emcp_oauth_states = array(
|
||||
'connected' => array( __( 'Connected', 'emcp-tools' ), '#007017' ),
|
||||
'signed_out' => array( __( 'Signed out', 'emcp-tools' ), '#996800' ),
|
||||
'registered' => array( __( 'Never signed in', 'emcp-tools' ), '#646970' ),
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice after a redirect.
|
||||
$emcp_oauth_removed = isset( $_GET['oauth_removed'] ) ? sanitize_key( wp_unslash( $_GET['oauth_removed'] ) ) : '';
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice after a redirect.
|
||||
$emcp_oauth_did_revoke = isset( $_GET['oauth_revoked'] );
|
||||
|
||||
if ( $emcp_oauth_ok ) :
|
||||
?>
|
||||
<div class="elementor-mcp-section" id="emcp-conn-manage-apps" data-authfor="oauth">
|
||||
<h2><?php esc_html_e( 'Connected apps', 'emcp-tools' ); ?></h2>
|
||||
|
||||
<?php if ( $emcp_oauth_did_revoke ) : ?>
|
||||
<div class="notice notice-success inline"><p><?php esc_html_e( 'App signed out. Its registration is kept, so it can sign in again.', 'emcp-tools' ); ?></p></div>
|
||||
<?php elseif ( '1' === $emcp_oauth_removed ) : ?>
|
||||
<div class="notice notice-success inline"><p><?php esc_html_e( 'Registration removed. The next time that app connects it will register again.', 'emcp-tools' ); ?></p></div>
|
||||
<?php elseif ( '0' === $emcp_oauth_removed ) : ?>
|
||||
<div class="notice notice-error inline"><p><?php esc_html_e( 'That registration was already gone.', 'emcp-tools' ); ?></p></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( empty( $emcp_oauth_clients ) ) : ?>
|
||||
<p class="description" style="max-width:760px;">
|
||||
<?php esc_html_e( 'No apps are connected yet. An app appears here once it has completed sign-in and holds a valid token. An app that has registered but not finished signing in is not listed.', 'emcp-tools' ); ?>
|
||||
<?php esc_html_e( 'Nothing has registered yet. Apps appear here as soon as they register, before they finish signing in.', 'emcp-tools' ); ?>
|
||||
</p>
|
||||
<?php else : ?>
|
||||
<table class="widefat striped" style="max-width:760px;">
|
||||
<p class="description" style="max-width:760px;">
|
||||
<?php esc_html_e( 'Every app that has registered with this site. Sign one out to end its session but let it back in, or remove its registration when it can no longer use it, for example when it asks to return to a different address than the one it registered.', 'emcp-tools' ); ?>
|
||||
</p>
|
||||
<?php if ( class_exists( 'EMCP_Tools_Admin_Pager' ) ) : ?>
|
||||
<p class="description emcp-pager-count">
|
||||
<?php echo EMCP_Tools_Admin_Pager::summary( $emcp_oauth_page, $emcp_oauth_per, count( $emcp_oauth_clients ), $emcp_oauth_total ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped by summary(). ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<table class="widefat striped emcp-oauth-clients" style="max-width:900px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Client', 'emcp-tools' ); ?></th>
|
||||
<th><?php esc_html_e( 'Status', 'emcp-tools' ); ?></th>
|
||||
<th><?php esc_html_e( 'Connected as', 'emcp-tools' ); ?></th>
|
||||
<th><?php esc_html_e( 'Active tokens', 'emcp-tools' ); ?></th>
|
||||
<th><?php esc_html_e( 'Registered', 'emcp-tools' ); ?></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ( $emcp_oauth_clients as $emcp_oc ) :
|
||||
$emcp_oc_user = get_userdata( $emcp_oc['user_id'] );
|
||||
$emcp_revoke = wp_nonce_url(
|
||||
$emcp_oc_state = EMCP_Tools_OAuth_Store::client_state( $emcp_oc );
|
||||
$emcp_oc_label = $emcp_oauth_states[ $emcp_oc_state ] ?? $emcp_oauth_states['registered'];
|
||||
$emcp_oc_user = $emcp_oc['user_id'] ? get_userdata( $emcp_oc['user_id'] ) : null;
|
||||
$emcp_revoke = wp_nonce_url(
|
||||
admin_url( 'admin-post.php?action=' . EMCP_Tools_Admin::ACTION_REVOKE_OAUTH . '&client=' . rawurlencode( $emcp_oc['client_id'] ) ),
|
||||
EMCP_Tools_Admin::ACTION_REVOKE_OAUTH . '_' . $emcp_oc['client_id']
|
||||
);
|
||||
$emcp_remove = wp_nonce_url(
|
||||
admin_url( 'admin-post.php?action=' . EMCP_Tools_Admin::ACTION_DELETE_OAUTH_CLIENT . '&client=' . rawurlencode( $emcp_oc['client_id'] ) ),
|
||||
EMCP_Tools_Admin::ACTION_DELETE_OAUTH_CLIENT . '_' . $emcp_oc['client_id']
|
||||
);
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php echo esc_html( $emcp_oc['client_name'] ); ?>
|
||||
<strong><?php echo esc_html( $emcp_oc['client_name'] ); ?></strong>
|
||||
<?php if ( class_exists( 'EMCP_Tools_Gateway_Credential' ) && EMCP_Tools_Gateway_Credential::CLIENT_NAME === $emcp_oc['client_name'] ) : ?>
|
||||
<span class="description" style="display:block;"><?php esc_html_e( 'Used to manage this site from EMCP Cloud across your other sites.', 'emcp-tools' ); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
// The callback is the whole point of this table: when an
|
||||
// authorization is refused, the address on the error page
|
||||
// is compared against this one.
|
||||
foreach ( $emcp_oc['redirect_uris'] as $emcp_oc_uri ) :
|
||||
?>
|
||||
<code class="emcp-oauth-clients__uri"><?php echo esc_html( $emcp_oc_uri ); ?></code>
|
||||
<?php endforeach; ?>
|
||||
</td>
|
||||
<td>
|
||||
<span style="color:<?php echo esc_attr( $emcp_oc_label[1] ); ?>;">
|
||||
<?php echo esc_html( $emcp_oc_label[0] ); ?>
|
||||
</span>
|
||||
<?php if ( 'connected' === $emcp_oc_state ) : ?>
|
||||
<span class="description" style="display:block;">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %d: number of active tokens. */
|
||||
esc_html( _n( '%d active token', '%d active tokens', $emcp_oc['active_tokens'], 'emcp-tools' ) ),
|
||||
(int) $emcp_oc['active_tokens']
|
||||
);
|
||||
?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo $emcp_oc_user ? esc_html( $emcp_oc_user->user_login ) : '<span class="description">—</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- both branches escaped. ?></td>
|
||||
<td>
|
||||
<?php
|
||||
echo $emcp_oc['created_at']
|
||||
? esc_html( date_i18n( (string) get_option( 'date_format' ), $emcp_oc['created_at'] ) )
|
||||
: '—';
|
||||
?>
|
||||
</td>
|
||||
<td class="emcp-oauth-clients__actions">
|
||||
<?php if ( 'connected' === $emcp_oc_state ) : ?>
|
||||
<a href="<?php echo esc_url( $emcp_revoke ); ?>" class="button button-small"><?php esc_html_e( 'Sign out', 'emcp-tools' ); ?></a>
|
||||
<?php endif; ?>
|
||||
<a
|
||||
href="<?php echo esc_url( $emcp_remove ); ?>"
|
||||
class="button button-small emcp-oauth-clients__remove"
|
||||
data-emcp-confirm="<?php esc_attr_e( 'Remove this registration? The app will have to register again the next time it connects.', 'emcp-tools' ); ?>"
|
||||
><?php esc_html_e( 'Remove', 'emcp-tools' ); ?></a>
|
||||
</td>
|
||||
<td><?php echo esc_html( $emcp_oc_user ? $emcp_oc_user->user_login : '#' . (int) $emcp_oc['user_id'] ); ?></td>
|
||||
<td><?php echo (int) $emcp_oc['active_tokens']; ?></td>
|
||||
<td><a href="<?php echo esc_url( $emcp_revoke ); ?>" class="button button-small"><?php esc_html_e( 'Revoke', 'emcp-tools' ); ?></a></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
if ( class_exists( 'EMCP_Tools_Admin_Pager' ) ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped by render().
|
||||
echo EMCP_Tools_Admin_Pager::render( $emcp_oauth_page, $emcp_oauth_pages, $emcp_oauth_href );
|
||||
}
|
||||
?>
|
||||
<script>
|
||||
( function () {
|
||||
document.querySelectorAll( '.emcp-oauth-clients__remove' ).forEach( function ( link ) {
|
||||
link.addEventListener( 'click', function ( e ) {
|
||||
/* global confirm */
|
||||
if ( ! confirm( link.getAttribute( 'data-emcp-confirm' ) ) ) { e.preventDefault(); }
|
||||
} );
|
||||
} );
|
||||
} )();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -430,6 +430,126 @@ class EMCP_Tools_OAuth_Store {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Every registration, live token or not, for the admin Connected apps table.
|
||||
*
|
||||
* list_authorized_clients() INNER JOINs live tokens, so it answers "who is
|
||||
* connected". This answers "what has registered", which is the question you
|
||||
* have when a connection is failing: a client that registered and never
|
||||
* finished signing in, or one whose tokens have lapsed, is invisible to the
|
||||
* other list and is exactly the row someone needs to see.
|
||||
*
|
||||
* Connected rows sort first, then newest, so the useful ones stay on page 1
|
||||
* however many dead registrations sit behind them. client_id breaks the
|
||||
* remaining ties: registrations made in the same second sort equal, and an
|
||||
* order that is not total can hand the same row to two pages and hide
|
||||
* another, which is a thing MySQL is allowed to do under LIMIT/OFFSET.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*
|
||||
* @param int $limit Rows to return.
|
||||
* @param int $offset Rows to skip.
|
||||
* @return array<int,array{client_id:string,client_name:string,redirect_uris:string[],created_at:int,authorized_at:int,user_id:int,active_tokens:int}>
|
||||
*/
|
||||
public static function list_clients( int $limit = 20, int $offset = 0 ): array {
|
||||
global $wpdb;
|
||||
$clients = self::clients_table();
|
||||
$tokens = self::tokens_table();
|
||||
|
||||
$rows = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT c.client_id, c.client_name, c.redirect_uris, c.created_at, c.authorized_at,
|
||||
COUNT( t.id ) AS active_tokens,
|
||||
MAX( t.user_id ) AS user_id
|
||||
FROM {$clients} c
|
||||
LEFT JOIN {$tokens} t
|
||||
ON t.client_id = c.client_id AND t.expires_at > %d
|
||||
GROUP BY c.client_id, c.client_name, c.redirect_uris, c.created_at, c.authorized_at
|
||||
ORDER BY active_tokens DESC, c.created_at DESC, c.client_id DESC
|
||||
LIMIT %d OFFSET %d",
|
||||
time(),
|
||||
max( 1, $limit ),
|
||||
max( 0, $offset )
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
if ( ! is_array( $rows ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_map(
|
||||
static function ( $r ) {
|
||||
$uris = json_decode( (string) ( $r['redirect_uris'] ?? '' ), true );
|
||||
return array(
|
||||
'client_id' => (string) $r['client_id'],
|
||||
'client_name' => (string) $r['client_name'],
|
||||
'redirect_uris' => is_array( $uris ) ? $uris : array(),
|
||||
'created_at' => (int) $r['created_at'],
|
||||
'authorized_at' => (int) $r['authorized_at'],
|
||||
'user_id' => (int) $r['user_id'],
|
||||
'active_tokens' => (int) $r['active_tokens'],
|
||||
);
|
||||
},
|
||||
$rows
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* How many registrations exist, for the pager.
|
||||
*
|
||||
* @since 3.15.0
|
||||
* @return int
|
||||
*/
|
||||
public static function count_clients(): int {
|
||||
global $wpdb;
|
||||
return (int) $wpdb->get_var( 'SELECT COUNT(*) FROM ' . self::clients_table() );
|
||||
}
|
||||
|
||||
/**
|
||||
* What state a row from list_clients() is in.
|
||||
*
|
||||
* Three cases, and telling them apart is the whole reason the table exists:
|
||||
* a client holding a live token is connected; one that authorized in the
|
||||
* past but holds none has simply signed out and can sign back in; one that
|
||||
* never authorized at all is a registration the app made and abandoned,
|
||||
* which is what a failing connection leaves behind.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*
|
||||
* @param array $row Row from list_clients().
|
||||
* @return string 'connected'|'signed_out'|'registered'
|
||||
*/
|
||||
public static function client_state( array $row ): string {
|
||||
if ( (int) ( $row['active_tokens'] ?? 0 ) > 0 ) {
|
||||
return 'connected';
|
||||
}
|
||||
return (int) ( $row['authorized_at'] ?? 0 ) > 0 ? 'signed_out' : 'registered';
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a registration outright, tokens included.
|
||||
*
|
||||
* revoke_client() deletes tokens and leaves the registration, which is right
|
||||
* for "sign this app out": the client_id keeps working and the app can sign
|
||||
* in again. This is the other thing, for a registration that can never be
|
||||
* used again, and it has to take the tokens with it. Bearer validation reads
|
||||
* the token table alone, so a token outliving its client row would keep
|
||||
* authenticating against a client that no longer exists.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*
|
||||
* @param string $client_id Client id.
|
||||
* @return bool Whether a registration row was removed.
|
||||
*/
|
||||
public static function delete_client( string $client_id ): bool {
|
||||
if ( '' === $client_id ) {
|
||||
return false;
|
||||
}
|
||||
global $wpdb;
|
||||
self::revoke_client( $client_id );
|
||||
return (bool) $wpdb->delete( self::clients_table(), array( 'client_id' => $client_id ), array( '%s' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Housekeeping: delete expired tokens, then delete orphan client rows (no
|
||||
* tokens and older than the grace window). Runs on the daily cron and
|
||||
|
||||
+1
-1
Submodule pro updated: f0f4351eac...67f93afb44
Reference in New Issue
Block a user