How To Restrict User to Self Posts in WordPress

I recently had to work with a third-party integration that used the WordPress REST API to interact with a website. We use this tool internally to move data around from other integrations, and finally into WordPress.

One of the things that I was worried about was the fact that this plugin would then have full access to the website, which is a private site. We only wanted to use it to post and then update WordPress posts, but there was always the concern that if the third party were to be hacked, then someone could read all of our posts on the private site through the REST API.

My solution was to hook into user_has_cap so that the user that was set up for the plugin to integrate with, through an application password, would only have access to read and edit its own posts. A bonus is that we wanted to be able to change the author of a post so that it would show up as specific users or project owners. That meant the authorized plugin user would also need access to these posts after the author was changed–so to get past that I scoured each post revision, and if the plugin user was the author of a revision it was also allowed access.

Finally, to make sure no other published posts were readable, I hooked into posts_results to set any post that didn’t meat the above criteria were marked as private. Below is a cleaned up version of that as an example if anyone else needs this type of functionality–feel free to use it as a starting point:

<?php
/**
 * Restricts post capabilities for a specific user.
 *
 * @param bool[]   $allcaps The current user capabilities.
 * @param string[] $caps    The requested capabilities.
 * @param array    $args {
 *     Arguments that accompany the requested capability check.
 *
 *     @type string    $0 Requested capability.
 *     @type int       $1 Concerned user ID.
 *     @type mixed  ...$2 Optional second and further parameters, typically object ID.
 * }
 * @param WP_User  $user    The user object.
 *
 * @return bool[] The modified user capabilities.
 */
function emrikol_restrict_post_capabilities( array $allcaps, array $caps, array $args, WP_User $user ): array {
	$post_id = isset( $args[2] ) ? absint( $args[2] ) : false;

	if ( false === $post_id || ! get_post( $post_id ) ) {
		return $allcaps;
	}

	if ( 'restricted' === get_user_meta( $user->ID, 'emrikol_restricted_post_capabilities', true ) ) {
		$allowed_caps  = array( 'read', 'read_private_posts', 'read_post', 'edit_post', 'delete_post', 'edit_others_posts', 'delete_others_posts' );
		$requested_cap = isset( $caps[0] ) ? $caps[0] : '';

		if ( in_array( $requested_cap, $allowed_caps, true ) ) {
			if ( emrikol_user_is_author_or_revision_author( $user->ID, $post_id ) ) {
				$allcaps[ $requested_cap ] = true;
			} else {
				$allcaps[ $requested_cap ] = false;
			}
		}
	}

	return $allcaps;
}
add_filter( 'user_has_cap', 'emrikol_restrict_post_capabilities', 10, 4 );

/**
 * Restricts the public posts results based on the query.
 *
 * @param WP_Post[] $posts  The array of posts returned by the query.
 * @param WP_Query  $query  The WP_Query instance (passed by reference).
 *
 * @return array           The filtered array of posts.
 */
function emrikol_restrict_public_posts_results( array $posts, WP_Query $query ): array {
	if ( ! is_admin() && $query->is_main_query() ) {
		$current_user = wp_get_current_user();

		if ( 'restricted' === get_user_meta( $user->ID, 'emrikol_restricted_post_capabilities', true ) ) {
			foreach ( $posts as $key => $post ) {
				if ( ! emrikol_user_is_author_or_revision_author( $current_user->ID, $post->ID ) ) {
					$posts[ $key ]->post_status = 'private';
				}
			}
		}
	}

	return $posts;
}
add_filter( 'posts_results', 'emrikol_restrict_public_posts_results', 10, 2 );

/**
 * Checks if the user is the author of the post or the author of a revision.
 *
 * @param int $user_id The ID of the user.
 * @param int $post_id The ID of the post.
 *
 * @return bool True if the user is the author or revision author, false otherwise.
 */
function emrikol_user_is_author_or_revision_author( int $user_id, int $post_id ): bool {
	$post_author_id = (int) get_post_field( 'post_author', $post_id );

	if ( $user_id === $post_author_id ) {
		return true;
	}

	$revisions = wp_get_post_revisions( $post_id );

	foreach ( $revisions as $revision ) {
		if ( $user_id === $revision->post_author ) {
			return true;
		}
	}

	return false;
}
Code language: PHP (php)

Other Posts Not Worth Reading

Hey, You!

Like this kind of garbage? Subscribe for more! I post like once a month or so, unless I found something interesting to write about.


Comments

Leave a Reply