Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions inc/admin/class-listtable.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function get_columns() {
$c = [
'cb' => '<input type="checkbox" />',
'name' => __( 'Name', 'oauth2' ),
'approved' => __( 'Approved', 'oauth2' ),
'description' => __( 'Description', 'oauth2' ),
];

Expand Down Expand Up @@ -103,17 +104,8 @@ protected function column_name( $item ) {
];

$post_type_object = get_post_type_object( $item->post_type );
if ( current_user_can( $post_type_object->cap->publish_posts ) && 'publish' !== $item->post_status ) {
$publish_link = add_query_arg(
[
'page' => 'rest-oauth2-apps',
'action' => 'approve',
'id' => $item->ID,
],
admin_url( 'users.php' )
);

$publish_link = wp_nonce_url( $publish_link, 'rest-oauth2-approve:' . $item->ID );
if ( current_user_can( $post_type_object->cap->publish_posts ) && in_array( $item->post_status, Client::UNAPPROVED_STATUSES, true ) ) {
$publish_link = get_approve_url( $item );
$actions['app-approve'] = sprintf(
'<a href="%s">%s</a>',
esc_url( $publish_link ),
Expand All @@ -128,14 +120,27 @@ protected function column_name( $item ) {
_post_states( $item );
$title = sprintf(
'<strong><a href="%s">%s</a>%s</strong>',
$edit_link,
esc_url( $edit_link ),
$title,
ob_get_clean()
);

return $title . ' ' . $action_html;
}

/**
* Print whether the client is approved.
*
* @param \WP_Post $item Post object.
*/
public function column_approved( $item ) {
if ( 'publish' === $item->post_status ) {
esc_html_e( 'Yes', 'oauth2' );
} else {
esc_html_e( 'No', 'oauth2' );
}
}

/**
* @param \WP_Post $item Post object.
* @return string Content of the column.
Expand Down
81 changes: 80 additions & 1 deletion inc/admin/namespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use WP\OAuth2\Client;
use WP_Error;
use WP_Post;

const BASE_SLUG = 'rest-oauth2-apps';

Expand Down Expand Up @@ -46,6 +47,26 @@ function get_url( $params = [] ) {
return add_query_arg( urlencode_deep( $params ), $url );
}

/**
* Get the URL that approves a client.
*
* @param WP_Post|int $client_post Client post or post ID.
* @param array|string $params Extra query parameters, such as `return=edit`.
*
* @return string Approve URL including the nonce.
*/
function get_approve_url( $client_post, $params = [] ) {
$id = $client_post instanceof WP_Post ? $client_post->ID : absint( $client_post );
$url = get_url(
[
'action' => 'approve',
'id' => $id,
] + wp_parse_args( $params )
);

return wp_nonce_url( $url, 'rest-oauth2-approve:' . $id );
}

/**
* Get the current page action.
*
Expand Down Expand Up @@ -273,6 +294,41 @@ function handle_edit_submit( ?Client $consumer = null ) {
exit;
}

/**
* Output the approval status notice for a client, with an approve button where allowed.
*
* @param WP_Post|int $client_post Client post or post ID.
*/
function render_approval_status( $client_post ) {
$client_post = get_post( $client_post );
if ( ! $client_post ) {
return;
}

if ( 'publish' === $client_post->post_status ) {
echo '<div class="notice notice-success inline"><p>' . esc_html__( 'This application is approved.', 'oauth2' ) . '</p></div>';
return;
}

if ( ! in_array( $client_post->post_status, Client::UNAPPROVED_STATUSES, true ) ) {
return;
}

$post_type_object = get_post_type_object( Client::POST_TYPE );
?>
<div class="notice notice-warning inline">
<p><?php esc_html_e( 'This application is not approved yet. It cannot be used to authorize users until it is approved.', 'oauth2' ); ?></p>
<p>
<?php if ( current_user_can( $post_type_object->cap->publish_posts ) ) : ?>
<a href="<?php echo esc_url( get_approve_url( $client_post, [ 'return' => 'edit' ] ) ); ?>" class="button button-primary"><?php esc_html_e( 'Approve', 'oauth2' ); ?></a>
<?php else : ?>
<?php esc_html_e( 'Ask an administrator to approve it.', 'oauth2' ); ?>
<?php endif; ?>
</p>
</div>
<?php
}

/**
* Output alias editing page
*/
Expand All @@ -288,7 +344,7 @@ function render_edit_page() {
if ( ! empty( $_REQUEST['id'] ) ) {
$id = absint( $_REQUEST['id'] );
$consumer = Client::get_by_post_id( $id );
if ( is_wp_error( $consumer ) || empty( $consumer ) ) {
if ( is_wp_error( $consumer ) || empty( $consumer ) || get_post_type( $id ) !== Client::POST_TYPE ) {
wp_die( esc_html__( 'Invalid client ID.', 'oauth2' ) );
}

Expand Down Expand Up @@ -378,6 +434,12 @@ function render_edit_page() {
}
?>

<?php
if ( $consumer ) {
render_approval_status( get_post( $consumer->get_post_id() ) );
}
?>

<form method="post" action="<?php echo esc_url( $form_action ); ?>">
<table class="form-table">
<tr>
Expand Down Expand Up @@ -589,6 +651,11 @@ function handle_approve() {
);
}

$post = get_post( $id );
if ( ! $post || Client::POST_TYPE !== $post->post_type || ! in_array( $post->post_status, Client::UNAPPROVED_STATUSES, true ) ) {
wp_die( esc_html__( 'This application cannot be approved.', 'oauth2' ), '', [ 'response' => 400 ] );
}

$client = Client::get_by_post_id( $id );
if ( is_wp_error( $client ) ) {
wp_die( $client ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
Expand All @@ -599,6 +666,18 @@ function handle_approve() {
wp_die( $did_approve ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

if ( isset( $_GET['return'] ) && 'edit' === $_GET['return'] ) {
wp_safe_redirect(
get_url(
[
'action' => 'edit',
'id' => $id,
]
)
);
exit;
}

wp_safe_redirect( get_url( 'approved=1' ) );
exit;
}
Expand Down
31 changes: 26 additions & 5 deletions inc/class-client.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

class Client implements ClientInterface {
const POST_TYPE = 'oauth2_client';
const UNAPPROVED_STATUSES = [ 'draft', 'pending' ];
const CLIENT_SECRET_KEY = '_oauth2_client_secret';
const TYPE_KEY = '_oauth2_client_type';
const REDIRECT_URI_KEY = '_oauth2_redirect_uri';
Expand Down Expand Up @@ -61,6 +62,15 @@ public function get_post_id() {
return $this->post->ID;
}

/**
* Check whether the client is approved.
*
* @return bool True if the client is published.
*/
public function is_approved() {
return 'publish' === $this->post->post_status;
}

/**
* Get the client's name.
*
Expand Down Expand Up @@ -310,14 +320,20 @@ public function issue_token( WP_User $user, $meta = [] ) {
/**
* Get a client by ID.
*
* @param string $id Client ID.
* @param string $id Client ID.
* @param bool $include_unapproved Whether to also match draft and pending clients. Trashed clients never match.
*
* @return static|null Token if ID is found, null otherwise.
* @return static|null Client if ID is found, null otherwise.
*/
public static function get_by_id( $id ) {
public static function get_by_id( $id, $include_unapproved = false ) {
$statuses = [ 'publish' ];
if ( $include_unapproved ) {
$statuses = array_merge( $statuses, static::UNAPPROVED_STATUSES );
}

$args = [
'post_type' => static::POST_TYPE,
'post_status' => 'publish',
'post_status' => $statuses,
'posts_per_page' => 1,
'no_found_rows' => true,

Expand Down Expand Up @@ -455,8 +471,13 @@ public function approve() {
'post_status' => 'publish',
];
$result = wp_update_post( wp_slash( $data ), true );
if ( is_wp_error( $result ) ) {
return $result;
}

return is_wp_error( $result ) ? $result : true;
$this->post = get_post( $this->get_post_id() );

return true;
}

/**
Expand Down
35 changes: 33 additions & 2 deletions inc/endpoints/class-authorization.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,40 @@ public function handle_request() {
}

if ( is_wp_error( $result ) ) {
// TODO: Handle it.
wp_die( esc_html( $result->get_error_message() ) );
$this->render_error( $result );
}
exit;
}

/**
* Get the HTTP status for an error.
*
* @param WP_Error $error Error to read the `status` error data from.
* @return int Status code, 400 if the error does not set one.
*/
public function get_error_status( WP_Error $error ) {
$data = $error->get_error_data();

return is_array( $data ) && ! empty( $data['status'] ) ? (int) $data['status'] : 400;
}

/**
* Render an authorization error on the login screen, then exit.
*
* @param WP_Error $error Error to display. The `status` error data sets the HTTP status.
*/
protected function render_error( WP_Error $error ) {
// login_header() prints error messages as HTML, so escape them first.
$escaped = new WP_Error();
foreach ( $error->get_error_codes() as $code ) {
foreach ( $error->get_error_messages( $code ) as $message ) {
$escaped->add( $code, esc_html( $message ) );
}
}

status_header( $this->get_error_status( $error ) );
login_header( __( 'Authorization error', 'oauth2' ), '', $escaped );
login_footer();
exit;
}
}
13 changes: 12 additions & 1 deletion inc/types/class-base.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function handle_authorisation() {
// State is opaque to the server and must be returned to the client unchanged.
$state = isset( $_GET['state'] ) && is_string( $_GET['state'] ) ? wp_unslash( $_GET['state'] ) : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

$client = Client::get_by_id( $client_id );
$client = Client::get_by_id( $client_id, true );
if ( empty( $client ) ) {
return new WP_Error(
'oauth2.types.authorization_code.handle_authorisation.invalid_client_id',
Expand All @@ -60,6 +60,17 @@ public function handle_authorisation() {
);
}

if ( ! $client->is_approved() ) {
return new WP_Error(
'oauth2.types.authorization_code.handle_authorisation.unapproved_client',
__( 'This application is not currently approved. Ask the site administrator to approve it.', 'oauth2' ),
[
'status' => WP_Http::FORBIDDEN,
'client_id' => $client_id,
]
);
}

// Validate the redirection URI.
$redirect_uri = $this->validate_redirect_uri( $client, $redirect_uri );
if ( is_wp_error( $redirect_uri ) ) {
Expand Down
9 changes: 6 additions & 3 deletions tests/class-test-case.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
abstract class Test_Case extends WP_UnitTestCase {

/**
* Create and approve a test client.
* Create and optionally approve a test client.
*
* @param array $meta_overrides Optional overrides for the client meta.
* @param string $name Client name.
* @param bool $approve Whether to approve the client.
*
* @return Client
*/
protected function create_client( array $meta_overrides = [], $name = 'Test Client' ) {
protected function create_client( array $meta_overrides = [], $name = 'Test Client', $approve = true ) {
$data = [
'name' => $name,
'description' => 'Test client description.',
Expand All @@ -39,7 +40,9 @@ protected function create_client( array $meta_overrides = [], $name = 'Test Clie

$client = Client::create( $data );
$this->assertInstanceOf( Client::class, $client, 'create_client helper: Client::create() must return a Client instance.' );
$client->approve();
if ( $approve ) {
$client->approve();
}

return $client;
}
Expand Down
Loading
Loading