groton-school / sky-api Goto Github PK
View Code? Open in Web Editor NEWPHP client for Blackbaud's SKY API
Home Page: https://groton-school.github.io/sky-api/
License: GNU General Public License v3.0
PHP client for Blackbaud's SKY API
Home Page: https://groton-school.github.io/sky-api/
License: GNU General Public License v3.0
<?php
namespace GrotonSchool\OAuth2\Client\Provider;
use Exception;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Token\AccessTokenInterface;
use League\OAuth2\Client\Tool\ArrayAccessorTrait;
use Psr\Http\Message\ResponseInterface;
class BlackbaudSKY extends AbstractProvider
{
use ArrayAccessorTrait;
public const ACCESS_KEY = "Bb-Api-Subscription-Key";
public const ACCESS_TOKEN = "access_token";
public const SESSION_STATE = "oauth2_state";
public const ARG_AUTH_CODE = "authorization_code";
public const PARAM_CODE = "code";
public const PARAM_STATE = "state";
public const OPT_PARAMS = "params";
public const OPT_REDIRECT_URI = "redirect_uri";
public const OPT_AUTH_CODE_CALLBACK = "authorization_code_callback";
public const OPT_ACCESS_TOKEN_CALLBACK = "access_token_callback";
public const OPT_ERROR_CALLBACK = "error_callback";
private $accessKey;
/** @var AccessToken */
protected $accessToken;
public function __construct(array $options = [], array $collaborators = [])
{
parent::__construct($options, $collaborators);
if (empty($options[self::ACCESS_KEY])) {
throw new Exception("Blackbaud access key required");
} else {
$this->accessKey = $options[self::ACCESS_KEY];
}
if (!empty($options[self::ACCESS_TOKEN])) {
$this->accessToken = $options[self::ACCESS_TOKEN];
}
}
public function getBaseAuthorizationUrl()
{
return "https://oauth2.sky.blackbaud.com/authorization";
}
public function getBaseAccessTokenUrl(array $params)
{
return "https://oauth2.sky.blackbaud.com/token";
}
public function getBaseApiUrl()
{
return "https://api.sky.blackbaud.com";
}
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
// TODO waiting on resolution of https://app.blackbaud.com/support/cases/018662802
}
protected function getDefaultScopes()
{
return [];
}
protected function checkResponse(ResponseInterface $response, $data)
{
}
protected function createResourceOwner(array $response, AccessToken $token)
{
}
/**
* Returns authorization headers for the 'bearer' grant.
*
* @param AccessTokenInterface|string|null $token Either a string or an access token instance
* @return array
*/
protected function getAuthorizationHeaders($token = null)
{
return [
self::ACCESS_KEY => $this->accessKey,
"Authorization" => "Bearer " . $token,
];
}
public function getAccessToken($grant = "", array $options = [])
{
if (!empty($grant)) {
$this->accessToken = parent::getAccessToken($grant, $options);
return $this->accessToken;
} elseif (!empty($this->accessToken)) {
return $this->accessToken->getToken();
} else {
throw new Exception("Stored access token or grant type required");
}
}
public function setAccessToken(AccessToken $accessToken)
{
$this->accessToken = $accessToken;
}
/** @deprecated 0.2.3 externalized to {@link https://github.com/groton-school/appengine-sky-api groton-school/appengine-sky-api} */
public function endpoint(
string $path,
?AccessToken $token = null
): SkyAPIEndpoint {
if (!$token) {
if ($this->accessToken) {
$token = $this->accessToken;
} else {
throw new Exception("No access token provided or cached");
}
}
return new SkyAPIEndpoint($this, $path, $token);
}
}
sky-api/src/OpenAPI/ObjectMap.php
Line 123 in a4cd452
" * @property $type$nullable $$key $description" . PHP_EOL;
}
}
// TODO additionalProperties
$fileContents .=
" * @api
*/
sky-api/src/OpenAPI/ObjectMap.php
Line 108 in a4cd452
if ($property["type"] == "array") {
// $description = $api["components"]["schemas"][$type]["description"];
}
// TODO handle enums
} else {
$type = basename($property['$ref']);
$description = array_key_exists(
Line 77 in a4cd452
if (false === isset($_GET[self::CODE])) {
$authorizationUrl = self::api()->getAuthorizationUrl();
$_SESSION[self::OAuth2_STATE] = self::api()->getState();
// TODO wipe existing token?
$this->cache->set(self::Request_URI, $_SERVER["REQUEST_URI"] ?? null);
header("Location: $authorizationUrl");
exit();
https://developer\.blackbaud\.com/skyapi/docs/in\-depth\-topics/api\-request\-throttling
<?php
namespace GrotonSchool\OAuth2\Client\Provider;
use Battis\DataUtilities\Path;
use Exception;
use GuzzleHttp\Client;
use League\OAuth2\Client\Token\AccessToken;
/** @deprecated 0.2.3 externalized to {@link https://github.com/groton-school/appengine-sky-api groton-school/appengine-sky-api} */
class SkyAPIEndpoint
{
/** @var Client */
private $client;
/** @var BlackbaudSKY */
private $sky;
/** @var string */
private $path;
/** @var AccessToken */
private $accessToken;
public function __construct(
BlackbaudSky $sky,
string $path,
AccessToken $accessToken
) {
assert(!empty($sky), new Exception("BlackbaudSKY instance required"));
$this->sky = $sky;
$this->path = $path;
$this->accessToken = $accessToken;
$this->client = new Client([
"base_uri" => Path::join($this->sky->getBaseApiUrl(), $this->path) . "/",
]);
}
public function send(string $method, string $url, array $options = []): mixed
{
/*
* TODO deal with refreshing tokens (need callback to store new refresh token)
* https://developer.blackbaud.com/skyapi/docs/in-depth-topics/api-request-throttling
*/
usleep(100000);
$request = $this->sky->getAuthenticatedRequest(
$method,
$url,
$this->accessToken,
$options
);
return json_decode(
$this->client
->send($request)
->getBody()
->getContents(),
true
);
}
public function get(string $url, array $options = []): mixed
{
return $this->send("get", $url, $options);
}
public function post(string $url, array $options = []): mixed
{
return $this->send("post", $url, $options);
}
public function patch(string $url, array $options = []): mixed
{
return $this->send("patch", $url, $options);
}
public function delete(string $url, array $options = []): mixed
{
return $this->send("delete", $url, $options);
}
public function endpoint(string $path): SkyAPIEndpoint
{
return new SkyAPIEndpoint(
$this->sky,
Path::join($this->path, $path),
$this->accessToken
);
}
}
Line 108 in a4cd452
$newToken = self::api()->getAccessToken(self::REFRESH_TOKEN, [
self::REFRESH_TOKEN => $token->getRefreshToken(),
]);
// FIXME need to handle _not_ being able to refresh!
$this->cache->set(self::Bb_TOKEN, $newToken);
$token = $newToken;
} else {
sky-api/src/OpenAPI/TypeMap.php
Line 50 in a4cd452
*/
public static function _string(): string
{
// TODO enum
return "string";
}
<?php
use GrotonSchool\OAuth2\Client\Provider\BlackbaudSKY;
use League\OAuth2\Client\Token\AccessToken;
require_once __DIR__ . '/../vendor/autoload.php';
$sky = new BlackbaudSKY([
/**
* Blackbaud subscription access key
* @link https://developer.blackbaud.com/subscriptions/
*/
BlackbaudSKY::ACCESS_KEY => getenv('BLACKBAUD_ACCESS_KEY'),
/**
* OAuth 2.0 App Credentials
* @link https://developer.blackbaud.com/apps/
*/
// The client ID assigned to you by the provider
'clientId' => getenv('OAUTH_CLIENT_ID'),
// The client password assigned to you by the provider
'clientSecret' => getenv('OAUTH_CLIENT_SECRET')
]);
$existingAccessToken = new AccessToken($_POST); // get access token from your data store
// FIXME normally we'd test $existingAccessToken->hasExpired() before refreshing
$newAccessToken = $sky->getAccessToken('refresh_token', [
'refresh_token' => $existingAccessToken->getRefreshToken()
]);
// Purge old access token and store new access token to your data store.
?>
<!DOCTYPE html>
<html>
<head>
<title>Refresh Token</title>
</head>
<body>
<h1>Refresh Token</h1>
<p>Requested an access token using <code>refresh</code> flow.</p>
<h3>Access Token</h3>
<pre lang="json"><?= json_encode($newAccessToken, JSON_PRETTY_PRINT) ?></pre>
<form method="post" action="refresh.php">
<?php
foreach ($newAccessToken->jsonSerialize() as $param => $value) {
echo <<<EOT
<input name="{$param}" value="$value" type="hidden" />
EOT;
}
?>
<button type="submit">Refresh Token</button>
</form>
</body>
</html>
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.