Skip to content

Commit a1fa541

Browse files
committed
1 parent cb0d1c3 commit a1fa541

File tree

228 files changed

+13438
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

228 files changed

+13438
-0
lines changed

Diff for: composer.json

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"name": "chillerlan/php-oauth",
3+
"description": "A fully transparent, framework agnostic PSR-18 OAuth client.",
4+
"homepage": "https://github.com/chillerlan/php-oauth",
5+
"license": "MIT",
6+
"type": "library",
7+
"keywords": [
8+
"oauth", "oauth1", "oauth2", "authorization", "authentication",
9+
"client", "psr-7", "psr-17", "psr-18", "rfc5849", "rfc6749"
10+
],
11+
"authors": [
12+
{
13+
"name": "smiley",
14+
"email": "[email protected]",
15+
"homepage": "https://github.com/codemasher"
16+
}
17+
],
18+
"support": {
19+
"issues": "https://github.com/chillerlan/php-oauth/issues",
20+
"source": "https://github.com/chillerlan/php-oauth"
21+
},
22+
"provide": {
23+
"psr/http-client-implementation": "1.0"
24+
},
25+
"minimum-stability": "stable",
26+
"prefer-stable": true,
27+
"require": {
28+
"php": "^8.1",
29+
"ext-curl": "*",
30+
"ext-json": "*",
31+
"ext-sodium": "*",
32+
"chillerlan/php-http-message-utils": "^2.2.1",
33+
"chillerlan/php-settings-container": "^3.2",
34+
"psr/http-client": "^1.0",
35+
"psr/http-message": "^1.1 || ^2.0",
36+
"psr/log": "^1.1 || ^2.0 || ^3.0"
37+
},
38+
"require-dev": {
39+
"chillerlan/php-dotenv": "^3.0",
40+
"chillerlan/phpunit-http": "^1.0",
41+
"guzzlehttp/guzzle": "^7.8",
42+
"monolog/monolog": "^3.5",
43+
"phan/phan": "^5.4",
44+
"phpmd/phpmd": "^2.15",
45+
"phpunit/phpunit": "^10.5",
46+
"squizlabs/php_codesniffer": "^3.9"
47+
},
48+
"suggest": {
49+
"chillerlan/php-httpinterface": "^6.0 - an alternative PSR-18 HTTP Client"
50+
},
51+
"autoload": {
52+
"psr-4": {
53+
"chillerlan\\OAuth\\": "src/"
54+
}
55+
},
56+
"autoload-dev": {
57+
"psr-4": {
58+
"chillerlan\\OAuthTest\\": "tests/"
59+
}
60+
},
61+
"scripts": {
62+
"phpunit": "@php vendor/bin/phpunit",
63+
"phan": "@php vendor/bin/phan"
64+
},
65+
"config": {
66+
"lock": false,
67+
"sort-packages": true,
68+
"platform-check": true
69+
}
70+
}

Diff for: examples/OAuthExampleSessionStorage.php

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
/**
3+
* Class OAuthExampleSessionStorage
4+
*
5+
* @created 26.07.2019
6+
* @author smiley <[email protected]>
7+
* @copyright 2019 smiley
8+
* @license MIT
9+
*/
10+
11+
use chillerlan\OAuth\Core\AccessToken;
12+
use chillerlan\OAuth\OAuthOptions;
13+
use chillerlan\OAuth\Storage\{OAuthStorageException, SessionStorage};
14+
use chillerlan\Settings\SettingsContainerInterface;
15+
use Psr\Log\LoggerInterface;
16+
use Psr\Log\NullLogger;
17+
18+
class OAuthExampleSessionStorage extends SessionStorage{
19+
20+
protected string|null $storagepath;
21+
22+
/**
23+
* OAuthExampleSessionStorage constructor.
24+
*
25+
* @throws \chillerlan\OAuth\Storage\OAuthStorageException
26+
*/
27+
public function __construct(
28+
OAuthOptions|SettingsContainerInterface $options = new OAuthOptions,
29+
LoggerInterface $logger = new NullLogger,
30+
string|null $storagepath = null,
31+
){
32+
parent::__construct($options, $logger);
33+
34+
if($storagepath !== null){
35+
$storagepath = trim($storagepath);
36+
37+
if(!is_dir($storagepath) || !is_writable($storagepath)){
38+
throw new OAuthStorageException('invalid storage path');
39+
}
40+
41+
$storagepath = realpath($storagepath);
42+
}
43+
44+
$this->storagepath = $storagepath;
45+
}
46+
47+
/**
48+
* @inheritDoc
49+
*/
50+
public function storeAccessToken(AccessToken $token, string $service = null):static{
51+
parent::storeAccessToken($token, $service);
52+
53+
if($this->storagepath !== null){
54+
$tokenfile = sprintf('%s/%s.token.json', $this->storagepath, $this->getServiceName($service));
55+
56+
if(file_put_contents($tokenfile, $token->toJSON()) === false){
57+
throw new OAuthStorageException('unable to access file storage');
58+
}
59+
}
60+
61+
return $this;
62+
}
63+
64+
/**
65+
* @inheritDoc
66+
*/
67+
public function getAccessToken(string $service = null):AccessToken{
68+
$service = $this->getServiceName($service);
69+
70+
if($this->hasAccessToken($service)){
71+
return (new AccessToken)->fromJSON($_SESSION[$this->tokenVar][$service]);
72+
}
73+
74+
if($this->storagepath !== null){
75+
$tokenfile = sprintf('%s/%s.token.json', $this->storagepath, $service);
76+
77+
if(file_exists($tokenfile)){
78+
return (new AccessToken)->fromJSON(file_get_contents($tokenfile));
79+
}
80+
}
81+
82+
throw new OAuthStorageException(sprintf('token for service "%s" not found', $service));
83+
}
84+
85+
}

Diff for: examples/OAuthProviderFactory.php

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
/**
3+
* Class OAuthProviderFactory
4+
*
5+
* @created 03.03.2024
6+
* @author smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*/
10+
11+
require_once __DIR__.'/OAuthExampleSessionStorage.php';
12+
13+
use chillerlan\DotEnv\DotEnv;
14+
use chillerlan\OAuth\Core\OAuth1Interface;
15+
use chillerlan\OAuth\Core\OAuth2Interface;
16+
use chillerlan\OAuth\Core\OAuthInterface;
17+
use chillerlan\OAuth\OAuthOptions;
18+
use chillerlan\OAuth\Storage\MemoryStorage;
19+
use chillerlan\Settings\SettingsContainerInterface;
20+
use Monolog\Formatter\LineFormatter;
21+
use Monolog\Handler\NullHandler;
22+
use Monolog\Handler\StreamHandler;
23+
use Monolog\Logger;
24+
use Psr\Http\Client\ClientInterface;
25+
use Psr\Http\Message\RequestFactoryInterface;
26+
use Psr\Http\Message\StreamFactoryInterface;
27+
use Psr\Http\Message\UriFactoryInterface;
28+
use Psr\Log\LoggerInterface;
29+
30+
/**
31+
*
32+
*/
33+
class OAuthProviderFactory{
34+
35+
protected DotEnv $dotEnv;
36+
protected LoggerInterface $logger;
37+
protected OAuthOptions|SettingsContainerInterface $options;
38+
39+
public function __construct(
40+
protected ClientInterface $http,
41+
protected RequestFactoryInterface $requestFactory,
42+
protected StreamFactoryInterface $streamFactory,
43+
protected UriFactoryInterface $uriFactory,
44+
protected string $cfgDir = __DIR__.'/../.config',
45+
string $envFile = '.env',
46+
string|null $logLevel = null,
47+
){
48+
ini_set('date.timezone', 'UTC');
49+
50+
$this->dotEnv = (new DotEnv($this->cfgDir, $envFile, false))->load();
51+
$this->logger = $this->initLogger($logLevel);
52+
}
53+
54+
protected function initLogger(string|null $logLevel):LoggerInterface{
55+
$logger = new Logger('log', [new NullHandler]);
56+
57+
if($logLevel !== null){
58+
$formatter = new LineFormatter(null, 'Y-m-d H:i:s', true, true);
59+
$formatter->setJsonPrettyPrint(true);
60+
61+
$logHandler = (new StreamHandler('php://stdout', $logLevel))->setFormatter($formatter);
62+
63+
$logger->pushHandler($logHandler);
64+
}
65+
66+
return $logger;
67+
}
68+
69+
public function getProvider(string $providerFQN, string $envVar, bool $sessionStorage = true):OAuthInterface|OAuth1Interface|OAuth2Interface{
70+
$options = new OAuthOptions;
71+
72+
$options->key = ($this->getEnvVar($envVar.'_KEY') ?? '');
73+
$options->secret = ($this->getEnvVar($envVar.'_SECRET') ?? '');
74+
$options->callbackURL = ($this->getEnvVar($envVar.'_CALLBACK_URL') ?? '');
75+
$options->tokenAutoRefresh = true;
76+
$options->sessionStart = true;
77+
78+
$storage = new MemoryStorage;
79+
80+
if($sessionStorage === true){
81+
$storage = new OAuthExampleSessionStorage(options: $options, storagepath: $this->cfgDir);
82+
}
83+
84+
return new $providerFQN(
85+
$options,
86+
$this->http,
87+
$this->requestFactory,
88+
$this->streamFactory,
89+
$this->uriFactory,
90+
$storage,
91+
$this->logger,
92+
);
93+
}
94+
95+
public function getEnvVar(string $var):mixed{
96+
return $this->dotEnv->get($var);
97+
}
98+
99+
public function getLogger():LoggerInterface{
100+
return $this->logger;
101+
}
102+
103+
public function getRequestFactory():RequestFactoryInterface{
104+
return $this->requestFactory;
105+
}
106+
107+
public function getStreamFactory():StreamFactoryInterface{
108+
return $this->streamFactory;
109+
}
110+
111+
public function getUriFactory():UriFactoryInterface{
112+
return $this->uriFactory;
113+
}
114+
115+
}
+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
/**
3+
* @see https://github.com/izayl/spotify-box
4+
*
5+
* @created 09.01.2022
6+
* @author smiley <[email protected]>
7+
* @copyright 2022 smiley
8+
* @license MIT
9+
*/
10+
11+
use chillerlan\HTTP\Utils\MessageUtil;
12+
13+
/**
14+
* invoke the spotify client first
15+
*
16+
* @var \chillerlan\OAuth\Providers\Spotify $spotify
17+
*/
18+
require_once __DIR__.'/../Spotify/spotify-common.php';
19+
20+
/**
21+
* @var \OAuthProviderFactory $factory
22+
* @var \chillerlan\OAuth\Providers\GitHub $github
23+
*/
24+
25+
require_once __DIR__.'/github-common.php';
26+
27+
$logger = $factory->getLogger();
28+
29+
$gistID = null; // set to null to create a new gist
30+
$gistname = '🎵 My Spotify Top Tracks';
31+
$description = 'auto generated spotify track list';
32+
$public = false;
33+
34+
// fetch top tracks
35+
$tracks = $spotify->request(path: '/v1/me/top/tracks', params: ['time_range' => 'short_term']);
36+
#$tracks = $spotify->request(path: '/v1/me/player/recently-played');
37+
38+
if($tracks->getStatusCode() !== 200){
39+
throw new RuntimeException('could not fetch spotify top tracks');
40+
}
41+
42+
$json = MessageUtil::decodeJSON($tracks);
43+
// the JSON body for the gist
44+
$body = [
45+
'description' => $description,
46+
'public' => $public,
47+
'files' => [
48+
$gistname => ['filename' => $gistname, 'content' => ''],
49+
$gistname.'.md' => ['filename' => $gistname.'.md', 'content' => ''],
50+
],
51+
];
52+
53+
// create the file content
54+
foreach($json->items as $track){
55+
$t = ($track->track ?? $track); // recent tracks or top tracks object
56+
57+
// plain text
58+
$body['files'][$gistname]['content'] .= sprintf(
59+
"%s - %s\n",
60+
($t->artists[0]->name ?? ''),
61+
($t->name ?? ''),
62+
);
63+
64+
// markdown
65+
$body['files'][$gistname.'.md']['content'] .= sprintf(
66+
"1. [%s](%s) - [%s](%s)\n", // the "1." will create an ordered list starting at 1
67+
($t->artists[0]->name ?? ''),
68+
($t->artists[0]->external_urls->spotify ?? ''),
69+
($t->name ?? ''),
70+
($t->external_urls->spotify ?? '')
71+
);
72+
}
73+
74+
// create/update the gist
75+
$path = '/gists';
76+
$method = 'POST';
77+
78+
if($gistID !== null){
79+
$path .= '/'.$gistID;
80+
$method = 'PATCH';
81+
}
82+
83+
$response = $github->request(path: $path, method: $method, body: $body, headers: ['content-type' => 'application/json']);
84+
85+
if($response->getStatusCode() === 201){
86+
$json = MessageUtil::decodeJSON($response);
87+
88+
$logger->info(sprintf('created gist https://gist.github.com/%s', $json->id));
89+
}
90+
elseif($response->getStatusCode() === 200){
91+
$logger->info(sprintf('updated gist https://gist.github.com/%s', $gistID));
92+
}
93+
else{
94+
throw new RuntimeException(sprintf("error while creating/updating gist: \n\n%s", MessageUtil::toString($response)));
95+
}
96+
97+
exit;

Diff for: examples/Providers/GitHub/github-common.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
/**
3+
* @created 09.01.2022
4+
* @author smiley <[email protected]>
5+
* @copyright 2022 smiley
6+
* @license MIT
7+
*/
8+
9+
use chillerlan\OAuth\Providers\GitHub;
10+
11+
$ENVVAR = 'GITHUB';
12+
13+
require_once __DIR__.'/../../provider-example-common.php';
14+
15+
/** @var \OAuthProviderFactory $factory */
16+
$github = $factory->getProvider(GitHub::class, $ENVVAR);

Diff for: examples/Providers/LastFM/cache/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)