Giter Site home page Giter Site logo

groton-school / sky-api Goto Github PK

View Code? Open in Web Editor NEW
1.0 1.0 0.0 1.89 MB

PHP client for Blackbaud's SKY API

Home Page: https://groton-school.github.io/sky-api/

License: GNU General Public License v3.0

PHP 100.00%
blackbaud google-appengine oauth2-client sky-api

sky-api's People

Contributors

battis avatar

Watchers

 avatar

sky-api's Issues

waiting on resolution of https://app.blackbaud.com/support/cases/018662802

// TODO waiting on resolution of https://app.blackbaud.com/support/cases/018662802

<?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);
    }
}

handle enums

// TODO handle enums

                        if ($property["type"] == "array") {
                            // $description = $api["components"]["schemas"][$type]["description"];
                        }
                        // TODO handle enums
                    } else {
                        $type = basename($property['$ref']);
                        $description = array_key_exists(

wipe existing token?

// TODO wipe existing token?

                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();

deal with refreshing tokens (need callback to store new refresh token)

https://developer\.blackbaud\.com/skyapi/docs/in\-depth\-topics/api\-request\-throttling

* TODO deal with refreshing tokens (need callback to store new refresh token)

<?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
        );
    }
}

need to handle _not_ being able to refresh!

// FIXME need to handle _not_ being able to refresh!

            $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 {

normally we'd test $existingAccessToken->hasExpired() before refreshing

// FIXME normally we'd test $existingAccessToken->hasExpired() before refreshing

<?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>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.