tigerstyle-scent/Client/OAuth2ClientManager.php
Ryan Malloy 1da0acd25a Initial commit: WordPress OAuth2 Server with PSR-4 architecture
- Implements complete OAuth2 authorization server for WordPress
- PSR-4 autoloading with WPOAuth2Server namespace structure
- Modular architecture with Auth, Client, Core, Storage components
- Successfully tested authorization code flow with bearer authentication
- Clean separation from WordPress plugin code for reusability
2025-09-16 20:53:00 -06:00

304 lines
9.5 KiB
PHP

<?php
/**
* OAuth2 Client Management
*
* Handles OAuth2 client registration and management
*
* @package WordPress OAuth2 PoC
*/
namespace WPOAuth2Server\Client;
defined('ABSPATH') or die('Direct access forbidden.');
class OAuth2ClientManager {
/**
* WordPress database instance
* @var \wpdb
*/
private \wpdb $wpdb;
/**
* Constructor
*/
public function __construct() {
global $wpdb;
$this->wpdb = $wpdb;
}
/**
* Create a new OAuth2 client
*/
public function create_client(array $client_data): array {
// Validate required fields
$required_fields = ['client_name', 'redirect_uri'];
foreach ($required_fields as $field) {
if (empty($client_data[$field])) {
throw new \InvalidArgumentException("Missing required field: {$field}");
}
}
// Generate client ID and secret
$client_id = $this->generate_client_id();
$client_secret = $this->generate_client_secret();
// Sanitize and prepare data
$insert_data = [
'client_id' => $client_id,
'client_secret' => hash('sha256', $client_secret), // Hash the secret for storage
'client_name' => sanitize_text_field($client_data['client_name']),
'redirect_uri' => esc_url_raw($client_data['redirect_uri']),
'grant_types' => sanitize_text_field($client_data['grant_types'] ?? 'authorization_code'),
'scope' => sanitize_text_field($client_data['scope'] ?? 'basic'),
'user_id' => get_current_user_id(),
'is_public' => !empty($client_data['is_public']) ? 1 : 0,
];
// Insert into database
$result = $this->wpdb->insert(
$this->wpdb->prefix . 'oauth2_clients',
$insert_data,
['%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d']
);
if ($result === false) {
throw new \Exception('Failed to create OAuth2 client');
}
// Return client information (including plain text secret)
return [
'client_id' => $client_id,
'client_secret' => $client_secret, // Return plain text secret (only time it's available)
'client_name' => $insert_data['client_name'],
'redirect_uri' => $insert_data['redirect_uri'],
'grant_types' => $insert_data['grant_types'],
'scope' => $insert_data['scope'],
'is_public' => (bool) $insert_data['is_public'],
];
}
/**
* Get all clients for current user
*/
public function get_user_clients(int $user_id = null): array {
if ($user_id === null) {
$user_id = get_current_user_id();
}
$results = $this->wpdb->get_results(
$this->wpdb->prepare(
"SELECT client_id, client_name, redirect_uri, grant_types, scope, is_public, created_at
FROM {$this->wpdb->prefix}oauth2_clients
WHERE user_id = %d
ORDER BY created_at DESC",
$user_id
),
ARRAY_A
);
return $results ?: [];
}
/**
* Get client by ID
*/
public function get_client(string $client_id): ?array {
$result = $this->wpdb->get_row(
$this->wpdb->prepare(
"SELECT * FROM {$this->wpdb->prefix}oauth2_clients WHERE client_id = %s",
$client_id
),
ARRAY_A
);
return $result ?: null;
}
/**
* Delete client
*/
public function delete_client(string $client_id, int $user_id = null): bool {
if ($user_id === null) {
$user_id = get_current_user_id();
}
// Only allow deletion by client owner or admin
if (!current_user_can('manage_options')) {
$client = $this->get_client($client_id);
if (!$client || $client['user_id'] != $user_id) {
return false;
}
}
// Delete associated tokens and codes first
$this->cleanup_client_data($client_id);
// Delete client
$result = $this->wpdb->delete(
$this->wpdb->prefix . 'oauth2_clients',
['client_id' => $client_id],
['%s']
);
return $result !== false;
}
/**
* Clean up all data associated with a client
*/
private function cleanup_client_data(string $client_id): void {
// Delete access tokens
$this->wpdb->delete(
$this->wpdb->prefix . 'oauth2_access_tokens',
['client_id' => $client_id],
['%s']
);
// Delete refresh tokens
$this->wpdb->delete(
$this->wpdb->prefix . 'oauth2_refresh_tokens',
['client_id' => $client_id],
['%s']
);
// Delete authorization codes
$this->wpdb->delete(
$this->wpdb->prefix . 'oauth2_authorization_codes',
['client_id' => $client_id],
['%s']
);
}
/**
* Generate client ID
*/
private function generate_client_id(): string {
return 'client_' . bin2hex(random_bytes(16));
}
/**
* Generate client secret
*/
private function generate_client_secret(): string {
return bin2hex(random_bytes(32));
}
/**
* Update client information
*/
public function update_client(string $client_id, array $update_data, int $user_id = null): bool {
if ($user_id === null) {
$user_id = get_current_user_id();
}
// Only allow update by client owner or admin
if (!current_user_can('manage_options')) {
$client = $this->get_client($client_id);
if (!$client || $client['user_id'] != $user_id) {
return false;
}
}
// Prepare update data
$allowed_fields = ['client_name', 'redirect_uri', 'grant_types', 'scope', 'is_public'];
$update_values = [];
$update_format = [];
foreach ($allowed_fields as $field) {
if (isset($update_data[$field])) {
switch ($field) {
case 'client_name':
case 'grant_types':
case 'scope':
$update_values[$field] = sanitize_text_field($update_data[$field]);
$update_format[] = '%s';
break;
case 'redirect_uri':
$update_values[$field] = esc_url_raw($update_data[$field]);
$update_format[] = '%s';
break;
case 'is_public':
$update_values[$field] = !empty($update_data[$field]) ? 1 : 0;
$update_format[] = '%d';
break;
}
}
}
if (empty($update_values)) {
return false;
}
$update_values['updated_at'] = current_time('mysql');
$update_format[] = '%s';
$result = $this->wpdb->update(
$this->wpdb->prefix . 'oauth2_clients',
$update_values,
['client_id' => $client_id],
$update_format,
['%s']
);
return $result !== false;
}
/**
* Get statistics for admin dashboard
*/
public function get_statistics(): array {
$stats = [];
// Total clients
$stats['total_clients'] = $this->wpdb->get_var(
"SELECT COUNT(*) FROM {$this->wpdb->prefix}oauth2_clients"
);
// Active tokens (non-expired)
$stats['active_tokens'] = $this->wpdb->get_var(
"SELECT COUNT(*) FROM {$this->wpdb->prefix}oauth2_access_tokens WHERE expires > NOW()"
);
// Today's authorizations
$stats['todays_authorizations'] = $this->wpdb->get_var(
"SELECT COUNT(*) FROM {$this->wpdb->prefix}oauth2_authorization_codes
WHERE DATE(created_at) = CURDATE()"
);
// Client types
$stats['public_clients'] = $this->wpdb->get_var(
"SELECT COUNT(*) FROM {$this->wpdb->prefix}oauth2_clients WHERE is_public = 1"
);
$stats['confidential_clients'] = $this->wpdb->get_var(
"SELECT COUNT(*) FROM {$this->wpdb->prefix}oauth2_clients WHERE is_public = 0"
);
return array_map('intval', $stats);
}
/**
* Clean up expired tokens and codes
*/
public function cleanup_expired_data(): array {
$cleanup_stats = [];
// Clean expired access tokens
$cleanup_stats['access_tokens'] = $this->wpdb->query(
"DELETE FROM {$this->wpdb->prefix}oauth2_access_tokens WHERE expires < NOW()"
);
// Clean expired refresh tokens
$cleanup_stats['refresh_tokens'] = $this->wpdb->query(
"DELETE FROM {$this->wpdb->prefix}oauth2_refresh_tokens WHERE expires < NOW()"
);
// Clean expired authorization codes
$cleanup_stats['authorization_codes'] = $this->wpdb->query(
"DELETE FROM {$this->wpdb->prefix}oauth2_authorization_codes WHERE expires < NOW()"
);
return $cleanup_stats;
}
}