-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathclass-wp-codebox-connector-credential-resolvers.php
More file actions
165 lines (141 loc) · 6.05 KB
/
Copy pathclass-wp-codebox-connector-credential-resolvers.php
File metadata and controls
165 lines (141 loc) · 6.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
<?php
/**
* Connector credential resolver registry.
*
* @package WPCodebox
*/
defined( 'ABSPATH' ) || exit;
final class WP_Codebox_Connector_Credential_Resolvers {
/** @param array<string,mixed> $resolution Existing inheritance resolution. @param array{connectors:string[],settings:string[]} $request Requested inheritance. @param array<string,mixed> $input Ability input. */
public static function resolve( array $resolution, array $request, array $input ): array {
$requested = array_values( array_unique( array_filter( array_map( 'strval', $request['connectors'] ?? array() ) ) ) );
if ( empty( $requested ) ) {
return $resolution;
}
if ( ! function_exists( 'apply_filters' ) ) {
return $resolution;
}
/**
* Registers generic connector credential resolvers.
*
* Resolver entries may be keyed by connector name. Each resolver can be a
* callable returning a connector row, or a spec array with provider, model,
* secret_env, secret_values, and secret_sources. Raw secret values are kept
* only in secret_env_values for the parent process and are omitted from the
* sanitized inheritance payload returned to callers.
*
* @param array<string,mixed> $resolvers Connector resolver map.
* @param array{connectors:string[],settings:string[]} $request Requested inheritance.
* @param array<string,mixed> $input Ability input.
*/
$resolvers = apply_filters( 'wp_codebox_connector_credential_resolvers', array(), $request, $input );
if ( ! is_array( $resolvers ) || empty( $resolvers ) ) {
return $resolution;
}
foreach ( $requested as $connector_name ) {
$connector = self::resolve_connector( $connector_name, $resolvers, $request, $input );
if ( empty( $connector ) || is_wp_error( $connector ) ) {
continue;
}
$resolution['connectors'] = self::replace_connector( is_array( $resolution['connectors'] ?? null ) ? $resolution['connectors'] : array(), $connector_name, $connector );
}
return $resolution;
}
/** @param array<string,mixed> $resolvers Resolver registry. @param array{connectors:string[],settings:string[]} $request Requested inheritance. @param array<string,mixed> $input Ability input. @return array<string,mixed>|WP_Error|null */
private static function resolve_connector( string $connector_name, array $resolvers, array $request, array $input ): array|WP_Error|null {
$resolver = $resolvers[ $connector_name ] ?? null;
if ( null === $resolver ) {
foreach ( $resolvers as $candidate ) {
if ( is_array( $candidate ) && $connector_name === (string) ( $candidate['name'] ?? $candidate['connector'] ?? $candidate['provider'] ?? '' ) ) {
$resolver = $candidate;
break;
}
}
}
if ( is_callable( $resolver ) ) {
$resolved = call_user_func( $resolver, $connector_name, $request, $input );
return is_array( $resolved ) || is_wp_error( $resolved ) ? $resolved : null;
}
return is_array( $resolver ) ? self::connector_from_spec( $connector_name, $resolver ) : null;
}
/** @param array<string,mixed> $spec Resolver spec. @return array<string,mixed> */
private static function connector_from_spec( string $connector_name, array $spec ): array {
$provider = trim( (string) ( $spec['provider'] ?? $connector_name ) );
$model = trim( (string) ( $spec['model'] ?? '' ) );
$secrets = self::secret_names( $spec['secret_env'] ?? $spec['secretEnv'] ?? array() );
$values = self::secret_values( $spec['secret_values'] ?? $spec['secretEnvValues'] ?? $spec['secret_env_values'] ?? array() );
$sources = self::secret_values( $spec['secret_sources'] ?? $spec['secretSources'] ?? array() );
$connector = array(
'name' => $connector_name,
'status' => 'resolved',
'provider' => '' !== $provider ? $provider : $connector_name,
'credentials' => array(
'schema' => 'wp-codebox/connector-credentials/v1',
'connector' => $connector_name,
'scope' => 'connector',
'status' => 'available',
'secrets' => array(),
),
);
if ( '' !== $model ) {
$connector['model'] = $model;
}
if ( ! empty( $secrets ) ) {
$connector['secret_env'] = $secrets;
}
$secret_values = array();
foreach ( $secrets as $name ) {
$available = isset( $values[ $name ] ) && '' !== (string) $values[ $name ];
if ( $available ) {
$secret_values[ $name ] = (string) $values[ $name ];
}
$connector['credentials']['secrets'][] = array_filter(
array(
'name' => $name,
'status' => $available ? 'available' : 'missing',
'scope' => $provider,
'source' => $sources[ $name ] ?? '',
),
static fn( mixed $value ): bool => '' !== $value
);
}
if ( empty( $secret_values ) && ! empty( $secrets ) ) {
$connector['credentials']['status'] = 'missing';
} elseif ( ! empty( $secret_values ) ) {
$connector['secret_env_values'] = $secret_values;
}
return $connector;
}
/** @param array<int,mixed> $connectors Existing connectors. @param array<string,mixed> $connector Resolved connector. @return array<int,mixed> */
private static function replace_connector( array $connectors, string $connector_name, array $connector ): array {
$connectors = array_values(
array_filter(
$connectors,
static fn( mixed $existing ): bool => ! is_array( $existing ) || $connector_name !== (string) ( $existing['name'] ?? '' )
)
);
$connectors[] = $connector;
return $connectors;
}
/** @return string[] */
private static function secret_names( mixed $value ): array {
return array_values( array_unique( array_filter( WP_Codebox_Agent_Task::string_list( $value ), static fn( string $name ): bool => 1 === preg_match( '/^[A-Z_][A-Z0-9_]*$/', $name ) ) ) );
}
/** @return array<string,string> */
private static function secret_values( mixed $value ): array {
if ( is_callable( $value ) ) {
$value = call_user_func( $value );
}
if ( ! is_array( $value ) ) {
return array();
}
$values = array();
foreach ( $value as $name => $secret ) {
$name = (string) $name;
if ( 1 === preg_match( '/^[A-Z_][A-Z0-9_]*$/', $name ) && is_scalar( $secret ) ) {
$values[ $name ] = (string) $secret;
}
}
return $values;
}
}