Initial Upload

This commit is contained in:
Matt Batchelder
2025-12-02 10:32:59 -05:00
commit 05ce0da296
2240 changed files with 467811 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
<?php
/*
* Copyright (C) 2023 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\OAuth;
use Carbon\Carbon;
use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Token\Builder;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\Traits\AccessTokenTrait;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
/**
* Class AccessTokenEntity
* @package Xibo\OAuth
*/
class AccessTokenEntity implements AccessTokenEntityInterface
{
use AccessTokenTrait, TokenEntityTrait, EntityTrait;
/**
* Generate a JWT from the access token
*
* @param CryptKey $privateKey
*
* @return Token
*/
private function convertToJWT(CryptKey $privateKey)
{
$userId = $this->getUserIdentifier();
$tokenBuilder = (new Builder(new JoseEncoder(), ChainedFormatter::default()));
$signingKey = Key\InMemory::file($privateKey->getKeyPath());
return $tokenBuilder
->issuedBy('info@xibosignage.com')
->permittedFor($this->getClient()->getIdentifier())
->identifiedBy($this->getIdentifier())
->issuedAt(Carbon::now()->toDateTimeImmutable())
->canOnlyBeUsedAfter(Carbon::now()->toDateTimeImmutable())
->expiresAt($this->getExpiryDateTime())
->relatedTo($userId)
->withClaim('scopes', $this->getScopes())
->getToken(new Sha256(), $signingKey)
;
}
/**
* Generate a string representation from the access token
*/
public function __toString()
{
return $this->convertToJWT($this->privateKey)->toString();
}
}

View File

@@ -0,0 +1,173 @@
<?php
/*
* Copyright (C) 2023 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\OAuth;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use Stash\Interfaces\PoolInterface;
use Xibo\Factory\ApplicationFactory;
/**
* Class AccessTokenRepository
* @package Xibo\Storage
*/
class AccessTokenRepository implements AccessTokenRepositoryInterface
{
/** @var \Xibo\Service\LogServiceInterface*/
private $logger;
/**
* @var ApplicationFactory
*/
private $applicationFactory;
/**
* @var PoolInterface
*/
private $pool;
/**
* AccessTokenRepository constructor.
* @param \Xibo\Service\LogServiceInterface $logger
*/
public function __construct($logger, PoolInterface $pool, ApplicationFactory $applicationFactory)
{
$this->logger = $logger;
$this->pool = $pool;
$this->applicationFactory = $applicationFactory;
}
/** @inheritDoc */
public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null)
{
$this->logger->debug('Getting new Access Token');
$accessToken = new AccessTokenEntity();
$accessToken->setClient($clientEntity);
foreach ($scopes as $scope) {
$accessToken->addScope($scope);
}
// client credentials, we take user from the client entity
// authentication code, we have userIdentifier already
$userIdentifier = $userIdentifier ?? $clientEntity->userId;
$accessToken->setUserIdentifier($userIdentifier);
// set user and log to audit
$this->logger->setUserId($userIdentifier);
$this->logger->audit(
'Auth',
0,
'Access Token issued',
[
'Application identifier ends with' => substr($clientEntity->getIdentifier(), -8),
'Application Name' => $clientEntity->getName()
]
);
return $accessToken;
}
/** @inheritDoc */
public function isAccessTokenRevoked($tokenId)
{
$cache = $this->pool->getItem('C_' . $tokenId);
$data = $cache->get();
// if cache is expired
if ($cache->isMiss() || empty($data)) {
return true;
}
$cache2 = $this->pool->getItem('C_' . $data['client'] . '/' . $data['userIdentifier']);
$data2 = $cache2->get();
// cache manually removed (revoke access, changed secret)
if ($cache2->isMiss() || empty($data2)) {
return true;
}
if ($data['client'] !== $data2['client'] || $data['userIdentifier'] !== $data2['userIdentifier']) {
return true;
}
// if it is correctly cached, double check that it is still authorized at the request time
// edge case being new access code requested with not yet expired code,
// otherwise one of the previous conditions will be met.
// Note: we can only do this if one grant type is selected on the client.
$client = $this->applicationFactory->getClientEntity($data['client']);
if ($client->clientCredentials === 0
&& $client->authCode === 1
&& !$this->applicationFactory->checkAuthorised($data['client'], $data['userIdentifier'])
) {
return true;
}
return false;
}
/** @inheritDoc */
public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity)
{
$date = clone $accessTokenEntity->getExpiryDateTime();
// since stash cache sets expiresAt at up to provided date
// with up to 15% less than the provided date
// add more time to normal token expire, to ensure cache does not expire before the token.
$date = $date->add(new \DateInterval('PT30M'));
// cache with token identifier
$cache = $this->pool->getItem('C_' . $accessTokenEntity->getIdentifier());
$cache->set(
[
'userIdentifier' => $accessTokenEntity->getUserIdentifier(),
'client' => $accessTokenEntity->getClient()->getIdentifier()
]
);
$cache->expiresAt($date);
$this->pool->saveDeferred($cache);
// double cache with client identifier and user identifier
// this will allow us to revoke access to client or for specific client/user combination in the backend
$cache2 = $this->pool->getItem(
'C_' . $accessTokenEntity->getClient()->getIdentifier() . '/' . $accessTokenEntity->getUserIdentifier()
);
$cache2->set(
[
'userIdentifier' => $accessTokenEntity->getUserIdentifier(),
'client' => $accessTokenEntity->getClient()->getIdentifier()
]
);
$cache2->expiresAt($date);
$this->pool->saveDeferred($cache2);
}
/** @inheritDoc */
public function revokeAccessToken($tokenId)
{
$this->pool->getItem('C_' . $tokenId)->clear();
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\OAuth;
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use League\OAuth2\Server\Entities\Traits\AuthCodeTrait;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
class AuthCodeEntity implements AuthCodeEntityInterface
{
use EntityTrait, TokenEntityTrait, AuthCodeTrait;
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\OAuth;
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
class AuthCodeRepository implements AuthCodeRepositoryInterface
{
/**
* {@inheritdoc}
*/
public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity)
{
// Some logic to persist the auth code to a database
}
/**
* {@inheritdoc}
*/
public function revokeAuthCode($codeId)
{
// Some logic to revoke the auth code in a database
}
/**
* {@inheritdoc}
*/
public function isAuthCodeRevoked($codeId)
{
return false; // The auth code has not been revoked
}
/**
* {@inheritdoc}
*/
public function getNewAuthCode()
{
return new AuthCodeEntity();
}
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\OAuth;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait;
class RefreshTokenEntity implements RefreshTokenEntityInterface
{
use RefreshTokenTrait, EntityTrait;
}

View File

@@ -0,0 +1,121 @@
<?php
/*
* Copyright (C) 2023 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\OAuth;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use Stash\Interfaces\PoolInterface;
class RefreshTokenRepository implements RefreshTokenRepositoryInterface
{
/**
* @var \Xibo\Service\LogServiceInterface
*/
private $logger;
/**
* @var PoolInterface
*/
private $pool;
/**
* AccessTokenRepository constructor.
* @param \Xibo\Service\LogServiceInterface $logger
*/
public function __construct(\Xibo\Service\LogServiceInterface $logger, PoolInterface $pool)
{
$this->logger = $logger;
$this->pool = $pool;
}
/**
* {@inheritdoc}
*/
public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity)
{
$date = clone $refreshTokenEntity->getExpiryDateTime();
// since stash cache sets expiresAt at up to provided date
// with up to 15% less than the provided date
// add more time to normal refresh token expire, to ensure cache does not expire before the token.
$date = $date->add(new \DateInterval('P15D'));
// cache with refresh token identifier
$cache = $this->pool->getItem('R_' . $refreshTokenEntity->getIdentifier());
$cache->set(
[
'accessToken' => $refreshTokenEntity->getAccessToken()->getIdentifier(),
]
);
$cache->expiresAt($date);
$this->pool->saveDeferred($cache);
}
/**
* {@inheritdoc}
*/
public function revokeRefreshToken($tokenId)
{
$this->pool->getItem('R_' . $tokenId)->clear();
}
/**
* {@inheritdoc}
*/
public function isRefreshTokenRevoked($tokenId)
{
// get cache by refresh token identifier
$cache = $this->pool->getItem('R_' . $tokenId);
$refreshTokenData = $cache->get();
if ($cache->isMiss() || empty($refreshTokenData)) {
return true;
}
// get access token cache by access token identifier
$tokenCache = $this->pool->getItem('C_' . $refreshTokenData['accessToken']);
$tokenCacheData = $tokenCache->get();
// if the token itself not expired yet
// check if it was unauthorised by the specific user
// we cannot always check this as it would revoke refresh token if the access token already expired.
if (!$tokenCache->isMiss() && !empty($tokenCacheData)) {
// check access token cache by client and user identifiers
// (see if application got changed secret/revoked access)
$cache2 = $this->pool->getItem('C_' . $tokenCacheData['client'] . '/' . $tokenCacheData['userIdentifier']);
$data2 = $cache2->get();
if ($cache2->isMiss() || empty($data2)) {
return true;
}
}
return false; // The refresh token has not been revoked
}
/**
* {@inheritdoc}
*/
public function getNewRefreshToken()
{
return new RefreshTokenEntity();
}
}

37
lib/OAuth/ScopeEntity.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
/*
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\OAuth;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\ScopeTrait;
/**
* Class ScopeEntity
* @package Xibo\OAuth
*/
class ScopeEntity implements ScopeEntityInterface
{
use ScopeTrait;
use EntityTrait;
}