├── src ├── Facades │ └── Bitbucket.php ├── Auth │ ├── Authenticator │ │ ├── AuthenticatorInterface.php │ │ ├── OauthAuthenticator.php │ │ ├── JwtAuthenticator.php │ │ ├── PasswordAuthenticator.php │ │ ├── AbstractAuthenticator.php │ │ └── PrivateKeyAuthenticator.php │ └── AuthenticatorFactory.php ├── HttpClient │ └── BuilderFactory.php ├── Cache │ ├── ConnectionFactory.php │ └── Connector │ │ └── IlluminateConnector.php ├── BitbucketFactory.php ├── BitbucketManager.php └── BitbucketServiceProvider.php ├── LICENSE ├── composer.json └── config └── bitbucket.php /src/Facades/Bitbucket.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\Facades; 15 | 16 | use Illuminate\Support\Facades\Facade; 17 | 18 | /** 19 | * This is the bitbucket facade class. 20 | * 21 | * @author Graham Campbell 22 | */ 23 | class Bitbucket extends Facade 24 | { 25 | /** 26 | * Get the registered name of the component. 27 | * 28 | * @return string 29 | */ 30 | protected static function getFacadeAccessor(): string 31 | { 32 | return 'bitbucket'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Auth/Authenticator/AuthenticatorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\Auth\Authenticator; 15 | 16 | use Bitbucket\Client; 17 | 18 | /** 19 | * This is the authenticator interface. 20 | * 21 | * @author Graham Campbell 22 | */ 23 | interface AuthenticatorInterface 24 | { 25 | /** 26 | * Set the client to perform the authentication on. 27 | * 28 | * @param \Bitbucket\Client $client 29 | * 30 | * @return self 31 | */ 32 | public function with(Client $client): AuthenticatorInterface; 33 | 34 | /** 35 | * Authenticate the client, and return it. 36 | * 37 | * @param string[] $config 38 | * 39 | * @throws \InvalidArgumentException 40 | * 41 | * @return \Bitbucket\Client 42 | */ 43 | public function authenticate(array $config): Client; 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2025 Graham Campbell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Auth/Authenticator/OauthAuthenticator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\Auth\Authenticator; 15 | 16 | use Bitbucket\Client; 17 | use InvalidArgumentException; 18 | 19 | /** 20 | * This is the oauth authenticator class. 21 | * 22 | * @author Graham Campbell 23 | */ 24 | final class OauthAuthenticator extends AbstractAuthenticator 25 | { 26 | /** 27 | * Authenticate the client, and return it. 28 | * 29 | * @param string[] $config 30 | * 31 | * @throws \InvalidArgumentException 32 | * 33 | * @return \Bitbucket\Client 34 | */ 35 | public function authenticate(array $config): Client 36 | { 37 | $client = $this->getClient(); 38 | 39 | if (!array_key_exists('token', $config)) { 40 | throw new InvalidArgumentException('The oauth authenticator requires a token.'); 41 | } 42 | 43 | $client->authenticate(Client::AUTH_OAUTH_TOKEN, $config['token']); 44 | 45 | return $client; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Auth/Authenticator/JwtAuthenticator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\Auth\Authenticator; 15 | 16 | use Bitbucket\Client; 17 | use InvalidArgumentException; 18 | 19 | /** 20 | * This is the jwt authenticator class. 21 | * 22 | * @author Graham Campbell 23 | * @author Lucas Michot 24 | */ 25 | final class JwtAuthenticator extends AbstractAuthenticator 26 | { 27 | /** 28 | * Authenticate the client, and return it. 29 | * 30 | * @param string[] $config 31 | * 32 | * @throws \InvalidArgumentException 33 | * 34 | * @return \Bitbucket\Client 35 | */ 36 | public function authenticate(array $config): Client 37 | { 38 | $client = $this->getClient(); 39 | 40 | if (!array_key_exists('token', $config)) { 41 | throw new InvalidArgumentException('The jwt authenticator requires a token.'); 42 | } 43 | 44 | $client->authenticate(Client::AUTH_JWT, $config['token']); 45 | 46 | return $client; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Auth/Authenticator/PasswordAuthenticator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\Auth\Authenticator; 15 | 16 | use Bitbucket\Client; 17 | use InvalidArgumentException; 18 | 19 | /** 20 | * This is the password authenticator class. 21 | * 22 | * @author Graham Campbell 23 | */ 24 | final class PasswordAuthenticator extends AbstractAuthenticator 25 | { 26 | /** 27 | * Authenticate the client, and return it. 28 | * 29 | * @param string[] $config 30 | * 31 | * @throws \InvalidArgumentException 32 | * 33 | * @return \Bitbucket\Client 34 | */ 35 | public function authenticate(array $config): Client 36 | { 37 | $client = $this->getClient(); 38 | 39 | if (!array_key_exists('username', $config) || !array_key_exists('password', $config)) { 40 | throw new InvalidArgumentException('The password authenticator requires a username and password.'); 41 | } 42 | 43 | $client->authenticate(Client::AUTH_HTTP_PASSWORD, $config['username'], $config['password']); 44 | 45 | return $client; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Auth/AuthenticatorFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\Auth; 15 | 16 | use GrahamCampbell\Bitbucket\Auth\Authenticator\AuthenticatorInterface; 17 | use InvalidArgumentException; 18 | 19 | /** 20 | * This is the authenticator factory class. 21 | * 22 | * @author Graham Campbell 23 | */ 24 | class AuthenticatorFactory 25 | { 26 | /** 27 | * Make a new authenticator instance. 28 | * 29 | * @param string $method 30 | * 31 | * @throws \InvalidArgumentException 32 | * 33 | * @return \GrahamCampbell\Bitbucket\Auth\Authenticator\AuthenticatorInterface 34 | */ 35 | public function make(string $method): AuthenticatorInterface 36 | { 37 | return match ($method) { 38 | 'jwt' => new Authenticator\JwtAuthenticator(), 39 | 'oauth' => new Authenticator\OauthAuthenticator(), 40 | 'password' => new Authenticator\PasswordAuthenticator(), 41 | 'private' => new Authenticator\PrivateKeyAuthenticator(), 42 | default => throw new InvalidArgumentException("Unsupported authentication method [$method]."), 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Auth/Authenticator/AbstractAuthenticator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\Auth\Authenticator; 15 | 16 | use Bitbucket\Client; 17 | use InvalidArgumentException; 18 | 19 | /** 20 | * This is the abstract authenticator class. 21 | * 22 | * @author Graham Campbell 23 | */ 24 | abstract class AbstractAuthenticator implements AuthenticatorInterface 25 | { 26 | private ?Client $client = null; 27 | 28 | /** 29 | * Set the client to perform the authentication on. 30 | * 31 | * @param \Bitbucket\Client $client 32 | * 33 | * @return \GrahamCampbell\Bitbucket\Auth\Authenticator\AuthenticatorInterface 34 | */ 35 | public function with(Client $client): AuthenticatorInterface 36 | { 37 | $this->client = $client; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * @throws \InvalidArgumentException 44 | * 45 | * @return \Bitbucket\Client 46 | */ 47 | protected function getClient(): Client 48 | { 49 | if (!$this->client) { 50 | throw new InvalidArgumentException('The client instance was not given to the authenticator.'); 51 | } 52 | 53 | return $this->client; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/HttpClient/BuilderFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\HttpClient; 15 | 16 | use Bitbucket\HttpClient\Builder; 17 | use Psr\Http\Client\ClientInterface; 18 | use Psr\Http\Message\RequestFactoryInterface; 19 | use Psr\Http\Message\StreamFactoryInterface; 20 | 21 | /** 22 | * This is the http client builder factory class. 23 | * 24 | * @author Graham Campbell 25 | */ 26 | class BuilderFactory 27 | { 28 | /** 29 | * Create a new connection factory instance. 30 | * 31 | * @param \Psr\Http\Client\ClientInterface $httpClient 32 | * @param \Psr\Http\Message\RequestFactoryInterface $requestFactory 33 | * @param \Psr\Http\Message\StreamFactoryInterface $streamFactory 34 | * 35 | * @return void 36 | */ 37 | public function __construct( 38 | private readonly ClientInterface $httpClient, 39 | private readonly RequestFactoryInterface $requestFactory, 40 | private readonly StreamFactoryInterface $streamFactory, 41 | ) { 42 | } 43 | 44 | /** 45 | * Return a new http client builder. 46 | * 47 | * @return \Bitbucket\HttpClient\Builder 48 | */ 49 | public function make(): Builder 50 | { 51 | return new Builder($this->httpClient, $this->requestFactory, $this->streamFactory); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graham-campbell/bitbucket", 3 | "description": "Bitbucket Is A Bitbucket Bridge For Laravel", 4 | "keywords": ["laravel", "framework", "bitbucket", "php-bitbucket-api", "PHP Bitbucket API", "bitbucket bridge", "bridge", "Bitbucket", "Laravel Bitbucket", "Laravel-Bitbucket", "Graham Campbell", "GrahamCampbell"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Graham Campbell", 9 | "email": "hello@gjcampbell.co.uk", 10 | "homepage": "https://github.com/GrahamCampbell" 11 | } 12 | ], 13 | "require": { 14 | "php": "^8.1", 15 | "bitbucket/client": "5.0.*", 16 | "graham-campbell/bounded-cache": "^3.0", 17 | "graham-campbell/manager": "^5.2", 18 | "guzzlehttp/guzzle": "^7.9.2", 19 | "guzzlehttp/psr7": "^2.7.0", 20 | "illuminate/contracts": "^10.44 || ^11.0 || ^12.0", 21 | "illuminate/support": "^10.44 || ^11.0 || ^12.0", 22 | "lcobucci/jwt": "^5.2", 23 | "symfony/cache": "^6.2 || ^7.0" 24 | }, 25 | "require-dev": { 26 | "graham-campbell/analyzer": "^5.0", 27 | "graham-campbell/testbench": "^6.2", 28 | "mockery/mockery": "^1.6.6", 29 | "phpunit/phpunit": "^10.5.45 || ^11.5.10" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "GrahamCampbell\\Bitbucket\\": "src/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "GrahamCampbell\\Tests\\Bitbucket\\": "tests/" 39 | } 40 | }, 41 | "config": { 42 | "preferred-install": "dist", 43 | "allow-plugins": { 44 | "php-http/discovery": true 45 | } 46 | }, 47 | "extra": { 48 | "laravel": { 49 | "providers": [ 50 | "GrahamCampbell\\Bitbucket\\BitbucketServiceProvider" 51 | ] 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Cache/ConnectionFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\Cache; 15 | 16 | use GrahamCampbell\BoundedCache\BoundedCacheInterface; 17 | use GrahamCampbell\Manager\ConnectorInterface; 18 | use Illuminate\Contracts\Cache\Factory; 19 | use InvalidArgumentException; 20 | 21 | /** 22 | * This is the cache connection factory class. 23 | * 24 | * @author Graham Campbell 25 | */ 26 | class ConnectionFactory 27 | { 28 | /** 29 | * Create a new connection factory instance. 30 | * 31 | * @param \Illuminate\Contracts\Cache\Factory|null $cache 32 | * 33 | * @return void 34 | */ 35 | public function __construct( 36 | private readonly ?Factory $cache = null, 37 | ) { 38 | } 39 | 40 | /** 41 | * Establish a cache connection. 42 | * 43 | * @param array $config 44 | * 45 | * @throws \InvalidArgumentException 46 | * 47 | * @return \GrahamCampbell\BoundedCache\BoundedCacheInterface 48 | */ 49 | public function make(array $config): BoundedCacheInterface 50 | { 51 | return $this->createConnector($config)->connect($config); 52 | } 53 | 54 | /** 55 | * Create a connector instance based on the configuration. 56 | * 57 | * @param array $config 58 | * 59 | * @throws \InvalidArgumentException 60 | * 61 | * @return \GrahamCampbell\Manager\ConnectorInterface 62 | */ 63 | public function createConnector(array $config): ConnectorInterface 64 | { 65 | if (!isset($config['driver'])) { 66 | throw new InvalidArgumentException('A driver must be specified.'); 67 | } 68 | 69 | switch ($config['driver']) { 70 | case 'illuminate': 71 | return new Connector\IlluminateConnector($this->cache); 72 | } 73 | 74 | throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]."); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Cache/Connector/IlluminateConnector.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\Cache\Connector; 15 | 16 | use GrahamCampbell\BoundedCache\BoundedCache; 17 | use GrahamCampbell\BoundedCache\BoundedCacheInterface; 18 | use GrahamCampbell\Manager\ConnectorInterface; 19 | use Illuminate\Contracts\Cache\Factory; 20 | use Illuminate\Contracts\Cache\Repository; 21 | use Illuminate\Support\Arr; 22 | use InvalidArgumentException; 23 | 24 | /** 25 | * This is the illuminate connector class. 26 | * 27 | * @author Graham Campbell 28 | */ 29 | final class IlluminateConnector implements ConnectorInterface 30 | { 31 | private const MIN_CACHE_LIFETIME = 43200; 32 | private const MAX_CACHE_LIFETIME = 172800; 33 | 34 | /** 35 | * Create a new illuminate connector instance. 36 | * 37 | * @param \Illuminate\Contracts\Cache\Factory|null $cache 38 | * 39 | * @return void 40 | */ 41 | public function __construct( 42 | private readonly ?Factory $cache = null, 43 | ) { 44 | } 45 | 46 | /** 47 | * Establish a cache connection. 48 | * 49 | * @param array $config 50 | * 51 | * @throws \InvalidArgumentException 52 | * 53 | * @return \GrahamCampbell\BoundedCache\BoundedCacheInterface 54 | */ 55 | public function connect(array $config): BoundedCacheInterface 56 | { 57 | $repository = $this->getRepository($config); 58 | 59 | return self::getBoundedCache($repository, $config); 60 | } 61 | 62 | /** 63 | * Get the cache repository. 64 | * 65 | * @param array $config 66 | * 67 | * @throws \InvalidArgumentException 68 | * 69 | * @return \Illuminate\Contracts\Cache\Repository 70 | */ 71 | private function getRepository(array $config): Repository 72 | { 73 | if (!$this->cache) { 74 | throw new InvalidArgumentException('Illuminate caching support not available.'); 75 | } 76 | 77 | $name = Arr::get($config, 'connector'); 78 | 79 | return $this->cache->store($name); 80 | } 81 | 82 | /** 83 | * Get the bounded cache instance. 84 | * 85 | * @param \Illuminate\Contracts\Cache\Repository $repository 86 | * @param array $config 87 | * 88 | * @return \GrahamCampbell\BoundedCache\BoundedCacheInterface 89 | */ 90 | private static function getBoundedCache(Repository $repository, array $config): BoundedCacheInterface 91 | { 92 | $min = Arr::get($config, 'min', self::MIN_CACHE_LIFETIME); 93 | $max = Arr::get($config, 'max', self::MAX_CACHE_LIFETIME); 94 | 95 | return new BoundedCache($repository, $min, $max); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/BitbucketFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket; 15 | 16 | use Bitbucket\Client; 17 | use Bitbucket\HttpClient\Builder; 18 | use GrahamCampbell\Bitbucket\Auth\Authenticator\AuthenticatorInterface; 19 | use GrahamCampbell\Bitbucket\Auth\AuthenticatorFactory; 20 | use GrahamCampbell\Bitbucket\Cache\ConnectionFactory; 21 | use GrahamCampbell\Bitbucket\HttpClient\BuilderFactory; 22 | use Http\Client\Common\Plugin\RetryPlugin; 23 | use Illuminate\Support\Arr; 24 | use InvalidArgumentException; 25 | use Symfony\Component\Cache\Adapter\Psr16Adapter; 26 | 27 | /** 28 | * This is the bitbucket factory class. 29 | * 30 | * @author Graham Campbell 31 | */ 32 | class BitbucketFactory 33 | { 34 | /** 35 | * Create a new bitbucket factory instance. 36 | * 37 | * @param \GrahamCampbell\Bitbucket\HttpClient\BuilderFactory $builder 38 | * @param \GrahamCampbell\Bitbucket\Auth\AuthenticatorFactory $auth 39 | * @param \GrahamCampbell\Bitbucket\Cache\ConnectionFactory $cache 40 | * 41 | * @return void 42 | */ 43 | public function __construct( 44 | private readonly BuilderFactory $builder, 45 | private readonly AuthenticatorFactory $auth, 46 | private readonly ConnectionFactory $cache, 47 | ) { 48 | } 49 | 50 | /** 51 | * Make a new bitbucket client. 52 | * 53 | * @param string[] $config 54 | * 55 | * @throws \InvalidArgumentException 56 | * 57 | * @return \Bitbucket\Client 58 | */ 59 | public function make(array $config): Client 60 | { 61 | $client = new Client($this->getBuilder($config)); 62 | 63 | if (!array_key_exists('method', $config)) { 64 | throw new InvalidArgumentException('The bitbucket factory requires an auth method.'); 65 | } 66 | 67 | if ($url = Arr::get($config, 'url')) { 68 | $client->setUrl($url); 69 | } 70 | 71 | if ($config['method'] === 'none') { 72 | return $client; 73 | } 74 | 75 | return $this->getAuthenticator($config['method'])->with($client)->authenticate($config); 76 | } 77 | 78 | /** 79 | * Get the http client builder. 80 | * 81 | * @param string[] $config 82 | * 83 | * @return \Bitbucket\HttpClient\Builder 84 | */ 85 | protected function getBuilder(array $config): Builder 86 | { 87 | $builder = $this->builder->make(); 88 | 89 | if ($backoff = Arr::get($config, 'backoff')) { 90 | $builder->addPlugin(new RetryPlugin(['retries' => $backoff === true ? 2 : $backoff])); 91 | } 92 | 93 | if (is_array($cache = Arr::get($config, 'cache', false))) { 94 | $boundedCache = $this->cache->make($cache); 95 | 96 | $builder->addCache( 97 | new Psr16Adapter($boundedCache), 98 | ['cache_lifetime' => $boundedCache->getMaximumLifetime()] 99 | ); 100 | } 101 | 102 | return $builder; 103 | } 104 | 105 | /** 106 | * Get the authenticator. 107 | * 108 | * @throws \InvalidArgumentException 109 | * 110 | * @return \GrahamCampbell\Bitbucket\Auth\Authenticator\AuthenticatorInterface 111 | */ 112 | protected function getAuthenticator(string $method): AuthenticatorInterface 113 | { 114 | return $this->auth->make($method); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/BitbucketManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket; 15 | 16 | use Bitbucket\Client; 17 | use GrahamCampbell\Manager\AbstractManager; 18 | use Illuminate\Contracts\Config\Repository; 19 | use Illuminate\Support\Arr; 20 | 21 | /** 22 | * This is the bitbucket manager class. 23 | * 24 | * @method \Bitbucket\Client connection(string|null $name = null) 25 | * @method \Bitbucket\Client reconnect(string|null $name = null) 26 | * @method void disconnect(string|null $name = null) 27 | * @method array getConnections() 28 | * @method \Bitbucket\Api\Addon addon() 29 | * @method \Bitbucket\Api\CurrentUser currentUser() 30 | * @method \Bitbucket\Api\HookEvents hookEvents() 31 | * @method \Bitbucket\Api\PullRequests pullRequests() 32 | * @method \Bitbucket\Api\Repositories repositories() 33 | * @method \Bitbucket\Api\Snippets snippets() 34 | * @method \Bitbucket\Api\Users users(string $username) 35 | * @method \Bitbucket\Api\Workspaces workspaces(string $workspace) 36 | * @method void authenticate(string $method, string $token, string|null $password = null) 37 | * @method void setUrl(string $url) 38 | * @method \Psr\Http\Message\ResponseInterface|null getLastResponse() 39 | * @method \Http\Client\Common\HttpMethodsClientInterface getHttpClient() 40 | * 41 | * @author Graham Campbell 42 | */ 43 | class BitbucketManager extends AbstractManager 44 | { 45 | protected readonly BitbucketFactory $factory; 46 | 47 | /** 48 | * Create a new bitbucket manager instance. 49 | * 50 | * @param \Illuminate\Contracts\Config\Repository $config 51 | * @param \GrahamCampbell\Bitbucket\BitbucketFactory $factory 52 | * 53 | * @return void 54 | */ 55 | public function __construct(Repository $config, BitbucketFactory $factory) 56 | { 57 | parent::__construct($config); 58 | $this->factory = $factory; 59 | } 60 | 61 | /** 62 | * Create the connection instance. 63 | * 64 | * @param array $config 65 | * 66 | * @return \Bitbucket\Client 67 | */ 68 | protected function createConnection(array $config): Client 69 | { 70 | return $this->factory->make($config); 71 | } 72 | 73 | /** 74 | * Get the configuration name. 75 | * 76 | * @return string 77 | */ 78 | protected function getConfigName(): string 79 | { 80 | return 'bitbucket'; 81 | } 82 | 83 | /** 84 | * Get the configuration for a connection. 85 | * 86 | * @param string|null $name 87 | * 88 | * @throws \InvalidArgumentException 89 | * 90 | * @return array 91 | */ 92 | public function getConnectionConfig(?string $name = null): array 93 | { 94 | $config = parent::getConnectionConfig($name); 95 | 96 | if (is_string($cache = Arr::get($config, 'cache'))) { 97 | $config['cache'] = $this->getNamedConfig('cache', 'Cache', $cache); 98 | } 99 | 100 | return $config; 101 | } 102 | 103 | /** 104 | * Get the factory instance. 105 | * 106 | * @return \GrahamCampbell\Bitbucket\BitbucketFactory 107 | */ 108 | public function getFactory(): BitbucketFactory 109 | { 110 | return $this->factory; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /config/bitbucket.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | return [ 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Default Connection Name 19 | |-------------------------------------------------------------------------- 20 | | 21 | | Here you may specify which of the connections below you wish to use as 22 | | your default connection for all work. Of course, you may use many 23 | | connections at once using the manager class. 24 | | 25 | */ 26 | 27 | 'default' => 'main', 28 | 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Bitbucket Connections 32 | |-------------------------------------------------------------------------- 33 | | 34 | | Here are each of the connections setup for your application. Example 35 | | configuration has been included, but you may add as many connections as 36 | | you would like. Note that the 5 supported authentication methods are: 37 | | "jwt", "none", "oauth", "private", and "token". 38 | | 39 | */ 40 | 41 | 'connections' => [ 42 | 43 | 'main' => [ 44 | 'method' => 'oauth', 45 | 'token' => 'your-token', 46 | // 'backoff' => false, 47 | // 'cache' => false, 48 | // 'url' => null, 49 | ], 50 | 51 | 'alternative' => [ 52 | 'method' => 'password', 53 | 'username' => 'foo', 54 | 'password' => 'bar', 55 | // 'backoff' => false, 56 | // 'cache' => false, 57 | // 'url' => null, 58 | ], 59 | 60 | 'jwt' => [ 61 | 'method' => 'jwt', 62 | 'token' => 'your-jwt-token', 63 | // 'backoff' => false, 64 | // 'cache' => false, 65 | // 'version' => 'v3', 66 | // 'enterprise' => false, 67 | ], 68 | 69 | 'private' => [ 70 | 'method' => 'private', 71 | 'appId' => 'your-bitbucket-app-id', 72 | 'keyPath' => 'your-private-key-path', 73 | // 'key' => 'your-private-key-content', 74 | // 'passphrase' => 'your-private-key-passphrase' 75 | // 'backoff' => false, 76 | // 'cache' => false, 77 | // 'version' => 'v3', 78 | // 'enterprise' => false, 79 | ], 80 | 81 | 'none' => [ 82 | 'method' => 'none', 83 | // 'backoff' => false, 84 | // 'cache' => false, 85 | // 'version' => 'v3', 86 | // 'enterprise' => false, 87 | ], 88 | 89 | ], 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | HTTP Cache 94 | |-------------------------------------------------------------------------- 95 | | 96 | | Here are each of the cache configurations setup for your application. 97 | | Only the "illuminate" driver is provided out of the box. Example 98 | | configuration has been included. 99 | | 100 | */ 101 | 102 | 'cache' => [ 103 | 104 | 'main' => [ 105 | 'driver' => 'illuminate', 106 | 'connector' => null, // null means use default driver 107 | // 'min' => 43200, 108 | // 'max' => 172800 109 | ], 110 | 111 | 'bar' => [ 112 | 'driver' => 'illuminate', 113 | 'connector' => 'redis', // config/cache.php 114 | // 'min' => 43200, 115 | // 'max' => 172800 116 | ], 117 | 118 | ], 119 | 120 | ]; 121 | -------------------------------------------------------------------------------- /src/Auth/Authenticator/PrivateKeyAuthenticator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket\Auth\Authenticator; 15 | 16 | use Bitbucket\Client; 17 | use DateInterval; 18 | use DateTimeImmutable; 19 | use InvalidArgumentException; 20 | use Lcobucci\JWT\Builder; 21 | use Lcobucci\JWT\ClaimsFormatter; 22 | use Lcobucci\JWT\Configuration; 23 | use Lcobucci\JWT\Encoding\ChainedFormatter; 24 | use Lcobucci\JWT\Encoding\UnifyAudience; 25 | use Lcobucci\JWT\Signer\Key; 26 | use Lcobucci\JWT\Signer\Key\InMemory; 27 | use Lcobucci\JWT\Signer\Rsa\Sha256; 28 | use Lcobucci\JWT\Token; 29 | use Lcobucci\JWT\Token\RegisteredClaims; 30 | 31 | /** 32 | * This is private key bitbucket authenticator. 33 | * 34 | * @author Graham Campbell 35 | * @author Pavel Zhytomirsky 36 | */ 37 | final class PrivateKeyAuthenticator extends AbstractAuthenticator 38 | { 39 | /** 40 | * Build JWT token from provided private key file and authenticate with it. 41 | * 42 | * @param array $config 43 | * 44 | * @throws \Exception 45 | * 46 | * @return \Bitbucket\Client 47 | */ 48 | public function authenticate(array $config): Client 49 | { 50 | $client = $this->getClient(); 51 | 52 | if (!array_key_exists('appId', $config)) { 53 | throw new InvalidArgumentException('The private key authenticator requires the application id to be configured.'); 54 | } 55 | 56 | $client->authenticate(self::getToken($config)->toString(), Client::AUTH_JWT); 57 | 58 | return $client; 59 | } 60 | 61 | /** 62 | * Build JWT token from provided private key file. 63 | * 64 | * @param array $config 65 | * 66 | * @throws \Exception 67 | * 68 | * @return \Lcobucci\JWT\Token 69 | */ 70 | private static function getToken(array $config): Token 71 | { 72 | $configuration = Configuration::forSymmetricSigner( 73 | new Sha256(), 74 | self::getKey($config) 75 | ); 76 | 77 | $issued = (new DateTimeImmutable()) 78 | ->sub(new DateInterval('PT30S')); 79 | 80 | $expires = $issued 81 | ->add(new DateInterval('PT10M')); 82 | 83 | $builder = self::getBuilder($configuration) 84 | ->expiresAt($expires) 85 | ->issuedAt($issued) 86 | ->issuedBy((string) $config['appId']); 87 | 88 | return $builder->getToken( 89 | $configuration->signer(), 90 | $configuration->signingKey() 91 | ); 92 | } 93 | 94 | /** 95 | * Get the key from the config. 96 | * 97 | * @param array $config 98 | * 99 | * @throws \InvalidArgumentException 100 | * 101 | * @return \Lcobucci\JWT\Signer\Key 102 | */ 103 | private static function getKey(array $config): Key 104 | { 105 | if ( 106 | !(array_key_exists('key', $config) || array_key_exists('keyPath', $config)) || 107 | (array_key_exists('key', $config) && array_key_exists('keyPath', $config)) 108 | ) { 109 | throw new InvalidArgumentException('The private key authenticator requires the key or key path to be configured.'); 110 | } 111 | 112 | if (array_key_exists('key', $config)) { 113 | return InMemory::plainText($config['key'], $config['passphrase'] ?? ''); 114 | } 115 | 116 | return InMemory::file($config['keyPath'], $config['passphrase'] ?? ''); 117 | } 118 | 119 | /** 120 | * Create a JWT builder. 121 | * 122 | * @param \Lcobucci\JWT\Configuration $configuration 123 | * 124 | * @return \Lcobucci\JWT\Builder 125 | */ 126 | private static function getBuilder(Configuration $configuration): Builder 127 | { 128 | if (!interface_exists(ClaimsFormatter::class)) { 129 | return $configuration->builder(); 130 | } 131 | 132 | $formatter = new class() implements ClaimsFormatter { 133 | public function formatClaims(array $claims): array 134 | { 135 | foreach (RegisteredClaims::DATE_CLAIMS as $claim) { 136 | if (!array_key_exists($claim, $claims)) { 137 | continue; 138 | } 139 | 140 | $claims[$claim] = $claims[$claim]->getTimestamp(); 141 | } 142 | 143 | return $claims; 144 | } 145 | }; 146 | 147 | return $configuration->builder( 148 | new ChainedFormatter(new UnifyAudience(), $formatter) 149 | ); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/BitbucketServiceProvider.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace GrahamCampbell\Bitbucket; 15 | 16 | use Bitbucket\Client; 17 | use GrahamCampbell\Bitbucket\Auth\AuthenticatorFactory; 18 | use GrahamCampbell\Bitbucket\Cache\ConnectionFactory; 19 | use GrahamCampbell\Bitbucket\HttpClient\BuilderFactory; 20 | use GuzzleHttp\Client as GuzzleClient; 21 | use GuzzleHttp\Psr7\HttpFactory as GuzzlePsrFactory; 22 | use Illuminate\Contracts\Container\Container; 23 | use Illuminate\Foundation\Application as LaravelApplication; 24 | use Illuminate\Support\ServiceProvider; 25 | use Laravel\Lumen\Application as LumenApplication; 26 | 27 | /** 28 | * This is the bitbucket service provider class. 29 | * 30 | * @author Graham Campbell 31 | */ 32 | class BitbucketServiceProvider extends ServiceProvider 33 | { 34 | /** 35 | * Boot the service provider. 36 | * 37 | * @return void 38 | */ 39 | public function boot(): void 40 | { 41 | $this->setupConfig(); 42 | } 43 | 44 | /** 45 | * Setup the config. 46 | * 47 | * @return void 48 | */ 49 | private function setupConfig(): void 50 | { 51 | $source = realpath($raw = __DIR__.'/../config/bitbucket.php') ?: $raw; 52 | 53 | if ($this->app instanceof LaravelApplication && $this->app->runningInConsole()) { 54 | $this->publishes([$source => config_path('bitbucket.php')]); 55 | } elseif ($this->app instanceof LumenApplication) { 56 | $this->app->configure('bitbucket'); 57 | } 58 | 59 | $this->mergeConfigFrom($source, 'bitbucket'); 60 | } 61 | 62 | /** 63 | * Register the service provider. 64 | * 65 | * @return void 66 | */ 67 | public function register(): void 68 | { 69 | $this->registerHttpClientFactory(); 70 | $this->registerAuthFactory(); 71 | $this->registerCacheFactory(); 72 | $this->registerBitbucketFactory(); 73 | $this->registerManager(); 74 | $this->registerBindings(); 75 | } 76 | 77 | /** 78 | * Register the http client factory class. 79 | * 80 | * @return void 81 | */ 82 | private function registerHttpClientFactory(): void 83 | { 84 | $this->app->singleton('bitbucket.httpclientfactory', function (): BuilderFactory { 85 | $psrFactory = new GuzzlePsrFactory(); 86 | 87 | return new BuilderFactory( 88 | new GuzzleClient(['connect_timeout' => 10, 'timeout' => 30]), 89 | $psrFactory, 90 | $psrFactory, 91 | ); 92 | }); 93 | 94 | $this->app->alias('bitbucket.httpclientfactory', BuilderFactory::class); 95 | } 96 | 97 | /** 98 | * Register the auth factory class. 99 | * 100 | * @return void 101 | */ 102 | private function registerAuthFactory(): void 103 | { 104 | $this->app->singleton('bitbucket.authfactory', function (): AuthenticatorFactory { 105 | return new AuthenticatorFactory(); 106 | }); 107 | 108 | $this->app->alias('bitbucket.authfactory', AuthenticatorFactory::class); 109 | } 110 | 111 | /** 112 | * Register the cache factory class. 113 | * 114 | * @return void 115 | */ 116 | private function registerCacheFactory(): void 117 | { 118 | $this->app->singleton('bitbucket.cachefactory', function (Container $app): ConnectionFactory { 119 | $cache = $app->bound('cache') ? $app->make('cache') : null; 120 | 121 | return new ConnectionFactory($cache); 122 | }); 123 | 124 | $this->app->alias('bitbucket.cachefactory', ConnectionFactory::class); 125 | } 126 | 127 | /** 128 | * Register the bitbucket factory class. 129 | * 130 | * @return void 131 | */ 132 | private function registerBitbucketFactory(): void 133 | { 134 | $this->app->singleton('bitbucket.factory', function (Container $app): BitbucketFactory { 135 | $builder = $app['bitbucket.httpclientfactory']; 136 | $auth = $app['bitbucket.authfactory']; 137 | $cache = $app['bitbucket.cachefactory']; 138 | 139 | return new BitbucketFactory($builder, $auth, $cache); 140 | }); 141 | 142 | $this->app->alias('bitbucket.factory', BitbucketFactory::class); 143 | } 144 | 145 | /** 146 | * Register the manager class. 147 | * 148 | * @return void 149 | */ 150 | private function registerManager(): void 151 | { 152 | $this->app->singleton('bitbucket', function (Container $app): BitbucketManager { 153 | $config = $app['config']; 154 | $factory = $app['bitbucket.factory']; 155 | 156 | return new BitbucketManager($config, $factory); 157 | }); 158 | 159 | $this->app->alias('bitbucket', BitbucketManager::class); 160 | } 161 | 162 | /** 163 | * Register the bindings. 164 | * 165 | * @return void 166 | */ 167 | private function registerBindings(): void 168 | { 169 | $this->app->bind('bitbucket.connection', function (Container $app): Client { 170 | $manager = $app['bitbucket']; 171 | 172 | return $manager->connection(); 173 | }); 174 | 175 | $this->app->alias('bitbucket.connection', Client::class); 176 | } 177 | 178 | /** 179 | * Get the services provided by the provider. 180 | * 181 | * @return string[] 182 | */ 183 | public function provides(): array 184 | { 185 | return [ 186 | 'bitbucket.httpclientfactory', 187 | 'bitbucket.authfactory', 188 | 'bitbucket.cachefactory', 189 | 'bitbucket.factory', 190 | 'bitbucket', 191 | 'bitbucket.connection', 192 | ]; 193 | } 194 | } 195 | --------------------------------------------------------------------------------