Merge branch 'feature/admin_registration' into develop

This commit is contained in:
Andy Miller
2015-12-10 13:22:54 -07:00
11 changed files with 369 additions and 41 deletions

194
admin.php
View File

@@ -1,13 +1,16 @@
<?php
namespace Grav\Plugin;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\GPM\GPM;
use Grav\Common\Grav;
use Grav\Common\Inflector;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
use Grav\Common\Plugin;
use Grav\Common\Uri;
use Grav\Common\User\User;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Session\Session;
@@ -66,11 +69,14 @@ class AdminPlugin extends Plugin
{
if (!Grav::instance()['config']->get('plugins.admin-pro.enabled')) {
return [
'onPluginsInitialized' => [['login', 100000], ['onPluginsInitialized', 1000]],
'onShutdown' => ['onShutdown', 1000]
'onPluginsInitialized' => [['setup', 100000], ['onPluginsInitialized', 1000]],
'onShutdown' => ['onShutdown', 1000],
'onFormProcessed' => ['onFormProcessed', 0]
];
} else {
return [];
return [
'onFormProcessed' => ['onFormProcessed', 0]
];
}
}
@@ -78,7 +84,7 @@ class AdminPlugin extends Plugin
* If the admin path matches, initialize the Login plugin configuration and set the admin
* as active.
*/
public function login()
public function setup()
{
$route = $this->config->get('plugins.admin.route');
if (!$route) {
@@ -87,14 +93,156 @@ class AdminPlugin extends Plugin
$this->base = '/' . trim($route, '/');
$this->uri = $this->grav['uri'];
$register_check = CACHE_DIR . 'register-check-' . $this->grav['cache']->getKey();
// See if we have performed register check recently
if (!file_exists($register_check)) {
// check for existence of a user account
$account_dir = $file_path = $this->grav['locator']->findResource('account://');
$user_check = (array) glob($account_dir . '/*.yaml');
// If no users found, go to register
if (!count($user_check) > 0) {
if (!$this->isAdminPath()) {
$this->grav->redirect($this->base);
}
$this->template = 'register';
} else {
touch($register_check);
}
}
// Only activate admin if we're inside the admin path.
if ($this->uri->route() == $this->base ||
substr($this->uri->route(), 0, strlen($this->base) + 1) == $this->base . '/') {
if ($this->isAdminPath()) {
$this->active = true;
}
}
/**
* Validate a value. Currently validates
*
* - 'user' for username format and username availability.
* - 'password1' for password format
* - 'password2' for equality to password1
*
* @param object $form The form
* @param string $type The field type
* @param string $value The field value
* @param string $extra Any extra value required
*
* @return mixed
*/
protected function validate($type, $value, $extra = '')
{
switch ($type) {
case 'username_format':
if (!preg_match('/^[a-z0-9_-]{3,16}$/', $value)) {
return false;
}
return true;
break;
case 'password1':
if (!preg_match('/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/', $value)) {
return false;
}
return true;
break;
case 'password2':
if (strcmp($value, $extra)) {
return false;
}
return true;
break;
}
}
/**
* Process the admin registration form.
*
* @param Event $event
*/
public function onFormProcessed(Event $event)
{
$form = $event['form'];
$action = $event['action'];
switch ($action) {
case 'register_admin_user':
if (!$this->config->get('plugins.login.enabled')) {
throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED'));
}
$data = [];
$username = $form->value('username');
if ($form->value('password1') != $form->value('password2')) {
$this->grav->fireEvent('onFormValidationError',
new Event([
'form' => $form,
'message' => $this->grav['language']->translate('PLUGIN_LOGIN.PASSWORDS_DO_NOT_MATCH')
]));
$event->stopPropagation();
return;
}
$data['password'] = $form->value('password1');
$fields = [
'email',
'fullname',
'title'
];
foreach($fields as $field) {
// Process value of field if set in the page process.register_user
if (!isset($data[$field]) && $form->value($field)) {
$data[$field] = $form->value($field);
}
}
unset($data['password1']);
unset($data['password2']);
// Don't store the username: that is part of the filename
unset($data['username']);
$inflector = new Inflector();
$data['fullname'] = isset($data['fullname']) ? $data['fullname'] : $inflector->titleize($username);
$data['title'] = isset($data['title']) ? $data['title'] : 'Administrator';
$data['state'] = 'enabled';
$data['access'] = ['admin' => ['login' => true, 'super' => true], 'site' => ['login' => true]];
// Create user object and save it
$user = new User($data);
$file = CompiledYamlFile::instance($this->grav['locator']->findResource('user://accounts/' . $username . YAML_EXT, true, true));
$user->file($file);
$user->save();
$user = User::load($username);
//Login user
$this->grav['session']->user = $user;
unset($this->grav['user']);
$this->grav['user'] = $user;
$user->authenticated = $user->authorize('site.login');
$messages = $this->grav['messages'];
$messages->add($this->grav['language']->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'), 'info');
$this->grav->redirect($this->base);
break;
}
}
/**
* If the admin plugin is set as active, initialize the admin
*/
@@ -153,13 +301,6 @@ class AdminPlugin extends Plugin
$this->session->expert = false;
}
// check for existence of a user account
$account_dir = $file_path = $this->grav['locator']->findResource('account://');
$user_check = (array) glob($account_dir . '/*.yaml');
if (!count($user_check) > 0) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.NO_USER_ACCOUNTS'), 'info');
}
/** @var Pages $pages */
$pages = $this->grav['pages'];
@@ -192,6 +333,9 @@ class AdminPlugin extends Plugin
$self = $this;
// make sure page is not frozen!
unset($this->grav['page']);
// Replace page service with admin.
$this->grav['page'] = function () use ($self) {
$page = new Page;
@@ -374,6 +518,9 @@ class AdminPlugin extends Plugin
'onTask.GPM' => ['onTaskGPM', 0]
]);
// Initialize admin class.
require_once __DIR__ . '/classes/admin.php';
// Check for required plugins
if (!$this->grav['config']->get('plugins.login.enabled') ||
!$this->grav['config']->get('plugins.form.enabled') ||
@@ -397,19 +544,20 @@ class AdminPlugin extends Plugin
$this->grav['session']->admin_lang = $language->getLanguage();
}
// Decide admin template and route.
$path = trim(substr($this->uri->route(), strlen($this->base)), '/');
$this->template = 'dashboard';
if ($path) {
if (empty($this->template)) {
$this->template = 'dashboard';
}
// Can't access path directly...
if ($path && $path != 'register') {
$array = explode('/', $path, 2);
$this->template = array_shift($array);
$this->route = array_shift($array);
}
// Initialize admin class.
require_once __DIR__ . '/classes/admin.php';
$this->admin = new Admin($this->grav, $this->base, $this->template, $this->route);
// And store the class into DI container.
@@ -466,4 +614,14 @@ class AdminPlugin extends Plugin
require_once(__DIR__.'/twig/AdminTwigExtension.php');
$this->grav['twig']->twig->addExtension(new AdminTwigExtension());
}
public function isAdminPath()
{
if ($this->uri->route() == $this->base ||
substr($this->uri->route(), 0, strlen($this->base) + 1) == $this->base . '/') {
return true;
}
return false;
}
}

View File

@@ -7,6 +7,8 @@ en:
LOGIN_BTN_FORGOT: "Forgot"
LOGIN_BTN_RESET: "Reset Password"
LOGIN_BTN_SEND_INSTRUCTIONS: "Send Reset Instructions"
LOGIN_BTN_CLEAR: "Clear Form"
LOGIN_BTN_CREATE_USER: "Create User"
LOGIN_LOGGED_IN: "You have been successfully logged in"
LOGIN_FAILED: "Login failed"
LOGGED_OUT: "You have been logged out"
@@ -171,6 +173,7 @@ en:
USERNAME: "Username"
EMAIL: "Email"
PASSWORD: "Password"
PASSWORD_CONFIRM: "Confirm Password"
TITLE: "Title"
LANGUAGE: "Language"
ACCOUNT: "Account"

57
pages/admin/register.md Normal file
View File

@@ -0,0 +1,57 @@
---
form:
fields:
- name: spacer
title: Required
type: spacer
- name: username
type: text
placeholder: PLUGIN_ADMIN.USERNAME
validate:
required: true
message: PLUGIN_LOGIN.USERNAME_NOT_VALID
pattern: '^[a-z0-9_-]{3,16}$'
- name: email
type: text
placeholder: PLUGIN_ADMIN.EMAIL
validate:
type: email
message: PLUGIN_ADMIN.EMAIL_VALIDATION_MESSAGE
required: true
- name: password1
type: password
placeholder: PLUGIN_ADMIN.PASSWORD
validate:
required: true
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
pattern: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
- name: password2
type: password
placeholder: PLUGIN_ADMIN.PASSWORD_CONFIRM
validate:
required: true
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
pattern: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
- name: spacer
title: Optional
type: spacer
- name: fullname
type: text
placeholder: PLUGIN_ADMIN.FULL_NAME
- name: title
type: text
placeholder: PLUGIN_ADMIN.TITLE
process:
register_admin_user: true
---
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...

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,30 @@
max-width: 24rem;
margin: 0 auto;
&.wide {
max-width: 50rem;
h1 {
height: 100px;
}
form {
> .padding {
padding: 3rem 2rem 6rem 2rem;
> div {
width: 50%;
display: inline-block;
margin-right: -2px;
}
.form-field {
padding: 0 1rem;
}
}
}
}
.form-field {
padding-left: 0;
margin-bottom: 0;
@@ -17,6 +41,25 @@
padding-right: 0;
}
.wrapper-spacer {
width: 100% !important;
display: block !important;
padding: 0 1rem;
h3 {
padding-left: 1rem;
color: rgba(white, 0.4);
border-bottom: 3px solid rgba(white, 0.1);
}
}
.instructions {
display: block;
padding: 2rem 4rem 0;
margin: 0;
font-size: 1.3rem;
color: rgba(white,0.8);
}
h1 {
background: $darker-accent-bg url(../images/logo.png) 50% 50% no-repeat;
font-size: 0;
@@ -26,7 +69,12 @@
}
form {
padding: 2rem 3rem;
position: relative;
.padding {
padding: 3rem 3rem 6rem 3rem;
}
input {
margin-bottom: 2rem;
background: $page-bg;
@@ -43,8 +91,13 @@
}
.form-actions {
display: block !important;
width: 100% !important;
text-align: center;
margin: 0 -3rem -3rem -3rem;
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 1.5rem 3rem;
button:first-child {

View File

@@ -1,26 +1,26 @@
{% embed 'partials/login.html.twig' with {title:'Grav Login'} %}
{% embed 'partials/login.html.twig' with {title:'Grav Admin Login'} %}
{% block form %}
{% for field in page.header.form.fields %}
{% if field.type %}
<div>
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
</div>
{% endif %}
{% endfor %}
{% if field.type %}
<div>
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
</div>
{% endif %}
{% endfor %}
<div class="form-actions secondary-accent">
{% if notAuthorized %}
<a class="button secondary" onclick="window.history.back()"><i class="fa fa-reply"></i> {{ 'PLUGIN_ADMIN.BACK'|tu }}</a>
<div class="form-actions secondary-accent">
{% if notAuthorized %}
<a class="button secondary" onclick="window.history.back()"><i class="fa fa-reply"></i> {{ 'PLUGIN_ADMIN.BACK'|tu }}</a>
{% else %}
{% if not authenticated %}
<a class="button secondary" href="{{ base_url_relative }}/forgot"><i class="fa fa-exclamation-circle"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_FORGOT'|tu }}</a>
<button type="submit" class="button primary" name="task" value="login"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN'|tu }}</button>
{% else %}
{% if not authenticated %}
<a class="button secondary" href="{{ base_url_relative }}/forgot"><i class="fa fa-exclamation-circle"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_FORGOT'|tu }}</a>
<button type="submit" class="button primary" name="task" value="login"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN'|tu }}</button>
{% else %}
<button type="submit" class="button primary" name="task" value="logout"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGOUT'|tu }}</button>
{% endif %}
<button type="submit" class="button primary" name="task" value="logout"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGOUT'|tu }}</button>
{% endif %}
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -1,17 +1,21 @@
{% extends 'partials/base.html.twig' %}
{% block page %}
<section id="admin-login" class="default-glow-shadow">
<section id="admin-login" class="default-glow-shadow {{ classes }}">
<h1>
{{ title }}
</h1>
{% include 'partials/messages.html.twig' %}
{% block instructions %}{% endblock %}
<form method="post" action="{{ base_url_relative }}">
<div class="padding">
{% block form %}{% endblock %}
{{ nonce_field('admin-form', 'admin-nonce') }}
</div>
</form>
</section>
{% endblock %}

View File

@@ -1,3 +1,7 @@
{% for message in admin.messages %}
<div class="{{ message.scope|e }} alert">{{ message.message }}</div>
{% endfor %}
{% if form.message %}
<div class="error alert">{{ form.message }}</div>
{% endif %}

View File

@@ -0,0 +1,21 @@
{% extends 'partials/base.html.twig' %}
{% block page %}
<section id="admin-login" class="default-glow-shadow {{ classes }}">
<h1>
{{ title }}
</h1>
{% include 'partials/messages.html.twig' %}
{% block instructions %}{% endblock %}
<form method="post" action="{{ base_url_relative }}">
<div class="padding">
{% block form %}{% endblock %}
{{ nonce_field('form', 'form-nonce') }}
</div>
</form>
</section>
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% embed 'partials/register.html.twig' with {title:'Grav Register Admin User', classes:'wide'} %}
{% block instructions %}
<div class="instructions">
{{ page.content }}
</div>
{% endblock %}
{% block form %}
{% for field in page.header.form.fields %}
{% if field.type %}
{% set value = form.value(field.name) %}
<div class="wrapper-{{ field.name }}">
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
</div>
{% endif %}
{% endfor %}
<div class="form-actions secondary-accent">
<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>
</div>
{% endblock %}
{% endembed %}