content restrictions in system settings and meta-tabs

This commit is contained in:
trendschau 2021-01-22 22:16:22 +01:00
parent 54012441b6
commit 5f01147e58
10 changed files with 209 additions and 46 deletions

View file

@ -26,15 +26,17 @@ class MetaApiController extends ContentController
$metatabs = $writeYaml->getYaml('system' . DIRECTORY_SEPARATOR . 'author', 'metatabs.yaml');
# add radio buttons to choose posts or pages for folder.
if($folder)
# the fields for user or role based access
if(!isset($this->settings['pageaccess']) || $this->settings['pageaccess'] === NULL )
{
$metatabs['meta']['fields']['contains'] = [
'type' => 'radio',
'label' => 'This folder contains:',
'options' => ['pages' => 'PAGES (sort in navigation with drag & drop)', 'posts' => 'POSTS (sorted by publish date, for news or blogs)'],
'class' => 'medium'
];
unset($metatabs['meta']['fields']['alloweduser']);
unset($metatabs['meta']['fields']['allowedrole']);
}
# add radio buttons to choose posts or pages for folder.
if(!$folder)
{
unset($metatabs['meta']['fields']['contains']);
}
# loop through all plugins
@ -352,6 +354,7 @@ class MetaApiController extends ContentController
return $response->withJson(array('metadata' => $metaInput, 'structure' => $structure, 'item' => $this->item, 'errors' => false));
}
# can be deleted ??
private function customfieldsPrepareForEdit($customfields)
{
# to edit fields in vue we have to transform the arrays in yaml into an array of objects like [{key: abc, value: xyz}{...}]
@ -374,6 +377,7 @@ class MetaApiController extends ContentController
return $customfieldsForEdit;
}
# can be deleted?
private function customfieldsPrepareForSave($customfields, $arrayFeatureOn)
{
# we have to convert the incoming array of objects from vue [{key: abc, value: xyz}{...}] into key-value arrays for yaml.

View file

@ -19,6 +19,7 @@ use Typemill\Events\OnMetaLoaded;
use Typemill\Events\OnMarkdownLoaded;
use Typemill\Events\OnContentArrayLoaded;
use Typemill\Events\OnHtmlLoaded;
use Typemill\Events\OnRestrictionsLoaded;
use Typemill\Extensions\ParsedownExtension;
class PageController extends Controller
@ -213,10 +214,50 @@ class PageController extends Controller
/* set safe mode to escape javascript and html in markdown */
$parsedown->setSafeMode(true);
# check access restriction here
$restricted = $this->checkRestrictions($metatabs['meta']);
if($restricted)
{
# convert markdown into array of markdown block-elements
$markdownBlocks = $parsedown->markdownToArrayBlocks($contentMD);
# infos that plugins need to add restriction content
$restrictions = [
'restricted' => $restricted,
'defaultContent' => true,
'markdownBlocks' => $markdownBlocks,
];
# dispatch the data
$restrictions = $this->c->dispatcher->dispatch('onRestrictionsLoaded', new OnRestrictionsLoaded( $restrictions ))->getData();
# use the returned markdown
$markdownBlocks = $restrictions['markdownBlocks'];
# if no plugin has disabled the default behavior
if($restrictions['defaultContent'])
{
# cut the restricted content
$shortenedPage = $this->cutRestrictedContent($markdownBlocks);
# check if there is customized content
$restrictionnotice = ( isset($this->settings['restrictionnotice']) && $this->settings['restrictionnotice'] != '' ) ? $this->settings['restrictionnotice'] : 'You are not allowed to access this content.';
# add notice to shortened content
$shortenedPage[] = $restrictionnotice;
# Use the shortened page
$markdownBlocks = $shortenedPage;
}
# finally transform the markdown blocks back to pure markdown text
$contentMD = $parsedown->arrayBlocksToMarkdown($markdownBlocks);
}
/* parse markdown-file to content-array */
$contentArray = $parsedown->text($contentMD);
$contentArray = $this->c->dispatcher->dispatch('onContentArrayLoaded', new OnContentArrayLoaded($contentArray))->getData();
/* parse markdown-content-array to content-string */
$contentHTML = $parsedown->markup($contentArray);
$contentHTML = $this->c->dispatcher->dispatch('onHtmlLoaded', new OnHtmlLoaded($contentHTML))->getData();
@ -426,4 +467,76 @@ class PageController extends Controller
return false;
}
# checks if a page has a restriction in meta and if the current user is blocked by that restriction
protected function checkRestrictions($meta)
{
# check if content restrictions are active
if(isset($this->settings['pageaccess']) && $this->settings['pageaccess'])
{
# check if page is restricted to certain user
if(isset($meta['alloweduser']) && $meta['alloweduser'] && $meta['alloweduser'] !== '' )
{
if(isset($_SESSION['user']) && $_SESSION['user'] == $meta['alloweduser'])
{
# user has access to the page, so there are no restrictions
return false;
}
# otherwise return array with type of restriction and allowed username
return [ 'alloweduser' => $meta['alloweduser'] ];
}
# check if page is restricted to certain userrole
if(isset($meta['allowedrole']) && $meta['allowedrole'] && $meta['allowedrole'] !== '' )
{
# var_dump($this->c->acl->inheritsRole('editor', 'member'));
# die();
if(
isset($_SESSION['role'])
AND (
$_SESSION['role'] == 'administrator'
OR $_SESSION['role'] == $meta['allowedrole']
OR $this->c->acl->inheritsRole($_SESSION['role'], $meta['allowedrole'])
)
)
{
# role has access to page, so there are no restrictions
return false;
}
return [ 'allowedrole' => $meta['allowedrole'] ];
}
}
return false;
}
protected function cutRestrictedContent($markdown)
{
#initially add only the title of the page.
$restrictedMarkdown = [$markdown[0]];
unset($markdown[0]);
if(isset($this->settings['hrdelimiter']) && $this->settings['hrdelimiter'] !== NULL )
{
foreach ($markdown as $block)
{
$firstCharacters = substr($block, 0, 3);
if($firstCharacters == '---' OR $firstCharacters == '***')
{
return $restrictedMarkdown;
}
$restrictedMarkdown[] = $block;
}
# no delimiter found, so use the title only
$restrictedMarkdown = [$restrictedMarkdown[0]];
}
return $restrictedMarkdown;
}
}

View file

@ -51,9 +51,6 @@ class SettingsController extends Controller
# set navigation active
$navigation['System']['active'] = true;
# set option for registered website
$options = ['' => 'all', 'registered' => 'registered users only'];
return $this->render($response, 'settings/system.twig', array(
'settings' => $settings,
'acl' => $this->c->acl,
@ -62,7 +59,6 @@ class SettingsController extends Controller
'languages' => $languages,
'locale' => $locale,
'formats' => $defaultSettings['formats'],
'access' => $options,
'route' => $route->getName()
));
}
@ -94,8 +90,11 @@ class SettingsController extends Controller
'language' => $newSettings['language'],
'langattr' => $newSettings['langattr'],
'editor' => $newSettings['editor'],
'access' => $newSettings['access'],
'formats' => $newSettings['formats'],
'access' => isset($newSettings['access']) ? true : null,
'pageaccess' => isset($newSettings['pageaccess']) ? true : null,
'hrdelimiter' => isset($newSettings['hrdelimiter']) ? true : null,
'restrictionnotice' => $newSettings['restrictionnotice'],
'headlineanchors' => isset($newSettings['headlineanchors']) ? $newSettings['headlineanchors'] : null,
'displayErrorDetails' => isset($newSettings['displayErrorDetails']) ? true : null,
'twigcache' => isset($newSettings['twigcache']) ? true : null,

View file

@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for page restrictions.
*/
class OnRestrictionsLoaded extends BaseEvent
{
}

View file

@ -16,23 +16,12 @@ class TwigUserExtension extends \Twig_Extension
public function isLoggedin()
{
// configure session
ini_set('session.cookie_httponly', 1 );
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_samesite', 'lax');
session_name('typemill-session');
// start session
session_start();
if(isset($_SESSION['user']))
if(isset($_SESSION['login']) && $_SESSION['login'])
{
return true;
}
session_destroy();
return false;
}
public function isRole($role)

View file

@ -283,6 +283,8 @@ class Validation
$v->rule('in', 'editor', ['raw', 'visual']);
$v->rule('values_allowed', 'formats', $formats);
$v->rule('in', 'copyright', $copyright);
$v->rule('noHTML', 'restrictionnotice');
$v->rule('lengthBetween', 'restrictionnotice', 2, 1000 );
$v->rule('iplist', 'trustedproxies');
return $this->validationResult($v, $name);

View file

@ -159,6 +159,9 @@ class Settings
'author' => true,
'year' => true,
'access' => true,
'pageaccess' => true,
'hrdelimiter' => true,
'restrictionnotice' => true,
'headlineanchors' => true,
'theme' => true,
'editor' => true,

View file

@ -58,11 +58,26 @@ meta:
label: Hide
checkboxlabel: Hide page from navigation
class: medium
# roles:
# type: select
# label: Show page to
# class: medium
# options:
# public: Public (standard)
# members: Members only (logged in)
# customers: Customers only (paying)
allowedrole:
type: select
label: For access the user must have this minimum role
class: medium
options:
false: All
member: Member
author: Author
editor: Editor
administrator: Administrator
description: Select the lowest userrole. Higher roles will have access too.
alloweduser:
type: text
label: Only the following user has access
class: medium
description: Only this certain user will have access to this site.
contains:
type: radio
label: This folder contains
class: medium
options:
pages: PAGES (sort in navigation with drag & drop)
posts: POSTS (sorted by publish date, for news or blogs)

View file

@ -30,7 +30,7 @@
{% endif %}
</div><div class="medium{{ errors.settings.author ? ' error' : '' }}">
<label for="settings[author]">{{ __('Author') }}</label>
<input type="text" name="settings[author]" id="author" pattern="[^()/><\]\{\}\?\$@#!*%§=[\\\x22;:|]{2,40}" value="{{ old.settings.author ? old.settings.author : __(settings.author) }}" title="{{ __('Use 2 to 40 characters') }} {{ __('Only the following special characters are allowed') }} a,b a.b a-b a_b a&b a+b" />
<input type="text" name="settings[author]" id="author" pattern="[^()/><\]\{\}\?\$@#!*%§=[\\\x22;:|]{2,40}" value="{{ old.settings.author ? old.settings.author : settings.author }}" title="{{ __('Use 2 to 40 characters') }} {{ __('Only the following special characters are allowed') }} a,b a.b a-b a_b a&b a+b" />
{% if errors.settings.author %}
<span class="error">{{ errors.settings.author | first }}</span>
{% endif %}
@ -69,16 +69,6 @@
</div><div class="medium">
<label for="settings[sitemap]">{{ __('Google Sitemap') }} <small>({{ __('Readonly') }})</small></label>
<input type="text" name="settings[sitemap]" id="sitemap" readonly value="{{ base_url }}/cache/sitemap.xml" />
</div><div class="medium{{ errors.settings.access ? ' error' : '' }}">
<label for="settings[access]">{{ __('Website visible for') }}</label>
<select name="settings[access]" id="access">
{% for key,option in access %}
<option value="{{ key }}"{% if (key == old.settings.access or key == settings.access) %} selected{% endif %}>{{ __(option) }}</option>
{% endfor %}
</select>
{% if errors.settings.access %}
<span class="error">{{ errors.settings.access | first }}</span>
{% endif %}
</div>
<hr>
<header class="headline">
@ -149,6 +139,39 @@
{% endfor %}
</div>
<hr>
<header class="headline">
<h2>{{ __('Access Control') }}</h2>
<p>{{ __('Limit the access for the whole website or for each page individually. If you activate the website restriction or the page restrictions, then sessions will be used in frontend.') }}</p>
</header>
<div class="large{{ errors.settings.access ? ' error' : '' }}">
<label for="settings[access]">{{ __('Website Restriction') }}</label>
<label class="control-group">{{ __('Show the website only to authenticated users and redirect all other users to the login page.') }}
<input name="settings[access]" type="checkbox" {% if (settings.access or old.settings.access) %} checked {% endif %}>
<span class="checkmark"></span>
</label>
</div>
<div class="large{{ errors.settings.pageaccess ? ' error' : '' }}">
<label for="settings[pageaccess]">{{ __('Page Restrictions - Activate') }}</label>
<label class="control-group">{{ __('Activate individual restrictions for pages in the meta-tab of each page.') }}
<input name="settings[pageaccess]" type="checkbox" {% if (settings.pageaccess or old.settings.pageaccess) %} checked {% endif %}>
<span class="checkmark"></span>
</label>
</div>
<div class="large{{ errors.settings.hrdelimiter ? ' error' : '' }}">
<label for="settings[hrdelimiter]">{{ __('Page Restrictions - Cut Restricted Content') }}</label>
<label class="control-group">{{ __('Cut restricted content after the first hr-element on a page (per default content will be cut after title).') }}
<input name="settings[hrdelimiter]" type="checkbox" {% if (settings.hrdelimiter or old.settings.hrdelimiter) %} checked {% endif %}>
<span class="checkmark"></span>
</label>
</div>
<div class="large{{ errors.settings.restrictionnotice ? ' error' : '' }}">
<label for="settings[restrictionnotice]">{{ __('Page Restrictions - Notice') }} <small>({{ __('use markdown') }})</small></label>
<textarea id="restrictionnotice" rows="8" name="settings[restrictionnotice]">{{ old.settings.restrictionnotice ? old.settings.restrictionnotice : settings.restrictionnotice }}</textarea>
{% if errors.settings.restrictionnotice %}
<span class="error">{{ errors.settings.restrictionnotice | first }}</span>
{% endif %}
</div>
<hr>
<header class="headline">
<h2>{{ __('Developer') }}</h2>
<p>{{ __('The following options are only for developers') }}</p>

View file

@ -181,11 +181,12 @@ $container['assets'] = function($c) use ($uri)
********************************/
# if website is restricted to registered user
if(isset($settings['settings']['access']) && $settings['settings']['access'] == 'registered')
if( ( isset($settings['settings']['access']) && $settings['settings']['access'] ) || ( isset($settings['settings']['pageaccess']) && $settings['settings']['pageaccess'] ) )
{
# activate session for all routes
$session_segments = [$uri->getPath()];
}
foreach($session_segments as $segment)
{
if(substr( $uri->getPath(), 0, strlen($segment) ) === $segment)