Files
Neon Overlord d2c70bc226 fork: rebrand as integration_gotosocial
Fork of nextcloud/integration_mastodon v5.2.0 (6a56211), AGPL-3.0, with
upstream copyright notices and CHANGELOG preserved unchanged.

Rebranded: app ID, namespace, dashboard widgets, reference provider, icon
component, search provider IDs and every user-facing string. GoToSocial
calls them posts, not toots, and its mascot is a sloth.

Deliberately NOT rebranded: MastodonAPIService, MastodonAPIController,
route names and config keys. They speak the Mastodon client API, which is
what GoToSocial implements -- renaming them would make the code claim a
protocol that does not exist, and would make this fork expensive to rebase
on future upstream releases.

Translations are inherited. Strings reworded by the rebrand fall back to
English; the rest still work in all 124 locales.

Assisted-by: Claude Code:claude-opus-5
2026-08-03 14:51:51 -07:00

230 lines
8.6 KiB
PHP

<?php
/**
* Nextcloud - mastodon
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Julien Veyssier <julien-nc@posteo.net>
* @copyright Julien Veyssier 2020
*/
namespace OCA\GoToSocial\Controller;
use OCA\GoToSocial\AppInfo\Application;
use OCA\GoToSocial\Service\MastodonAPIService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\PreConditionNotMetException;
use OCP\Security\ICrypto;
use Psr\Log\LoggerInterface;
class ConfigController extends Controller {
public function __construct(
string $appName,
IRequest $request,
private IAppConfig $appConfig,
private IConfig $config,
private IURLGenerator $urlGenerator,
private IL10N $l,
private IInitialState $initialStateService,
private LoggerInterface $logger,
private ICrypto $crypto,
private MastodonAPIService $mastodonAPIService,
private ?string $userId,
) {
parent::__construct($appName, $request);
}
/**
* Set config values
*
* @param array $values
* @return DataResponse
* @throws PreConditionNotMetException
*/
#[NoAdminRequired]
public function setConfig(array $values): DataResponse {
foreach ($values as $key => $value) {
if (in_array($key, ['token', 'url'], true)) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
$this->config->setUserValue($this->userId, Application::APP_ID, $key, $value);
}
return new DataResponse([]);
}
/**
* Set sensitive config values
*
* @param array $values
* @return DataResponse
* @throws PreConditionNotMetException
*/
#[NoAdminRequired]
#[PasswordConfirmationRequired]
public function setSensitiveConfig(array $values): DataResponse {
foreach ($values as $key => $value) {
$this->config->setUserValue($this->userId, Application::APP_ID, $key, $value);
}
if (isset($values['token']) && $values['token'] === '') {
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'user_id');
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'user_name');
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'client_id');
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'client_secret');
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'instance_image_hostname');
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'instance_contact_image_hostname');
}
return new DataResponse([]);
}
/**
* Set admin config values
*
* @param array $values
* @return DataResponse
*/
public function setAdminConfig(array $values): DataResponse {
foreach ($values as $key => $value) {
if (in_array($key, ['oauth_instance_url'], true)) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
$this->appConfig->setAppValueString($key, $value, lazy: true);
}
return new DataResponse('');
}
/**
* Set sensitive admin config values
*
* @param array $values
* @return DataResponse
*/
#[PasswordConfirmationRequired]
public function setSensitiveAdminConfig(array $values): DataResponse {
foreach ($values as $key => $value) {
$this->appConfig->setAppValueString($key, $value, lazy: true);
}
return new DataResponse('');
}
/**
* @param string $user_name
* @param string $user_displayname
* @return TemplateResponse
*/
#[NoAdminRequired]
#[NoCSRFRequired]
public function popupSuccessPage(string $user_name, string $user_displayname): TemplateResponse {
$this->initialStateService->provideInitialState('popup-data', ['user_name' => $user_name, 'user_displayname' => $user_displayname]);
return new TemplateResponse(Application::APP_ID, 'popupSuccess', [], TemplateResponse::RENDER_AS_GUEST);
}
/**
* receive oauth code and get oauth access token
*
* @param string $code
* @return RedirectResponse
* @throws PreConditionNotMetException
*/
#[NoAdminRequired]
#[NoCSRFRequired]
public function oauthRedirect(string $code = ''): RedirectResponse {
$mastodonUrl = $this->mastodonAPIService->getGoToSocialUrl($this->userId);
$clientID = $this->config->getUserValue($this->userId, Application::APP_ID, 'client_id');
$clientID = $clientID === '' ? '' : $this->crypto->decrypt($clientID);
$clientSecret = $this->config->getUserValue($this->userId, Application::APP_ID, 'client_secret');
$clientSecret = $clientSecret === '' ? '' : $this->crypto->decrypt($clientSecret);
$redirect_uri = $this->config->getUserValue($this->userId, Application::APP_ID, 'redirect_uri');
if ($mastodonUrl !== '' && $clientID !== '' && $clientSecret !== '' && $code !== '') {
$result = $this->mastodonAPIService->requestOAuthAccessToken($mastodonUrl, [
'client_id' => $clientID,
'client_secret' => $clientSecret,
'code' => $code,
'redirect_uri' => $redirect_uri,
'grant_type' => 'authorization_code',
'scope' => 'read write follow'
], 'POST');
if (isset($result['access_token'])) {
$accessToken = $result['access_token'];
$encryptedAccessToken = $accessToken === '' ? '' : $this->crypto->encrypt($accessToken);
$this->config->setUserValue($this->userId, Application::APP_ID, 'token', $encryptedAccessToken);
// get user info accounts/verify_credentials
$info = $this->mastodonAPIService->request($this->userId, 'accounts/verify_credentials');
if (isset($info['id'], $info['username'], $info['avatar'])) {
$this->config->setUserValue($this->userId, Application::APP_ID, 'user_id', $info['id']);
$this->config->setUserValue($this->userId, Application::APP_ID, 'user_name', $info['username']);
$aUrl = parse_url($info['avatar']);
$instanceImageHostname = $aUrl['host'] ?? '';
$this->config->setUserValue($this->userId, Application::APP_ID, 'instance_image_hostname', $instanceImageHostname);
$instanceInfo = $this->mastodonAPIService->request($this->userId, 'instance');
if (isset($instanceInfo['contact_account'], $instanceInfo['contact_account']['avatar'])) {
$aUrl = parse_url($instanceInfo['contact_account']['avatar']);
$instanceContactImageHostname = $aUrl['host'] ?? '';
$this->config->setUserValue($this->userId, Application::APP_ID, 'instance_contact_image_hostname', $instanceContactImageHostname);
}
}
$usePopup = $this->appConfig->getAppValueString('use_popup', '0', lazy: true) === '1';
if ($usePopup) {
return new RedirectResponse(
$this->urlGenerator->linkToRoute('integration_gotosocial.config.popupSuccessPage', [
'user_name' => $info['username'] ?? '',
'user_displayname' => $info['userdisplayname'] ?? '',
])
);
} else {
$oauthOrigin = $this->config->getUserValue($this->userId, Application::APP_ID, 'oauth_origin');
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'oauth_origin');
if ($oauthOrigin === 'settings') {
return new RedirectResponse(
$this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'connected-accounts'])
. '?mastodonToken=success'
);
} elseif ($oauthOrigin === 'dashboard') {
return new RedirectResponse(
$this->urlGenerator->linkToRoute('dashboard.dashboard.index')
);
}
return new RedirectResponse(
$this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'connected-accounts'])
. '?mastodonToken=success'
);
}
} else {
$warning = 'GoToSocial OAuth get token error : code=' . $code . ' ; url=' . $mastodonUrl
. ' ; clientId=' . $clientID . ' ; clientSecret=' . $clientSecret . ' ; redirect_uri=' . $redirect_uri;
$this->logger->warning($warning, ['app' => Application::APP_ID]);
$result = $this->l->t('Error getting OAuth access token') . ' ' . ($result['error'] ?? 'undefined token');
}
} else {
$warning = 'GoToSocial OAuth redirect error : code=' . $code . ' ; url=' . $mastodonUrl
. ' ; clientId=' . $clientID . ' ; clientSecret=' . $clientSecret . ' ; redirect_uri=' . $redirect_uri;
$this->logger->warning($warning, ['app' => Application::APP_ID]);
$result = $this->l->t('Error during OAuth exchanges');
}
return new RedirectResponse(
$this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'connected-accounts'])
. '?mastodonToken=error&message=' . urlencode($result)
);
}
}