. */ namespace Xibo\Middleware; use Carbon\Carbon; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\ResourceServer; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface as Middleware; use Psr\Http\Server\RequestHandlerInterface as RequestHandler; use Slim\App as App; use Slim\Routing\RouteContext; use Xibo\Factory\ApplicationScopeFactory; use Xibo\Helper\UserLogProcessor; use Xibo\OAuth\AccessTokenRepository; use Xibo\Support\Exception\AccessDeniedException; /** * Class ApiAuthenticationOAuth * This middleware protects the API entry point * @package Xibo\Middleware */ class ApiAuthorization implements Middleware { /* @var App $app */ private $app; /** * ApiAuthenticationOAuth constructor. * @param $app */ public function __construct($app) { $this->app = $app; } /** * @param Request $request * @param RequestHandler $handler * @return Response * @throws OAuthServerException * @throws \Xibo\Support\Exception\AccessDeniedException * @throws \Xibo\Support\Exception\ConfigurationException * @throws \Xibo\Support\Exception\NotFoundException */ public function process(Request $request, RequestHandler $handler): Response { /* @var \Xibo\Entity\User $user */ $user = null; /** @var \Xibo\Service\LogServiceInterface $logger */ $logger = $this->app->getContainer()->get('logService'); // Setup the authorization server $this->app->getContainer()->set('server', function (ContainerInterface $container) use ($logger) { // oAuth Resource $apiKeyPaths = $container->get('configService')->getApiKeyDetails(); $accessTokenRepository = new AccessTokenRepository( $logger, $container->get('pool'), $container->get('applicationFactory') ); return new ResourceServer( $accessTokenRepository, $apiKeyPaths['publicKeyPath'] ); }); /** @var ResourceServer $server */ $server = $this->app->getContainer()->get('server'); $validatedRequest = $server->validateAuthenticatedRequest($request); // We have a valid JWT/token // get our user from it. $userFactory = $this->app->getContainer()->get('userFactory'); // What type of Access Token to we have? Client Credentials or AuthCode // client_credentials grants are issued with the correct oauth_user_id in the token, so we don't need to // distinguish between them here! nice! $userId = $validatedRequest->getAttribute('oauth_user_id'); $user = $userFactory->getById($userId); $user->setChildAclDependencies($this->app->getContainer()->get('userGroupFactory')); $user->load(); // Block access by retired users. if ($user->retired === 1) { throw new AccessDeniedException(__('Sorry this account does not exist or cannot be authenticated.')); } // We must check whether this user has access to the route they have requested. // Get the current route pattern $routeContext = RouteContext::fromRequest($request); $route = $routeContext->getRoute(); $resource = $route->getPattern(); // Allow public routes if (!in_array($resource, $validatedRequest->getAttribute('publicRoutes', []))) { $request = $request->withAttribute('public', false); // Check that the Scopes granted to this token are allowed access to the route/method of this request /** @var ApplicationScopeFactory $applicationScopeFactory */ $applicationScopeFactory = $this->app->getContainer()->get('applicationScopeFactory'); $scopes = $validatedRequest->getAttribute('oauth_scopes'); // If there are no scopes in the JWT, we should not authorise // An older client which makes a request with no scopes should get an access token with // all scopes configured for the application if (!is_array($scopes) || count($scopes) <= 0) { throw new AccessDeniedException(); } $logger->debug('Scopes provided with request: ' . count($scopes)); // Check all scopes in the request before we deny access. $grantAccess = false; foreach ($scopes as $scope) { // If I have the "all" scope granted then there isn't any need to test further if ($scope === 'all') { $grantAccess = true; break; } $logger->debug( sprintf( 'Test authentication for %s %s against scope %s', $resource, $request->getMethod(), $scope ) ); // Check the route and request method if ($applicationScopeFactory->getById($scope)->checkRoute($request->getMethod(), $resource)) { $grantAccess = true; break; } } if (!$grantAccess) { throw new AccessDeniedException(__('Access to this route is denied for this scope')); } } else { // Public request $validatedRequest = $validatedRequest->withAttribute('public', true); } $requestId = $this->app->getContainer()->get('store')->insert(' INSERT INTO `application_requests_history` ( userId, applicationId, url, method, startTime, endTime, duration ) VALUES ( :userId, :applicationId, :url, :method, :startTime, :endTime, :duration ) ', [ 'userId' => $user->userId, 'applicationId' => $validatedRequest->getAttribute('oauth_client_id'), 'url' => htmlspecialchars($request->getUri()->getPath()), 'method' => $request->getMethod(), 'startTime' => Carbon::now(), 'endTime' => Carbon::now(), 'duration' => 0 ], 'api_requests_history'); $this->app->getContainer()->get('store')->commitIfNecessary('api_requests_history'); $logger->setUserId($user->userId); $this->app->getContainer()->set('user', $user); $logger->setRequestId($requestId); // Add this request information to the logger. $logger->getLoggerInterface()->pushProcessor(new UserLogProcessor( $user->userId, null, $requestId, )); return $handler->handle($validatedRequest->withAttribute('name', 'API')); } }