Skip to content

Commit

Permalink
[RUA-48] Setting - REST API Content Protection (#48)
Browse files Browse the repository at this point in the history
* [RUA-48] settings screen cleanup. new setting type

* [RUA-48] setting: rest api content protection

* [RUA-48] intercept rest api call

* [RUA-48] label
  • Loading branch information
intoxstudio authored Feb 19, 2024
1 parent 3c36443 commit 67328d2
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 30 deletions.
135 changes: 105 additions & 30 deletions admin/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

final class RUA_Settings_Page extends RUA_Admin
{
const PREFIX = 'rua_';
/**
* Settings slug
* @var string
Expand All @@ -25,6 +26,7 @@ final class RUA_Settings_Page extends RUA_Admin
/**
* Settings prefix
* @var string
* @deprecated
*/
private $prefix = 'rua-';

Expand Down Expand Up @@ -152,6 +154,7 @@ public function render_screen()
*/
public function add_scripts_styles()
{
WPCACore::enqueue_scripts_styles(RUA_App::TYPE_RESTRICT);
}

public function init_settings()
Expand All @@ -162,44 +165,68 @@ public function init_settings()
'title' => __('General', 'restrict-user-access'),
'callback' => '',
'fields' => []
],
'security' => [
'name' => 'security',
'title' => __('Security', 'restrict-user-access'),
'callback' => '',
'fields' => []
]
];

$levels = [
0 => __('-- None --')
];
foreach (RUA_App::instance()->get_levels() as $id => $level) {
$levels[$level->ID] = $level->post_title;
}

$this->settings['general']['fields'][] = [
'name' => 'registration-level',
'name' => 'rua-registration-level',
'title' => __('New User Default Level', 'restrict-user-access'),
'callback' => [$this,'dropdown_levels'],
'callback' => [$this,'dropdown'],
'args' => [
'label_for' => $this->prefix . 'registration-level'
'options' => $levels
]
];

$default_role = get_option('default_role');
$roles = get_editable_roles();
$this->settings['general']['fields'][] = [
'name' => 'registration-role',
'name' => 'rua-registration-role',
'title' => __('New User Default Role'),
'callback' => [$this,'setting_moved'],
'args' => [
'option' => !empty($roles[$default_role]) ? $roles[$default_role]['name'] : $default_role,
'title' => __('General Settings'),
'url' => 'options-general.php'
'option' => !empty($roles[$default_role]) ? $roles[$default_role]['name'] : $default_role,
'wp_title' => __('General Settings'),
'url' => 'options-general.php'
],
'register' => false
];

$this->settings['general']['fields'][] = [
'name' => 'registration',
'name' => 'rua-registration',
'title' => __('Enable Registration', 'restrict-user-access'),
'callback' => [$this,'setting_moved'],
'args' => [
'option' => get_option('users_can_register') ? __('Yes') : __('No'),
'title' => __('General Settings'),
'url' => 'options-general.php'
'option' => get_option('users_can_register') ? __('Yes') : __('No'),
'wp_title' => __('General Settings'),
'url' => 'options-general.php'
],
'register' => false
];

$this->settings['security']['fields'][] = [
'name' => self::PREFIX . 'rest_api_access',
'title' => __('REST API Content Protection', 'restrict-user-access'),
'callback' => [$this,'checkbox'],
'args' => [
'default_value' => 1,
'recommended' => __('Enabled'),
'description' => __('Deny access to content in REST API for users without legitimate a purpose.', 'restrict-user-access') .
' <a target="_blank" rel="noopener" href="https://dev.institute/docs/restrict-user-access/faq/restricted-content-not-hidden/">' . __('Learn more') . '</a>'
],
];
foreach ($this->settings as $section) {
add_settings_section(
$this->prefix . $section['name'],
Expand All @@ -208,50 +235,88 @@ public function init_settings()
$this->slug
);
foreach ($section['fields'] as $field) {
$field['args']['title'] = $field['title'];
$field['args']['label_for'] = $field['name'];
add_settings_field(
$this->prefix . $field['name'],
$field['name'],
$field['title'],
$field['callback'],
$this->slug,
$this->prefix . $section['name'],
$field['args']
);
if (!isset($field['register']) || $field['register']) {
register_setting($this->option_group, $this->prefix . $field['name']);
register_setting($this->option_group, $field['name']);
}
}
}
}

/**
* Render levels dropdown
* Skip synchronized levels
* Render checkbox
*
* @since 0.17
* @since 0.10
* @param array $args
* @return void
*/
public function dropdown_levels($args)
public function checkbox($args)
{
echo '<select name="' . $this->prefix . 'registration-level" id="' . $this->prefix . 'registration-level">';
echo '<option value="0">' . __('-- None --') . '</option>';
foreach (RUA_App::instance()->get_levels() as $id => $level) {
echo '<option value="' . $level->ID . '" ' . selected($level->ID, get_option($this->prefix . 'registration-level'), false) . '>' . $level->post_title . '</option>';
$option = $this->get_setting_value($args);

echo '<label class="cae-toggle">';
echo '<input type="checkbox" name="' . $args['label_for'] . '" value="1"' . checked($option, 1, 0) . '>';
echo '<div class="cae-toggle-bar"></div>';
echo '</label>';
if (isset($args['description'])) {
echo '<p class="description">' . $args['description'] . '</p>';
}
if (isset($args['recommended'])) {
echo '<p class="description">Recommended: <code>' . $args['recommended'] . '</code></p>';
}
echo '</select>';
}

/**
* Render checkbox
*
* @since 0.10
* @param array $args
* @param $args
* @return void
*/
public function checkbox($args)
public function radio($args)
{
$option = get_option($args['label_for']);
echo '<input type="checkbox" name="' . $args['label_for'] . '" value="1" ' . checked($option, 1, 0) . '/>';
$current_value = $this->get_setting_value($args);

echo '<fieldset>';
echo '<legend class="screen-reader-text">' . $args['title'] . '</legend>';
echo '<p>';
foreach ($this->get_options($args) as $option_value => $label) {
echo '<label>';
echo '<input type="radio" name="' . $args['label_for'] . '" value="' . $option_value . '"' . checked($current_value, $option_value, false) . '> ' . $label;
echo '</label><br />';
}
echo '</p>';
if (isset($args['description'])) {
echo '<p class="description">' . $args['description'] . '</p>';
}
if (isset($args['recommended'])) {
echo '<p class="description">Recommended: <code>' . $args['recommended'] . '</code></p>';
}
echo '</fieldset>';
}

/**
* @param $args
* @return void
*/
public function dropdown($args)
{
$current_value = $this->get_setting_value($args);

echo '<select name="' . $args['label_for'] . '" id="' . $args['label_for'] . '">';
foreach ($this->get_options($args) as $option_value => $label) {
echo '<option value="' . $option_value . '" ' . selected($option_value, $current_value, false) . '>' . $label . '</option>';
}
echo '</select>';
if (isset($args['recommended'])) {
echo '<p class="description">Recommended: <code>' . $args['recommended'] . '</code></p>';
}
}

/**
Expand All @@ -266,7 +331,17 @@ public function setting_moved($args)
echo $args['option'];
echo '<p class="description">' . sprintf(
__('Setting can be changed in %s', 'restrict-user-access'),
'<a href="' . admin_url($args['url']) . '">' . $args['title'] . '</a>'
'<a href="' . admin_url($args['url']) . '">' . $args['wp_title'] . '</a>'
) . '</p>';
}

private function get_setting_value($args)
{
return get_option($args['label_for'], isset($args['default_value']) ? $args['default_value'] : false);
}

private function get_options($args)
{
return isset($args['options']) ? $args['options'] : [];
}
}
76 changes: 76 additions & 0 deletions app.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ public function __construct()
'cas/user_visibility',
[$this,'sidebars_check_levels']
);

add_filter(
'rest_authentication_errors',
[$this, 'rest_api_access']
);
}

public function ensure_wpca_loaded()
Expand Down Expand Up @@ -664,4 +669,75 @@ public function process_level_automators()
}
}
}

public function rest_api_access($result)
{
//bail if auth has been handled elsewhere
if ($result === true || is_wp_error($result)) {
return $result;
}

if (rua_get_user()->has_global_access()) {
return $result;
}

if (!get_option('rua_rest_api_access', 1)) {
return $result;
}

//Contributor is the lowest role that should have access,
//since they can see content in admin area
if (current_user_can('edit_posts')) {
return $result;
}

$restricted = [
'/wp/v2/search' => true,
'/wp/v2/users' => true
];

$ignored_post_types = [
'nav_menu_item' => true,
'wp_block' => true,
'wp_template' => true,
'wp_template_part' => true,
'wp_navigation' => true
];
foreach (get_post_types(['show_in_rest' => true], 'objects') as $post_type) {
if (empty($post_type->rest_base)) {
continue;
}
if (isset($ignored_post_types[$post_type->name])) {
continue;
}
$restricted['/' . $post_type->rest_namespace . '/' . $post_type->rest_base] = true;
}
$ignored_taxonomies = [
'menu' => true,
];
foreach (get_taxonomies(['show_in_rest' => true], 'objects') as $taxonomy) {
if (empty($taxonomy->rest_base)) {
continue;
}
if (isset($ignored_taxonomies[$post_type->name])) {
continue;
}
$restricted['/' . $taxonomy->rest_namespace . '/' . $taxonomy->rest_base] = true;
}

global $wp;

$route = $wp->query_vars['rest_route'];
$route = preg_replace('/(\/\d+)$/', '', $route, 1);

if (!isset($restricted[$route])) {
return $result;
}

return new WP_Error(
'rest_forbidden',
__('Sorry, you are not allowed to do that.'),
['status' => rest_authorization_required_code()]
);
}
}

0 comments on commit 67328d2

Please sign in to comment.