warlof/slackbot

View on GitHub
src/Repositories/Slack/SlackApi.php

Summary

Maintainability
A
45 mins
Test Coverage
<?php
/**
 * This file is part of slackbot and provide user synchronization between both SeAT and a Slack Team
 *
 * Copyright (C) 2016, 2017, 2018, 2019  Loïc Leuilliot <loic.leuilliot@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace Warlof\Seat\Slackbot\Repositories\Slack;

use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\UriInterface;
use Warlof\Seat\Slackbot\Repositories\Slack\Access\AccessInterface;
use Warlof\Seat\Slackbot\Repositories\Slack\Access\CheckAccess;
use Warlof\Seat\Slackbot\Repositories\Slack\Cache\CacheInterface;
use Warlof\Seat\Slackbot\Repositories\Slack\Containers\SlackAuthentication;
use Warlof\Seat\Slackbot\Repositories\Slack\Containers\SlackResponse;
use Warlof\Seat\Slackbot\Repositories\Slack\Exceptions\InvalidAuthenticationException;
use Warlof\Seat\Slackbot\Repositories\Slack\Exceptions\InvalidContainerDataException;
use Warlof\Seat\Slackbot\Repositories\Slack\Exceptions\RequestFailedException;
use Warlof\Seat\Slackbot\Repositories\Slack\Exceptions\SlackScopeAccessDeniedException;
use Warlof\Seat\Slackbot\Repositories\Slack\Exceptions\UriDataMissingException;
use Warlof\Seat\Slackbot\Repositories\Slack\Fetchers\FetcherInterface;
use Warlof\Seat\Slackbot\Repositories\Slack\Log\LogInterface;

class SlackApi {

    protected $authentication;

    protected $fetcher;

    protected $cache;

    protected $access_checker;

    protected $query_string = [];

    protected $request_body = [];

    protected $api = [
        'scheme' => 'https',
        'host'   => 'slack.com/api',
    ];

    public function __construct(SlackAuthentication $authentication = null) {
        if (!is_null($authentication))
            $this->authentication = $authentication;

        return $this;
    }

    /**
     * @return Configuration
     * @throws InvalidContainerDataException
     */
    public function getConfiguration() : Configuration
    {
        return Configuration::getInstance();
    }

    /**
     * @return SlackAuthentication
     * @throws InvalidAuthenticationException
     */
    public function getAuthentication() : SlackAuthentication
    {
        if (is_null($this->authentication))
            throw new InvalidAuthenticationException('Authentication data not set.');

        return $this->authentication;
    }

    /**
     * @param SlackAuthentication $authentication
     *
     * @return SlackApi
     * @throws InvalidContainerDataException
     */
    public function setAuthentication(SlackAuthentication $authentication) : self
    {
        if (!$authentication->valid())
            throw new InvalidContainerDataException('Authentication data are invalid or empty');

        $this->authentication = $authentication;

        return $this;
    }

    /**
     * @return FetcherInterface
     * @throws InvalidContainerDataException
     */
    public function getFetcher() : FetcherInterface
    {
        if (!$this->fetcher) {
            $fetcher_class = $this->getConfiguration()->fetcher;
            $this->fetcher = new $fetcher_class(...[$this->authentication]);
        }

        return $this->fetcher;
    }

    public function setFetcher(FetcherInterface $fetcher)
    {
        $this->fetcher = $fetcher;
    }

    /**
     * @return LogInterface
     * @throws InvalidContainerDataException
     */
    public function getLogger() : LogInterface
    {
        return $this->getConfiguration()->getLogger();
    }

    /**
     * @return CacheInterface
     * @throws InvalidContainerDataException
     */
    public function getCache() : CacheInterface
    {
        return $this->getConfiguration()->getCache();
    }

    public function getAccessChecker() : CheckAccess
    {
        if (!$this->access_checker)
            $this->access_checker = new CheckAccess();

        return $this->access_checker;
    }

    public function setAccessChecker(AccessInterface $checker) : self
    {
        $this->access_checker = $checker;

        return $this;
    }

    public function getQueryString() : array
    {
        return $this->query_string;
    }

    public function setQueryString(array $query) : self
    {
        $this->query_string = $query;

        return $this;
    }

    public function getBody() : array
    {
        return $this->request_body;
    }

    public function setBody(array $body) : self
    {
        $this->request_body = $body;

        return $this;
    }

    /**
     * @param string $method
     * @param string $uri
     * @param array $uri_data
     * @return SlackResponse
     * @throws InvalidContainerDataException
     * @throws RequestFailedException
     * @throws SlackScopeAccessDeniedException
     * @throws UriDataMissingException
     */
    public function invoke(string $method, string $uri, array $uri_data = []) : SlackResponse
    {
        if (!$this->getAccessChecker()->can($method, $uri, $this->getFetcher()->getAuthenticationScopes())) {
            $uri = $this->buildDataUri($uri, $uri_data);

            $this->getLogger()->error('Access denied to ' . $uri . ' due to missing scopes.');

            throw new SlackScopeAccessDeniedException('Access denied to ' . $uri);
        }

        $uri = $this->buildDataUri($uri, $uri_data);
        $this->setQueryString([]);

        $result = $this->rawFetch($method, $uri, $this->getBody());

        $this->setBody([]);

        return $result;
    }

    /**
     * @param string $method
     * @param string $uri
     * @param array $body
     * @return SlackResponse
     * @throws InvalidContainerDataException
     * @throws RequestFailedException
     */
    private function rawFetch(string $method, string $uri, array $body) : SlackResponse
    {
        return $this->getFetcher()->call($method, $uri, $body);
    }

    /**
     * @param string $uri
     * @param array $data
     *
     * @return UriInterface
     * @throws UriDataMissingException
     */
    public function buildDataUri(string $uri, array $data = []) : UriInterface
    {
        $query_params = $this->getQueryString();

        return Uri::fromParts([
            'scheme' => $this->api['scheme'],
            'host'   => $this->api['host'],
            'path'   => $this->mapDataToUri($uri, $data),
            'query'  => http_build_query($query_params),
        ]);
    }

    /**
     * @param string $uri
     * @param array $data
     *
     * @return string
     * @throws UriDataMissingException
     */
    private function mapDataToUri(string $uri, array $data) : string
    {
        if (preg_match('/{+(.*?)}/', $uri, $matches)) {
            if (empty($data))
                throw new UriDataMissingException(
                    'The data array for the uri ' . $uri . ' is empty. Please provide data to use.');

            foreach ($matches[1] as $field) {
                if (!array_key_exists($field, $data))
                    throw new UriDataMissingException(
                        'Data for ' . $field . ' is missing. Please provide this by setting a value ' .
                        'for ' . $field . '.');

                $uri = str_replace('{' . $field . '}', $data[$field], $uri);
            }
        }

        return $uri;
    }
}