Skip to content

Commit a67b24e

Browse files
authored
Add AccessControl Layer for WordPress (#12)
1 parent 2890c32 commit a67b24e

File tree

9 files changed

+279
-41
lines changed

9 files changed

+279
-41
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
"wp-coding-standards/wpcs": "^3.1",
5151
"squizlabs/php_codesniffer": "^3.10",
5252
"phpcompatibility/phpcompatibility-wp": "^2.1",
53-
"overtrue/phplint": "^3.4"
53+
"overtrue/phplint": "^3.4",
54+
"roots/wordpress-no-content": "^6.6"
5455
},
5556
"scripts": {
5657
"build": [

composer.lock

Lines changed: 75 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
namespace DataKit\Plugin\AccessControl;
4+
5+
use DataKit\DataViews\AccessControl\AccessController;
6+
use DataKit\DataViews\AccessControl\Capability\Capability;
7+
use DataKit\DataViews\AccessControl\ReadOnlyAccessController;
8+
use WP_User;
9+
10+
/**
11+
* Access Controller backed by a {@see WP_User}.
12+
*
13+
* @since $ver$
14+
*/
15+
final class WordPressAccessController implements AccessController {
16+
/**
17+
* The user to test against.
18+
*
19+
* @since $ver$
20+
*
21+
* @var WP_User|null
22+
*/
23+
private ?WP_User $user;
24+
25+
/**
26+
* The previous access controller.
27+
*
28+
* @since $ver$
29+
*
30+
* @var AccessController
31+
*/
32+
private AccessController $previous;
33+
34+
/**
35+
* Creates the Access Controller.
36+
*
37+
* @since $ver$
38+
*
39+
* @param WP_User|null $user The user to test against.
40+
*/
41+
public function __construct( ?WP_User $user ) {
42+
$this->user = $user;
43+
$this->previous = new ReadOnlyAccessController();
44+
}
45+
46+
/**
47+
* {@inheritDoc}
48+
*
49+
* @since $ver$
50+
*/
51+
public function can( Capability $capability ): bool {
52+
$can = $this->previous->can( $capability );
53+
54+
if ( $this->user && $this->user->exists() ) {
55+
$can = $this->user->has_cap( 'administrator' );
56+
}
57+
58+
return $this->filter_result( $can, $capability );
59+
}
60+
61+
/**
62+
* Applies filter on the result.
63+
*
64+
* @since $ver$
65+
*
66+
* @param bool $can The result to return.
67+
* @param Capability $capability The capability.
68+
*
69+
* @return bool The filtered result.
70+
*/
71+
private function filter_result( bool $can, Capability $capability ): bool {
72+
/**
73+
* Modifies the capability check.
74+
*
75+
* @filter `datakit/access-control/can`
76+
*
77+
* @since $ver$
78+
*
79+
* @param bool $can Whether the user can.
80+
* @param Capability $capability Whether the user can.
81+
* @param ?WP_User $user The WP_User.
82+
*/
83+
return (bool) apply_filters( 'datakit/access-control/can', $can, $capability, $this->user );
84+
}
85+
}

src/Component/DataViewShortcode.php

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace DataKit\Plugin\Component;
44

5+
use DataKit\DataViews\AccessControl\AccessController;
6+
use DataKit\DataViews\AccessControl\Capability;
57
use DataKit\DataViews\DataView\DataViewRepository;
68
use DataKit\DataViews\DataViewException;
79
use DataKit\Plugin\Rest\Router;
@@ -48,13 +50,23 @@ final class DataViewShortcode {
4850
*/
4951
private array $rendered = [];
5052

53+
/**
54+
* The Access Controller.
55+
*
56+
* @since $ver$
57+
*
58+
* @var AccessController
59+
*/
60+
private AccessController $access_controller;
61+
5162
/**
5263
* Creates the shortcode instance.
5364
*
5465
* @since $ver$
5566
*/
56-
private function __construct( DataViewRepository $data_view_repository ) {
67+
private function __construct( DataViewRepository $data_view_repository, AccessController $access_controller ) {
5768
$this->data_view_repository = $data_view_repository;
69+
$this->access_controller = $access_controller;
5870

5971
add_shortcode( self::SHORTCODE, [ $this, 'render_shortcode' ] );
6072
}
@@ -81,13 +93,17 @@ public function render_shortcode( array $attributes ): string {
8193

8294
// Only add data set once per ID.
8395
if ( ! in_array( $id, $this->rendered, true ) ) {
84-
wp_enqueue_script( 'datakit/dataview' );
85-
wp_enqueue_style( 'datakit/dataview' );
86-
8796
try {
8897
$dataview = $this->data_view_repository->get( $id );
89-
$js = sprintf( 'datakit_dataviews["%s"] = %s;', esc_attr( $id ), $dataview->to_js() );
90-
$js = str_replace( '{REST_ENDPOINT}', Router::get_url(), $js );
98+
if ( ! $this->access_controller->can( new Capability\ViewDataView( $dataview ) ) ) {
99+
return '';
100+
}
101+
102+
wp_enqueue_script( 'datakit/dataview' );
103+
wp_enqueue_style( 'datakit/dataview' );
104+
105+
$js = sprintf( 'datakit_dataviews["%s"] = %s;', esc_attr( $id ), $dataview->to_js() );
106+
$js = str_replace( '{REST_ENDPOINT}', Router::get_url(), $js );
91107
} catch ( DataViewException $e ) {
92108
return '';
93109
}
@@ -119,9 +135,12 @@ function () use ( $js ) {
119135
*
120136
* @return self The singleton.
121137
*/
122-
public static function get_instance( DataViewRepository $data_view_repository ): self {
138+
public static function get_instance(
139+
DataViewRepository $data_view_repository,
140+
AccessController $access_controller
141+
): self {
123142
if ( ! isset( self::$instance ) ) {
124-
self::$instance = new self( $data_view_repository );
143+
self::$instance = new self( $data_view_repository, $access_controller );
125144
}
126145

127146
return self::$instance;

src/DataKitPlugin.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace DataKit\Plugin;
44

5+
use DataKit\DataViews\AccessControl\AccessControlManager;
56
use DataKit\DataViews\DataView\DataView;
67
use DataKit\DataViews\DataView\DataViewRepository;
78
use DataKit\DataViews\DataView\Pagination;
9+
use DataKit\Plugin\AccessControl\WordPressAccessController;
810
use DataKit\Plugin\Rest\Router;
911
use DataKit\Plugin\Component\DataViewShortcode;
1012

@@ -41,8 +43,12 @@ final class DataKitPlugin {
4143
*/
4244
private function __construct( DataViewRepository $data_view_repository ) {
4345
$this->data_view_repository = $data_view_repository;
46+
47+
do_action( 'datakit/loading' );
48+
49+
AccessControlManager::set( new WordPressAccessController( wp_get_current_user() ) );
4450
Router::get_instance( $this->data_view_repository );
45-
DataViewShortcode::get_instance( $this->data_view_repository );
51+
DataViewShortcode::get_instance( $this->data_view_repository, AccessControlManager::current() );
4652

4753
/**
4854
* Modifies the default amount of results per page.
@@ -59,6 +65,8 @@ private function __construct( DataViewRepository $data_view_repository ) {
5965
add_action( 'datakit/dataview/register', [ $this, 'register_data_view' ] );
6066
add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] );
6167
add_action( 'admin_enqueue_scripts', [ $this, 'register_scripts' ] );
68+
69+
do_action( 'datakit/loaded' );
6270
}
6371

6472
/**
@@ -133,8 +141,6 @@ public function register_scripts(): void {
133141
public static function get_instance( DataViewRepository $repository ): self {
134142
if ( ! isset( self::$instance ) ) {
135143
self::$instance = new self( $repository );
136-
137-
do_action( 'datakit/loaded' );
138144
}
139145

140146
return self::$instance;

src/Rest/Router.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace DataKit\Plugin\Rest;
44

5+
use DataKit\DataViews\AccessControl\AccessControlManager;
56
use DataKit\DataViews\Data\MutableDataSource;
67
use DataKit\DataViews\DataView\DataViewRepository;
78
use DataKit\DataViews\Translation\Translatable;
@@ -69,10 +70,13 @@ final class Router {
6970
private function __construct( DataViewRepository $data_view_repository ) {
7071
$this->data_view_repository = $data_view_repository;
7172
$this->translator = new WordPressTranslator();
72-
$this->view_controller = new ViewController( $data_view_repository, $this->translator );
73+
$this->view_controller = new ViewController(
74+
$data_view_repository,
75+
AccessControlManager::current(),
76+
$this->translator,
77+
);
7378

74-
// @phpstan-ignore return.missing
75-
add_filter( 'rest_api_init', [ $this, 'register_routes' ] );
79+
add_action( 'rest_api_init', [ $this, 'register_routes' ] );
7680
}
7781

7882
/**

0 commit comments

Comments
 (0)