Skip to content

Commit d52b0c8

Browse files
authored
fix: support token-authenticated workspace clones (#443)
1 parent ca0546f commit d52b0c8

2 files changed

Lines changed: 56 additions & 4 deletions

File tree

inc/Abilities/WorkspaceAbilities.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,10 @@ private function registerAbilities(): void {
383383
'type' => 'boolean',
384384
'description' => 'Disable the default blobless partial clone for remote repositories.',
385385
),
386+
'auth_token_env' => array(
387+
'type' => 'string',
388+
'description' => 'Optional environment variable name containing a bearer token for HTTPS clone authentication.',
389+
),
386390
),
387391
'required' => array( 'url' ),
388392
),
@@ -2360,7 +2364,10 @@ public static function cloneRepo( array $input ): array|\WP_Error {
23602364
return $workspace->clone_repo(
23612365
$input['url'] ?? '',
23622366
$input['name'] ?? null,
2363-
array( 'full' => (bool) ( $input['full'] ?? false ) )
2367+
array(
2368+
'full' => (bool) ( $input['full'] ?? false ),
2369+
'auth_token_env' => $input['auth_token_env'] ?? '',
2370+
)
23642371
);
23652372
}
23662373

inc/Workspace/WorkspaceRepositoryLifecycle.php

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,13 @@ public function clone_repo( string $url, ?string $name = null, array $options =
159159
$started_at
160160
);
161161

162+
$env = $this->build_clone_environment( $url, $options );
163+
if ( is_wp_error( $env ) ) {
164+
return $env;
165+
}
166+
162167
$command = $this->build_clone_command( $url, $repo_path, $partial_clone );
163-
$result = $this->run_clone_command( $command, $progress_callback, $started_at );
168+
$result = $this->run_clone_command( $command, $progress_callback, $started_at, $env );
164169

165170
if ( is_wp_error( $result ) ) {
166171
return $this->clone_failed_error( $result, $name, $repo_path, $url );
@@ -196,6 +201,46 @@ private function build_clone_command( string $url, string $repo_path, bool $part
196201
return 'GIT_TERMINAL_PROMPT=0 git ' . implode( ' ', $args );
197202
}
198203

204+
/**
205+
* Build additional environment values for git clone.
206+
*
207+
* @param string $url Git clone URL.
208+
* @param array $options Optional clone options.
209+
* @return array<string,string>|null|\WP_Error Extra environment values, null for default environment, or error.
210+
*/
211+
private function build_clone_environment( string $url, array $options ): array|null|\WP_Error {
212+
$auth_token_env = isset( $options['auth_token_env'] ) && is_scalar( $options['auth_token_env'] ) ? trim( (string) $options['auth_token_env'] ) : '';
213+
if ( '' === $auth_token_env ) {
214+
return null;
215+
}
216+
217+
if ( ! preg_match( '/^[A-Za-z_][A-Za-z0-9_]*$/', $auth_token_env ) ) {
218+
return new \WP_Error( 'invalid_auth_token_env', 'Clone auth token environment variable name is invalid.', array( 'status' => 400 ) );
219+
}
220+
221+
$token = trim( (string) getenv( $auth_token_env ) );
222+
if ( '' === $token ) {
223+
return new \WP_Error( 'missing_auth_token_env', sprintf( 'Clone auth token environment variable %s is empty or unavailable.', $auth_token_env ), array( 'status' => 400 ) );
224+
}
225+
226+
$parts = wp_parse_url( $url );
227+
$host = is_array( $parts ) && isset( $parts['host'] ) ? strtolower( (string) $parts['host'] ) : '';
228+
if ( '' === $host ) {
229+
return new \WP_Error( 'unsupported_auth_token_url', 'Clone auth token support requires an HTTPS repository URL.', array( 'status' => 400 ) );
230+
}
231+
232+
$env = getenv();
233+
if ( ! is_array( $env ) ) {
234+
$env = array();
235+
}
236+
237+
$env['GIT_CONFIG_COUNT'] = '1';
238+
$env['GIT_CONFIG_KEY_0'] = sprintf( 'http.https://%s/.extraheader', $host );
239+
$env['GIT_CONFIG_VALUE_0'] = 'AUTHORIZATION: bearer ' . $token;
240+
241+
return $env;
242+
}
243+
199244
/**
200245
* Remote HTTP(S) and SSH hosts generally support safe blobless clones; local
201246
* paths and file URLs often do not, and are usually test fixtures anyway.
@@ -215,14 +260,14 @@ private function should_use_partial_clone( string $url ): bool {
215260
* @param float $started_at Clone start timestamp.
216261
* @return array{success: true, output: string}|\WP_Error
217262
*/
218-
private function run_clone_command( string $command, ?callable $progress_callback, float $started_at ): array|\WP_Error {
263+
private function run_clone_command( string $command, ?callable $progress_callback, float $started_at, ?array $env = null ): array|\WP_Error {
219264
$descriptor_spec = array(
220265
1 => array( 'pipe', 'w' ),
221266
2 => array( 'pipe', 'w' ),
222267
);
223268

224269
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_proc_open
225-
$process = proc_open( $command, $descriptor_spec, $pipes );
270+
$process = proc_open( $command, $descriptor_spec, $pipes, null, $env );
226271
if ( ! is_resource( $process ) ) {
227272
return new \WP_Error( 'clone_failed', 'Git clone failed to start.', array( 'status' => 500 ) );
228273
}

0 commit comments

Comments
 (0)