feat: 添加身份认证服务商配置页
This commit is contained in:
@@ -11,6 +11,7 @@ class UserController extends ControllerBase {
|
||||
use UserControllerReportOperations;
|
||||
use UserControllerInviteOperations;
|
||||
use UserControllerVerifyIntegrations;
|
||||
use UserControllerIdentitySettings;
|
||||
use UserControllerAuthPhoneOperations;
|
||||
use UserControllerRestrictionOperations;
|
||||
use UserControllerFollowOperations;
|
||||
@@ -260,6 +261,25 @@ class UserController extends ControllerBase {
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
]);
|
||||
|
||||
// Identity verification provider settings (admin only)
|
||||
register_rest_route(self::API_NAMESPACE, '/identity/settings', [
|
||||
[
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_identity_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
],
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_identity_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/identity/settings/test', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'test_identity_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// SMS provider status
|
||||
register_rest_route(self::API_NAMESPACE, '/sms/status', [
|
||||
'methods' => 'GET',
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin REST handlers for identity provider configuration.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Admin;
|
||||
|
||||
use AKSRT\Settings\IdentityAdmin;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait UserControllerIdentitySettings {
|
||||
public function get_identity_settings(): \WP_REST_Response {
|
||||
return $this->success([
|
||||
'data' => $this->identity_settings_response(IdentityAdmin::read()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update_identity_settings(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$settings = IdentityAdmin::update($this->get_json_params($request));
|
||||
|
||||
return $this->success([
|
||||
'message' => __('身份认证服务商配置已保存', 'aksrt'),
|
||||
'data' => $this->identity_settings_response($settings),
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_identity_settings(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$provider = sanitize_key((string) ($params['provider'] ?? ''));
|
||||
$result = IdentityAdmin::test($provider);
|
||||
|
||||
if (empty($result['success'])) {
|
||||
return $this->error((string) ($result['message'] ?? __('配置检查失败', 'aksrt')), 422);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => (string) $result['message'],
|
||||
'data' => ['provider' => $provider, 'configured' => true],
|
||||
]);
|
||||
}
|
||||
|
||||
private function identity_settings_response(array $settings): array {
|
||||
$settings['callback_url'] = rest_url(self::API_NAMESPACE . '/identity/callback');
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* Identity provider configuration storage for the admin REST surface.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Settings;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class IdentityAdmin {
|
||||
private const OPTION_KEY = 'aksrt_identity_settings';
|
||||
|
||||
private const PROVIDER_FIELDS = [
|
||||
'aliyun' => ['access_key_id', 'access_key_secret'],
|
||||
'tencent' => ['secret_id', 'secret_key'],
|
||||
];
|
||||
|
||||
public static function read(): array {
|
||||
$stored = get_option(self::OPTION_KEY, []);
|
||||
$stored = is_array($stored) ? $stored : [];
|
||||
$provider = sanitize_key((string) ($stored['provider'] ?? 'aliyun'));
|
||||
|
||||
if (!isset(self::PROVIDER_FIELDS[$provider])) {
|
||||
$provider = 'aliyun';
|
||||
}
|
||||
|
||||
return [
|
||||
'provider' => $provider,
|
||||
'providers' => [
|
||||
'aliyun' => self::provider_state('aliyun', $stored),
|
||||
'tencent' => self::provider_state('tencent', $stored),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public static function update(array $input): array {
|
||||
$stored = get_option(self::OPTION_KEY, []);
|
||||
$stored = is_array($stored) ? $stored : [];
|
||||
|
||||
if (array_key_exists('provider', $input)) {
|
||||
$provider = sanitize_key((string) $input['provider']);
|
||||
if (isset(self::PROVIDER_FIELDS[$provider])) {
|
||||
$stored['provider'] = $provider;
|
||||
}
|
||||
}
|
||||
|
||||
$provider_input = isset($input['providers']) && is_array($input['providers'])
|
||||
? $input['providers']
|
||||
: [];
|
||||
|
||||
foreach (self::PROVIDER_FIELDS as $provider => $fields) {
|
||||
$changes = isset($provider_input[$provider]) && is_array($provider_input[$provider])
|
||||
? $provider_input[$provider]
|
||||
: [];
|
||||
|
||||
if (!empty($changes['clear_credentials'])) {
|
||||
unset($stored['providers'][$provider]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$credentials = isset($changes['credentials']) && is_array($changes['credentials'])
|
||||
? $changes['credentials']
|
||||
: [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
if (!isset($credentials[$field]) || trim((string) $credentials[$field]) === '') {
|
||||
continue;
|
||||
}
|
||||
$stored['providers'][$provider][$field] = self::encrypt(
|
||||
sanitize_text_field((string) $credentials[$field])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
update_option(self::OPTION_KEY, $stored, false);
|
||||
return self::read();
|
||||
}
|
||||
|
||||
public static function test(string $provider): array {
|
||||
$settings = self::read();
|
||||
if (!isset(self::PROVIDER_FIELDS[$provider])) {
|
||||
return ['success' => false, 'message' => '不支持的身份认证服务商'];
|
||||
}
|
||||
|
||||
$state = $settings['providers'][$provider];
|
||||
if (!$state['configured']) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => sprintf('%s凭证不完整,请补充后保存', $state['label']),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => sprintf('%s配置检查通过,凭证字段完整', $state['label']),
|
||||
];
|
||||
}
|
||||
|
||||
private static function encrypt(string $value): string {
|
||||
if ($value === '' || !function_exists('openssl_encrypt')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$key = hash('sha256', wp_salt('auth'), true);
|
||||
$iv = random_bytes(12);
|
||||
$tag = '';
|
||||
$ciphertext = openssl_encrypt($value, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
|
||||
if ($ciphertext === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return base64_encode($iv . $tag . $ciphertext);
|
||||
}
|
||||
|
||||
private static function decrypt(string $encoded): string {
|
||||
if ($encoded === '' || !function_exists('openssl_decrypt')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$payload = base64_decode($encoded, true);
|
||||
if ($payload === false || strlen($payload) <= 28) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$iv = substr($payload, 0, 12);
|
||||
$tag = substr($payload, 12, 16);
|
||||
$ciphertext = substr($payload, 28);
|
||||
$key = hash('sha256', wp_salt('auth'), true);
|
||||
$plaintext = openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
|
||||
|
||||
return is_string($plaintext) ? $plaintext : '';
|
||||
}
|
||||
|
||||
private static function provider_state(string $provider, array $stored): array {
|
||||
$credentials = isset($stored['providers'][$provider]) && is_array($stored['providers'][$provider])
|
||||
? $stored['providers'][$provider]
|
||||
: [];
|
||||
$missing = [];
|
||||
$configured_fields = [];
|
||||
|
||||
foreach (self::PROVIDER_FIELDS[$provider] as $field) {
|
||||
$has_value = self::decrypt((string) ($credentials[$field] ?? '')) !== '';
|
||||
$configured_fields[$field] = $has_value;
|
||||
if (!$has_value) {
|
||||
$missing[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'label' => $provider === 'aliyun' ? '阿里云' : '腾讯云',
|
||||
'configured' => $missing === [],
|
||||
'configured_fields' => $configured_fields,
|
||||
'missing_fields' => $missing,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user