OAuth (part 1 draft)
This commit is contained in:
parent
27cf2152c8
commit
8b72a4a859
19 changed files with 1102 additions and 2 deletions
|
@ -94,6 +94,24 @@ class Routing
|
|||
'Redirect:toIndex'
|
||||
);
|
||||
}
|
||||
// OAuth
|
||||
if (
|
||||
$user->isAdmin
|
||||
|| 1 === $config->b_oauth_allow
|
||||
) {
|
||||
$r->add(
|
||||
$r::GET,
|
||||
'/reglog/callback/{name}',
|
||||
'RegLog:callback',
|
||||
'RegLogCallback'
|
||||
);
|
||||
$r->add(
|
||||
$r::PST,
|
||||
'/reglog/redirect',
|
||||
'RegLog:redirect',
|
||||
'RegLogRedirect'
|
||||
);
|
||||
}
|
||||
// просмотр разрешен
|
||||
if (1 === $user->g_read_board) {
|
||||
// главная
|
||||
|
@ -557,6 +575,18 @@ class Routing
|
|||
'AdminOptions:edit',
|
||||
'AdminOptions'
|
||||
);
|
||||
$r->add(
|
||||
$r::DUO,
|
||||
'/admin/options/providers',
|
||||
'AdminProviders:view',
|
||||
'AdminProviders'
|
||||
);
|
||||
$r->add(
|
||||
$r::DUO,
|
||||
'/admin/options/providers/{name}',
|
||||
'AdminProviders:edit',
|
||||
'AdminProvider'
|
||||
);
|
||||
$r->add(
|
||||
$r::DUO,
|
||||
'/admin/parser',
|
||||
|
|
|
@ -110,6 +110,7 @@ class Options extends Admin
|
|||
'i_poll_term' => 'required|integer|min:0|max:99',
|
||||
'b_poll_guest' => 'required|integer|in:0,1',
|
||||
'b_pm' => 'required|integer|in:0,1',
|
||||
'b_oauth_allow' => 'required|integer|in:0,1',
|
||||
])->addAliases([
|
||||
])->addArguments([
|
||||
])->addMessages([
|
||||
|
@ -610,6 +611,19 @@ class Options extends Admin
|
|||
'caption' => 'Report new label',
|
||||
'help' => 'Report new help',
|
||||
],
|
||||
'b_oauth_allow' => [
|
||||
'type' => 'radio',
|
||||
'value' => $config->b_oauth_allow,
|
||||
'values' => $yn,
|
||||
'caption' => 'Allow oauth label',
|
||||
'help' => 'Allow oauth help',
|
||||
],
|
||||
'configure_providers' => [
|
||||
'type' => 'link',
|
||||
'value' => __('Configure providers'),
|
||||
'href' => $this->c->Router->link('AdminProviders'),
|
||||
'title' => __('Configure providers'),
|
||||
],
|
||||
'b_rules' => [
|
||||
'type' => 'radio',
|
||||
'value' => $config->b_rules,
|
||||
|
|
234
app/Models/Pages/Admin/Providers.php
Normal file
234
app/Models/Pages/Admin/Providers.php
Normal file
|
@ -0,0 +1,234 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Models\Pages\Admin;
|
||||
|
||||
use ForkBB\Core\Container;
|
||||
use ForkBB\Models\Page;
|
||||
use ForkBB\Models\Pages\Admin;
|
||||
use ForkBB\Models\Provider\Driver;
|
||||
use function \ForkBB\__;
|
||||
|
||||
class Providers extends Admin
|
||||
{
|
||||
/**
|
||||
* Выводит сообщение
|
||||
*/
|
||||
protected function mDisabled(): void
|
||||
{
|
||||
if (1 !== $this->c->config->b_oauth_allow) {
|
||||
$this->fIswev = ['w', ['OAuth authorization disabled', $this->c->Router->link('AdminOptions', ['#' => 'id-fs-registration'])]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Просмотр, редактирвоание и добавление категорий
|
||||
*/
|
||||
public function view(array $args, string $method): Page
|
||||
{
|
||||
$this->c->Lang->load('validator');
|
||||
$this->c->Lang->load('admin_providers');
|
||||
|
||||
if ('POST' === $method) {
|
||||
$v = $this->c->Validator->reset()
|
||||
->addRules([
|
||||
'token' => 'token:AdminProviders',
|
||||
'form.*.pr_pos' => 'required|integer|min:0|max:9999999999',
|
||||
'form.*.pr_allow' => 'checkbox',
|
||||
])->addAliases([
|
||||
])->addArguments([
|
||||
])->addMessages([
|
||||
]);
|
||||
|
||||
if ($v->validation($_POST)) {
|
||||
$this->c->providers->update($v->form);
|
||||
|
||||
return $this->c->Redirect->page('AdminProviders')->message('Providers updated redirect');
|
||||
}
|
||||
|
||||
$this->fIswev = $v->getErrors();
|
||||
}
|
||||
|
||||
$this->mDisabled();
|
||||
|
||||
$this->nameTpl = 'admin/form';
|
||||
$this->aIndex = 'options';
|
||||
$this->aCrumbs[] = [$this->c->providers->link(''), 'Providers'];
|
||||
$this->form = $this->formView();
|
||||
$this->classForm = ['providers', 'inline'];
|
||||
$this->titleForm = 'Providers';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подготавливает массив данных для формы
|
||||
*/
|
||||
protected function formView(): array
|
||||
{
|
||||
$form = [
|
||||
'action' => $this->c->providers->link(''),
|
||||
'hidden' => [
|
||||
'token' => $this->c->Csrf->create('AdminProviders'),
|
||||
],
|
||||
'sets' => [],
|
||||
'btns' => [
|
||||
'save' => [
|
||||
'type' => 'submit',
|
||||
'value' => __('Save changes'),
|
||||
],
|
||||
],
|
||||
];
|
||||
$fields = [];
|
||||
|
||||
foreach ($this->c->providers->init()->list() as $provider) {
|
||||
$fields["name-{$provider->name}"] = [
|
||||
'class' => ['name', 'provider'],
|
||||
'type' => 'btn',
|
||||
'value' => $provider->name,
|
||||
'caption' => 'Provider label',
|
||||
'link' => $this->c->providers->link($provider->name),
|
||||
];
|
||||
$fields["form[{$provider->name}][pr_pos]"] = [
|
||||
'class' => ['position', 'provider'],
|
||||
'type' => 'number',
|
||||
'min' => '0',
|
||||
'max' => '9999999999',
|
||||
'value' => $provider->pos,
|
||||
'caption' => 'Position label',
|
||||
];
|
||||
$fields["form[{$provider->name}][pr_allow]"] = [
|
||||
'class' => ['allow', 'provider'],
|
||||
'type' => 'checkbox',
|
||||
'checked' => $provider->allow,
|
||||
'caption' => 'Allow label',
|
||||
];
|
||||
$form['sets']["provider-{$provider->name}"] = [
|
||||
'class' => ['provider', 'inline'],
|
||||
'legend' => $provider->name,
|
||||
'fields' => $fields,
|
||||
];
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Просмотр, редактирвоание и добавление категорий
|
||||
*/
|
||||
public function edit(array $args, string $method): Page
|
||||
{
|
||||
$provider = $this->c->providers->init()->get($args['name']);
|
||||
|
||||
if (! $provider instanceof Driver) {
|
||||
return $this->c->Message->message('Bad request');
|
||||
}
|
||||
|
||||
$this->c->Lang->load('validator');
|
||||
$this->c->Lang->load('admin_providers');
|
||||
|
||||
if ('POST' === $method) {
|
||||
$v = $this->c->Validator->reset()
|
||||
->addRules([
|
||||
'token' => 'token:AdminProvider',
|
||||
'client_id' => 'exist|string:trim|max:255',
|
||||
'client_secret' => 'exist|string:trim|max:255',
|
||||
'changeData' => 'checkbox',
|
||||
])->addAliases([
|
||||
])->addArguments([
|
||||
'token' => $args,
|
||||
])->addMessages([
|
||||
]);
|
||||
|
||||
if ($v->validation($_POST)) {
|
||||
if ($v->changeData) {
|
||||
$this->c->providers->update([
|
||||
$provider->name => [
|
||||
'pr_cl_id' => $v->client_id,
|
||||
'pr_cl_sec' => $v->client_secret,
|
||||
],
|
||||
]);
|
||||
|
||||
$message = 'Provider updated redirect';
|
||||
} else {
|
||||
$message = 'No confirm redirect';
|
||||
}
|
||||
|
||||
return $this->c->Redirect->page('AdminProvider', $args)->message($message);
|
||||
}
|
||||
|
||||
$this->fIswev = $v->getErrors();
|
||||
}
|
||||
|
||||
$this->mDisabled();
|
||||
|
||||
$this->nameTpl = 'admin/form';
|
||||
$this->aIndex = 'options';
|
||||
$this->aCrumbs[] = [$this->c->providers->link($provider->name), $provider->name];
|
||||
$this->aCrumbs[] = [$this->c->providers->link(''), 'Providers'];
|
||||
$this->form = $this->formEdit($provider);
|
||||
$this->classForm = ['provider'];
|
||||
$this->titleForm = $provider->name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подготавливает массив данных для формы
|
||||
*/
|
||||
protected function formEdit(Driver $provider): array
|
||||
{
|
||||
$form = [
|
||||
'action' => $this->c->providers->link($provider->name),
|
||||
'hidden' => [
|
||||
'token' => $this->c->Csrf->create('AdminProvider', ['name' => $provider->name]),
|
||||
],
|
||||
'sets' => [
|
||||
'provider' => [
|
||||
'fields' => [
|
||||
'callback' => [
|
||||
'type' => 'str',
|
||||
'value' => $provider->linkCallback,
|
||||
'caption' => 'Callback label',
|
||||
'help' => 'Callback help',
|
||||
],
|
||||
'client_id' => [
|
||||
'type' => 'text',
|
||||
'maxlength' => '255',
|
||||
'value' => $provider->client_id,
|
||||
'caption' => 'ID label',
|
||||
'help' => 'ID help',
|
||||
],
|
||||
'client_secret' => [
|
||||
'type' => 'text',
|
||||
'maxlength' => '255',
|
||||
'value' => '' == $provider->client_secret ? '' : '********',
|
||||
'caption' => 'Secret label',
|
||||
'help' => 'Secret help',
|
||||
],
|
||||
'changeData' => [
|
||||
'type' => 'checkbox',
|
||||
'caption' => '',
|
||||
'label' => 'Change data help',
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
'btns' => [
|
||||
'save' => [
|
||||
'type' => 'submit',
|
||||
'value' => __('Save changes'),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ class Update extends Admin
|
|||
{
|
||||
const PHP_MIN = '8.0.0';
|
||||
const REV_MIN_FOR_UPDATE = 53;
|
||||
const LATEST_REV_WITH_DB_CHANGES = 53;
|
||||
const LATEST_REV_WITH_DB_CHANGES = 55;
|
||||
const LOCK_NAME = 'lock_update';
|
||||
const LOCK_TTL = 1800;
|
||||
const JSON_OPTIONS = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR;
|
||||
|
@ -410,4 +410,98 @@ class Update extends Admin
|
|||
#
|
||||
# return null;
|
||||
# }
|
||||
|
||||
/**
|
||||
* rev.54 to rev.55
|
||||
*/
|
||||
protected function stageNumber54(array $args): ?int
|
||||
{
|
||||
$config = $this->c->config;
|
||||
|
||||
$config->b_oauth_allow = 0;
|
||||
|
||||
$config->save();
|
||||
|
||||
$schema = [
|
||||
'FIELDS' => [
|
||||
'pr_name' => ['VARCHAR(25)', false],
|
||||
'pr_allow' => ['TINYINT(1)', false, 0],
|
||||
'pr_pos' => ['INT(10) UNSIGNED', false, 0],
|
||||
'pr_cl_id' => ['VARCHAR(255)', false, ''],
|
||||
'pr_cl_sec' => ['VARCHAR(255)', false, ''],
|
||||
],
|
||||
'UNIQUE KEYS' => [
|
||||
'pr_name_idx' => ['pr_name'],
|
||||
],
|
||||
];
|
||||
$this->c->DB->createTable('::providers', $schema);
|
||||
|
||||
$schema = [
|
||||
'FIELDS' => [
|
||||
'uid' => ['INT(10) UNSIGNED', false],
|
||||
'pr_name' => ['VARCHAR(25)', false],
|
||||
'pu_uid' => ['VARCHAR(165)', false],
|
||||
'pu_email' => ['VARCHAR(190)', false, ''],
|
||||
'pu_email_normal' => ['VARCHAR(190)', false, ''],
|
||||
'pu_email_verified' => ['TINYINT(1)', false, 0],
|
||||
],
|
||||
'UNIQUE KEYS' => [
|
||||
'pr_name_pu_uid_idx' => ['pr_name', 'pu_uid'],
|
||||
],
|
||||
'INDEXES' => [
|
||||
'uid_idx' => ['uid'],
|
||||
'pu_email_normal_idx' => ['pu_email_normal'],
|
||||
],
|
||||
];
|
||||
$this->c->DB->createTable('::providers_users', $schema);
|
||||
|
||||
$providers = [
|
||||
'github',
|
||||
];
|
||||
|
||||
$query = 'INSERT INTO ::providers (pr_name, pr_pos)
|
||||
SELECT tmp.*
|
||||
FROM (SELECT ?s:name AS f1, ?i:pos AS f2) AS tmp
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM ::providers
|
||||
WHERE pr_name=?s:name
|
||||
)';
|
||||
|
||||
foreach ($providers as $pos => $name) {
|
||||
$vars = [
|
||||
':name' => $name,
|
||||
':pos' => $pos,
|
||||
];
|
||||
|
||||
$this->c->DB->exec($query, $vars);
|
||||
}
|
||||
|
||||
$coreConfig = new CoreConfig($this->configFile);
|
||||
|
||||
$coreConfig->add(
|
||||
'multiple=>AdminProviders',
|
||||
'\\ForkBB\\Models\\Pages\\Admin\\Providers::class',
|
||||
'AdminOptions'
|
||||
);
|
||||
$coreConfig->add(
|
||||
'shared=>providers',
|
||||
[
|
||||
'class' => '\\ForkBB\\Models\\Provider\\Providers::class',
|
||||
'drivers' => [
|
||||
'github' => '\\ForkBB\\Models\\Provider\\Driver\\GitHub::class'
|
||||
],
|
||||
],
|
||||
'pms'
|
||||
);
|
||||
$coreConfig->add(
|
||||
'multiple=>RegLog',
|
||||
'\\ForkBB\\Models\\Pages\\RegLog::class',
|
||||
'Register'
|
||||
);
|
||||
|
||||
$coreConfig->save();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,12 +13,15 @@ namespace ForkBB\Models\Pages;
|
|||
use ForkBB\Core\Validator;
|
||||
use ForkBB\Core\Exceptions\MailException;
|
||||
use ForkBB\Models\Page;
|
||||
use ForkBB\Models\Pages\RegLogTrait;
|
||||
use ForkBB\Models\User\User;
|
||||
use SensitiveParameter;
|
||||
use function \ForkBB\__;
|
||||
|
||||
class Auth extends Page
|
||||
{
|
||||
use RegLogTrait;
|
||||
|
||||
/**
|
||||
* Выход пользователя
|
||||
*/
|
||||
|
@ -105,6 +108,7 @@ class Auth extends Page
|
|||
$save = $v->save ?? true;
|
||||
$redirect = $v->redirect ?? $this->c->Router->validate($ref, 'Index');
|
||||
$this->form = $this->formLogin($username, (bool) $save, $redirect);
|
||||
$this->formOAuth = $this->reglogForm();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
82
app/Models/Pages/RegLog.php
Normal file
82
app/Models/Pages/RegLog.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Models\Pages;
|
||||
|
||||
use ForkBB\Core\Validator;
|
||||
use ForkBB\Core\Exceptions\MailException;
|
||||
use ForkBB\Models\Page;
|
||||
use ForkBB\Models\User\User;
|
||||
use function \ForkBB\__;
|
||||
|
||||
class RegLog extends Page
|
||||
{
|
||||
/**
|
||||
* Обрабатывает нажатие одной из кнопок провайдеров
|
||||
*/
|
||||
public function redirect(): Page
|
||||
{
|
||||
if (
|
||||
1 !== $this->c->config->b_oauth_allow
|
||||
|| empty($list = $this->c->providers->active())
|
||||
) {
|
||||
return $this->c->Message->message('Bad request');
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'token' => 'token:RegLogRedirect',
|
||||
];
|
||||
|
||||
foreach ($list as $name) {
|
||||
$rules[$name] = 'string';
|
||||
}
|
||||
|
||||
$v = $this->c->Validator->reset()->addRules($rules);
|
||||
|
||||
if (
|
||||
! $v->validation($_POST)
|
||||
|| 1 !== \count($form = $v->getData(false, ['token']))
|
||||
) {
|
||||
return $this->c->Message->message('Bad request');
|
||||
}
|
||||
|
||||
return $this->c->Redirect->url($this->c->providers->init()->get(\array_key_first($form))->linkAuth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает ответ сервера
|
||||
*/
|
||||
public function callback(array $args): Page
|
||||
{
|
||||
if (
|
||||
1 !== $this->c->config->b_oauth_allow
|
||||
|| empty($list = $this->c->providers->active())
|
||||
|| empty($list[$args['name']])
|
||||
) {
|
||||
return $this->c->Message->message('Bad request');
|
||||
}
|
||||
|
||||
$this->c->Lang->load('admin_providers');
|
||||
|
||||
$provider = $this->c->providers->init()->get($args['name']);
|
||||
|
||||
if (true !== ($result = $provider->verifyAuth($_GET))) {
|
||||
return $this->c->Message->message($result);
|
||||
}
|
||||
|
||||
if (true !== $provider->reqAccessToken()) {
|
||||
return $this->c->Message->message('Error token');
|
||||
}
|
||||
|
||||
if (true !== $provider->reqUserInfo()) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
47
app/Models/Pages/RegLogTrait.php
Normal file
47
app/Models/Pages/RegLogTrait.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Models\Pages;
|
||||
|
||||
use ForkBB\Models\Model;
|
||||
use function \ForkBB\__;
|
||||
|
||||
trait RegLogTrait
|
||||
{
|
||||
protected function reglogForm(): array
|
||||
{
|
||||
if (
|
||||
1 !== $this->c->config->b_oauth_allow
|
||||
|| empty($list = $this->c->providers->active())
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->c->Lang->load('admin_providers');
|
||||
|
||||
$btns = [];
|
||||
|
||||
foreach ($list as $name) {
|
||||
$btns[$name] = [
|
||||
'type' => 'submit',
|
||||
'value' => __(['Sign in with %s', __($name)]),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'action' => $this->c->Router->link('RegLogRedirect'),
|
||||
'hidden' => [
|
||||
'token' => $this->c->Csrf->create('RegLogRedirect'),
|
||||
],
|
||||
'sets' => [],
|
||||
'btns' => $btns,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -13,11 +13,14 @@ namespace ForkBB\Models\Pages;
|
|||
use ForkBB\Core\Validator;
|
||||
use ForkBB\Core\Exceptions\MailException;
|
||||
use ForkBB\Models\Page;
|
||||
use ForkBB\Models\Pages\RegLogTrait;
|
||||
use ForkBB\Models\User\User;
|
||||
use function \ForkBB\__;
|
||||
|
||||
class Register extends Page
|
||||
{
|
||||
use RegLogTrait;
|
||||
|
||||
/**
|
||||
* Регистрация
|
||||
*/
|
||||
|
@ -85,6 +88,7 @@ class Register extends Page
|
|||
$this->titles = 'Register';
|
||||
$this->robots = 'noindex';
|
||||
$this->form = $this->formReg($v);
|
||||
$this->formOAuth = $this->reglogForm();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
162
app/Models/Provider/Driver.php
Normal file
162
app/Models/Provider/Driver.php
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Models\Provider;
|
||||
|
||||
use ForkBB\Core\Container;
|
||||
use ForkBB\Models\Model;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class Driver extends Model
|
||||
{
|
||||
/**
|
||||
* Ключ модели для контейнера
|
||||
*/
|
||||
protected string $cKey = 'Provider';
|
||||
|
||||
protected string $code;
|
||||
|
||||
protected string $originalName;
|
||||
protected string $authURL;
|
||||
protected string $tokenURL;
|
||||
protected string $userURL;
|
||||
protected string $scope;
|
||||
|
||||
public function __construct(protected string $client_id, protected string $client_secret, Container $c)
|
||||
{
|
||||
parent::__construct($c);
|
||||
}
|
||||
|
||||
protected function setname(string $name):void
|
||||
{
|
||||
if ($this->originalName !== $name) {
|
||||
throw new RuntimeException("Invalid name: {$name}");
|
||||
}
|
||||
|
||||
$this->setAttr('name', $name);
|
||||
}
|
||||
|
||||
protected function getlinkCallback(): string
|
||||
{
|
||||
return $this->c->Router->link('RegLogCallback', ['name' => $this->name]);
|
||||
}
|
||||
|
||||
protected function getclient_id(): string
|
||||
{
|
||||
return $this->client_id;
|
||||
}
|
||||
|
||||
protected function getclient_secret(): string
|
||||
{
|
||||
return $this->client_secret;
|
||||
}
|
||||
|
||||
protected function getlinkAuth(): string
|
||||
{
|
||||
$params = [
|
||||
'response_type' => 'code',
|
||||
'scope' => $this->scope,
|
||||
'state' => $this->c->Csrf->createHash($this->originalName, ['ip' => $this->c->user->ip]),
|
||||
'client_id' => $this->client_id,
|
||||
'redirect_uri' => $this->linkCallback,
|
||||
];
|
||||
|
||||
return $this->authURL . '?' . \http_build_query($params);
|
||||
}
|
||||
|
||||
protected function verifyState(string $state): bool
|
||||
{
|
||||
return $this->c->Csrf->verify($state, $this->originalName, ['ip' => $this->c->user->ip]);
|
||||
}
|
||||
|
||||
public function verifyAuth(array $data): bool|string|array
|
||||
{
|
||||
if (! \is_string($data['code'] ?? null)) {
|
||||
if (\is_string($data['error'] ?? null)) {
|
||||
return ['Provider response: %s', $data['error']];
|
||||
} else {
|
||||
return ['Provider response: %s', 'undefined'];
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
! \is_string($data['state'] ?? null)
|
||||
|| ! $this->verifyState($data['state'])
|
||||
) {
|
||||
return 'State error';
|
||||
}
|
||||
|
||||
$this->code = $data['code'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function reqAccessToken(): bool
|
||||
{
|
||||
$params = [
|
||||
'grant_type' => 'authorization_code',
|
||||
'client_id' => $this->client_id,
|
||||
'client_secret' => $this->client_secret,
|
||||
'code' => $this->code,
|
||||
'redirect_uri' => $this->linkCallback,
|
||||
];
|
||||
|
||||
$ch = \curl_init($this->tokenURL);
|
||||
|
||||
\curl_setopt($ch, \CURLOPT_HTTPHEADER, ['Accept: application/json']);
|
||||
\curl_setopt($ch, \CURLOPT_POST, true);
|
||||
\curl_setopt($ch, \CURLOPT_POSTFIELDS, \http_build_query($params));
|
||||
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true);
|
||||
\curl_setopt($ch, \CURLOPT_HEADER, false);
|
||||
|
||||
$html = \curl_exec($ch);
|
||||
# $type = \curl_getinfo($ch, \CURLINFO_CONTENT_TYPE);
|
||||
|
||||
\curl_close($ch);
|
||||
|
||||
if (
|
||||
! isset($html[1])
|
||||
|| '{' !== $html[0]
|
||||
|| '}' !== $html[-1]
|
||||
|| ! \is_array($json = \json_decode($html, true, 20, \JSON_BIGINT_AS_STRING & JSON_UNESCAPED_UNICODE & JSON_INVALID_UTF8_SUBSTITUTE))
|
||||
|| ! isset($json['access_token'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->access_token = $json['access_token'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function reqUserInfo(): bool
|
||||
{
|
||||
$headers = [
|
||||
'Accept: application/json',
|
||||
"Authorization: Bearer {$this->access_token}",
|
||||
'User-Agent: ForkBB (Client ID: {$this->client_id})',
|
||||
];
|
||||
|
||||
$ch = \curl_init($this->userURL);
|
||||
|
||||
\curl_setopt($ch, \CURLOPT_HTTPHEADER, $headers);
|
||||
\curl_setopt($ch, \CURLOPT_POST, false);
|
||||
#\curl_setopt($ch, \CURLOPT_POSTFIELDS, \http_build_query($params));
|
||||
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true);
|
||||
\curl_setopt($ch, \CURLOPT_HEADER, false);
|
||||
|
||||
$html = \curl_exec($ch);
|
||||
# $type = \curl_getinfo($ch, \CURLINFO_CONTENT_TYPE);
|
||||
|
||||
\curl_close($ch);
|
||||
|
||||
exit(var_dump("<pre>".$html));
|
||||
}
|
||||
}
|
24
app/Models/Provider/Driver/GitHub.php
Normal file
24
app/Models/Provider/Driver/GitHub.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Models\Provider\Driver;
|
||||
|
||||
use ForkBB\Models\Provider\Driver;
|
||||
use RuntimeException;
|
||||
|
||||
class GitHub extends Driver
|
||||
{
|
||||
protected string $originalName = 'github';
|
||||
protected string $authURL = 'https://github.com/login/oauth/authorize';
|
||||
protected string $tokenURL = 'https://github.com/login/oauth/access_token';
|
||||
protected string $userURL = 'https://api.github.com/user';
|
||||
protected string $scope = 'read:user';
|
||||
|
||||
}
|
192
app/Models/Provider/Providers.php
Normal file
192
app/Models/Provider/Providers.php
Normal file
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Models\Provider;
|
||||
|
||||
use ForkBB\Core\Container;
|
||||
use ForkBB\Models\Manager;
|
||||
use ForkBB\Models\Provider\Driver;
|
||||
use RuntimeException;
|
||||
|
||||
class Providers extends Manager
|
||||
{
|
||||
const CACHE_KEY = 'providers';
|
||||
|
||||
/**
|
||||
* Ключ модели для контейнера
|
||||
*/
|
||||
protected string $cKey = 'Providers';
|
||||
|
||||
protected ?array $cache = null;
|
||||
protected bool $ready = false;
|
||||
|
||||
public function __construct(protected array $drivers, Container $c)
|
||||
{
|
||||
parent::__construct($c);
|
||||
}
|
||||
|
||||
public function init(): self
|
||||
{
|
||||
if (! $this->ready) {
|
||||
foreach ($this->cache() as $cur) {
|
||||
$this->create($cur);
|
||||
}
|
||||
|
||||
$this->ready = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function link(string $name): string
|
||||
{
|
||||
return '' === $name
|
||||
? $this->c->Router->link('AdminProviders')
|
||||
: $this->c->Router->link('AdminProvider', ['name' => $name]);
|
||||
}
|
||||
|
||||
public function create(array $attrs = []): Driver
|
||||
{
|
||||
if (! isset($attrs['pr_name'])) {
|
||||
throw new RuntimeException('Provider name missing');
|
||||
}
|
||||
|
||||
if (! isset($this->drivers[$attrs['pr_name']])) {
|
||||
throw new RuntimeException("No driver for '{$attrs['pr_name']}' provider");
|
||||
}
|
||||
|
||||
if ($this->isset($attrs['pr_name'])) {
|
||||
throw new RuntimeException("Driver '{$attrs['pr_name']}' already exists");
|
||||
}
|
||||
|
||||
$class = $this->drivers[$attrs['pr_name']];
|
||||
$driver = new $class($attrs['pr_cl_id'], $attrs['pr_cl_sec'], $this->c);
|
||||
$driver->name = $attrs['pr_name'];
|
||||
$driver->pos = $attrs['pr_pos'];
|
||||
$driver->allow = $attrs['pr_allow'];
|
||||
|
||||
$this->set($driver->name, $driver);
|
||||
|
||||
return $driver;
|
||||
}
|
||||
|
||||
public function list(): array
|
||||
{
|
||||
return $this->repository;
|
||||
}
|
||||
|
||||
public function active(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->cache() as $cur) {
|
||||
if (
|
||||
$cur['pr_allow']
|
||||
&& '' != $cur['pr_cl_id']
|
||||
&& '' != $cur['pr_cl_sec']
|
||||
) {
|
||||
$result[$cur['pr_name']] = $cur['pr_name'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function cache(): array
|
||||
{
|
||||
if (! \is_array($this->cache)) {
|
||||
$this->cache = $this->c->Cache->get(self::CACHE_KEY);
|
||||
}
|
||||
|
||||
if (! \is_array($this->cache)) {
|
||||
$query = 'SELECT pr_name, pr_allow, pr_pos, pr_cl_id, pr_cl_sec
|
||||
FROM ::providers
|
||||
ORDER BY pr_pos';
|
||||
|
||||
$stmt = $this->c->DB->query($query);
|
||||
|
||||
while ($cur = $stmt->fetch()) {
|
||||
$this->cache[$cur['pr_name']] = $cur;
|
||||
}
|
||||
|
||||
if (true !== $this->c->Cache->set(self::CACHE_KEY, $this->cache)) {
|
||||
throw new RuntimeException('Unable to write value to cache - ' . self::CACHE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
protected function emptyField(string $key, array $sets, array $cache): bool
|
||||
{
|
||||
if (isset($sets[$key])) {
|
||||
return '' == $sets[$key];
|
||||
} else {
|
||||
return '' == $cache[$key];
|
||||
}
|
||||
}
|
||||
|
||||
public function update(array $form): self
|
||||
{
|
||||
$cache = $this->cache();
|
||||
$fields = $this->c->dbMap->providers;
|
||||
|
||||
foreach ($form as $driver => $sets) {
|
||||
if (! isset($this->drivers[$driver], $cache[$driver])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$set = $vars = [];
|
||||
|
||||
if (
|
||||
(
|
||||
! empty($sets['pr_allow'])
|
||||
|| ! empty($cache[$driver]['pr_allow'])
|
||||
)
|
||||
&& (
|
||||
$this->emptyField('pr_cl_id', $sets, $cache[$driver])
|
||||
|| $this->emptyField('pr_cl_sec', $sets, $cache[$driver])
|
||||
)
|
||||
) {
|
||||
$sets['pr_allow'] = 0;
|
||||
}
|
||||
|
||||
foreach ($sets as $name => $value) {
|
||||
if (
|
||||
! isset($fields[$name])
|
||||
|| $cache[$driver][$name] == $value
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$vars[] = $value;
|
||||
$set[] = $name . '=?' . $fields[$name];
|
||||
}
|
||||
|
||||
if (empty($set)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$vars[] = $driver;
|
||||
$set = \implode(', ', $set);
|
||||
$query = "UPDATE ::providers
|
||||
SET {$set}
|
||||
WHERE pr_name=?s";
|
||||
|
||||
$this->c->DB->exec($query, $vars);
|
||||
}
|
||||
|
||||
if (true !== $this->c->Cache->delete(self::CACHE_KEY)) {
|
||||
throw new RuntimeException('Unable to delete cache - ' . self::CACHE_KEY);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -163,6 +163,12 @@ return [
|
|||
'subscriptions' => \ForkBB\Models\Subscription\Subscription::class,
|
||||
'bbcode' => '@BBCodeListModel:init',
|
||||
'pms' => \ForkBB\Models\PM\PM::class,
|
||||
'providers' => [
|
||||
'class' => \ForkBB\Models\Provider\Providers::class,
|
||||
'drivers' => [
|
||||
'github' => \ForkBB\Models\Provider\Driver\GitHub::class,
|
||||
],
|
||||
],
|
||||
|
||||
'Csrf' => [
|
||||
'class' => \ForkBB\Core\Csrf::class,
|
||||
|
@ -333,6 +339,7 @@ return [
|
|||
'Userlist' => \ForkBB\Models\Pages\Userlist::class,
|
||||
'Search' => \ForkBB\Models\Pages\Search::class,
|
||||
'Register' => \ForkBB\Models\Pages\Register::class,
|
||||
'RegLog' => \ForkBB\Models\Pages\RegLog::class,
|
||||
'Redirect' => \ForkBB\Models\Pages\Redirect::class,
|
||||
'Maintenance' => \ForkBB\Models\Pages\Maintenance::class,
|
||||
'Ban' => \ForkBB\Models\Pages\Ban::class,
|
||||
|
@ -360,6 +367,7 @@ return [
|
|||
'AdminIndex' => \ForkBB\Models\Pages\Admin\Index::class,
|
||||
'AdminStatistics' => \ForkBB\Models\Pages\Admin\Statistics::class,
|
||||
'AdminOptions' => \ForkBB\Models\Pages\Admin\Options::class,
|
||||
'AdminProviders' => \ForkBB\Models\Pages\Admin\Providers::class,
|
||||
'AdminCategories' => \ForkBB\Models\Pages\Admin\Categories::class,
|
||||
'AdminForums' => \ForkBB\Models\Pages\Admin\Forums::class,
|
||||
'AdminGroups' => \ForkBB\Models\Pages\Admin\Groups::class,
|
||||
|
|
|
@ -425,3 +425,12 @@ msgstr "Allow private messages"
|
|||
|
||||
msgid "Allow PM help"
|
||||
msgstr "Settings for individual groups are available in <a href="%2$s">%1$s</a>."
|
||||
|
||||
msgid "Allow oauth label"
|
||||
msgstr "OAuth-authorization"
|
||||
|
||||
msgid "Allow oauth help"
|
||||
msgstr "Allow users to login/register on this bulletin board using accounts on third-party resources."
|
||||
|
||||
msgid "Configure providers"
|
||||
msgstr "Configure providers"
|
||||
|
|
70
app/lang/en/admin_providers.po
Normal file
70
app/lang/en/admin_providers.po
Normal file
|
@ -0,0 +1,70 @@
|
|||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Project-Id-Version: ForkBB\n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: ForkBB <mio.visman@yandex.ru>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: en\n"
|
||||
|
||||
msgid "OAuth authorization disabled"
|
||||
msgstr "OAuth authorization <a href=\"%s\">disabled</a>."
|
||||
|
||||
msgid "Providers"
|
||||
msgstr "Providers"
|
||||
|
||||
msgid "Provider label"
|
||||
msgstr "Provider"
|
||||
|
||||
msgid "Position label"
|
||||
msgstr "Position"
|
||||
|
||||
msgid "Allow label"
|
||||
msgstr "Allow"
|
||||
|
||||
msgid "Providers updated redirect"
|
||||
msgstr "Provider table updated."
|
||||
|
||||
msgid "Provider updated redirect"
|
||||
msgstr "Provider data updated."
|
||||
|
||||
msgid "github"
|
||||
msgstr "GitHub"
|
||||
|
||||
msgid "Callback label"
|
||||
msgstr "Callback URI"
|
||||
|
||||
msgid "Callback help"
|
||||
msgstr "The URI that the user is returned to after they have granted or denied access to the application (corresponds to the redirect_uri of the OAuth protocol). It must be specified when registering your application on the provider's server."
|
||||
|
||||
msgid "ID label"
|
||||
msgstr "Client ID"
|
||||
|
||||
msgid "ID help"
|
||||
msgstr "Identifier that you can get when registering your application on the provider's server."
|
||||
|
||||
msgid "Secret label"
|
||||
msgstr "Client secret"
|
||||
|
||||
msgid "Secret help"
|
||||
msgstr "The secret (password) that you can get when registering your application on the provider's server."
|
||||
|
||||
msgid "Change data help"
|
||||
msgstr "Enable this checkbox if you want to change the data"
|
||||
|
||||
msgid "Sign in with %s"
|
||||
msgstr "Sign in with %s"
|
||||
|
||||
msgid "Provider response: %s"
|
||||
msgstr "Provider response: %s."
|
||||
|
||||
msgid "State error"
|
||||
msgstr "Error in state parameter."
|
||||
|
||||
msgid "Error token"
|
||||
msgstr "Error request access token"
|
|
@ -280,7 +280,7 @@ msgid "SMTP password label"
|
|||
msgstr "SMTP password"
|
||||
|
||||
msgid "SMTP change password help"
|
||||
msgstr "Установите галку, если хотите поменять или удалить пароль"
|
||||
msgstr "Установите флажок, если хотите поменять или удалить пароль"
|
||||
|
||||
msgid "SMTP password help"
|
||||
msgstr "Пароль для сервера SMTP. Заполняйте только если сервер этого требует."
|
||||
|
@ -425,3 +425,12 @@ msgstr "Разрешить личные сообщения"
|
|||
|
||||
msgid "Allow PM help"
|
||||
msgstr "Настройки для отдельных групп доступны в <a href="%2$s">%1$s</a>."
|
||||
|
||||
msgid "Allow oauth label"
|
||||
msgstr "OAuth-авторизация"
|
||||
|
||||
msgid "Allow oauth help"
|
||||
msgstr "Разрешить пользователям вход/регистрацию на форуме с помощью аккаунтов на сторонних ресурсах."
|
||||
|
||||
msgid "Configure providers"
|
||||
msgstr "Настроить провайдеров"
|
||||
|
|
70
app/lang/ru/admin_providers.po
Normal file
70
app/lang/ru/admin_providers.po
Normal file
|
@ -0,0 +1,70 @@
|
|||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"Project-Id-Version: ForkBB\n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: ForkBB <mio.visman@yandex.ru>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ru\n"
|
||||
|
||||
msgid "OAuth authorization disabled"
|
||||
msgstr "OAuth-авторизация <a href=\"%s\">выключена</a>."
|
||||
|
||||
msgid "Providers"
|
||||
msgstr "Провайдеры"
|
||||
|
||||
msgid "Provider label"
|
||||
msgstr "Провайдер"
|
||||
|
||||
msgid "Position label"
|
||||
msgstr "Позиция"
|
||||
|
||||
msgid "Allow label"
|
||||
msgstr "Вкл."
|
||||
|
||||
msgid "Providers updated redirect"
|
||||
msgstr "Таблица провайдеров обновлена."
|
||||
|
||||
msgid "Provider updated redirect"
|
||||
msgstr "Данные провайдера обновлены."
|
||||
|
||||
msgid "github"
|
||||
msgstr "GitHub"
|
||||
|
||||
msgid "Callback label"
|
||||
msgstr "URI перенаправления"
|
||||
|
||||
msgid "Callback help"
|
||||
msgstr "Адрес, на который пользователь возращается после того, как он разрешил или отказал приложению в доступе (соответствует redirect_uri протокола OAuth). Его нужно указать при регистрации своего приложения на сервере провайдера."
|
||||
|
||||
msgid "ID label"
|
||||
msgstr "Идентификатор клиента"
|
||||
|
||||
msgid "ID help"
|
||||
msgstr "Идентификатор (ID), который можно получить при регистрации своего приложения на сервере провайдера."
|
||||
|
||||
msgid "Secret label"
|
||||
msgstr "Секрет клиента"
|
||||
|
||||
msgid "Secret help"
|
||||
msgstr "Секрет (пароль), который можно получить при регистрации своего приложения на сервере провайдера."
|
||||
|
||||
msgid "Change data help"
|
||||
msgstr "Установите флажок, если хотите изменить данные"
|
||||
|
||||
msgid "Sign in with %s"
|
||||
msgstr "Войти с помощью %s"
|
||||
|
||||
msgid "Provider response: %s"
|
||||
msgstr "Ответ провайдера: %s."
|
||||
|
||||
msgid "State error"
|
||||
msgstr "Ошибка в параметре state."
|
||||
|
||||
msgid "Error token"
|
||||
msgstr "Ошибка запроса токена доступа"
|
|
@ -14,3 +14,10 @@
|
|||
@endif
|
||||
</section>
|
||||
@endif
|
||||
@if ($form = $p->formOAuth)
|
||||
<div id="fork-oauth" class="f-main">
|
||||
<div class="f-fdiv f-lrdiv">
|
||||
@include ('layouts/form')
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
@ -7,3 +7,10 @@
|
|||
</div>
|
||||
</section>
|
||||
@endif
|
||||
@if ($form = $p->formOAuth)
|
||||
<div id="fork-oauth" class="f-main">
|
||||
<div class="f-fdiv f-lrdiv">
|
||||
@include ('layouts/form')
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
@ -208,7 +208,9 @@
|
|||
}
|
||||
|
||||
#forka .f-fs-inline dl {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#forka .f-fs-inline dt {
|
||||
|
@ -1026,3 +1028,34 @@
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************/
|
||||
/* Админка/Провайдеры */
|
||||
/****************************************/
|
||||
#forka .f-field-provider.f-field-name {
|
||||
width: calc(100% - 8rem);
|
||||
}
|
||||
|
||||
#forka .f-field-provider.f-field-position {
|
||||
width: 5rem;
|
||||
}
|
||||
|
||||
#forka .f-field-provider.f-field-allow {
|
||||
width: 3rem;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#forka .f-field-provider.f-field-name .f-ybtn {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#forka .f-field-provider.f-field-allow .f-ychk {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#forka .f-field-provider.f-field-allow .f-flblch {
|
||||
height: 2rem;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue