mirror of
https://github.com/getgrav/grav-plugin-admin.git
synced 2025-11-02 19:36:08 +01:00
Merge remote-tracking branch 'security/advisory-fix-1' into develop
# Conflicts: # admin.php
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -10,12 +10,23 @@
|
|||||||
# v1.10.8
|
# v1.10.8
|
||||||
## 03/19/2021
|
## 03/19/2021
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Requires **Grav 1.7.10**
|
||||||
1. [](#improved)
|
1. [](#improved)
|
||||||
* Include alt text and title for images added to the editor [#2098](https://github.com/getgrav/grav-plugin-admin/issues/2098)
|
* Include alt text and title for images added to the editor [#2098](https://github.com/getgrav/grav-plugin-admin/issues/2098)
|
||||||
1. [](#bugfix)
|
1. [](#bugfix)
|
||||||
* Fixed issue replacing `wildcard` field names in flex collections [#2092](https://github.com/getgrav/grav-plugin-admin/pull/2092)
|
* Fixed issue replacing `wildcard` field names in flex collections [#2092](https://github.com/getgrav/grav-plugin-admin/pull/2092)
|
||||||
* Fixed legacy Pages having old `modular` reference instead of `module` [#2093](https://github.com/getgrav/grav-plugin-admin/issues/2093)
|
* Fixed legacy Pages having old `modular` reference instead of `module` [#2093](https://github.com/getgrav/grav-plugin-admin/issues/2093)
|
||||||
* Fixed issue where Add New modal would close if selecting an item outside of the modal window. It is now necessary go through the Cancel button and clicking the overlay won't trigger the closing of the modal [#2089](https://github.com/getgrav/grav-plugin-admin/issues/2089), [#2065](https://github.com/getgrav/grav-plugin-admin/issues/2065)
|
* Fixed issue where Add New modal would close if selecting an item outside of the modal window. It is now necessary go through the Cancel button and clicking the overlay won't trigger the closing of the modal [#2089](https://github.com/getgrav/grav-plugin-admin/issues/2089), [#2065](https://github.com/getgrav/grav-plugin-admin/issues/2065)
|
||||||
|
1. [](#branch)
|
||||||
|
* Better isolate admin to prevent session related vulnerabilities
|
||||||
|
* Removed support for custom login redirects for improved security
|
||||||
|
* Shorten forgot password link lifetime from 7 days to 1 hour
|
||||||
|
* Fixed login related pages being accessible from admin when user has logged in
|
||||||
|
* Fixed admin user creation and password reset allowing unsafe passwords
|
||||||
|
* Fixed missing validation when registering the first admin user
|
||||||
|
* Fixed reset password email not to have session specific token in it
|
||||||
|
* Fixed admin controller running before setting `$grav['page']`
|
||||||
|
|
||||||
# v1.10.7
|
# v1.10.7
|
||||||
## 03/17/2021
|
## 03/17/2021
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ docs: https://github.com/getgrav/grav-plugin-admin/blob/develop/README.md
|
|||||||
license: MIT
|
license: MIT
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
- { name: grav, version: '>=1.7.4' }
|
- { name: grav, version: '>=1.7.10' }
|
||||||
- { name: form, version: '>=4.1.0' }
|
- { name: form, version: '>=4.1.0' }
|
||||||
- { name: login, version: '>=3.3.5' }
|
- { name: login, version: '>=3.3.5' }
|
||||||
- { name: email, version: '>=3.0.9' }
|
- { name: email, version: '>=3.0.9' }
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ use Grav\Common\Themes;
|
|||||||
use Grav\Common\Uri;
|
use Grav\Common\Uri;
|
||||||
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||||
use Grav\Common\User\Interfaces\UserInterface;
|
use Grav\Common\User\Interfaces\UserInterface;
|
||||||
use Grav\Common\User\User;
|
|
||||||
use Grav\Common\Utils;
|
use Grav\Common\Utils;
|
||||||
use Grav\Framework\Acl\Action;
|
use Grav\Framework\Acl\Action;
|
||||||
use Grav\Framework\Acl\Permissions;
|
use Grav\Framework\Acl\Permissions;
|
||||||
@@ -40,6 +39,7 @@ use Grav\Plugin\AdminPlugin;
|
|||||||
use Grav\Plugin\Login\Login;
|
use Grav\Plugin\Login\Login;
|
||||||
use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth;
|
use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth;
|
||||||
use PicoFeed\Parser\MalformedXmlException;
|
use PicoFeed\Parser\MalformedXmlException;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use RocketTheme\Toolbox\Event\Event;
|
use RocketTheme\Toolbox\Event\Event;
|
||||||
use RocketTheme\Toolbox\File\File;
|
use RocketTheme\Toolbox\File\File;
|
||||||
use RocketTheme\Toolbox\File\JsonFile;
|
use RocketTheme\Toolbox\File\JsonFile;
|
||||||
@@ -52,72 +52,63 @@ use PicoFeed\Reader\Reader;
|
|||||||
|
|
||||||
define('LOGIN_REDIRECT_COOKIE', 'grav-login-redirect');
|
define('LOGIN_REDIRECT_COOKIE', 'grav-login-redirect');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Admin
|
||||||
|
* @package Grav\Plugin\Admin
|
||||||
|
*/
|
||||||
class Admin
|
class Admin
|
||||||
{
|
{
|
||||||
|
/** @var int */
|
||||||
public const DEBUG = 1;
|
public const DEBUG = 1;
|
||||||
|
/** @var int */
|
||||||
public const MEDIA_PAGINATION_INTERVAL = 20;
|
public const MEDIA_PAGINATION_INTERVAL = 20;
|
||||||
|
/** @var string */
|
||||||
public const TMP_COOKIE_NAME = 'tmp-admin-message';
|
public const TMP_COOKIE_NAME = 'tmp-admin-message';
|
||||||
|
|
||||||
/** @var Grav */
|
/** @var Grav */
|
||||||
public $grav;
|
public $grav;
|
||||||
|
/** @var ServerRequestInterface|null */
|
||||||
|
public $request;
|
||||||
|
/** @var AdminForm */
|
||||||
|
public $form;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $base;
|
public $base;
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $location;
|
public $location;
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $route;
|
public $route;
|
||||||
|
/** @var UserInterface */
|
||||||
/** @var User */
|
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
/** @var array */
|
/** @var array */
|
||||||
public $forgot;
|
public $forgot;
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $task;
|
public $task;
|
||||||
|
|
||||||
/** @var array */
|
/** @var array */
|
||||||
public $json_response;
|
public $json_response;
|
||||||
|
|
||||||
/** @var Collection */
|
/** @var Collection */
|
||||||
public $collection;
|
public $collection;
|
||||||
|
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
public $multilang;
|
public $multilang;
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public $language;
|
public $language;
|
||||||
|
|
||||||
/** @var array */
|
/** @var array */
|
||||||
public $languages_enabled = [];
|
public $languages_enabled = [];
|
||||||
|
|
||||||
/** @var Uri $uri */
|
/** @var Uri $uri */
|
||||||
protected $uri;
|
protected $uri;
|
||||||
|
|
||||||
/** @var array */
|
/** @var array */
|
||||||
protected $pages = [];
|
protected $pages = [];
|
||||||
|
|
||||||
/** @var Session */
|
/** @var Session */
|
||||||
protected $session;
|
protected $session;
|
||||||
|
|
||||||
/** @var Data\Blueprints */
|
/** @var Data\Blueprints */
|
||||||
protected $blueprints;
|
protected $blueprints;
|
||||||
|
|
||||||
/** @var GPM */
|
/** @var GPM */
|
||||||
protected $gpm;
|
protected $gpm;
|
||||||
|
|
||||||
/** @var int */
|
/** @var int */
|
||||||
protected $pages_count;
|
protected $pages_count;
|
||||||
|
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
protected $load_additional_files_in_background = false;
|
protected $load_additional_files_in_background = false;
|
||||||
|
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
protected $loading_additional_files_in_background = false;
|
protected $loading_additional_files_in_background = false;
|
||||||
|
|
||||||
/** @var array */
|
/** @var array */
|
||||||
protected $temp_messages = [];
|
protected $temp_messages = [];
|
||||||
|
|
||||||
@@ -127,7 +118,7 @@ class Admin
|
|||||||
* @param Grav $grav
|
* @param Grav $grav
|
||||||
* @param string $base
|
* @param string $base
|
||||||
* @param string $location
|
* @param string $location
|
||||||
* @param string $route
|
* @param string|null $route
|
||||||
*/
|
*/
|
||||||
public function __construct(Grav $grav, $base, $location, $route)
|
public function __construct(Grav $grav, $base, $location, $route)
|
||||||
{
|
{
|
||||||
@@ -137,7 +128,7 @@ class Admin
|
|||||||
$this->grav = $grav;
|
$this->grav = $grav;
|
||||||
$this->base = $base;
|
$this->base = $base;
|
||||||
$this->location = $location;
|
$this->location = $location;
|
||||||
$this->route = $route;
|
$this->route = $route ?? '';
|
||||||
$this->uri = $grav['uri'];
|
$this->uri = $grav['uri'];
|
||||||
$this->session = $grav['session'];
|
$this->session = $grav['session'];
|
||||||
|
|
||||||
@@ -176,7 +167,7 @@ class Admin
|
|||||||
$this->languages_enabled = (array)$this->grav['config']->get('system.languages.supported', []);
|
$this->languages_enabled = (array)$this->grav['config']->get('system.languages.supported', []);
|
||||||
|
|
||||||
//Set the currently active language for the admin
|
//Set the currently active language for the admin
|
||||||
$languageCode = $this->grav['uri']->param('lang');
|
$languageCode = $this->uri->param('lang');
|
||||||
if (null === $languageCode && !$this->session->admin_lang) {
|
if (null === $languageCode && !$this->session->admin_lang) {
|
||||||
$this->session->admin_lang = $language->getActive() ?? '';
|
$this->session->admin_lang = $language->getActive() ?? '';
|
||||||
}
|
}
|
||||||
@@ -190,7 +181,8 @@ class Admin
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $message
|
* @param string $message
|
||||||
* @param array $data
|
* @param array|object $data
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function addDebugMessage(string $message, $data = [])
|
public static function addDebugMessage(string $message, $data = [])
|
||||||
{
|
{
|
||||||
@@ -199,6 +191,9 @@ class Admin
|
|||||||
$debugger->addMessage($message, 'debug', $data);
|
$debugger->addMessage($message, 'debug', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
public static function contentEditor()
|
public static function contentEditor()
|
||||||
{
|
{
|
||||||
$options = [
|
$options = [
|
||||||
@@ -238,6 +233,9 @@ class Admin
|
|||||||
return $languages;
|
return $languages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getLanguage(): string
|
public function getLanguage(): string
|
||||||
{
|
{
|
||||||
return $this->language ?: $this->grav['language']->getLanguage() ?: 'en';
|
return $this->language ?: $this->grav['language']->getLanguage() ?: 'en';
|
||||||
@@ -317,6 +315,9 @@ class Admin
|
|||||||
return $tools;
|
return $tools;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
public static function toolsPermissions()
|
public static function toolsPermissions()
|
||||||
{
|
{
|
||||||
$tools = static::tools();
|
$tools = static::tools();
|
||||||
@@ -349,12 +350,11 @@ class Admin
|
|||||||
/**
|
/**
|
||||||
* Static helper method to return the admin form nonce
|
* Static helper method to return the admin form nonce
|
||||||
*
|
*
|
||||||
|
* @param string $action
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function getNonce()
|
public static function getNonce(string $action = 'admin-form')
|
||||||
{
|
{
|
||||||
$action = 'admin-form';
|
|
||||||
|
|
||||||
return Utils::getNonce($action);
|
return Utils::getNonce($action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,11 +388,16 @@ class Admin
|
|||||||
return $admin->getCurrentRoute();
|
return $admin->getCurrentRoute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param string|null $languageCode
|
||||||
|
* @return Route
|
||||||
|
*/
|
||||||
public function getAdminRoute(string $path = '', $languageCode = null): Route
|
public function getAdminRoute(string $path = '', $languageCode = null): Route
|
||||||
{
|
{
|
||||||
/** @var Language $language */
|
/** @var Language $language */
|
||||||
$language = $this->grav['language'];
|
$language = $this->grav['language'];
|
||||||
$languageCode = $languageCode ?? $language->getActive();
|
$languageCode = $languageCode ?? ($language->getActive() ?: null);
|
||||||
$languagePrefix = $languageCode ? '/' . $languageCode : '';
|
$languagePrefix = $languageCode ? '/' . $languageCode : '';
|
||||||
|
|
||||||
$root = $this->grav['uri']->rootUrl();
|
$root = $this->grav['uri']->rootUrl();
|
||||||
@@ -415,6 +420,11 @@ class Admin
|
|||||||
return RouteFactory::createFromParts($parts);
|
return RouteFactory::createFromParts($parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $route
|
||||||
|
* @param string|null $languageCode
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function adminUrl(string $route = '', $languageCode = null)
|
public function adminUrl(string $route = '', $languageCode = null)
|
||||||
{
|
{
|
||||||
return $this->getAdminRoute($route, $languageCode)->toString(true);
|
return $this->getAdminRoute($route, $languageCode)->toString(true);
|
||||||
@@ -435,6 +445,9 @@ class Admin
|
|||||||
return $admin->getCurrentRoute();
|
return $admin->getCurrentRoute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
public function getCurrentRoute()
|
public function getCurrentRoute()
|
||||||
{
|
{
|
||||||
$pages = static::enablePages();
|
$pages = static::enablePages();
|
||||||
@@ -459,7 +472,8 @@ class Admin
|
|||||||
* Route may or may not be prefixed by /en or /admin or /en/admin.
|
* Route may or may not be prefixed by /en or /admin or /en/admin.
|
||||||
*
|
*
|
||||||
* @param string $redirect
|
* @param string $redirect
|
||||||
* @param int$redirectCode
|
* @param int $redirectCode
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function redirect($redirect, $redirectCode = 303)
|
public function redirect($redirect, $redirectCode = 303)
|
||||||
{
|
{
|
||||||
@@ -520,6 +534,9 @@ class Admin
|
|||||||
return count($this->grav['config']->get('system.languages.supported', [])) > 1;
|
return count($this->grav['config']->get('system.languages.supported', [])) > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public static function getTempDir()
|
public static function getTempDir()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -531,6 +548,9 @@ class Admin
|
|||||||
return $tmp_dir;
|
return $tmp_dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
public static function getPageMedia()
|
public static function getPageMedia()
|
||||||
{
|
{
|
||||||
$files = [];
|
$files = [];
|
||||||
@@ -541,7 +561,7 @@ class Admin
|
|||||||
$route = '/' . ltrim($grav['admin']->route, '/');
|
$route = '/' . ltrim($grav['admin']->route, '/');
|
||||||
|
|
||||||
/** @var PageInterface $page */
|
/** @var PageInterface $page */
|
||||||
$page = $pages->find($route);
|
$page = $pages->find($route);
|
||||||
$parent_route = null;
|
$parent_route = null;
|
||||||
if ($page) {
|
if ($page) {
|
||||||
$media = $page->media()->all();
|
$media = $page->media()->all();
|
||||||
@@ -564,8 +584,7 @@ class Admin
|
|||||||
/**
|
/**
|
||||||
* Fetch and delete messages from the session queue.
|
* Fetch and delete messages from the session queue.
|
||||||
*
|
*
|
||||||
* @param string $type
|
* @param string|null $type
|
||||||
*
|
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function messages($type = null)
|
public function messages($type = null)
|
||||||
@@ -579,7 +598,9 @@ class Admin
|
|||||||
/**
|
/**
|
||||||
* Authenticate user.
|
* Authenticate user.
|
||||||
*
|
*
|
||||||
* @param array $credentials User credentials.
|
* @param array $credentials User credentials.
|
||||||
|
* @param array $post
|
||||||
|
* @return never-return
|
||||||
*/
|
*/
|
||||||
public function authenticate($credentials, $post)
|
public function authenticate($credentials, $post)
|
||||||
{
|
{
|
||||||
@@ -658,6 +679,10 @@ class Admin
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check Two-Factor Authentication.
|
* Check Two-Factor Authentication.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @param array $post
|
||||||
|
* @return never-return
|
||||||
*/
|
*/
|
||||||
public function twoFa($data, $post)
|
public function twoFa($data, $post)
|
||||||
{
|
{
|
||||||
@@ -695,6 +720,10 @@ class Admin
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout from admin.
|
* Logout from admin.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @param array $post
|
||||||
|
* @return never-return
|
||||||
*/
|
*/
|
||||||
public function logout($data, $post)
|
public function logout($data, $post)
|
||||||
{
|
{
|
||||||
@@ -718,15 +747,8 @@ class Admin
|
|||||||
public static function doAnyUsersExist()
|
public static function doAnyUsersExist()
|
||||||
{
|
{
|
||||||
$accounts = Grav::instance()['accounts'] ?? null;
|
$accounts = Grav::instance()['accounts'] ?? null;
|
||||||
if ($accounts instanceof \Countable) {
|
|
||||||
return $accounts->count() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove old way to check for existence of a user account (Grav < v1.6.9)
|
return $accounts && $accounts->count() > 0;
|
||||||
$account_dir = $file_path = Grav::instance()['locator']->findResource('account://');
|
|
||||||
$user_check = glob($account_dir . '/*.yaml');
|
|
||||||
|
|
||||||
return $user_check;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -734,6 +756,7 @@ class Admin
|
|||||||
*
|
*
|
||||||
* @param string $msg
|
* @param string $msg
|
||||||
* @param string $type
|
* @param string $type
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function setMessage($msg, $type = 'info')
|
public function setMessage($msg, $type = 'info')
|
||||||
{
|
{
|
||||||
@@ -742,11 +765,19 @@ class Admin
|
|||||||
$messages->add($msg, $type);
|
$messages->add($msg, $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $msg
|
||||||
|
* @param string $type
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function addTempMessage($msg, $type)
|
public function addTempMessage($msg, $type)
|
||||||
{
|
{
|
||||||
$this->temp_messages[] = ['message' => $msg, 'scope' => $type];
|
$this->temp_messages[] = ['message' => $msg, 'scope' => $type];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
public function getTempMessages()
|
public function getTempMessages()
|
||||||
{
|
{
|
||||||
return $this->temp_messages;
|
return $this->temp_messages;
|
||||||
@@ -755,11 +786,9 @@ class Admin
|
|||||||
/**
|
/**
|
||||||
* Translate a string to the user-defined language
|
* Translate a string to the user-defined language
|
||||||
*
|
*
|
||||||
* @param array|mixed $args
|
* @param array|string $args
|
||||||
*
|
* @param array|null $languages
|
||||||
* @param mixed $languages
|
* @return string|string[]|null
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public static function translate($args, $languages = null)
|
public static function translate($args, $languages = null)
|
||||||
{
|
{
|
||||||
@@ -812,7 +841,6 @@ class Admin
|
|||||||
* Checks user authorisation to the action.
|
* Checks user authorisation to the action.
|
||||||
*
|
*
|
||||||
* @param string|string[] $action
|
* @param string|string[] $action
|
||||||
*
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function authorize($action = 'admin.login')
|
public function authorize($action = 'admin.login')
|
||||||
@@ -839,7 +867,6 @@ class Admin
|
|||||||
*
|
*
|
||||||
* @param string $type
|
* @param string $type
|
||||||
* @param array $post
|
* @param array $post
|
||||||
*
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
*/
|
*/
|
||||||
@@ -958,7 +985,6 @@ class Admin
|
|||||||
*
|
*
|
||||||
* @param string $type
|
* @param string $type
|
||||||
* @param array|null $post
|
* @param array|null $post
|
||||||
*
|
|
||||||
* @return object
|
* @return object
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
*/
|
*/
|
||||||
@@ -990,7 +1016,7 @@ class Admin
|
|||||||
if (preg_match('|plugins/|', $type)) {
|
if (preg_match('|plugins/|', $type)) {
|
||||||
$obj = Plugins::get(preg_replace('|plugins/|', '', $type));
|
$obj = Plugins::get(preg_replace('|plugins/|', '', $type));
|
||||||
if (null === $obj) {
|
if (null === $obj) {
|
||||||
return [];
|
return new \stdClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($post) {
|
if ($post) {
|
||||||
@@ -1005,7 +1031,7 @@ class Admin
|
|||||||
$themes = $this->grav['themes'];
|
$themes = $this->grav['themes'];
|
||||||
$obj = $themes->get(preg_replace('|themes/|', '', $type));
|
$obj = $themes->get(preg_replace('|themes/|', '', $type));
|
||||||
if (null === $obj) {
|
if (null === $obj) {
|
||||||
return [];
|
return new \stdClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($post) {
|
if ($post) {
|
||||||
@@ -1070,6 +1096,11 @@ class Admin
|
|||||||
return $data[$type];
|
return $data[$type];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Data\Data $object
|
||||||
|
* @param array $post
|
||||||
|
* @return Data\Data
|
||||||
|
*/
|
||||||
protected function mergePost(Data\Data $object, array $post)
|
protected function mergePost(Data\Data $object, array $post)
|
||||||
{
|
{
|
||||||
$object->merge($post);
|
$object->merge($post);
|
||||||
@@ -1105,6 +1136,9 @@ class Admin
|
|||||||
return $post;
|
return $post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
protected function hasErrorMessage()
|
protected function hasErrorMessage()
|
||||||
{
|
{
|
||||||
$msgs = $this->grav['messages']->all();
|
$msgs = $this->grav['messages']->all();
|
||||||
@@ -1120,7 +1154,6 @@ class Admin
|
|||||||
* Returns blueprints for the given type.
|
* Returns blueprints for the given type.
|
||||||
*
|
*
|
||||||
* @param string $type
|
* @param string $type
|
||||||
*
|
|
||||||
* @return Data\Blueprint
|
* @return Data\Blueprint
|
||||||
*/
|
*/
|
||||||
public function blueprints($type)
|
public function blueprints($type)
|
||||||
@@ -1136,7 +1169,6 @@ class Admin
|
|||||||
* Converts dot notation to array notation.
|
* Converts dot notation to array notation.
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
*
|
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function field($name)
|
public function field($name)
|
||||||
@@ -1150,7 +1182,6 @@ class Admin
|
|||||||
* Get all routes.
|
* Get all routes.
|
||||||
*
|
*
|
||||||
* @param bool $unique
|
* @param bool $unique
|
||||||
*
|
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function routes($unique = false)
|
public function routes($unique = false)
|
||||||
@@ -1231,6 +1262,10 @@ class Admin
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $package_slug
|
||||||
|
* @return string[]|string
|
||||||
|
*/
|
||||||
public function license($package_slug)
|
public function license($package_slug)
|
||||||
{
|
{
|
||||||
return Licenses::get($package_slug);
|
return Licenses::get($package_slug);
|
||||||
@@ -1241,7 +1276,6 @@ class Admin
|
|||||||
* packages that can be removed when removing a package.
|
* packages that can be removed when removing a package.
|
||||||
*
|
*
|
||||||
* @param string $slug The package slug
|
* @param string $slug The package slug
|
||||||
*
|
|
||||||
* @return array|bool
|
* @return array|bool
|
||||||
*/
|
*/
|
||||||
public function dependenciesThatCanBeRemovedWhenRemoving($slug)
|
public function dependenciesThatCanBeRemovedWhenRemoving($slug)
|
||||||
@@ -1255,21 +1289,17 @@ class Admin
|
|||||||
|
|
||||||
$package = $this->getPackageFromGPM($slug);
|
$package = $this->getPackageFromGPM($slug);
|
||||||
|
|
||||||
if ($package) {
|
if ($package && $package->dependencies) {
|
||||||
if ($package->dependencies) {
|
foreach ($package->dependencies as $dependency) {
|
||||||
foreach ($package->dependencies as $dependency) {
|
// if (count($gpm->getPackagesThatDependOnPackage($dependency)) > 1) {
|
||||||
// if (count($gpm->getPackagesThatDependOnPackage($dependency)) > 1) {
|
// continue;
|
||||||
// continue;
|
// }
|
||||||
// }
|
if (isset($dependency['name'])) {
|
||||||
if (isset($dependency['name'])) {
|
$dependency = $dependency['name'];
|
||||||
$dependency = $dependency['name'];
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array($dependency, $dependencies, true)) {
|
if (!in_array($dependency, $dependencies, true) && !in_array($dependency, ['admin', 'form', 'login', 'email', 'php'])) {
|
||||||
if (!in_array($dependency, ['admin', 'form', 'login', 'email', 'php'])) {
|
$dependencies[] = $dependency;
|
||||||
$dependencies[] = $dependency;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1295,6 +1325,10 @@ class Admin
|
|||||||
return $this->gpm;
|
return $this->gpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $package_slug
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
public function getPackageFromGPM($package_slug)
|
public function getPackageFromGPM($package_slug)
|
||||||
{
|
{
|
||||||
$package = $this->plugins(true)[$package_slug];
|
$package = $this->plugins(true)[$package_slug];
|
||||||
@@ -1309,7 +1343,6 @@ class Admin
|
|||||||
* Get all plugins.
|
* Get all plugins.
|
||||||
*
|
*
|
||||||
* @param bool $local
|
* @param bool $local
|
||||||
*
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function plugins($local = true)
|
public function plugins($local = true)
|
||||||
@@ -1338,7 +1371,6 @@ class Admin
|
|||||||
* Get all themes.
|
* Get all themes.
|
||||||
*
|
*
|
||||||
* @param bool $local
|
* @param bool $local
|
||||||
*
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function themes($local = true)
|
public function themes($local = true)
|
||||||
@@ -1384,9 +1416,8 @@ class Admin
|
|||||||
* Check the passed packages list can be updated
|
* Check the passed packages list can be updated
|
||||||
*
|
*
|
||||||
* @param array $packages
|
* @param array $packages
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
* @return bool
|
* @return bool
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function checkPackagesCanBeInstalled($packages)
|
public function checkPackagesCanBeInstalled($packages)
|
||||||
{
|
{
|
||||||
@@ -1405,7 +1436,6 @@ class Admin
|
|||||||
* to be installed.
|
* to be installed.
|
||||||
*
|
*
|
||||||
* @param array $packages The packages slugs
|
* @param array $packages The packages slugs
|
||||||
*
|
|
||||||
* @return array|bool
|
* @return array|bool
|
||||||
*/
|
*/
|
||||||
public function getDependenciesNeededToInstall($packages)
|
public function getDependenciesNeededToInstall($packages)
|
||||||
@@ -1422,8 +1452,7 @@ class Admin
|
|||||||
* Used by the Dashboard in the admin to display the X latest pages
|
* Used by the Dashboard in the admin to display the X latest pages
|
||||||
* that have been modified
|
* that have been modified
|
||||||
*
|
*
|
||||||
* @param integer $count number of pages to pull back
|
* @param int $count number of pages to pull back
|
||||||
*
|
|
||||||
* @return array|null
|
* @return array|null
|
||||||
*/
|
*/
|
||||||
public function latestPages($count = 10)
|
public function latestPages($count = 10)
|
||||||
@@ -1517,7 +1546,6 @@ class Admin
|
|||||||
* Determine if the plugin or theme info passed is from Team Grav
|
* Determine if the plugin or theme info passed is from Team Grav
|
||||||
*
|
*
|
||||||
* @param object $info Plugin or Theme info object
|
* @param object $info Plugin or Theme info object
|
||||||
*
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isTeamGrav($info)
|
public function isTeamGrav($info)
|
||||||
@@ -1529,7 +1557,6 @@ class Admin
|
|||||||
* Determine if the plugin or theme info passed is premium
|
* Determine if the plugin or theme info passed is premium
|
||||||
*
|
*
|
||||||
* @param object $info Plugin or Theme info object
|
* @param object $info Plugin or Theme info object
|
||||||
*
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isPremiumProduct($info)
|
public function isPremiumProduct($info)
|
||||||
@@ -1542,13 +1569,12 @@ class Admin
|
|||||||
*
|
*
|
||||||
* @return string The phpinfo() output
|
* @return string The phpinfo() output
|
||||||
*/
|
*/
|
||||||
function phpinfo()
|
public function phpinfo()
|
||||||
{
|
{
|
||||||
if (function_exists('phpinfo')) {
|
if (function_exists('phpinfo')) {
|
||||||
ob_start();
|
ob_start();
|
||||||
phpinfo();
|
phpinfo();
|
||||||
$pinfo = ob_get_clean();
|
$pinfo = ob_get_clean();
|
||||||
|
|
||||||
$pinfo = preg_replace('%^.*<body>(.*)</body>.*$%ms', '$1', $pinfo);
|
$pinfo = preg_replace('%^.*<body>(.*)</body>.*$%ms', '$1', $pinfo);
|
||||||
|
|
||||||
return $pinfo;
|
return $pinfo;
|
||||||
@@ -1560,8 +1586,7 @@ class Admin
|
|||||||
/**
|
/**
|
||||||
* Guest date format based on euro/US
|
* Guest date format based on euro/US
|
||||||
*
|
*
|
||||||
* @param string $date
|
* @param string|null $date
|
||||||
*
|
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function guessDateFormat($date)
|
public function guessDateFormat($date)
|
||||||
@@ -1584,6 +1609,7 @@ class Admin
|
|||||||
'g:ia'
|
'g:ia'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$date = (string)$date;
|
||||||
if (!isset($guess[$date])) {
|
if (!isset($guess[$date])) {
|
||||||
$guess[$date] = 'd-m-Y H:i';
|
$guess[$date] = 'd-m-Y H:i';
|
||||||
foreach ($date_formats as $date_format) {
|
foreach ($date_formats as $date_format) {
|
||||||
@@ -1605,6 +1631,11 @@ class Admin
|
|||||||
return $guess[$date];
|
return $guess[$date];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $date
|
||||||
|
* @param string $format
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
public function validateDate($date, $format)
|
public function validateDate($date, $format)
|
||||||
{
|
{
|
||||||
$d = DateTime::createFromFormat($format, $date);
|
$d = DateTime::createFromFormat($format, $date);
|
||||||
@@ -1614,7 +1645,6 @@ class Admin
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $php_format
|
* @param string $php_format
|
||||||
*
|
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function dateformatToMomentJS($php_format)
|
public function dateformatToMomentJS($php_format)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use Grav\Common\Page\Pages;
|
|||||||
use Grav\Common\Page\Collection;
|
use Grav\Common\Page\Collection;
|
||||||
use Grav\Common\Security;
|
use Grav\Common\Security;
|
||||||
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||||
use Grav\Common\User\User;
|
use Grav\Common\User\Interfaces\UserInterface;
|
||||||
use Grav\Common\Utils;
|
use Grav\Common\Utils;
|
||||||
use Grav\Framework\Psr7\Response;
|
use Grav\Framework\Psr7\Response;
|
||||||
use Grav\Framework\RequestHandler\Exception\RequestException;
|
use Grav\Framework\RequestHandler\Exception\RequestException;
|
||||||
@@ -195,29 +195,7 @@ class AdminController extends AdminBaseController
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// LOGIN & USER TASKS
|
// USER TASKS
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle login.
|
|
||||||
*
|
|
||||||
* @return bool True if the action was performed.
|
|
||||||
*/
|
|
||||||
protected function taskLogin()
|
|
||||||
{
|
|
||||||
$this->admin->authenticate($this->data, $this->post);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return bool True if the action was performed.
|
|
||||||
*/
|
|
||||||
protected function taskTwofa()
|
|
||||||
{
|
|
||||||
$this->admin->twoFa($this->data, $this->post);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle logout.
|
* Handle logout.
|
||||||
@@ -226,6 +204,10 @@ class AdminController extends AdminBaseController
|
|||||||
*/
|
*/
|
||||||
protected function taskLogout()
|
protected function taskLogout()
|
||||||
{
|
{
|
||||||
|
if (!$this->authorizeTask('logout', ['admin.login'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$this->admin->logout($this->data, $this->post);
|
$this->admin->logout($this->data, $this->post);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -241,7 +223,7 @@ class AdminController extends AdminBaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/** @var User $user */
|
/** @var UserInterface $user */
|
||||||
$user = $this->grav['user'];
|
$user = $this->grav['user'];
|
||||||
|
|
||||||
/** @var TwoFactorAuth $twoFa */
|
/** @var TwoFactorAuth $twoFa */
|
||||||
@@ -278,172 +260,6 @@ class AdminController extends AdminBaseController
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the reset password action.
|
|
||||||
*
|
|
||||||
* @return bool True if the action was performed.
|
|
||||||
*/
|
|
||||||
public function taskReset()
|
|
||||||
{
|
|
||||||
$data = $this->data;
|
|
||||||
|
|
||||||
if (isset($data['password'])) {
|
|
||||||
/** @var UserCollectionInterface $users */
|
|
||||||
$users = $this->grav['accounts'];
|
|
||||||
|
|
||||||
$username = isset($data['username']) ? strip_tags(strtolower($data['username'])) : null;
|
|
||||||
$user = $username ? $users->load($username) : null;
|
|
||||||
$password = $data['password'] ?? null;
|
|
||||||
$token = $data['token'] ?? null;
|
|
||||||
|
|
||||||
if ($user && $user->exists() && !empty($user->get('reset'))) {
|
|
||||||
list($good_token, $expire) = explode('::', $user->get('reset'));
|
|
||||||
|
|
||||||
if ($good_token === $token) {
|
|
||||||
if (time() > $expire) {
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_LINK_EXPIRED'), 'error');
|
|
||||||
$this->setRedirect('/forgot');
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$user->undef('hashed_password');
|
|
||||||
$user->undef('reset');
|
|
||||||
$user->set('password', $password);
|
|
||||||
$user->save();
|
|
||||||
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_PASSWORD_RESET'), 'info');
|
|
||||||
$this->setRedirect('/');
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
|
|
||||||
$this->setRedirect('/forgot');
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->grav['uri']->param('user');
|
|
||||||
$token = $this->grav['uri']->param('token');
|
|
||||||
|
|
||||||
if (empty($user) || empty($token)) {
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
|
|
||||||
$this->setRedirect('/forgot');
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_NEW_PASSWORD'), 'info');
|
|
||||||
|
|
||||||
$this->admin->forgot = ['username' => $user, 'token' => $token];
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the email password recovery procedure.
|
|
||||||
*
|
|
||||||
* @return bool True if the action was performed.
|
|
||||||
*/
|
|
||||||
protected function taskForgot()
|
|
||||||
{
|
|
||||||
$param_sep = $this->grav['config']->get('system.param_sep', ':');
|
|
||||||
$post = $this->post;
|
|
||||||
$data = $this->data;
|
|
||||||
$login = $this->grav['login'];
|
|
||||||
|
|
||||||
/** @var UserCollectionInterface $users */
|
|
||||||
$users = $this->grav['accounts'];
|
|
||||||
|
|
||||||
$username = isset($data['username']) ? strip_tags(strtolower($data['username'])) : '';
|
|
||||||
$user = !empty($username) ? $users->load($username) : null;
|
|
||||||
|
|
||||||
if (!isset($this->grav['Email'])) {
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error');
|
|
||||||
$this->setRedirect($post['redirect']);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$user || !$user->exists()) {
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'),
|
|
||||||
'info');
|
|
||||||
$this->setRedirect($post['redirect']);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($user->email)) {
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'),
|
|
||||||
'info');
|
|
||||||
$this->setRedirect($post['redirect']);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$count = $this->grav['config']->get('plugins.login.max_pw_resets_count', 0);
|
|
||||||
$interval =$this->grav['config']->get('plugins.login.max_pw_resets_interval', 2);
|
|
||||||
|
|
||||||
if ($login->isUserRateLimited($user, 'pw_resets', $count, $interval)) {
|
|
||||||
$this->admin->setMessage($this->admin::translate(['PLUGIN_LOGIN.FORGOT_CANNOT_RESET_IT_IS_BLOCKED', $user->email, $interval]), 'error');
|
|
||||||
$this->setRedirect($post['redirect']);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$token = md5(uniqid(mt_rand(), true));
|
|
||||||
$expire = time() + 604800; // next week
|
|
||||||
|
|
||||||
$user->set('reset', $token . '::' . $expire);
|
|
||||||
$user->save();
|
|
||||||
|
|
||||||
$author = $this->grav['config']->get('site.author.name', '');
|
|
||||||
$fullname = $user->fullname ?: $username;
|
|
||||||
$reset_link = rtrim($this->grav['uri']->rootUrl(true), '/') . '/' . trim($this->admin->base,
|
|
||||||
'/') . '/reset/task' . $param_sep . 'reset/user' . $param_sep . $username . '/token' . $param_sep . $token . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
|
|
||||||
|
|
||||||
$sitename = $this->grav['config']->get('site.title', 'Website');
|
|
||||||
$from = $this->grav['config']->get('plugins.email.from');
|
|
||||||
|
|
||||||
if (empty($from)) {
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error');
|
|
||||||
$this->setRedirect($post['redirect']);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$to = $user->email;
|
|
||||||
|
|
||||||
$subject = $this->admin::translate(['PLUGIN_ADMIN.FORGOT_EMAIL_SUBJECT', $sitename]);
|
|
||||||
$content = $this->admin::translate([
|
|
||||||
'PLUGIN_ADMIN.FORGOT_EMAIL_BODY',
|
|
||||||
$fullname,
|
|
||||||
$reset_link,
|
|
||||||
$author,
|
|
||||||
$sitename
|
|
||||||
]);
|
|
||||||
|
|
||||||
$body = $this->grav['twig']->processTemplate('email/base.html.twig', ['content' => $content]);
|
|
||||||
|
|
||||||
$message = $this->grav['Email']->message($subject, $body, 'text/html')->setFrom($from)->setTo($to);
|
|
||||||
|
|
||||||
$sent = $this->grav['Email']->send($message);
|
|
||||||
|
|
||||||
if ($sent < 1) {
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_FAILED_TO_EMAIL'), 'error');
|
|
||||||
} else {
|
|
||||||
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'),
|
|
||||||
'info');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setRedirect('/');
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save user account.
|
* Save user account.
|
||||||
*
|
*
|
||||||
|
|||||||
182
classes/plugin/AdminForm.php
Normal file
182
classes/plugin/AdminForm.php
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package Grav\Plugin\Admin
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||||
|
* @license MIT License; see LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Grav\Plugin\Admin;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Exception;
|
||||||
|
use Grav\Common\Data\Blueprint;
|
||||||
|
use Grav\Common\Data\Data;
|
||||||
|
use Grav\Framework\Form\Interfaces\FormFlashInterface;
|
||||||
|
use Grav\Framework\Form\Interfaces\FormInterface;
|
||||||
|
use Grav\Framework\Form\Traits\FormTrait;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AdminForm
|
||||||
|
* @package Grav\Plugin\Admin
|
||||||
|
*/
|
||||||
|
class AdminForm implements FormInterface, JsonSerializable
|
||||||
|
{
|
||||||
|
use FormTrait;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $nonce_name;
|
||||||
|
/** @var string */
|
||||||
|
protected $nonce_action;
|
||||||
|
/** @var callable */
|
||||||
|
protected $submitMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AdminForm constructor.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param array $options
|
||||||
|
*/
|
||||||
|
public function __construct(string $name, array $options)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->nonce_name = $options['nonce_name'] ?? 'admin-nonce';
|
||||||
|
$this->nonce_action = $options['nonce_action'] ?? 'admin-form';
|
||||||
|
|
||||||
|
$this->setId($options['id'] ?? $this->getName());
|
||||||
|
$this->setUniqueId($options['unique_id'] ?? $this->getName());
|
||||||
|
$this->setBlueprint($options['blueprint']);
|
||||||
|
$this->setSubmitMethod($options['submit_method'] ?? null);
|
||||||
|
$this->setFlashLookupFolder('tmp://admin/forms/[SESSIONID]');
|
||||||
|
|
||||||
|
if (!empty($options['reset'])) {
|
||||||
|
$this->getFlash()->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function initialize(): AdminForm
|
||||||
|
{
|
||||||
|
$this->messages = [];
|
||||||
|
$this->submitted = false;
|
||||||
|
$this->unsetFlash();
|
||||||
|
|
||||||
|
/** @var FormFlashInterface $flash */
|
||||||
|
$flash = $this->getFlash();
|
||||||
|
if ($flash->exists()) {
|
||||||
|
$data = $flash->getData();
|
||||||
|
if (null !== $data) {
|
||||||
|
$data = new Data($data, $this->getBlueprint());
|
||||||
|
$data->setKeepEmptyValues(true);
|
||||||
|
$data->setMissingValuesAsNull(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data = $data;
|
||||||
|
$this->files = $flash->getFilesByFields(false);
|
||||||
|
} else {
|
||||||
|
$this->data = new Data([], $this->getBlueprint());
|
||||||
|
$this->files = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getNonceName(): string
|
||||||
|
{
|
||||||
|
return $this->nonce_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getNonceAction(): string
|
||||||
|
{
|
||||||
|
return $this->nonce_action;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getScope(): string
|
||||||
|
{
|
||||||
|
return 'data.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Blueprint $blueprint
|
||||||
|
*/
|
||||||
|
public function setBlueprint(Blueprint $blueprint): void
|
||||||
|
{
|
||||||
|
if (null === $blueprint) {
|
||||||
|
throw new InvalidArgumentException('Blueprint is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->blueprint = $blueprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $field
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function setData(string $field, $value): void
|
||||||
|
{
|
||||||
|
$this->getData()->set($field, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Blueprint
|
||||||
|
*/
|
||||||
|
public function getBlueprint(): Blueprint
|
||||||
|
{
|
||||||
|
return $this->blueprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable|null $submitMethod
|
||||||
|
*/
|
||||||
|
public function setSubmitMethod(?callable $submitMethod): void
|
||||||
|
{
|
||||||
|
if (null === $submitMethod) {
|
||||||
|
throw new InvalidArgumentException('Submit method is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->submitMethod = $submitMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $data
|
||||||
|
* @param array $files
|
||||||
|
* @return void
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected function doSubmit(array $data, array $files): void
|
||||||
|
{
|
||||||
|
$method = $this->submitMethod;
|
||||||
|
$method($data, $files);
|
||||||
|
|
||||||
|
$this->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter validated data.
|
||||||
|
*
|
||||||
|
* @param ArrayAccess|Data|null $data
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function filterData($data = null): void
|
||||||
|
{
|
||||||
|
if ($data instanceof Data) {
|
||||||
|
$data->filter(true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
classes/plugin/AdminFormFactory.php
Normal file
44
classes/plugin/AdminFormFactory.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Grav\Plugin\Admin;
|
||||||
|
|
||||||
|
use Grav\Common\Grav;
|
||||||
|
use Grav\Common\Page\Interfaces\PageInterface;
|
||||||
|
use Grav\Common\Page\Page;
|
||||||
|
use Grav\Framework\Form\Interfaces\FormFactoryInterface;
|
||||||
|
use Grav\Framework\Form\Interfaces\FormInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class FlexFormFactory
|
||||||
|
* @package Grav\Plugin\FlexObjects
|
||||||
|
*/
|
||||||
|
class AdminFormFactory implements FormFactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Page $page
|
||||||
|
* @param string $name
|
||||||
|
* @param array $form
|
||||||
|
* @return FormInterface|null
|
||||||
|
*/
|
||||||
|
public function createPageForm(Page $page, string $name, array $form): ?FormInterface
|
||||||
|
{
|
||||||
|
return $this->createFormForPage($page, $name, $form);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PageInterface $page
|
||||||
|
* @param string $name
|
||||||
|
* @param array $form
|
||||||
|
* @return FormInterface|null
|
||||||
|
*/
|
||||||
|
public function createFormForPage(PageInterface $page, string $name, array $form): ?FormInterface
|
||||||
|
{
|
||||||
|
/** @var Admin|null $admin */
|
||||||
|
$admin = Grav::instance()['admin'] ?? null;
|
||||||
|
$object = $admin->form ?? null;
|
||||||
|
|
||||||
|
return $object && $object->getName() === $name ? $object : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
359
classes/plugin/Controllers/AdminController.php
Normal file
359
classes/plugin/Controllers/AdminController.php
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package Grav\Plugin\Admin
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||||
|
* @license MIT License; see LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Grav\Plugin\Admin\Controllers;
|
||||||
|
|
||||||
|
use Grav\Common\Config\Config;
|
||||||
|
use Grav\Common\Data\Blueprint;
|
||||||
|
use Grav\Common\Grav;
|
||||||
|
use Grav\Common\Language\Language;
|
||||||
|
use Grav\Common\Page\Interfaces\PageInterface;
|
||||||
|
use Grav\Common\Page\Page;
|
||||||
|
use Grav\Common\Page\Pages;
|
||||||
|
use Grav\Common\Uri;
|
||||||
|
use Grav\Common\User\Interfaces\UserInterface;
|
||||||
|
use Grav\Common\Utils;
|
||||||
|
use Grav\Framework\Controller\Traits\ControllerResponseTrait;
|
||||||
|
use Grav\Framework\RequestHandler\Exception\PageExpiredException;
|
||||||
|
use Grav\Framework\Session\SessionInterface;
|
||||||
|
use Grav\Plugin\Admin\Admin;
|
||||||
|
use Grav\Plugin\Admin\AdminForm;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use RocketTheme\Toolbox\Session\Message;
|
||||||
|
|
||||||
|
abstract class AdminController
|
||||||
|
{
|
||||||
|
use ControllerResponseTrait {
|
||||||
|
createRedirectResponse as traitCreateRedirectResponse;
|
||||||
|
getErrorJson as traitGetErrorJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $nonce_action = 'admin-form';
|
||||||
|
/** @var string */
|
||||||
|
protected $nonce_name = 'admin-nonce';
|
||||||
|
/** @var Grav */
|
||||||
|
protected $grav;
|
||||||
|
/** @var PageInterface */
|
||||||
|
protected $page;
|
||||||
|
/** @var AdminForm|null */
|
||||||
|
protected $form;
|
||||||
|
|
||||||
|
public function __construct(Grav $grav)
|
||||||
|
{
|
||||||
|
$this->grav = $grav;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return PageInterface|null
|
||||||
|
*/
|
||||||
|
public function getPage(): ?PageInterface
|
||||||
|
{
|
||||||
|
return $this->page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get currently active form.
|
||||||
|
*
|
||||||
|
* @return AdminForm|null
|
||||||
|
*/
|
||||||
|
public function getActiveForm(): ?AdminForm
|
||||||
|
{
|
||||||
|
if (null === $this->form) {
|
||||||
|
$post = $this->getPost();
|
||||||
|
|
||||||
|
$active = $post['__form-name__'] ?? null;
|
||||||
|
|
||||||
|
$this->form = $active ? $this->getForm($active) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->form;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a form.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param array $options
|
||||||
|
* @return AdminForm|null
|
||||||
|
*/
|
||||||
|
public function getForm(string $name, array $options = []): ?AdminForm
|
||||||
|
{
|
||||||
|
$post = $this->getPost();
|
||||||
|
$page = $this->getPage();
|
||||||
|
$forms = $page ? $page->forms() : [];
|
||||||
|
$blueprint = $forms[$name] ?? null;
|
||||||
|
if (null === $blueprint) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$active = $post['__form-name__'] ?? null;
|
||||||
|
$unique_id = $active && $active === $name ? ($post['__unique_form_id__'] ?? null) : null;
|
||||||
|
|
||||||
|
$options += [
|
||||||
|
'unique_id' => $unique_id,
|
||||||
|
'blueprint' => new Blueprint(null, ['form' => $blueprint]),
|
||||||
|
'submit_method' => $this->getFormSubmitMethod($name),
|
||||||
|
'nonce_name' => $this->nonce_name,
|
||||||
|
'nonce_action' => $this->nonce_action,
|
||||||
|
];
|
||||||
|
|
||||||
|
return new AdminForm($name, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function getFormSubmitMethod(string $name): callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $route
|
||||||
|
* @param string|null $lang
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAdminUrl(string $route, string $lang = null): string
|
||||||
|
{
|
||||||
|
/** @var Pages $pages */
|
||||||
|
$pages = $this->grav['pages'];
|
||||||
|
$admin = $this->getAdmin();
|
||||||
|
|
||||||
|
return $pages->baseUrl($lang) . $admin->base . trim($route, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $route
|
||||||
|
* @param string|null $lang
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAbsoluteAdminUrl(string $route, string $lang = null): string
|
||||||
|
{
|
||||||
|
/** @var Pages $pages */
|
||||||
|
$pages = $this->grav['pages'];
|
||||||
|
$admin = $this->getAdmin();
|
||||||
|
|
||||||
|
return $pages->baseUrl($lang, true) . $admin->base . trim($route, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get session.
|
||||||
|
*
|
||||||
|
* @return SessionInterface
|
||||||
|
*/
|
||||||
|
public function getSession(): SessionInterface
|
||||||
|
{
|
||||||
|
return $this->grav['session'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Admin
|
||||||
|
*/
|
||||||
|
protected function getAdmin(): Admin
|
||||||
|
{
|
||||||
|
return $this->grav['admin'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return UserInterface
|
||||||
|
*/
|
||||||
|
protected function getUser(): UserInterface
|
||||||
|
{
|
||||||
|
return $this->getAdmin()->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ServerRequestInterface
|
||||||
|
*/
|
||||||
|
public function getRequest(): ServerRequestInterface
|
||||||
|
{
|
||||||
|
return $this->getAdmin()->request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getPost(): array
|
||||||
|
{
|
||||||
|
return (array)($this->getRequest()->getParsedBody() ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a string.
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @param mixed ...$args
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function translate(string $string, ...$args): string
|
||||||
|
{
|
||||||
|
/** @var Language $language */
|
||||||
|
$language = $this->grav['language'];
|
||||||
|
|
||||||
|
array_unshift($args, $string);
|
||||||
|
|
||||||
|
return $language->translate($args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set message to be shown in the admin.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param string $type
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMessage(string $message, string $type = 'info'): AdminController
|
||||||
|
{
|
||||||
|
/** @var Message $messages */
|
||||||
|
$messages = $this->grav['messages'];
|
||||||
|
$messages->add($message, $type);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Config
|
||||||
|
*/
|
||||||
|
protected function getConfig(): Config
|
||||||
|
{
|
||||||
|
return $this->grav['config'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if request nonce is valid.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws PageExpiredException If nonce is not valid.
|
||||||
|
*/
|
||||||
|
protected function checkNonce(): void
|
||||||
|
{
|
||||||
|
$nonce = null;
|
||||||
|
|
||||||
|
$nonce_name = $this->form ? $this->form->getNonceName() : $this->nonce_name;
|
||||||
|
$nonce_action = $this->form ? $this->form->getNonceAction() : $this->nonce_action;
|
||||||
|
|
||||||
|
if (\in_array(strtoupper($this->getRequest()->getMethod()), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
|
||||||
|
$post = $this->getPost();
|
||||||
|
$nonce = $post[$nonce_name] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Uri $uri */
|
||||||
|
$uri = $this->grav['uri'];
|
||||||
|
if (!$nonce) {
|
||||||
|
$nonce = $uri->param($nonce_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$nonce) {
|
||||||
|
$nonce = $uri->query($nonce_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$nonce || !Utils::verifyNonce($nonce, $nonce_action)) {
|
||||||
|
throw new PageExpiredException($this->getRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the best matching mime type for the request.
|
||||||
|
*
|
||||||
|
* @param string[] $compare
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function getAccept(array $compare): ?string
|
||||||
|
{
|
||||||
|
$accepted = [];
|
||||||
|
foreach ($this->getRequest()->getHeader('Accept') as $accept) {
|
||||||
|
foreach (explode(',', $accept) as $item) {
|
||||||
|
if (!$item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$split = explode(';q=', $item);
|
||||||
|
$mime = array_shift($split);
|
||||||
|
$priority = array_shift($split) ?? 1.0;
|
||||||
|
|
||||||
|
$accepted[$mime] = $priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arsort($accepted);
|
||||||
|
|
||||||
|
// TODO: add support for image/* etc
|
||||||
|
$list = array_intersect($compare, array_keys($accepted));
|
||||||
|
if (!$list && (isset($accepted['*/*']) || isset($accepted['*']))) {
|
||||||
|
return reset($compare) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reset($list) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $template
|
||||||
|
* @return PageInterface
|
||||||
|
*/
|
||||||
|
protected function createPage(string $template): PageInterface
|
||||||
|
{
|
||||||
|
$page = new Page();
|
||||||
|
|
||||||
|
// Plugins may not have the correct Cache-Control header set, force no-store for the proxies.
|
||||||
|
$page->expires(0);
|
||||||
|
|
||||||
|
$filename = "plugin://admin/pages/admin/{$template}.md";
|
||||||
|
if (!file_exists($filename)) {
|
||||||
|
throw new \RuntimeException(sprintf('Creating admin page %s failed: not found', $template));
|
||||||
|
}
|
||||||
|
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage("Admin page: {$template}");
|
||||||
|
|
||||||
|
$page->init(new \SplFileInfo($filename));
|
||||||
|
$page->slug($template);
|
||||||
|
|
||||||
|
return $page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $url
|
||||||
|
* @param int|null $code
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
protected function createRedirectResponse(string $url = null, int $code = null): ResponseInterface
|
||||||
|
{
|
||||||
|
$request = $this->getRequest();
|
||||||
|
|
||||||
|
if (null === $url || '' === $url) {
|
||||||
|
$url = (string)$request->getUri();
|
||||||
|
} elseif (mb_strpos($url, '/') === 0) {
|
||||||
|
$url = $this->getAbsoluteAdminUrl($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $code) {
|
||||||
|
if (in_array($request->getMethod(), ['GET', 'HEAD'])) {
|
||||||
|
$code = 302;
|
||||||
|
} else {
|
||||||
|
$code = 303;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->traitCreateRedirectResponse($url, $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Throwable $e
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getErrorJson(\Throwable $e): array
|
||||||
|
{
|
||||||
|
$json = $this->traitGetErrorJson($e);
|
||||||
|
$code = $e->getCode();
|
||||||
|
if ($code === 401) {
|
||||||
|
$json['redirect'] = $this->getAbsoluteAdminUrl('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $json;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
630
classes/plugin/Controllers/Login/LoginController.php
Normal file
630
classes/plugin/Controllers/Login/LoginController.php
Normal file
@@ -0,0 +1,630 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package Grav\Plugin\Admin
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||||
|
* @license MIT License; see LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Grav\Plugin\Admin\Controllers\Login;
|
||||||
|
|
||||||
|
use Grav\Common\Debugger;
|
||||||
|
use Grav\Common\Page\Pages;
|
||||||
|
use Grav\Common\Uri;
|
||||||
|
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||||
|
use Grav\Common\User\Interfaces\UserInterface;
|
||||||
|
use Grav\Framework\RequestHandler\Exception\PageExpiredException;
|
||||||
|
use Grav\Framework\RequestHandler\Exception\RequestException;
|
||||||
|
use Grav\Plugin\Admin\Admin;
|
||||||
|
use Grav\Plugin\Admin\Controllers\AdminController;
|
||||||
|
use Grav\Plugin\Email\Email;
|
||||||
|
use Grav\Plugin\Login\Login;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use RobThree\Auth\TwoFactorAuthException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class LoginController
|
||||||
|
* @package Grav\Plugin\Admin\Controllers\Login
|
||||||
|
*/
|
||||||
|
class LoginController extends AdminController
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
protected $nonce_action = 'admin-login';
|
||||||
|
/** @var string */
|
||||||
|
protected $nonce_name = 'login-nonce';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function displayLogin(): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->page = $this->createPage('login');
|
||||||
|
|
||||||
|
$user = $this->getUser();
|
||||||
|
if ($this->is2FA($user)) {
|
||||||
|
$this->form = $this->getForm('login-twofa', ['reset' => true]);
|
||||||
|
} else {
|
||||||
|
$this->form = $this->getForm('login', ['reset' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function displayForgot(): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->page = $this->createPage('forgot');
|
||||||
|
$this->form = $this->getForm('admin-login-forgot', ['reset' => true]);
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the reset password action.
|
||||||
|
*
|
||||||
|
* @param string|null $username
|
||||||
|
* @param string|null $token
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function displayReset(string $username = null, string $token = null): ResponseInterface
|
||||||
|
{
|
||||||
|
if ('' === (string)$username || '' === (string)$token) {
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
|
||||||
|
|
||||||
|
return $this->createRedirectResponse('/forgot');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->page = $this->createPage('reset');
|
||||||
|
$this->form = $this->getForm('admin-login-reset', ['reset' => true]);
|
||||||
|
$this->form->setData('username', $username);
|
||||||
|
$this->form->setData('token', $token);
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.RESET_NEW_PASSWORD'));
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function displayRegister(): ResponseInterface
|
||||||
|
{
|
||||||
|
$route = $this->getRequest()->getAttribute('admin')['route'] ?? '';
|
||||||
|
if ('' !== $route) {
|
||||||
|
return $this->createRedirectResponse('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->page = $this->createPage('register');
|
||||||
|
$this->form = $this->getForm('admin-login-register');
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function displayUnauthorized(): ResponseInterface
|
||||||
|
{
|
||||||
|
$uri = (string)$this->getRequest()->getUri();
|
||||||
|
|
||||||
|
$ext = pathinfo($uri, PATHINFO_EXTENSION);
|
||||||
|
$accept = $this->getAccept(['application/json', 'text/html']);
|
||||||
|
if ($ext === 'json' || $accept === 'application/json') {
|
||||||
|
return $this->createErrorResponse(new RequestException($this->getRequest(), $this->translate('PLUGIN_ADMIN.LOGGED_OUT'), 401));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.LOGGED_OUT'), 'warning');
|
||||||
|
|
||||||
|
return $this->createRedirectResponse('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle login.
|
||||||
|
*
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function taskLogin(): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->page = $this->createPage('login');
|
||||||
|
$this->form = $this->getActiveForm() ?? $this->getForm('login');
|
||||||
|
try {
|
||||||
|
$this->checkNonce();
|
||||||
|
} catch (PageExpiredException $e) {
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$post = $this->getPost();
|
||||||
|
$credentials = $post['data'] ?? [];
|
||||||
|
$login = $this->getLogin();
|
||||||
|
$config = $this->getConfig();
|
||||||
|
|
||||||
|
$userKey = (string)($credentials['username'] ?? '');
|
||||||
|
// Pseudonymization of the IP.
|
||||||
|
$ipKey = sha1(Uri::ip() . $config->get('security.salt'));
|
||||||
|
|
||||||
|
$rateLimiter = $login->getRateLimiter('login_attempts');
|
||||||
|
|
||||||
|
// Check if the current IP has been used in failed login attempts.
|
||||||
|
$attempts = count($rateLimiter->getAttempts($ipKey, 'ip'));
|
||||||
|
|
||||||
|
$rateLimiter->registerRateLimitedAction($ipKey, 'ip')->registerRateLimitedAction($userKey);
|
||||||
|
|
||||||
|
// Check rate limit for both IP and user, but allow each IP a single try even if user is already rate limited.
|
||||||
|
if ($rateLimiter->isRateLimited($ipKey, 'ip') || ($attempts && $rateLimiter->isRateLimited($userKey))) {
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage('Admin login: rate limit, redirecting', $credentials);
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_LOGIN.TOO_MANY_LOGIN_ATTEMPTS', $rateLimiter->getInterval()), 'error');
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
/** @var Pages $pages */
|
||||||
|
$pages = $this->grav['pages'];
|
||||||
|
|
||||||
|
// Redirect to the home page of the site.
|
||||||
|
return $this->createRedirectResponse($pages->homeUrl(null, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage('Admin login', $credentials);
|
||||||
|
|
||||||
|
// Fire Login process.
|
||||||
|
$event = $login->login(
|
||||||
|
$credentials,
|
||||||
|
['admin' => true, 'twofa' => $config->get('plugins.admin.twofa_enabled', false)],
|
||||||
|
['authorize' => 'admin.login', 'return_event' => true]
|
||||||
|
);
|
||||||
|
$user = $event->getUser();
|
||||||
|
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage('Admin login: user', $user);
|
||||||
|
|
||||||
|
$redirect = (string)$this->getRequest()->getUri();
|
||||||
|
|
||||||
|
if ($user->authenticated) {
|
||||||
|
$rateLimiter->resetRateLimit($ipKey, 'ip')->resetRateLimit($userKey);
|
||||||
|
if ($user->authorized) {
|
||||||
|
$event->defMessage('PLUGIN_ADMIN.LOGIN_LOGGED_IN', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
$event->defRedirect($redirect);
|
||||||
|
} elseif ($user->authorized) {
|
||||||
|
$event->defMessage('PLUGIN_LOGIN.ACCESS_DENIED', 'error');
|
||||||
|
} else {
|
||||||
|
$event->defMessage('PLUGIN_LOGIN.LOGIN_FAILED', 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
$event->defRedirect($redirect);
|
||||||
|
|
||||||
|
$message = $event->getMessage();
|
||||||
|
if ($message) {
|
||||||
|
$this->setMessage($this->translate($message), $event->getMessageType());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
return $this->createRedirectResponse($event->getRedirect());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle logout when user isn't fully logged in.
|
||||||
|
*
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function taskLogout(): ResponseInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->checkNonce();
|
||||||
|
} catch (PageExpiredException $e) {
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$login = $this->getLogin();
|
||||||
|
$event = $login->logout(['admin' => true], ['return_event' => true]);
|
||||||
|
|
||||||
|
$event->defMessage('PLUGIN_ADMIN.LOGGED_OUT', 'info');
|
||||||
|
$message = $event->getMessage();
|
||||||
|
if ($message) {
|
||||||
|
$this->getSession()->setFlashCookieObject(Admin::TMP_COOKIE_NAME, ['message' => $this->translate($message), 'status' => $event->getMessageType()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->createRedirectResponse('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle 2FA verification.
|
||||||
|
*
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function taskTwofa(): ResponseInterface
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
if (!$this->is2FA($user)) {
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage('Admin login: user is not logged in or does not have 2FA enabled', $user);
|
||||||
|
|
||||||
|
// Task is visible only for users who have enabled 2FA.
|
||||||
|
return $this->createRedirectResponse('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->page = $this->createPage('login');
|
||||||
|
$this->form = $this->getForm('admin-login-twofa');
|
||||||
|
try {
|
||||||
|
$this->checkNonce();
|
||||||
|
} catch (PageExpiredException $e) {
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$post = $this->getPost();
|
||||||
|
$data = $post['data'] ?? [];
|
||||||
|
|
||||||
|
$login = $this->getLogin();
|
||||||
|
try {
|
||||||
|
$twoFa = $login->twoFactorAuth();
|
||||||
|
} catch (TwoFactorAuthException $e) {
|
||||||
|
/** @var Debugger $debugger */
|
||||||
|
$debugger = $this->grav['debugger'];
|
||||||
|
$debugger->addException($e);
|
||||||
|
|
||||||
|
$twoFa = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = $data['2fa_code'] ?? null;
|
||||||
|
$secret = $user->twofa_secret ?? null;
|
||||||
|
$redirect = (string)$this->getRequest()->getUri();
|
||||||
|
|
||||||
|
if (null === $twoFa || !$user->authenticated || !$code || !$secret || !$twoFa->verifyCode($secret, $code)) {
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage('Admin login: 2FA check failed, log out!');
|
||||||
|
|
||||||
|
// Failed 2FA auth, logout and redirect to the current page.
|
||||||
|
$login->logout(['admin' => true]);
|
||||||
|
|
||||||
|
$this->grav['session']->setFlashCookieObject(Admin::TMP_COOKIE_NAME, ['message' => $this->translate('PLUGIN_ADMIN.2FA_FAILED'), 'status' => 'error']);
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
return $this->createRedirectResponse($redirect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Successful 2FA, authorize user and redirect.
|
||||||
|
$user->authorized = true;
|
||||||
|
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage('Admin login: 2FA check succeeded, authorize user and redirect');
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'));
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
return $this->createRedirectResponse($redirect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the reset password action.
|
||||||
|
*
|
||||||
|
* @param string|null $username
|
||||||
|
* @param string|null $token
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function taskReset(string $username = null, string $token = null): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->page = $this->createPage('reset');
|
||||||
|
$this->form = $this->getForm('admin-login-reset');
|
||||||
|
try {
|
||||||
|
$this->checkNonce();
|
||||||
|
} catch (PageExpiredException $e) {
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$post = $this->getPost();
|
||||||
|
$data = $post['data'] ?? [];
|
||||||
|
$users = $this->getAccounts();
|
||||||
|
|
||||||
|
$username = $username ?? $data['username'] ?? null;
|
||||||
|
$token = $token ?? $data['token'] ?? null;
|
||||||
|
|
||||||
|
$user = $username ? $users->load($username) : null;
|
||||||
|
$password = $data['password'];
|
||||||
|
|
||||||
|
if ($user && $user->exists() && !empty($user->get('reset'))) {
|
||||||
|
[$good_token, $expire] = explode('::', $user->get('reset'));
|
||||||
|
|
||||||
|
if ($good_token === $token) {
|
||||||
|
if (time() > $expire) {
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.RESET_LINK_EXPIRED'), 'error');
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
return $this->createRedirectResponse('/forgot');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set new password.
|
||||||
|
$login = $this->getLogin();
|
||||||
|
try {
|
||||||
|
$login->validateField('password1', $password);
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
$this->setMessage($this->translate($e->getMessage()), 'error');
|
||||||
|
|
||||||
|
return $this->createRedirectResponse("/reset/u/{$username}/{$token}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->undef('hashed_password');
|
||||||
|
$user->undef('reset');
|
||||||
|
$user->update(['password' => $password]);
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.RESET_PASSWORD_RESET'));
|
||||||
|
|
||||||
|
return $this->createRedirectResponse('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage(sprintf('Failed to reset password: Token %s is not good', $token));
|
||||||
|
} else {
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage(sprintf('Failed to reset password: User %s does not exist or has not requested reset', $username));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
return $this->createRedirectResponse('/forgot');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the email password recovery procedure.
|
||||||
|
*
|
||||||
|
* Sends email to the user.
|
||||||
|
*
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function taskForgot(): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->page = $this->createPage('forgot');
|
||||||
|
$this->form = $this->getForm('admin-login-forgot');
|
||||||
|
try {
|
||||||
|
$this->checkNonce();
|
||||||
|
} catch (PageExpiredException $e) {
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$post = $this->getPost();
|
||||||
|
$data = $post['data'] ?? [];
|
||||||
|
$login = $this->getLogin();
|
||||||
|
$users = $this->getAccounts();
|
||||||
|
$email = $this->getEmail();
|
||||||
|
|
||||||
|
$current = (string)$this->getRequest()->getUri();
|
||||||
|
|
||||||
|
$search = isset($data['username']) ? strip_tags($data['username']) : '';
|
||||||
|
$user = !empty($search) ? $users->load($search) : null;
|
||||||
|
$username = $user->username ?? null;
|
||||||
|
$to = $user->email ?? null;
|
||||||
|
|
||||||
|
// Only send email to users which are enabled and have an email address.
|
||||||
|
if (null === $user || $user->state !== 'enabled' || !$to) {
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage(sprintf('Failed sending email: %s <%s> was not found or is blocked', $search, $to ?? 'N/A'));
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'));
|
||||||
|
|
||||||
|
return $this->createRedirectResponse($current);
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $this->getConfig();
|
||||||
|
|
||||||
|
// Check rate limit for the user.
|
||||||
|
$rateLimiter = $login->getRateLimiter('pw_resets');
|
||||||
|
$rateLimiter->registerRateLimitedAction($username);
|
||||||
|
if ($rateLimiter->isRateLimited($username)) {
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage(sprintf('Failed sending email: user %s <%s> is rate limited', $search, $to));
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
$interval = $config->get('plugins.login.max_pw_resets_interval', 2);
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_LOGIN.FORGOT_CANNOT_RESET_IT_IS_BLOCKED', $to, $interval), 'error');
|
||||||
|
|
||||||
|
return $this->createRedirectResponse($current);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = md5(uniqid(mt_rand(), true));
|
||||||
|
$expire = time() + 3600; // 1 hour
|
||||||
|
|
||||||
|
$user->set('reset', $token . '::' . $expire);
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
$from = $config->get('plugins.email.from');
|
||||||
|
if (empty($from)) {
|
||||||
|
Admin::DEBUG && Admin::addDebugMessage('Failed sending email: from address is not configured in email plugin');
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error');
|
||||||
|
|
||||||
|
return $this->createRedirectResponse($current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not trust username from the request.
|
||||||
|
$fullname = $user->fullname ?: $username;
|
||||||
|
$author = $config->get('site.author.name', '');
|
||||||
|
$sitename = $config->get('site.title', 'Website');
|
||||||
|
$reset_link = $this->getAbsoluteAdminUrl("/reset/u/{$username}/{$token}");
|
||||||
|
|
||||||
|
// For testing only!
|
||||||
|
//Admin::DEBUG && Admin::addDebugMessage(sprintf('Reset link: %s', $reset_link));
|
||||||
|
|
||||||
|
$subject = $this->translate('PLUGIN_ADMIN.FORGOT_EMAIL_SUBJECT', $sitename);
|
||||||
|
$content = $this->translate('PLUGIN_ADMIN.FORGOT_EMAIL_BODY', $fullname, $reset_link, $author, $sitename);
|
||||||
|
|
||||||
|
$this->grav['twig']->init();
|
||||||
|
$body = $this->grav['twig']->processTemplate('email/base.html.twig', ['content' => $content]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$message = $email->message($subject, $body, 'text/html')->setFrom($from)->setTo($to);
|
||||||
|
$sent = $email->send($message);
|
||||||
|
if ($sent < 1) {
|
||||||
|
throw new \RuntimeException('Sending email failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// For testing only!
|
||||||
|
//Admin::DEBUG && Admin::addDebugMessage(sprintf('Email sent to %s', $to), $body);
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'));
|
||||||
|
} catch (\RuntimeException|\Swift_SwiftException $e) {
|
||||||
|
$rateLimiter->resetRateLimit($username);
|
||||||
|
|
||||||
|
/** @var Debugger $debugger */
|
||||||
|
$debugger = $this->grav['debugger'];
|
||||||
|
$debugger->addException($e);
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.FORGOT_FAILED_TO_EMAIL'), 'error');
|
||||||
|
|
||||||
|
return $this->createRedirectResponse('/forgot');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->form->reset();
|
||||||
|
|
||||||
|
return $this->createRedirectResponse('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function taskRegister(): ResponseInterface
|
||||||
|
{
|
||||||
|
$this->page = $this->createPage('register');
|
||||||
|
$this->form = $form = $this->getForm('admin-login-register');
|
||||||
|
try {
|
||||||
|
$this->checkNonce();
|
||||||
|
} catch (PageExpiredException $e) {
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Calls $this->doRegistration() to perform the user registration.
|
||||||
|
$form->handleRequest($this->getRequest());
|
||||||
|
$error = $form->getError();
|
||||||
|
$errors = $form->getErrors();
|
||||||
|
if ($error || $errors) {
|
||||||
|
foreach ($errors as $field => $list) {
|
||||||
|
foreach ((array)$list as $message) {
|
||||||
|
if ($message !== $error) {
|
||||||
|
$this->setMessage($message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->createDisplayResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setMessage($this->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'));
|
||||||
|
|
||||||
|
return $this->createRedirectResponse('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param UserInterface $user
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function is2FA(UserInterface $user): bool
|
||||||
|
{
|
||||||
|
return $user && $user->authenticated && !$user->authorized && $user->get('twofa_enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name
|
||||||
|
* @return callable
|
||||||
|
*/
|
||||||
|
protected function getFormSubmitMethod(string $name): callable
|
||||||
|
{
|
||||||
|
switch ($name) {
|
||||||
|
case 'login':
|
||||||
|
case 'login-twofa':
|
||||||
|
case 'admin-login-forgot':
|
||||||
|
case 'admin-login-reset':
|
||||||
|
return static function(array $data, array $files) {};
|
||||||
|
case 'admin-login-register':
|
||||||
|
return function(array $data, array $files) {
|
||||||
|
$this->doRegistration($data, $files);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException('Unknown form');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by registration form when calling handleRequest().
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @param array $files
|
||||||
|
*/
|
||||||
|
private function doRegistration(array $data, array $files): void
|
||||||
|
{
|
||||||
|
if (Admin::doAnyUsersExist()) {
|
||||||
|
throw new \RuntimeException('A user account already exists, please create an admin account manually.', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$login = $this->getLogin();
|
||||||
|
if (!$login) {
|
||||||
|
throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED', 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['title'] = $data['title'] ?? 'Administrator';
|
||||||
|
|
||||||
|
// Do not allow form to set the following fields (make super user):
|
||||||
|
$data['state'] = 'enabled';
|
||||||
|
$data['access'] = ['admin' => ['login' => true, 'super' => true], 'site' => ['login' => true]];
|
||||||
|
unset($data['groups']);
|
||||||
|
|
||||||
|
// Create user.
|
||||||
|
$user = $login->register($data, $files);
|
||||||
|
|
||||||
|
// Log in the new super admin user.
|
||||||
|
unset($this->grav['user']);
|
||||||
|
$this->grav['user'] = $user;
|
||||||
|
$this->grav['session']->user = $user;
|
||||||
|
$user->authenticated = true;
|
||||||
|
$user->authorized = $user->authorize('admin.login') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Login
|
||||||
|
*/
|
||||||
|
private function getLogin(): Login
|
||||||
|
{
|
||||||
|
return $this->grav['login'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Email
|
||||||
|
*/
|
||||||
|
private function getEmail(): Email
|
||||||
|
{
|
||||||
|
return $this->grav['Email'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return UserCollectionInterface
|
||||||
|
*/
|
||||||
|
private function getAccounts(): UserCollectionInterface
|
||||||
|
{
|
||||||
|
return $this->grav['accounts'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
namespace Grav\Plugin\Admin;
|
namespace Grav\Plugin\Admin;
|
||||||
|
|
||||||
|
use Grav\Common\Grav;
|
||||||
use Grav\Common\Processors\ProcessorBase;
|
use Grav\Common\Processors\ProcessorBase;
|
||||||
use Grav\Framework\Route\Route;
|
use Grav\Framework\Route\Route;
|
||||||
|
use Grav\Plugin\Admin\Routers\LoginRouter;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
@@ -13,6 +15,16 @@ class Router extends ProcessorBase
|
|||||||
public $id = 'admin_router';
|
public $id = 'admin_router';
|
||||||
public $title = 'Admin Panel';
|
public $title = 'Admin Panel';
|
||||||
|
|
||||||
|
/** @var Admin */
|
||||||
|
protected $admin;
|
||||||
|
|
||||||
|
public function __construct(Grav $container, Admin $admin)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
|
||||||
|
$this->admin = $admin;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle routing to the dashboard, group and build objects.
|
* Handle routing to the dashboard, group and build objects.
|
||||||
*
|
*
|
||||||
@@ -25,31 +37,32 @@ class Router extends ProcessorBase
|
|||||||
$this->startTimer();
|
$this->startTimer();
|
||||||
|
|
||||||
$context = $request->getAttributes();
|
$context = $request->getAttributes();
|
||||||
|
$query = $request->getQueryParams();
|
||||||
|
|
||||||
/** @var Route $route */
|
/** @var Route $route */
|
||||||
$route = $context['route'];
|
$route = $context['route'];
|
||||||
$normalized = mb_strtolower(trim($route->getRoute(), '/'));
|
$normalized = mb_strtolower(trim($route->getRoute(), '/'));
|
||||||
$parts = explode('/', $normalized);
|
$parts = explode('/', $normalized);
|
||||||
array_shift($parts);
|
array_shift($parts); // Admin path
|
||||||
$key = array_shift($parts);
|
$routeStr = implode('/', $parts);
|
||||||
|
$view = array_shift($parts);
|
||||||
$path = implode('/', $parts);
|
$path = implode('/', $parts);
|
||||||
|
$task = $this->container['task'] ?? $query['task'] ?? null;
|
||||||
|
$action = $this->container['action'] ?? $query['action'] ?? null;
|
||||||
|
|
||||||
$request = $request->withAttribute('admin', ['path' => $path, 'parts' => $parts]);
|
$params = ['view' => $view, 'route' => $routeStr, 'path' => $path, 'parts' => $parts, 'task' => $task, 'action' => $action];
|
||||||
|
$request = $request->withAttribute('admin', $params);
|
||||||
|
|
||||||
$response = null;
|
// Run login controller if user isn't fully logged in or asks to logout.
|
||||||
/*
|
$user = $this->admin->user;
|
||||||
if ($key === '__TODO__') {
|
if (!$user->authorized || !$user->authorize('admin.login')) {
|
||||||
|
$params = (new LoginRouter())->matchServerRequest($request);
|
||||||
$controller = new TodoController();
|
$request = $request->withAttribute('admin', $params + $request->getAttribute('admin'));
|
||||||
|
|
||||||
$response = $controller->handle($request);
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
if (!$response) {
|
$this->admin->request = $request;
|
||||||
// Fallback to the old admin behavior.
|
|
||||||
$response = $handler->handle($request);
|
$response = $handler->handle($request);
|
||||||
}
|
|
||||||
|
|
||||||
$this->stopTimer();
|
$this->stopTimer();
|
||||||
|
|
||||||
|
|||||||
93
classes/plugin/Routers/LoginRouter.php
Normal file
93
classes/plugin/Routers/LoginRouter.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package Grav\Plugin\Admin
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||||
|
* @license MIT License; see LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Grav\Plugin\Admin\Routers;
|
||||||
|
|
||||||
|
use Grav\Plugin\Admin\Admin;
|
||||||
|
use Grav\Plugin\Admin\Controllers\Login\LoginController;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
class LoginRouter
|
||||||
|
{
|
||||||
|
/** @var string[] */
|
||||||
|
private $taskTemplates = [
|
||||||
|
'logout' => 'login',
|
||||||
|
'twofa' => 'login',
|
||||||
|
'forgot' => 'forgot',
|
||||||
|
'reset' => 'reset'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function matchServerRequest(ServerRequestInterface $request): array
|
||||||
|
{
|
||||||
|
$adminInfo = $request->getAttribute('admin');
|
||||||
|
$task = $adminInfo['task'];
|
||||||
|
$class = LoginController::class;
|
||||||
|
|
||||||
|
// Special controller for the new sites.
|
||||||
|
if (!Admin::doAnyUsersExist()) {
|
||||||
|
$method = $task === 'register' ? 'taskRegister' : 'displayRegister';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'controller' => [
|
||||||
|
'class' => $class,
|
||||||
|
'method' => $method,
|
||||||
|
'params' => []
|
||||||
|
],
|
||||||
|
'template' => 'register',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpMethod = $request->getMethod();
|
||||||
|
$template = $this->taskTemplates[$task] ?? $adminInfo['view'];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
switch ($template) {
|
||||||
|
case 'forgot':
|
||||||
|
break;
|
||||||
|
case 'reset':
|
||||||
|
$path = $adminInfo['path'];
|
||||||
|
if (str_starts_with($path, 'u/')) {
|
||||||
|
// Path is 'u/username/token'
|
||||||
|
$parts = explode('/', $path, 4);
|
||||||
|
$user = $parts[1] ?? null;
|
||||||
|
$token = $parts[2] ?? null;
|
||||||
|
} else {
|
||||||
|
// Old path used to be 'task:reset/user:username/token:token'
|
||||||
|
if ($httpMethod === 'GET' || $httpMethod === 'HEAD') {
|
||||||
|
$task = null;
|
||||||
|
}
|
||||||
|
$route = $request->getAttribute('route');
|
||||||
|
$user = $route->getGravParam('user');
|
||||||
|
$token = $route->getGravParam('token');
|
||||||
|
}
|
||||||
|
$params = [$user, $token];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$template = 'login';
|
||||||
|
}
|
||||||
|
|
||||||
|
$method = ($task ? 'task' : 'display') . ucfirst($task ?? $template);
|
||||||
|
if (!method_exists($class, $method)) {
|
||||||
|
$method = 'displayUnauthorized';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'controller' => [
|
||||||
|
'class' => $class,
|
||||||
|
'method' => $method,
|
||||||
|
'params' => $params
|
||||||
|
],
|
||||||
|
'template' => $template,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
---
|
---
|
||||||
title: Forgot password
|
title: Forgot password
|
||||||
expires: 0
|
expires: 0
|
||||||
|
access:
|
||||||
|
admin.login: false
|
||||||
|
|
||||||
|
forms:
|
||||||
|
admin-login-forgot:
|
||||||
|
type: admin
|
||||||
|
method: post
|
||||||
|
|
||||||
form:
|
|
||||||
fields:
|
fields:
|
||||||
- name: username
|
username:
|
||||||
type: text
|
type: text
|
||||||
placeholder: PLUGIN_ADMIN.USERNAME
|
placeholder: PLUGIN_ADMIN.USERNAME
|
||||||
autofocus: true
|
autofocus: true
|
||||||
validate:
|
validate:
|
||||||
required: true
|
required: true
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: Admin Login
|
title: Admin Login
|
||||||
expires: 0
|
expires: 0
|
||||||
|
access:
|
||||||
|
admin.login: false
|
||||||
|
|
||||||
forms:
|
forms:
|
||||||
login:
|
login:
|
||||||
action:
|
type: admin
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
@@ -22,7 +24,7 @@ forms:
|
|||||||
required: true
|
required: true
|
||||||
|
|
||||||
login-twofa:
|
login-twofa:
|
||||||
action:
|
type: admin
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
|
|||||||
@@ -1,58 +1,63 @@
|
|||||||
---
|
---
|
||||||
|
title: Register Admin User
|
||||||
expires: 0
|
expires: 0
|
||||||
|
access:
|
||||||
|
admin.login: false
|
||||||
|
|
||||||
form:
|
forms:
|
||||||
fields:
|
admin-login-register:
|
||||||
- name: username
|
type: admin
|
||||||
type: text
|
method: post
|
||||||
label: PLUGIN_ADMIN.USERNAME
|
|
||||||
autofocus: true
|
|
||||||
placeholder: PLUGIN_ADMIN.USERNAME_PLACEHOLDER
|
|
||||||
validate:
|
|
||||||
required: true
|
|
||||||
message: PLUGIN_LOGIN.USERNAME_NOT_VALID
|
|
||||||
config-pattern@: system.username_regex
|
|
||||||
|
|
||||||
- name: email
|
fields:
|
||||||
type: email
|
username:
|
||||||
label: PLUGIN_ADMIN.EMAIL
|
type: text
|
||||||
placeholder: "valid email address"
|
label: PLUGIN_ADMIN.USERNAME
|
||||||
validate:
|
autofocus: true
|
||||||
|
placeholder: PLUGIN_ADMIN.USERNAME_PLACEHOLDER
|
||||||
|
validate:
|
||||||
|
required: true
|
||||||
|
message: PLUGIN_LOGIN.USERNAME_NOT_VALID
|
||||||
|
config-pattern@: system.username_regex
|
||||||
|
|
||||||
|
email:
|
||||||
type: email
|
type: email
|
||||||
message: PLUGIN_ADMIN.EMAIL_VALIDATION_MESSAGE
|
label: PLUGIN_ADMIN.EMAIL
|
||||||
required: true
|
placeholder: "valid email address"
|
||||||
|
validate:
|
||||||
|
type: email
|
||||||
|
message: PLUGIN_ADMIN.EMAIL_VALIDATION_MESSAGE
|
||||||
|
required: true
|
||||||
|
|
||||||
- name: password1
|
password1:
|
||||||
type: password
|
type: password
|
||||||
label: PLUGIN_ADMIN.PASSWORD
|
label: PLUGIN_ADMIN.PASSWORD
|
||||||
placeholder: PLUGIN_ADMIN.PWD_PLACEHOLDER
|
placeholder: PLUGIN_ADMIN.PWD_PLACEHOLDER
|
||||||
validate:
|
validate:
|
||||||
required: true
|
required: true
|
||||||
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
|
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
|
||||||
config-pattern@: system.pwd_regex
|
config-pattern@: system.pwd_regex
|
||||||
|
|
||||||
- name: password2
|
password2:
|
||||||
type: password
|
type: password
|
||||||
label: PLUGIN_ADMIN.PASSWORD_CONFIRM
|
label: PLUGIN_ADMIN.PASSWORD_CONFIRM
|
||||||
placeholder: PLUGIN_ADMIN.PWD_PLACEHOLDER
|
placeholder: PLUGIN_ADMIN.PWD_PLACEHOLDER
|
||||||
validate:
|
validate:
|
||||||
required: true
|
required: true
|
||||||
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
|
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
|
||||||
config-pattern@: system.pwd_regex
|
config-pattern@: system.pwd_regex
|
||||||
|
|
||||||
- name: fullname
|
fullname:
|
||||||
type: text
|
type: text
|
||||||
placeholder: "e.g. 'Joe Schmoe'"
|
placeholder: "e.g. 'Joe Schmoe'"
|
||||||
label: PLUGIN_ADMIN.FULL_NAME
|
label: PLUGIN_ADMIN.FULL_NAME
|
||||||
|
validate:
|
||||||
- name: title
|
required: true
|
||||||
type: text
|
|
||||||
placeholder: "e.g. 'Administrator'"
|
|
||||||
label: PLUGIN_ADMIN.TITLE
|
|
||||||
|
|
||||||
process:
|
|
||||||
register_admin_user: true
|
|
||||||
|
|
||||||
|
title:
|
||||||
|
type: text
|
||||||
|
placeholder: "e.g. 'Administrator'"
|
||||||
|
label: PLUGIN_ADMIN.TITLE
|
||||||
---
|
---
|
||||||
|
|
||||||
The Admin plugin has been installed, but no **admin accounts** could be found. Please create an admin account to ensure your Grav install is secure...
|
The Admin plugin has been installed, but no **admin accounts** could be found. Please create an admin account to ensure your Grav install is secure...
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
---
|
---
|
||||||
title: Reset password
|
title: Reset password
|
||||||
expires: 0
|
expires: 0
|
||||||
|
access:
|
||||||
|
admin.login: false
|
||||||
|
|
||||||
|
|
||||||
|
forms:
|
||||||
|
admin-login-reset:
|
||||||
|
type: admin
|
||||||
|
method: post
|
||||||
|
|
||||||
form:
|
|
||||||
fields:
|
fields:
|
||||||
- name: username
|
username:
|
||||||
type: text
|
type: text
|
||||||
placeholder: PLUGIN_ADMIN.USERNAME
|
placeholder: PLUGIN_ADMIN.USERNAME
|
||||||
readonly: true
|
readonly: true
|
||||||
- name: password
|
password:
|
||||||
type: password
|
type: password
|
||||||
placeholder: PLUGIN_ADMIN.PASSWORD
|
placeholder: PLUGIN_ADMIN.PASSWORD
|
||||||
autofocus: true
|
autofocus: true
|
||||||
- name: token
|
token:
|
||||||
type: hidden
|
type: hidden
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{% embed 'partials/login.html.twig' with {title:'Grav Forgot Password'} %}
|
{% embed 'partials/login.html.twig' with {title:'Grav Forgot Password'} %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
{% for field in form.fields %}
|
{% for field_name,field in form.fields %}
|
||||||
{% if field.type %}
|
{% if field.type %}
|
||||||
|
{% set field = field|merge({ name: field.name ?? field_name }) %}
|
||||||
|
{% set value = form.value(field.name) %}
|
||||||
<div>
|
<div>
|
||||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,9 +12,10 @@
|
|||||||
{% block form %}
|
{% block form %}
|
||||||
{% set form = forms['login'] %}
|
{% set form = forms['login'] %}
|
||||||
|
|
||||||
{% for field in form.fields %}
|
{% for field_name,field in form.fields %}
|
||||||
{% set value = field.name == 'username' ? username : '' %}
|
|
||||||
{% if field.type %}
|
{% if field.type %}
|
||||||
|
{% set field = field|merge({ name: field.name ?? field_name }) %}
|
||||||
|
{% set value = form.value(field.name) %}
|
||||||
<div>
|
<div>
|
||||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
|
|
||||||
{% set form = forms['login-twofa'] %}
|
{% set form = forms['login-twofa'] %}
|
||||||
|
|
||||||
{% for field in form.fields %}
|
{% for field_name, field in form.fields %}
|
||||||
{% if field.type %}
|
{% if field.type %}
|
||||||
|
{% set field = field|merge({ name: field.name ?? field_name }) %}
|
||||||
|
{% set value = form.value(field.name) %}
|
||||||
<div>
|
<div>
|
||||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{% extends 'partials/base.html.twig' %}
|
{% extends 'partials/base.html.twig' %}
|
||||||
{% set scope = scope ?: 'data.' %}
|
{% set scope = form.scope %}
|
||||||
|
|
||||||
{% block messages %}{% endblock %}
|
{% block messages %}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
@@ -14,13 +15,10 @@
|
|||||||
|
|
||||||
{% block integration %}{% endblock %}
|
{% block integration %}{% endblock %}
|
||||||
|
|
||||||
{% set redirect = redirect ?: uri.path ~ uri.params ~ (uri.query ? '?' ~ uri.query : '') %}
|
<form method="post" action="">
|
||||||
|
|
||||||
<form method="post" action="{{ admin_route('/')|trim('/', 'right') }}">
|
|
||||||
<div class="padding">
|
<div class="padding">
|
||||||
{% block form %}{% endblock %}
|
{% block form %}{% endblock %}
|
||||||
<input type="hidden" name="redirect" value="{{ redirect }}" />
|
{{ nonce_field(form.getNonceAction(), form.getNonceName())|raw }}
|
||||||
{{ nonce_field('admin-form', 'admin-nonce')|raw }}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{% extends 'partials/base.html.twig' %}
|
{% extends 'partials/base.html.twig' %}
|
||||||
{% set scope = scope ?: 'data.' %}
|
{% set scope = form.scope %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<body id="admin-login-wrapper">
|
<body id="admin-login-wrapper">
|
||||||
@@ -10,11 +10,12 @@
|
|||||||
|
|
||||||
{% block instructions %}{% endblock %}
|
{% block instructions %}{% endblock %}
|
||||||
|
|
||||||
<form method="post" action="{{ admin_route('/') }}">
|
<form method="post" action="">
|
||||||
<div class="padding">
|
<div class="padding">
|
||||||
{% block form %}{% endblock %}
|
{% block form %}{% endblock %}
|
||||||
|
{% include "forms/fields/formname/formname.html.twig" %}
|
||||||
{{ nonce_field('form', 'form-nonce')|raw }}
|
{% include 'forms/fields/uniqueid/uniqueid.html.twig' %}
|
||||||
|
{{ nonce_field(form.getNonceAction() ?? 'form', form.getNonceName() ?? 'form-nonce')|raw }}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -7,8 +7,9 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
{% for field in form.fields %}
|
{% for field_name,field in form.fields %}
|
||||||
{% if field.type %}
|
{% if field.type %}
|
||||||
|
{% set field = field|merge({ name: field.name ?? field_name }) %}
|
||||||
{% set value = form.value(field.name) %}
|
{% set value = form.value(field.name) %}
|
||||||
<div class="wrapper-{{ field.name }}">
|
<div class="wrapper-{{ field.name }}">
|
||||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
<div class="form-actions primary-accent">
|
<div class="form-actions primary-accent">
|
||||||
|
|
||||||
<button type="reset" class="button secondary"><i class="fa fa-exclamation-circle"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_CLEAR'|tu }}</button>
|
<button type="reset" class="button secondary"><i class="fa fa-exclamation-circle"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_CLEAR'|tu }}</button>
|
||||||
<button type="submit" class="button primary"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_CREATE_USER'|tu }}</button>
|
<button type="submit" class="button primary" name="task" value="register"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_CREATE_USER'|tu }}</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{% embed 'partials/login.html.twig' with {title:'Grav Reset Password'} %}
|
{% embed 'partials/login.html.twig' with {title:'Grav Reset Password'} %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
{% for field in form.fields %}
|
{% for field_name,field in form.fields %}
|
||||||
{% set value = attribute(admin.forgot, field.name) is defined ? attribute(admin.forgot, field.name) : null %}
|
|
||||||
|
|
||||||
{% if field.type %}
|
{% if field.type %}
|
||||||
|
{% set field = field|merge({ name: field.name ?? field_name }) %}
|
||||||
|
{% set value = form.value(field.name) %}
|
||||||
<div>
|
<div>
|
||||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user