Add user endpoints #146

Merged
merged 45 commits into from Apr 30, 2014

3 participants

@rmccue
WordPress REST API Team member

This is a continuation of #128 with some cleanups, and adding extra functionality.

Insanely huge props to @tobych on this one; he's done almost all of the heavy lifting here.

Things left to do:

  • Implement /users/me
  • Unify user update/insertion code
  • Add missing fields to update_user
  • Audit everything for security
  • Replace Posts::prepare_author()

Will fix #20.

tobych and others added some commits Mar 29, 2014
@tobych tobych Add User endpoint (no user_meta handling) 2bbb1af
@tobych tobych Handle POST to create a user 39d1b32
@tobych tobych Add calls to apply_filters for update and create 7c43da7
@tobych tobych Fix HTTP status code usage; use constants for them
Also updates some error messages
b1044c4
@tobych tobych Use absint() instead of (int) throughout 98375ac
@tobych tobych Remove redundant $type parameter to get_users 22fc082
@tobych tobych Remove unused Last-Modified code in get_user() 1de6ea7
@tobych tobych Fix null response from DELETE (and do what Post endpoint does) 3a33c08
@tobych tobych Fix broken build (PHP 5.2.17) by moving const's into class fef2b7f
@tobych tobych Check more appropriate capabilities, including meta caps
Also, make error messages less apologetic. Sorry about that.
5866f8f
@rmccue rmccue Reformat some control statements and blocks fad81d4
@rmccue rmccue registerRoutes -> register_routes 724603e
@rmccue rmccue Use consistent error messages
This brings permissions error messages in line with the post endpoints.
8d3b6ef
@rmccue rmccue Check permissions on user creation d59579f
@rmccue rmccue Rework some control flows 7b9b2a6
@rmccue rmccue Remove some redundant comments 6de2a84
@rmccue rmccue Use the result of json_pre_insert_data 616f24a
@rmccue rmccue Correct hook name 56d45fd
@rmccue rmccue Add json_prepare_user filter cc96ba4
@rmccue rmccue Don't double-check user ID
get_userdata will check if the user ID is valid, let's not second guess
this ourselves.
398cd53
@rmccue rmccue Pass get_users $filter/$page into WP_User_Query() dcc8f14
@rmccue rmccue Remove excess comments 7d5c391
@rmccue rmccue Add caps/roles to data returned from prepare_user 99a6e28
@rmccue rmccue Add extra name fields to prepare_user 0b92ba1
@rmccue rmccue Add user description to prepare_user 299cea3
@rmccue rmccue Ensure meta is always last e32e664
@rmccue rmccue Don't reveal whether users exist before checking permissions 80db2b5
@rmccue rmccue Ensure all methods have visibility specified d69b983
@rmccue rmccue Remove HttpStatusCode class (enum)
I like the concept, but it doesn't belong in this PR.
034d1ca
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+
+ $user = get_userdata( $id );
+
+ if ( ! $user ) {
+ return new WP_Error( 'json_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 400 ) );
+ }
+
+ // https://codex.wordpress.org/Function_Reference/wp_delete_user
+ // TODO: Allow posts to be reassigned (see the docs for wp_delete_user) - use a HTTP parameter?
+ $result = wp_delete_user( $id );
+
+ if ( ! $result ) {
+ return new WP_Error( 'json_cannot_delete', __( 'The user cannot be deleted.' ), array( 'status' => 500 ) );
+ }
+ else {
+ // "TODO: return a HTTP 202 here instead"... says the Post endpoint... really? Inappropriate (says tobych)?
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

@tobych Just FYI: the reason the comment is here in the post endpoint is because it's a different check there. In Post::delete_post, this branch is for trashed posts; a 202 would indicate that the request to delete has been received, but the post is still available, just in a trashed state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue
WordPress REST API Team member

Looking good so far @tobych, mostly just bringing it in line with all the rest.

Will attack the rest of the bullet points above ASAP.

@rmccue rmccue added this to the 1.0 milestone Apr 20, 2014
@rmccue rmccue added the Enhancement label Apr 20, 2014
@rmccue rmccue self-assigned this Apr 20, 2014
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-server.php
@@ -295,16 +295,8 @@ public function get_routes() {
'/' => array( array( $this, 'get_index' ), self::READABLE ),
// Users
- '/users' => array(
- array( '__return_null', self::READABLE ),
- array( '__return_null', self::CREATABLE | self::ACCEPT_JSON ),
- ),
// /users/me is an alias, and simply redirects to /users/<id>
'/users/me' => array( '__return_null', self::ALLMETHODS ),
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

Still need to handle this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+<?php
+
+// These are the relevant capabilities we can use:
+// https://codex.wordpress.org/Roles_and_Capabilities
+// https://codex.wordpress.org/Function_Reference/map_meta_cap
+// edit_users - 2.0
+// edit_user (meta)
+// delete_users - 2.1
+// delete_user (meta)
+// remove_users - 3.0 (what's the difference?)
+// remove_user (meta)
+// create_users - 2.1
+// list_users - 3.0
+// add_users - 3.0
+// promote_users - 3.0 (this is about changing a users's level... not sure it's relevant to roles/caps)
+// promote_user (meta)
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

To be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on the diff Apr 20, 2014
lib/class-wp-json-users.php
+ // Pagination
+ $args['number'] = empty( $args['number'] ) ? 10 : absint( $args['number'] );
+ $page = absint( $page );
+ $args['offset'] = ( $page - 1 ) * $args['number'];
+
+ $user_query = new WP_User_Query( $args );
+ if ( empty( $user_query->results ) ) {
+ return array();
+ }
+
+ $struct = array();
+ foreach ( $user_query->results as $user ) {
+ $struct[] = $this->prepare_user( $user, $context );
+ }
+
+ return $struct;
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

Should include pagination headers here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+ $user = get_userdata( $id );
+ if ( ! $user ) {
+ return new WP_Error( 'json_user_invalid_id', __( 'User ID is invalid.' ), array( 'status' => 400 ) );
+ }
+
+ // Update attributes of the user from $data
+ $retval = $this->update_user( $user, $data );
+ if ( is_wp_error( $retval ) ) {
+ return $retval;
+ }
+
+ // Pre-update hook
+ $user = apply_filters( 'json_pre_update_user', $user, $id, $data, $_headers );
+
+ // Update the user in the database
+ // http://codex.wordpress.org/Function_Reference/wp_update_user
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

To be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+ return $retval;
+ }
+
+ do_action( 'json_insert_user', $user, $data, true ); // $update is always true
+
+ return $this->get_user( $id );
+ }
+
+ /**
+ * Create a new user.
+ *
+ * @param $data
+ * @return mixed
+ */
+ public function new_user( $data ) {
+ # TODO: Use WP_User here, and refactor so we're sharing code with edit_user
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

To be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+ $userdata['display_name'] = $data['name'];
+ }
+ if ( ! empty( $data['URL'] ) ) {
+ $userdata['user_url'] = $data['URL'];
+ }
+
+
+ // Pre-insert hook
+ // TODO: Or json_pre_insert_user? Or insert rather than create? "Insert" seems to mean create or edit in WP...?
+ $userdata = apply_filters( 'json_pre_create_user', $userdata, $data );
+ if ( is_wp_error( $userdata ) ) {
+ return $userdata;
+ }
+
+ $user_id = wp_insert_user( $userdata );
+ // TODO: Send appropriate HTTP error codes along with the JSON rendering of the WP_Error we send back
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

To be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+ }
+ if ( ! empty( $data['URL'] ) ) {
+ $userdata['user_url'] = $data['URL'];
+ }
+
+
+ // Pre-insert hook
+ // TODO: Or json_pre_insert_user? Or insert rather than create? "Insert" seems to mean create or edit in WP...?
+ $userdata = apply_filters( 'json_pre_create_user', $userdata, $data );
+ if ( is_wp_error( $userdata ) ) {
+ return $userdata;
+ }
+
+ $user_id = wp_insert_user( $userdata );
+ // TODO: Send appropriate HTTP error codes along with the JSON rendering of the WP_Error we send back
+ // TODO: I guess we can just add/overwrite the 'status' code in there ourselves... nested WP_Error?
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

I like the thinking here @tobych; this is something we should consider across the project whenever we get a WP_Error back from a core function.

@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 29, 2014

Splitting this into a separate issue: #153

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+ if ( empty( $id ) ) {
+ return new WP_Error( 'json_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 400 ) );
+ }
+
+ // Permissions check
+ if ( ! current_user_can( 'delete_user', $id ) ) {
+ return new WP_Error( 'json_user_cannot_delete', __( 'Sorry, you are not allowed to delete this user.' ), array( 'status' => 403 ) );
+ }
+
+ $user = get_userdata( $id );
+
+ if ( ! $user ) {
+ return new WP_Error( 'json_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 400 ) );
+ }
+
+ // https://codex.wordpress.org/Function_Reference/wp_delete_user
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

To be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+ return new WP_Error( 'json_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 400 ) );
+ }
+
+ // Permissions check
+ if ( ! current_user_can( 'delete_user', $id ) ) {
+ return new WP_Error( 'json_user_cannot_delete', __( 'Sorry, you are not allowed to delete this user.' ), array( 'status' => 403 ) );
+ }
+
+ $user = get_userdata( $id );
+
+ if ( ! $user ) {
+ return new WP_Error( 'json_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 400 ) );
+ }
+
+ // https://codex.wordpress.org/Function_Reference/wp_delete_user
+ // TODO: Allow posts to be reassigned (see the docs for wp_delete_user) - use a HTTP parameter?
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

Hmm, good question.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+ }
+ else {
+ return array( 'message' => __( 'Deleted user' ) );
+ }
+ }
+
+ /**
+ *
+ * Update a WP_User instance from a User entity.
+ *
+ * @param WP_User $user
+ * @param $data
+ */
+ protected function update_user( $user, $data ) {
+
+ // Won't let them update these fields: ID, login, pass, registered (silently ignored)
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

To be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+ else {
+ return array( 'message' => __( 'Deleted user' ) );
+ }
+ }
+
+ /**
+ *
+ * Update a WP_User instance from a User entity.
+ *
+ * @param WP_User $user
+ * @param $data
+ */
+ protected function update_user( $user, $data ) {
+
+ // Won't let them update these fields: ID, login, pass, registered (silently ignored)
+ // TODO: Raise an exception if they try to update those? Always ignore ID though.
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

Good thinking; we've had this problem with people using the content field on Posts::edit_post because it's silently ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+
+ /**
+ *
+ * Update a WP_User instance from a User entity.
+ *
+ * @param WP_User $user
+ * @param $data
+ */
+ protected function update_user( $user, $data ) {
+
+ // Won't let them update these fields: ID, login, pass, registered (silently ignored)
+ // TODO: Raise an exception if they try to update those? Always ignore ID though.
+
+ // Note that you can pass wp_update_user() an array of fields to
+ // update; we won't bother using it as they don't match the User entity
+ // and it's just one more level of indirection to maintain.
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

Will probably need to in order to keep code consistent between insert/update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+
+ // There are more fields in WP_User we might
+
+ if ( ! empty( $data['name'] ) ) {
+ $user->display_name = $data[ 'name' ];
+ }
+ if ( ! empty( $data['slug'] ) ) {
+ $user->user_nicename = $data[ 'slug' ];
+ }
+ if ( ! empty( $data['URL'] ) ) {
+ $user->user_url = $data[ 'URL' ];
+ }
+ // ignore avatar - read-only
+ // ignore username - can't change this
+ if ( ! empty( $data['email'] ) ) {
+ $user->user_email = $data['email'];
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

This should be validated for is_email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rmccue rmccue commented on an outdated diff Apr 20, 2014
lib/class-wp-json-users.php
+ // and it's just one more level of indirection to maintain.
+
+ // https://github.com/WP-API/WP-API/blob/master/docs/schema.md#user
+ // http://codex.wordpress.org/Class_Reference/WP_User
+ // http://wpsmith.net/2012/wp/an-introduction-to-wp_user-class/
+
+ // There are more fields in WP_User we might
+
+ if ( ! empty( $data['name'] ) ) {
+ $user->display_name = $data[ 'name' ];
+ }
+ if ( ! empty( $data['slug'] ) ) {
+ $user->user_nicename = $data[ 'slug' ];
+ }
+ if ( ! empty( $data['URL'] ) ) {
+ $user->user_url = $data[ 'URL' ];
@rmccue
WordPress REST API Team member
rmccue added a line comment Apr 20, 2014

This should be validated using parse_url

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
rmccue added some commits Apr 29, 2014
@rmccue rmccue Add /users/me endpoint
This returns the data for the current user, and also issues a 302
redirect to the current user's endpoint permalink (e.g. /users/42)
881cb5e
@rmccue rmccue Allow reassigning posts when deleting a user a4352b4
@rmccue
WordPress REST API Team member

@rachelbaker #reviewmerge :)

@rachelbaker
WordPress REST API Team member

@rmccue This all works great! Merging into trunk.

@rachelbaker rachelbaker merged commit b3b79d3 into master Apr 30, 2014

1 check passed

Details continuous-integration/travis-ci The Travis CI build passed
@rachelbaker rachelbaker deleted the user-endpoints branch May 1, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment