Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
'@PHP8x2Migration' => true,
'php_unit_test_class_requires_covers' => true,
'nullable_type_declaration_for_default_null_value' => true,
'fully_qualified_strict_types' => true,
'global_namespace_import' => [
'import_classes' => true,
'import_constants' => true,
'import_functions' => true,
],
'Horde/remove_php_version_comment' => true,
'Horde/update_copyright_year' => true,
])
Expand Down
14 changes: 7 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,18 @@
"horde/eventdispatcher": "* || dev-FRAMEWORK_6_0",
"horde/githubapiclient": "* || dev-FRAMEWORK_6_0",
"horde/composer": "* || dev-FRAMEWORK_6_0",
"horde/phpconfigfile": "^0.0.1-alpha4 || dev-FRAMEWORK_6_0"
"horde/phpconfigfile": "^0.0.1-alpha5 || dev-FRAMEWORK_6_0",
"horde/core": "* || dev-FRAMEWORK_6_0"
},
"require-dev": {
"horde/test": "^3 || dev-FRAMEWORK_6_0",
"phpunit/phpunit": "^12 || ^11 || ^10 || ^9"
"horde/test": "^3 || dev-FRAMEWORK_6_0"
},
"suggest": {
"horde/test": "^3 || dev-FRAMEWORK_6_0",
"squizlabs/php_codesniffer": "^3.5",
"pear/archive_tar": "^1.4",
"phploc/phploc": "^7",
"phpmd/phpmd": "^2.9",
"phpunit/phpunit": "^9"
"phpmd/phpmd": "^2.9"
},
"autoload": {
"psr-0": {
Expand Down Expand Up @@ -83,5 +82,6 @@
"branch-alias": {
"dev-FRAMEWORK_6_0": "1.x-dev"
}
}
}
},
"minimum-stability": "dev"
}
84 changes: 50 additions & 34 deletions config/conf.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,15 @@
* PHP Version 7
*/

/* PEAR server name. Only change for testing purposes. */
$conf['releaseserver'] = 'pear.horde.org';

/* PEAR server directory. Only change for testing purposes. */
$conf['releasedir'] = '/horde/web/pear.horde.org';

/* Username for horde.org.
* Make sure you belong to the "horde" group there. */
$conf['horde_user'] = '';

/* Needed to update the Whups versions */
$conf['horde_pass'] = '';
/**
* Whups (bugs.horde.org) Credentials
*
* Username and password for updating bugs.horde.org (the Horde bug tracker).
* Required for the Bugs release task which updates Whups versions.
* Make sure you belong to the "horde" group on bugs.horde.org.
*/
$conf['whups.user'] = '';
$conf['whups.pass'] = '';

/* From: address for announcements. */
$conf['from'] = 'Full name <user@horde.org>';
Expand Down Expand Up @@ -53,11 +50,16 @@ $conf['from'] = 'Full name <user@horde.org>';
*/
$conf['github.token'] = '';

/* Path to a checkout of the horde-web repository. */
$conf['web_dir'] = '/var/www/horde-web';
/**
* Horde Web Repository Directory
*
* Path to a checkout of the horde-web repository (www.horde.org website).
* This directory contains the SQLite database for version tracking.
*/
$conf['hordeweb.dir'] = '/var/www/horde-web';

// Target directory for a developer installation. The webroot will be the ./web dir under that install dir
$conf['install_base'] = '/var/www/horde-dev';
$conf['install.dir'] = '/var/www/horde-dev';

/**
* Vendor: Used by the composer, packagist, satis and git-related tasks
Expand All @@ -66,37 +68,51 @@ $conf['install_base'] = '/var/www/horde-dev';
// $conf['vendor'] = 'horde';

/**
* Git Repo Base: The base URL for your remote git repo
* Defaults to https://github.com/$vendor if unset
* SCM Repository Base URL
*
* The base URL for cloning remote git repositories.
* Used to construct clone URLs: scm.repo.base + ComponentName.git
*
* Defaults to https://github.com/{vendor}/ if unset
* If you configure this, $vendor will NOT be appended
*
* Examples:
* https://github.com/horde/ (default for vendor=horde)
* https://git.mycompany.com/horde/ (custom git server)
*/
//$conf['git_repo_base'] = 'https://git.mycompany.com/level/nextlevel/';
//$conf['scm.repo.base'] = 'https://git.mycompany.com/horde/';

/** composer_repo - Type of loader hints to generate
* Default is not to generate any hints. Dependencies will be from packagist
/**
* Composer Repository Type
*
* Type of repository hints to generate in composer.json files
* Default is not to generate any hints. Dependencies will be from packagist.
*
* Options:
* 'vcs' - Generate a VCS repo source per horde internal dependency
* 'satis:https://satis.url' - Generate a satis source for all horde namespace
*/
// generate a vcs repo source per horde internal dependency
// $conf['composer_repo'] = 'vcs';
// generate a satis source for all horde namespace
// $conf['composer_repo'] = 'satis:https://satis.horde.org';
// $conf['composer.repo'] = 'vcs';
// $conf['composer.repo'] = 'satis:https://satis.horde.org';

/**
* composer_version - tweak all dependency versions
* to a common branch name or version
* Composer Version Override
*
* Tweak all dependency versions to a common branch name or version.
* This is used by the horde-components composer command and
* the composer release task unless overwritten there
* the composer release task unless overwritten there.
*
* Default: Depend on versions from .horde.yml
*
* Examples:
* 'dev-master' - Depend on master branch
* 'dev-FRAMEWORK_6_0' - Depend on FRAMEWORK_6_0 branch
* 'dev-staging' - Depend on a staging branch
*/
// depend on master branch
// $conf['composer_version'] = 'dev-master';
// depend on a staging branch - components won't check if it even exists!
// $conf['composer_version'] = 'dev-staging';

$conf['composer_version'] = 'dev-FRAMEWORK_6_0';
$conf['composer.version'] = 'dev-FRAMEWORK_6_0';

/* Well known composer native substitutes for pear dependencies */
$conf['composer_opts']['pear-substitutes'] = [
$conf['composer.opts']['pear-substitutes'] = [
['pear.php.net/Archive_Tar' =>
['source' => 'Packagist', 'name' => 'pear/archive_tar']],
['pear.php.net/Console_Color2' =>
Expand Down
1 change: 1 addition & 0 deletions data/scaffolding/library/src/Example.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* Example class for Skeleton library.
*
Expand Down
1 change: 1 addition & 0 deletions data/scaffolding/library/test/ExampleTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* Tests for Example class.
*
Expand Down
3 changes: 2 additions & 1 deletion src/ArgvWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use IteratorAggregate;
use RuntimeException;
use Traversable;
use ArrayIterator;

/**
* Wrap a copy of Argv into a simple, typed object for DI
Expand All @@ -33,7 +34,7 @@ public function __construct(array $argv)

public function getIterator(): Traversable
{
return new \ArrayIterator($this->argv);
return new ArrayIterator($this->argv);
}

public static function fromGlobal()
Expand Down
7 changes: 4 additions & 3 deletions src/Auth/AuthenticationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use RuntimeException;
use InvalidArgumentException;

/**
* Authentication factory that determines which authentication method to use
Expand Down Expand Up @@ -99,7 +100,7 @@ private function createGitHubAppStrategy(): ?AuthenticationStrategyInterface
return null;
}

$jwtGenerator = new Rs256JwtGenerator();
$jwtGenerator = new GitHubJwtGenerator();
$authService = new GitHubAppAuthenticationService(
$appConfig,
$jwtGenerator,
Expand All @@ -123,7 +124,7 @@ private function loadGitHubAppConfig(): ?GitHubAppConfig
// Try environment variables first
try {
return GitHubAppConfig::fromEnvironment();
} catch (\InvalidArgumentException $e) {
} catch (InvalidArgumentException $e) {
// Environment variables not set or incomplete, try config file
}

Expand Down Expand Up @@ -153,7 +154,7 @@ private function loadGitHubAppConfig(): ?GitHubAppConfig
installationId: (int) $installationId,
privateKeyPath: (string) $privateKeyPath
);
} catch (\InvalidArgumentException $e) {
} catch (InvalidArgumentException $e) {
// Invalid configuration, return null
return null;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Auth/AuthenticationStrategyInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Horde\Components\Auth;

use Horde\GithubApiClient\GithubApiClient;
use RuntimeException;

/**
* Interface for GitHub authentication strategies
Expand All @@ -24,7 +25,7 @@ interface AuthenticationStrategyInterface
* Authenticate and return configured GitHub API client
*
* @return GithubApiClient Configured client ready to make API calls
* @throws \RuntimeException If authentication fails
* @throws RuntimeException If authentication fails
*/
public function authenticate(): GithubApiClient;

Expand Down
3 changes: 2 additions & 1 deletion src/Auth/GitHubAppAuthenticationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use RuntimeException;
use Exception;

/**
* GitHub App authentication service
Expand Down Expand Up @@ -160,7 +161,7 @@ private function generateInstallationAccessToken(): InstallationAccessToken
$this->config->installationId,
new CreateInstallationAccessTokenParams()
);
} catch (\Exception $e) {
} catch (Exception $e) {
throw new RuntimeException(
"Failed to create installation access token: {$e->getMessage()}",
0,
Expand Down
5 changes: 3 additions & 2 deletions src/Auth/GitHubAppAuthenticationStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Horde\GithubApiClient\GithubApiClient;
use RuntimeException;
use Exception;

/**
* GitHub App authentication strategy
Expand Down Expand Up @@ -35,7 +36,7 @@ public function authenticate(): GithubApiClient
{
try {
return $this->authService->getAuthenticatedClient();
} catch (\Exception $e) {
} catch (Exception $e) {
throw new RuntimeException(
"GitHub App authentication failed: {$e->getMessage()}",
0,
Expand All @@ -60,7 +61,7 @@ public function getJwtAuthenticatedClient(): GithubApiClient
{
try {
return $this->authService->getJwtAuthenticatedClient();
} catch (\Exception $e) {
} catch (Exception $e) {
throw new RuntimeException(
"GitHub App JWT authentication failed: {$e->getMessage()}",
0,
Expand Down
65 changes: 65 additions & 0 deletions src/Auth/GitHubJwtGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Horde\Components\Auth;

use Horde\Core\Auth\Jwt\GeneratedJwt as CoreGeneratedJwt;
use Horde\Core\Auth\Jwt\PrivateKey as CorePrivateKey;
use Horde\Core\Auth\Jwt\Rs256Generator;
use RuntimeException;

/**
* GitHub-specific JWT generator wrapper
*
* Wraps the Core Rs256Generator with GitHub App constraints (max 600 second expiry).
* This maintains GitHub-specific business logic while using the shared JWT implementation.
*
* Copyright 2026 The Horde Project (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @category Horde
* @package Components
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
*/
class GitHubJwtGenerator implements JwtGeneratorInterface
{
private Rs256Generator $generator;

public function __construct()
{
$this->generator = new Rs256Generator();
}

/**
* Generate a JWT for GitHub App authentication
*
* @param int $appId GitHub App ID (iss claim)
* @param PrivateKey $privateKey Private key for signing
* @param int $expirySeconds JWT expiry time in seconds (max 600 for GitHub)
* @return GeneratedJwt
* @throws RuntimeException If JWT generation fails or expiry exceeds GitHub limit
*/
public function generate(
int $appId,
PrivateKey $privateKey,
int $expirySeconds = 600
): GeneratedJwt {
if ($expirySeconds > 600) {
throw new RuntimeException(
'GitHub App JWTs cannot have expiry greater than 600 seconds (10 minutes)'
);
}

// Convert components types to Core types
$corePrivateKey = CorePrivateKey::fromString($privateKey->content);

// Generate JWT using Core implementation
$coreJwt = $this->generator->generate($appId, $corePrivateKey, $expirySeconds);

// Convert Core result back to components type
return new GeneratedJwt($coreJwt->token, $coreJwt->expiresAt);
}
}
4 changes: 3 additions & 1 deletion src/Auth/JwtGeneratorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Horde\Components\Auth;

use RuntimeException;

/**
* Interface for JWT generation
*
Expand All @@ -25,7 +27,7 @@ interface JwtGeneratorInterface
* @param PrivateKey $privateKey Private key for signing
* @param int $expirySeconds JWT expiry time in seconds (max 600 for GitHub)
* @return GeneratedJwt
* @throws \RuntimeException If JWT generation fails
* @throws RuntimeException If JWT generation fails
*/
public function generate(
int $appId,
Expand Down
4 changes: 3 additions & 1 deletion src/Ci/Config/CiConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

namespace Horde\Components\Ci\Config;

use InvalidArgumentException;

/**
* CI configuration container.
*
Expand Down Expand Up @@ -126,7 +128,7 @@ public function __construct(array $config)

// Components path must be provided - no guessing
if (!isset($config['components_path'])) {
throw new \InvalidArgumentException('components_path must be provided in configuration');
throw new InvalidArgumentException('components_path must be provided in configuration');
}
$this->componentsPath = $config['components_path'];
}
Expand Down
Loading
Loading