Removed Twitter integrations.
This commit is contained in:
parent
521a8fb0d1
commit
d05046ff1f
17 changed files with 2 additions and 638 deletions
|
@ -6,9 +6,7 @@ If you need to reach us outside of this website, this is the page for you. Below
|
|||
- [flash](mailto:flashii@flash.moe): Site Administrator
|
||||
|
||||
## Twitter
|
||||
- [@flashiinet](https://twitter.com/flashiinet): General updates and conversation.
|
||||
- [@flashiistatus](https://twitter.com/flashiistatus): Exclusively system status updates, posts by this accounts are generally retweeted by @flashiinet.
|
||||
- [@flashwahaha](https://twitter.com/flashwahaha): Twitter of the owner. This is a personal space, proceed with caution!
|
||||
|
||||
## Source Code
|
||||
- [Misuzu](https://git.flash.moe/flashii/misuzu): Backend of the main website.
|
||||
|
|
|
@ -152,7 +152,6 @@ Template::set('globals', [
|
|||
'site_name' => $cfg->getValue('site.name', IConfig::T_STR, 'Misuzu'),
|
||||
'site_description' => $cfg->getValue('site.desc', IConfig::T_STR),
|
||||
'site_url' => $cfg->getValue('site.url', IConfig::T_STR),
|
||||
'site_twitter' => $cfg->getValue('social.twitter', IConfig::T_STR),
|
||||
'site_chat' => $cfg->getValue('sockChat.chatPath.normal', IConfig::T_STR),
|
||||
'eeprom' => [
|
||||
'path' => $cfg->getValue('eeprom.path', IConfig::T_STR),
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Twitter\TwitterAccessToken;
|
||||
use Misuzu\Twitter\TwitterClient;
|
||||
|
||||
require_once '../../../misuzu.php';
|
||||
|
||||
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_TWITTER)) {
|
||||
echo render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
$tCfg = $cfg->scopeTo('twitter');
|
||||
|
||||
$tClient = $msz->createTwitterClient();
|
||||
$tHasClientId = $tClient->hasClientId();
|
||||
$tHasAccessToken = $tClient->hasAccessToken();
|
||||
$tHasRefreshToken = $tClient->hasRefreshToken();
|
||||
$tExpires = $tClient->getAccessToken()->getExpiresTime();
|
||||
|
||||
if(isset($_GET['m'])) {
|
||||
if(CSRF::validateRequest()) {
|
||||
$mode = (string)filter_input(INPUT_GET, 'm');
|
||||
|
||||
if($mode === 'authorise' && $tHasClientId && !$tHasAccessToken) {
|
||||
$tAuthorise = $tClient->authorise(TwitterClient::SYSTEM_SCOPES, url_prefix(false) . url('twitter-callback'));
|
||||
setcookie('msz_twitter', $tAuthorise->getVerifier(), strtotime('+5 minutes'), '/', msz_cookie_domain(), !empty($_SERVER['HTTPS']), true);
|
||||
header('Location: ' . $tAuthorise->getUri());
|
||||
return;
|
||||
}
|
||||
|
||||
if($mode === 'refresh' && $tHasClientId && $tHasAccessToken && $tHasRefreshToken) {
|
||||
$tRefresh = TwitterAccessToken::fromTwitterResponse($tClient->authRefresh());
|
||||
TwitterAccessToken::save($tCfg->scopeTo('access'), $tRefresh);
|
||||
header('Location: ' . url('manage-general-twitter'));
|
||||
return;
|
||||
}
|
||||
|
||||
if($mode === 'revoke' && $tHasClientId && $tHasAccessToken) {
|
||||
$tRevoke = $tClient->authRevoke();
|
||||
if(!empty($tRevoke->revoked))
|
||||
TwitterAccessToken::nuke($tCfg->scopeTo('access'));
|
||||
|
||||
header('Location: ' . url('manage-general-twitter'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: ' . url('manage-general-twitter'));
|
||||
return;
|
||||
}
|
||||
|
||||
Template::render('manage.general.twitter', [
|
||||
'twitter_has_oauth2' => $tHasClientId,
|
||||
'twitter_has_access' => $tHasAccessToken,
|
||||
'twitter_has_refresh' => $tHasRefreshToken,
|
||||
'twitter_expires' => $tExpires,
|
||||
]);
|
|
@ -53,12 +53,7 @@ if(!empty($_POST['post']) && CSRF::validateRequest()) {
|
|||
|
||||
if(!empty($isNew)) {
|
||||
if($postInfo->isFeatured()) {
|
||||
$twitter = $msz->createTwitterClient();
|
||||
|
||||
if($twitter->hasAccessToken()) {
|
||||
$url = url('news-post', ['post' => $postInfo->getId()]);
|
||||
$twitter->sendTweet("News :: {$postInfo->getTitle()}\nhttps://{$_SERVER['HTTP_HOST']}{$url}");
|
||||
}
|
||||
// Twitter integration used to be here, replace with Railgun Pulse integration
|
||||
}
|
||||
|
||||
header('Location: ' . url('manage-news-post', ['post' => $postInfo->getId()]));
|
||||
|
|
|
@ -5,7 +5,6 @@ use Misuzu\DB;
|
|||
use Misuzu\MisuzuContext;
|
||||
use Misuzu\Console\CommandArgs;
|
||||
use Misuzu\Console\CommandInterface;
|
||||
use Misuzu\Twitter\TwitterAccessToken;
|
||||
|
||||
class CronCommand implements CommandInterface {
|
||||
private MisuzuContext $context;
|
||||
|
@ -45,14 +44,6 @@ class CronCommand implements CommandInterface {
|
|||
forum_count_synchronise();
|
||||
}
|
||||
|
||||
private function refreshTwitterToken(): void {
|
||||
$tClient = $this->context->createTwitterClient();
|
||||
if($tClient->hasAccessToken() && $tClient->hasRefreshToken()) {
|
||||
$tRefresh = TwitterAccessToken::fromTwitterResponse($tClient->authRefresh());
|
||||
TwitterAccessToken::save($this->context->getConfig()->scopeTo('twitter.access'), $tRefresh);
|
||||
}
|
||||
}
|
||||
|
||||
private const TASKS = [
|
||||
[
|
||||
'name' => 'Ensures main role exists.',
|
||||
|
@ -171,11 +162,5 @@ class CronCommand implements CommandInterface {
|
|||
WHERE `tfa_created` < NOW() - INTERVAL 15 MINUTE
|
||||
",
|
||||
],
|
||||
[
|
||||
'name' => 'Refresh Twitter authentication token.',
|
||||
'type' => 'func',
|
||||
'slow' => true,
|
||||
'command' => 'refreshTwitterToken',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@ use Misuzu\Config\IConfig;
|
|||
use Misuzu\GeoIP\GeoIPHelper;
|
||||
use Misuzu\SharpChat\SharpChatRoutes;
|
||||
use Misuzu\Users\Users;
|
||||
use Misuzu\Twitter\TwitterClient;
|
||||
use Misuzu\Twitter\TwitterRoutes;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\Migration\IDbMigrationRepo;
|
||||
use Index\Data\Migration\DbMigrationManager;
|
||||
|
@ -63,10 +61,6 @@ class MisuzuContext {
|
|||
return $this->geoIP;
|
||||
}
|
||||
|
||||
public function createTwitterClient(): TwitterClient {
|
||||
return TwitterClient::create($this->config->scopeTo('twitter'));
|
||||
}
|
||||
|
||||
public function setUpHttp(bool $legacy = false): void {
|
||||
$this->router = new HttpFx;
|
||||
$this->router->use('/', function($response) {
|
||||
|
@ -136,7 +130,6 @@ class MisuzuContext {
|
|||
$this->router->post('/forum/mark-as-read', msz_compat_handler('Forum', 'markAsReadPOST'));
|
||||
|
||||
new SharpChatRoutes($this->router, $this->config->scopeTo('sockChat'));
|
||||
new TwitterRoutes($this, $this->router, $this->config->scopeTo('twitter'));
|
||||
}
|
||||
|
||||
private function registerLegacyRedirects(): void {
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Twitter;
|
||||
|
||||
use Stringable;
|
||||
use Misuzu\Config\IConfig;
|
||||
|
||||
class TwitterAccessToken implements Stringable {
|
||||
public function __construct(
|
||||
private string $type,
|
||||
private string $accessToken,
|
||||
private int $expires,
|
||||
private array $scope,
|
||||
private string $refreshToken
|
||||
) {}
|
||||
|
||||
public function getType(): string {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getAccessToken(): string {
|
||||
return $this->accessToken;
|
||||
}
|
||||
|
||||
public function hasAccessToken(): bool {
|
||||
return $this->type !== '' && $this->accessToken !== '';
|
||||
}
|
||||
|
||||
public function getExpiresTime(): int {
|
||||
return $this->expires;
|
||||
}
|
||||
|
||||
public function hasExpires(): bool {
|
||||
return time() > $this->expires;
|
||||
}
|
||||
|
||||
public function getScope(): array {
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
public function getRefreshToken(): string {
|
||||
return $this->refreshToken;
|
||||
}
|
||||
|
||||
public function hasRefreshToken(): bool {
|
||||
return $this->refreshToken !== '';
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return 'Bearer ' . $this->accessToken;
|
||||
}
|
||||
|
||||
public static function empty(): self {
|
||||
return new static('', '', 0, [], '');
|
||||
}
|
||||
|
||||
public static function fromTwitterResponse(object $obj): self {
|
||||
return new static(
|
||||
$obj->token_type ?? '',
|
||||
$obj->access_token ?? '',
|
||||
time() + ($obj->expires_in ?? 0),
|
||||
explode(' ', ($obj->scope ?? '')),
|
||||
$obj->refresh_token ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
public static function load(IConfig $config): self {
|
||||
return new static(
|
||||
$config->getValue('type', IConfig::T_STR),
|
||||
$config->getValue('token', IConfig::T_STR),
|
||||
$config->getValue('expires', IConfig::T_INT),
|
||||
$config->getValue('token', IConfig::T_ARR),
|
||||
$config->getValue('refresh', IConfig::T_STR)
|
||||
);
|
||||
}
|
||||
|
||||
public static function save(IConfig $config, self $tokenInfo): void {
|
||||
$config->setValue('type', $tokenInfo->getType());
|
||||
$config->setValue('token', $tokenInfo->getAccessToken());
|
||||
$config->setValue('expires', $tokenInfo->getExpiresTime());
|
||||
$config->setValue('scope', $tokenInfo->getScope());
|
||||
$config->setValue('refresh', $tokenInfo->getRefreshToken());
|
||||
}
|
||||
|
||||
public static function nuke(IConfig $config): void {
|
||||
$config->removeValue('type');
|
||||
$config->removeValue('token');
|
||||
$config->removeValue('expires');
|
||||
$config->removeValue('scope');
|
||||
$config->removeValue('refresh');
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Twitter;
|
||||
|
||||
use Index\XString;
|
||||
use Index\Serialisation\Serialiser;
|
||||
|
||||
class TwitterAuthorisation {
|
||||
private const AUTHORIZE = 'https://twitter.com/i/oauth2/authorize';
|
||||
|
||||
private const STATE_RNG_LENGTH = 16;
|
||||
private const STATE_EPOCH = 1661126400;
|
||||
private const STATE_TOLERANCE = 5 * 60;
|
||||
private const VERIFIER_LENGTH = 48;
|
||||
|
||||
private TwitterClientId $clientId;
|
||||
private array $scope;
|
||||
private string $redirect;
|
||||
private string $state;
|
||||
private string $verifier;
|
||||
private string $verifierHash;
|
||||
|
||||
public function __construct(TwitterClientId $clientId, array $scope, string $redirect) {
|
||||
$this->clientId = $clientId;
|
||||
$this->scope = $scope;
|
||||
$this->redirect = $redirect;
|
||||
|
||||
$this->state = self::generateState($clientId);
|
||||
[$this->verifier, $this->verifierHash] = self::generateVerifier();
|
||||
}
|
||||
|
||||
public function getClientId(): TwitterClientId {
|
||||
return $this->clientId;
|
||||
}
|
||||
|
||||
public function getScope(): array {
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
public function getRedirectUri(): string {
|
||||
return $this->redirect;
|
||||
}
|
||||
|
||||
public function getState(): string {
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
public function getVerifier(): string {
|
||||
return $this->verifier;
|
||||
}
|
||||
|
||||
public function getVerifierHash(): string {
|
||||
return $this->verifierHash;
|
||||
}
|
||||
|
||||
public function getUri(): string {
|
||||
return self::AUTHORIZE . '?' . http_build_query([
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->clientId->getClientId(),
|
||||
'redirect_uri' => $this->redirect,
|
||||
'scope' => implode(' ', $this->scope),
|
||||
'state' => $this->state,
|
||||
'code_challenge' => $this->verifierHash,
|
||||
'code_challenge_method' => 'S256',
|
||||
], '', null, PHP_QUERY_RFC3986);
|
||||
}
|
||||
|
||||
public static function generateVerifier(): array {
|
||||
$verifier = XString::random(self::VERIFIER_LENGTH);
|
||||
return [
|
||||
$verifier,
|
||||
Serialiser::uriBase64()->serialise(hash('sha256', $verifier, true)),
|
||||
];
|
||||
}
|
||||
|
||||
private static function currentStateTime(): int {
|
||||
return time() - self::STATE_EPOCH;
|
||||
}
|
||||
|
||||
public static function generateState(TwitterClientId $clientId): string {
|
||||
$rng = XString::random(self::STATE_RNG_LENGTH);
|
||||
$time = self::currentStateTime();
|
||||
|
||||
$string = $rng . ':' . (string)$time;
|
||||
$hash = hash_hmac('sha256', $string, $clientId->getClientSecret(), true);
|
||||
|
||||
$time = Serialiser::base62()->serialise($time);
|
||||
$hash = Serialiser::uriBase64()->serialise($hash);
|
||||
|
||||
return $rng . '.' . $time . '.' . $hash;
|
||||
}
|
||||
|
||||
public static function verifyState(TwitterClientId $clientId, string $state): bool {
|
||||
$parts = explode('.', $state, 4);
|
||||
if(count($parts) !== 3)
|
||||
return false;
|
||||
|
||||
$rng = $parts[0];
|
||||
if(strlen($rng) !== self::STATE_RNG_LENGTH)
|
||||
return false;
|
||||
|
||||
$currentTime = self::currentStateTime();
|
||||
$time = Serialiser::base62()->deserialise($parts[1]);
|
||||
if($currentTime < $time || $currentTime >= ($time + self::STATE_TOLERANCE))
|
||||
return false;
|
||||
|
||||
$hash = Serialiser::uriBase64()->deserialise($parts[2]);
|
||||
if(strlen($hash) !== 32)
|
||||
return false;
|
||||
|
||||
$string = $rng . ':' . (string)$time;
|
||||
$realHash = hash_hmac('sha256', $string, $clientId->getClientSecret(), true);
|
||||
|
||||
return hash_equals($realHash, $hash);
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Twitter;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\Config\IConfig;
|
||||
|
||||
class TwitterClient {
|
||||
public const SYSTEM_SCOPES = [
|
||||
'tweet.read', 'tweet.write',
|
||||
'users.read', 'offline.access',
|
||||
'follows.read', 'follows.write',
|
||||
'like.read', 'like.write',
|
||||
];
|
||||
|
||||
private const API_BASE = 'https://api.twitter.com';
|
||||
private const API_V2 = self::API_BASE . '/2';
|
||||
|
||||
private const API_OAUTH2 = self::API_V2 . '/oauth2';
|
||||
private const API_OAUTH2_TOKEN = self::API_OAUTH2 . '/token';
|
||||
private const API_OAUTH2_REVOKE = self::API_OAUTH2 . '/revoke';
|
||||
|
||||
private const API_TWEETS = self::API_V2 . '/tweets';
|
||||
|
||||
public function __construct(
|
||||
private TwitterClientId $clientId,
|
||||
private TwitterAccessToken $accessToken
|
||||
) {}
|
||||
|
||||
public function getClientId(): TwitterClientId {
|
||||
return $this->clientId;
|
||||
}
|
||||
public function hasClientId(): bool {
|
||||
return $this->clientId->hasClientId();
|
||||
}
|
||||
|
||||
public function getAccessToken(): TwitterAccessToken {
|
||||
return $this->accessToken;
|
||||
}
|
||||
public function hasAccessToken(): bool {
|
||||
return $this->accessToken->hasAccessToken();
|
||||
}
|
||||
public function hasRefreshToken(): bool {
|
||||
return $this->accessToken->hasRefreshToken();
|
||||
}
|
||||
|
||||
public function authorise(array $scope, string $redirect): TwitterAuthorisation {
|
||||
return new TwitterAuthorisation($this->clientId, $scope, $redirect);
|
||||
}
|
||||
|
||||
public function token(string $code, string $verifier, string $redirect): object {
|
||||
if(!$this->clientId->hasClientId())
|
||||
throw new RuntimeException('Need OAuth2 info in order to manage tokens.');
|
||||
|
||||
$req = json_decode(self::request('POST', self::API_OAUTH2_TOKEN, [
|
||||
'Authorization: ' . (string)$this->clientId,
|
||||
], [], [
|
||||
'code' => $code,
|
||||
'grant_type' => 'authorization_code',
|
||||
'code_verifier' => $verifier,
|
||||
'redirect_uri' => $redirect, // needed because????????
|
||||
], false));
|
||||
|
||||
if($req === false)
|
||||
return new RuntimeException('Unable to parse token response.');
|
||||
|
||||
return $req;
|
||||
}
|
||||
|
||||
public function authRefresh(): object {
|
||||
if(!$this->clientId->hasClientId())
|
||||
throw new RuntimeException('Need OAuth2 info in order to manage tokens.');
|
||||
if(!$this->accessToken->hasRefreshToken())
|
||||
throw new RuntimeException('There is no refresh token.');
|
||||
|
||||
$req = json_decode(self::request('POST', self::API_OAUTH2_TOKEN, [
|
||||
'Authorization: ' . (string)$this->clientId,
|
||||
], [], [
|
||||
'refresh_token' => $this->accessToken->getRefreshToken(),
|
||||
'grant_type' => 'refresh_token',
|
||||
], false));
|
||||
|
||||
if($req === false)
|
||||
return new RuntimeException('Unable to parse token response.');
|
||||
|
||||
return $req;
|
||||
}
|
||||
|
||||
public function authRevoke(): object {
|
||||
if(!$this->clientId->hasClientId())
|
||||
throw new RuntimeException('Need OAuth2 info in order to manage tokens.');
|
||||
if(!$this->accessToken->hasAccessToken())
|
||||
throw new RuntimeException('Cannot revoke an access token we do not have.');
|
||||
|
||||
$req = json_decode(self::request('POST', self::API_OAUTH2_REVOKE, [
|
||||
'Authorization: ' . (string)$this->clientId,
|
||||
], [], [
|
||||
'token' => $this->accessToken->getAccessToken(),
|
||||
'token_type_hint' => 'access_token',
|
||||
], false));
|
||||
|
||||
if($req === false)
|
||||
return new RuntimeException('Unable to parse token response.');
|
||||
|
||||
return $req;
|
||||
}
|
||||
|
||||
public function sendTweet(string $text): object {
|
||||
if(!$this->accessToken->hasAccessToken())
|
||||
throw new RuntimeException('Need access token in order to post Tweets.');
|
||||
|
||||
$req = json_decode(self::request('POST', self::API_TWEETS, [
|
||||
'Authorization: ' . (string)$this->accessToken,
|
||||
], [], [
|
||||
'text' => $text,
|
||||
]));
|
||||
|
||||
if($req === false)
|
||||
return new RuntimeException('Unable to parse Tweet response.');
|
||||
|
||||
return $req;
|
||||
}
|
||||
|
||||
public function request(
|
||||
string $method,
|
||||
string $uri,
|
||||
array $headers = [],
|
||||
array $queryFields = [],
|
||||
mixed $bodyFields = [],
|
||||
bool $bodyAsJson = true,
|
||||
): string|bool {
|
||||
if(!empty($queryFields))
|
||||
$uri .= '?' . http_build_query($queryFields, '', null, PHP_QUERY_RFC3986);
|
||||
|
||||
$curl = curl_init($uri);
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_AUTOREFERER => true,
|
||||
CURLOPT_FAILONERROR => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TCP_NODELAY => true,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_NOBODY => false,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TCP_FASTOPEN => true,
|
||||
CURLOPT_MAXREDIRS => 3,
|
||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
|
||||
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS,
|
||||
CURLOPT_TIMEOUT => 2,
|
||||
CURLOPT_USERAGENT => 'Misuzu TwitterClient/20230105',
|
||||
]);
|
||||
|
||||
if($method === 'GET')
|
||||
curl_setopt($curl, CURLOPT_HTTPGET, true);
|
||||
elseif($method === 'HEAD') {
|
||||
curl_setopt($curl, CURLOPT_HEADER, true);
|
||||
curl_setopt($curl, CURLOPT_NOBODY, true);
|
||||
} elseif($method === 'POST')
|
||||
curl_setopt($curl, CURLOPT_POST, true);
|
||||
else
|
||||
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
|
||||
|
||||
if(!empty($bodyFields)) {
|
||||
if($bodyAsJson) {
|
||||
$headers[] = 'Content-Type: application/json';
|
||||
$bodyFields = json_encode($bodyFields);
|
||||
} elseif(is_array($bodyFields)) {
|
||||
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
|
||||
$bodyFields = http_build_query($bodyFields, '', null, PHP_QUERY_RFC3986);
|
||||
}
|
||||
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $bodyFields);
|
||||
}
|
||||
|
||||
if(!empty($headers))
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
$out = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function create(IConfig $config): self {
|
||||
return new static(
|
||||
TwitterClientId::load($config->scopeTo('oauth2')),
|
||||
TwitterAccessToken::load($config->scopeTo('access'))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Twitter;
|
||||
|
||||
use Stringable;
|
||||
use Misuzu\Config\IConfig;
|
||||
|
||||
class TwitterClientId implements Stringable {
|
||||
public function __construct(
|
||||
private string $clientId,
|
||||
private string $clientSecret
|
||||
) {}
|
||||
|
||||
public function hasClientId(): bool {
|
||||
return $this->clientId !== '' && $this->clientSecret !== '';
|
||||
}
|
||||
|
||||
public function getClientId(): string {
|
||||
return $this->clientId;
|
||||
}
|
||||
|
||||
public function getClientSecret(): string {
|
||||
return $this->clientSecret;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return 'Basic ' . base64_encode($this->clientId . ':' . $this->clientSecret);
|
||||
}
|
||||
|
||||
public static function load(IConfig $config): self {
|
||||
return new static(
|
||||
$config->getValue('clientId', IConfig::T_STR),
|
||||
$config->getValue('clientSecret', IConfig::T_STR)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Twitter;
|
||||
|
||||
use Index\Routing\IRouter;
|
||||
use Misuzu\MisuzuContext;
|
||||
use Misuzu\Config\IConfig;
|
||||
use Misuzu\Twitter\TwitterAccessToken;
|
||||
use Misuzu\Twitter\TwitterAuthorisation;
|
||||
use Misuzu\Twitter\TwitterClient;
|
||||
use Misuzu\Twitter\TwitterClientId;
|
||||
|
||||
final class TwitterRoutes {
|
||||
private MisuzuContext $context;
|
||||
private IConfig $config;
|
||||
private ?TwitterClientId $clientId = null;
|
||||
|
||||
public function __construct(MisuzuContext $ctx, IRouter $router, IConfig $config) {
|
||||
$this->context = $ctx;
|
||||
$this->config = $config;
|
||||
|
||||
$router->get('/_twitter/callback', [$this, 'callback']);
|
||||
}
|
||||
|
||||
private function getClientId(): TwitterClientId {
|
||||
if($this->clientId === null)
|
||||
$this->clientId = TwitterClientId::load($this->config->scopeTo('oauth2'));
|
||||
return $this->clientId;
|
||||
}
|
||||
|
||||
public function callback($response, $request) {
|
||||
$qState = (string)$request->getParam('state');
|
||||
$qCode = (string)$request->getParam('code');
|
||||
$cVerifier = (string)$request->getCookie('msz_twitter');
|
||||
|
||||
if(empty($qState) || empty($qCode) || empty($cVerifier))
|
||||
return 400;
|
||||
|
||||
$response->removeCookie('msz_twitter', '/', msz_cookie_domain(), !empty($_SERVER['HTTPS']), true);
|
||||
|
||||
$clientId = $this->getClientId();
|
||||
if(!TwitterAuthorisation::verifyState($clientId, $qState))
|
||||
return 403;
|
||||
|
||||
$accessToken = TwitterAccessToken::empty();
|
||||
$client = new TwitterClient($clientId, $accessToken);
|
||||
|
||||
$redirect = url_prefix(false) . url('twitter-callback');
|
||||
$tokenInfo = TwitterAccessToken::fromTwitterResponse($client->token($qCode, $cVerifier, $redirect));
|
||||
TwitterAccessToken::save($this->config->scopeTo('access'), $tokenInfo);
|
||||
|
||||
$response->redirect(url('manage-general-twitter'));
|
||||
}
|
||||
}
|
|
@ -15,8 +15,6 @@ function manage_get_menu(int $userId): array {
|
|||
$menu['General']['Emoticons'] = url('manage-general-emoticons');
|
||||
if(perms_check_user(MSZ_PERMS_GENERAL, $userId, MSZ_PERM_GENERAL_MANAGE_CONFIG))
|
||||
$menu['General']['Settings'] = url('manage-general-settings');
|
||||
if(perms_check_user(MSZ_PERMS_GENERAL, $userId, MSZ_PERM_GENERAL_MANAGE_TWITTER))
|
||||
$menu['General']['Twitter Connection'] = url('manage-general-twitter');
|
||||
|
||||
if(perms_check_user(MSZ_PERMS_USER, $userId, MSZ_PERM_USER_MANAGE_USERS))
|
||||
$menu['Users & Roles']['Users'] = url('manage-users');
|
||||
|
@ -135,11 +133,6 @@ function manage_perms_list(array $rawPerms): array {
|
|||
'title' => 'Can manage general Misuzu settings.',
|
||||
'perm' => MSZ_PERM_GENERAL_MANAGE_CONFIG,
|
||||
],
|
||||
[
|
||||
'section' => 'manage-twitter',
|
||||
'title' => 'Can manage Twitter connection.',
|
||||
'perm' => MSZ_PERM_GENERAL_MANAGE_TWITTER,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
|
|
|
@ -6,7 +6,7 @@ define('MSZ_PERM_GENERAL_MANAGE_EMOTES', 0x00000004);
|
|||
define('MSZ_PERM_GENERAL_MANAGE_CONFIG', 0x00000008);
|
||||
//define('MSZ_PERM_GENERAL_IS_TESTER', 0x00000010); Has been unused for a while
|
||||
//define('MSZ_PERM_GENERAL_MANAGE_BLACKLIST', 0x00000020); Blacklist has been removed for now to reduce overhead and because it was broken(?)
|
||||
define('MSZ_PERM_GENERAL_MANAGE_TWITTER', 0x00000040);
|
||||
//define('MSZ_PERM_GENERAL_MANAGE_TWITTER', 0x00000040); Twitter integration has been removed
|
||||
|
||||
define('MSZ_PERMS_USER', 'user');
|
||||
define('MSZ_PERM_USER_EDIT_PROFILE', 0x00000001);
|
||||
|
|
|
@ -85,13 +85,10 @@ define('MSZ_URLS', [
|
|||
'comment-pin' => ['/comments.php', ['c' => '<comment>', 'csrf' => '{csrf}', 'm' => 'pin']],
|
||||
'comment-unpin' => ['/comments.php', ['c' => '<comment>', 'csrf' => '{csrf}', 'm' => 'unpin']],
|
||||
|
||||
'twitter-callback' => ['/_twitter/callback'],
|
||||
|
||||
'manage-index' => ['/manage'],
|
||||
|
||||
'manage-general-overview' => ['/manage/general'],
|
||||
'manage-general-logs' => ['/manage/general/logs.php'],
|
||||
'manage-general-twitter' => ['/manage/general/twitter.php'],
|
||||
|
||||
'manage-general-emoticons' => ['/manage/general/emoticons.php'],
|
||||
'manage-general-emoticon' => ['/manage/general/emoticon.php', ['e' => '<emote>']],
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
{% apply spaceless %}
|
||||
{% set description = description|default(globals.site_description) %}
|
||||
{% set site_twitter = site_twitter|default(globals.site_twitter) %}
|
||||
|
||||
{% if title is defined %}
|
||||
{% set browser_title = title ~ ' :: ' ~ globals.site_name %}
|
||||
|
@ -10,22 +9,15 @@
|
|||
|
||||
<title>{{ browser_title }}</title>
|
||||
|
||||
<meta name="twitter:title" content="{{ title|default(globals.site_name)|slice(0, 70) }}">
|
||||
<meta property="og:title" content="{{ title|default(globals.site_name) }}">
|
||||
<meta property="og:site_name" content="{{ globals.site_name }}">
|
||||
|
||||
{% if description|length > 0 %}
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="twitter:description" content="{{ description }}">
|
||||
<meta property="og:description" content="{{ description }}">
|
||||
{% endif %}
|
||||
|
||||
{% if site_twitter|length > 0 %}
|
||||
<meta name="twitter:site" content="{{ site_twitter }}">
|
||||
{% endif %}
|
||||
|
||||
<meta property="og:type" content="object">
|
||||
<meta name="twitter:card" content="summary">
|
||||
|
||||
{% if image is defined %}
|
||||
{% if image|slice(0, 1) == '/' %}
|
||||
|
@ -37,7 +29,6 @@
|
|||
{% endif %}
|
||||
|
||||
{% if image|length > 0 %}
|
||||
<meta name="twitter:image:src" content="{{ image }}">
|
||||
<meta property="og:image" content="{{ image }}">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
{% extends 'manage/general/master.twig' %}
|
||||
{% from 'macros.twig' import container_title %}
|
||||
|
||||
{% block manage_content %}
|
||||
<div class="container manage-settings">
|
||||
{{ container_title('<i class="fab fa-twitter fa-fw"></i> Twitter Connection') }}
|
||||
|
||||
<div class="manage__description">
|
||||
Manages the Twitter connection for announcing news posts on the Twitter account.
|
||||
</div>
|
||||
|
||||
{% if twitter_has_oauth2 %}
|
||||
{% if twitter_has_access %}
|
||||
<div style="padding: 2px 5px">
|
||||
A Twitter user has been authenticated.
|
||||
Current access token expires <time datetime="{{ twitter_expires|date('c') }}" title="{{ twitter_expires|date('r') }}">{{ twitter_expires|time_diff }}</time>.
|
||||
</div>
|
||||
<div class="manage__emote__actions">
|
||||
{% if twitter_has_refresh %}
|
||||
<a class="input__button" href="?m=refresh&csrf={{ csrf_token() }}">Refresh Access</a>
|
||||
{% endif %}
|
||||
<a class="input__button" href="?m=revoke&csrf={{ csrf_token() }}">Revoke Access</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="padding: 2px 5px">
|
||||
No Twitter user has been authorised yet.
|
||||
Before beginning authorization, make sure you're logged into Twitter with the desired user.
|
||||
</div>
|
||||
<div class="manage__emote__actions">
|
||||
<a class="input__button" href="?m=authorise&csrf={{ csrf_token() }}">Begin Authorisation</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div style="padding: 2px 5px">
|
||||
Twitter OAuth2 credentials have not been registered.
|
||||
Add them through <a href="{{ url('manage-general-settings') }}" class="link">Settings</a> as <a href="{{ url('manage-general-setting', {'name': 'twitter.oauth2.clientId', 'type': 'string'}) }}" class="link"><code>twitter.oauth2.clientId</code></a> and <a href="{{ url('manage-general-setting', {'name': 'twitter.oauth2.clientSecret', 'type': 'string'}) }}" class="link"><code>twitter.oauth2.clientSecret</code></a>.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -48,11 +48,6 @@
|
|||
'title': 'Rules',
|
||||
'url': url('info', {'title': 'rules'}),
|
||||
},
|
||||
{
|
||||
'title': 'Twitter',
|
||||
'url': 'https://twitter.com/' ~ globals.site_twitter|default(''),
|
||||
'display': globals.site_twitter is defined and globals.site_twitter is not empty,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue