├── assets
├── icon-128x128.png
├── icon-256x256.png
├── banner-1544x500.png
└── banner-772x250.png
├── docs
├── advanced
│ ├── README.md
│ ├── Desktop.md
│ └── Web.md
├── introduction
│ ├── README.md
│ ├── Setup.md
│ ├── OAuth.md
│ └── OAuth-1.md
├── basics
│ ├── README.md
│ ├── Registering.md
│ ├── Auth-Flow.md
│ └── Signing.md
├── README.md
└── spec.md
├── inc
├── class-scopes.php
├── tokens
│ ├── namespace.php
│ ├── class-token.php
│ ├── class-access-token.php
│ └── class-authorization-code.php
├── types
│ ├── class-type.php
│ ├── class-implicit.php
│ ├── class-authorization-code.php
│ └── class-base.php
├── endpoints
│ ├── namespace.php
│ ├── class-authorization.php
│ └── class-token.php
├── class-clientinterface.php
├── admin
│ ├── class-listtable.php
│ ├── profile
│ │ ├── personaltokens
│ │ │ └── namespace.php
│ │ └── namespace.php
│ └── namespace.php
├── namespace.php
├── class-personalclient.php
├── authentication
│ └── namespace.php
└── class-client.php
├── book.json
├── .gitignore
├── phpunit.xml.dist
├── bin
├── readme.txt
├── release.sh
└── install-wp-tests.sh
├── composer.json
├── tests
├── bootstrap.php
└── install-tests.sh
├── .travis.yml
├── .phpcs.xml.dist
├── README.md
├── plugin.php
├── theme
└── oauth2-authorize.php
└── LICENSE.txt
/assets/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WP-API/OAuth2/HEAD/assets/icon-128x128.png
--------------------------------------------------------------------------------
/assets/icon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WP-API/OAuth2/HEAD/assets/icon-256x256.png
--------------------------------------------------------------------------------
/assets/banner-1544x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WP-API/OAuth2/HEAD/assets/banner-1544x500.png
--------------------------------------------------------------------------------
/assets/banner-772x250.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WP-API/OAuth2/HEAD/assets/banner-772x250.png
--------------------------------------------------------------------------------
/docs/advanced/README.md:
--------------------------------------------------------------------------------
1 | # Advanced
2 |
3 | * [Desktop/Mobile Clients](Desktop.md)
4 | * [Web Clients](Web.md)
5 |
--------------------------------------------------------------------------------
/docs/introduction/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | * [Why OAuth?](OAuth.md)
4 | * [Why OAuth 1.0a?](OAuth-1.md)
5 | * [Setup](Setup.md)
--------------------------------------------------------------------------------
/docs/basics/README.md:
--------------------------------------------------------------------------------
1 | # Basics
2 |
3 | * [Registering an Application](Registering.md)
4 | * [The Authorization Flow](Auth-Flow.md)
5 | * [Signing Requests](Signing.md)
--------------------------------------------------------------------------------
/inc/class-scopes.php:
--------------------------------------------------------------------------------
1 | capabilities = [];
15 | }
16 |
17 | public function register( $id, $capabilities ) {
18 | $this->scopes[ $id ] = $capabilities;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/inc/tokens/namespace.php:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 | ./tests/
13 | ./tests/test-sample.php
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/docs/introduction/Setup.md:
--------------------------------------------------------------------------------
1 | # Setup
2 |
3 | Before you can connect to your site with OAuth, you'll need to get it set up.
4 |
5 | Right now, you'll need to install the plugin [from GitHub](https://github.com/WP-API/OAuth1). Inside your plugin directory, clone the plugin down:
6 |
7 | git clone https://github.com/WP-API/OAuth1
8 |
9 | Once you've done this, head to your WordPress dashboard and activate the plugin. You should see an "Applications" item appear under the users menu: this is where you manage OAuth clients.
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ## Table of Contents
2 |
3 | * [Start Here](/README.md)
4 | * [Introduction](/docs/introduction/README.md)
5 | * [Why OAuth?](/docs/introduction/OAuth.md)
6 | * [Why OAuth 1.0a?](/docs/introduction/OAuth-1.md)
7 | * [Setup](/docs/introduction/Setup.md)
8 | * [Basics](/docs/basics/README.md)
9 | * [Registering an Application](/docs/basics/Registering.md)
10 | * [The Authorization Flow](/docs/basics/Auth-Flow.md)
11 | * [Signing Requests](/docs/basics/Signing.md)
12 | * [Advanced](/docs/advanced/README.md)
13 | * [Desktop/Mobile Clients](/docs/advanced/Desktop.md)
14 | * [Web Clients](/docs/advanced/Web.md)
15 |
--------------------------------------------------------------------------------
/bin/readme.txt:
--------------------------------------------------------------------------------
1 | === WordPress REST API - OAuth 2 Server ===
2 | Contributors: rmccue, rachelbaker, danielbachhuber, joehoyle
3 | Tags: json, rest, api, rest-api
4 | Requires at least: 4.8
5 | Tested up to: 4.8
6 | Stable tag: {{TAG}}
7 | License: GPLv2 or later
8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html
9 |
10 | == Description ==
11 | Connect applications to your WordPress site without ever giving away your password.
12 |
13 | This plugin uses the OAuth 2 protocol to allow delegated authorization; that is, to allow applications to access a site using a set of secondary credentials. This allows server administrators to control which applications can access the site, as well as allowing users to control which applications have access to their data.
14 |
15 | This plugin only supports WordPress >= 4.8.
16 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wp-api/oauth2",
3 | "description": "OAuth 2 Server for WordPress",
4 | "type": "wordpress-plugin",
5 | "license": "GPL2+",
6 | "authors": [
7 | {
8 | "name": "WP-API Team",
9 | "homepage": "http://wp-api.org/"
10 | }
11 | ],
12 | "require": {
13 | "composer/installers": "~1.0",
14 | "php": "^5.6.0||^7.0||^8.0"
15 | },
16 | "require-dev": {
17 | "squizlabs/php_codesniffer": "^3.3.1",
18 | "wp-coding-standards/wpcs": "^2.1.1",
19 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
20 | "phpcompatibility/phpcompatibility-wp": "^2.0",
21 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0"
22 | },
23 | "scripts": {
24 | "post-install-cmd": "\"vendor/bin/phpcs\" --config-set installed_paths vendor/wp-coding-standards/wpcs",
25 | "post-update-cmd" : "\"vendor/bin/phpcs\" --config-set installed_paths vendor/wp-coding-standards/wpcs"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | register_routes();
20 |
21 | // Register convenience URL.
22 | register_rest_route(
23 | 'oauth2',
24 | '/authorize',
25 | [
26 | 'methods' => 'GET',
27 | 'callback' => __NAMESPACE__ . '\\redirect_to_authorize',
28 | ]
29 | );
30 | }
31 |
32 | /**
33 | * Handle authorize endpoint request.
34 | *
35 | * This endpoint exists as a convenience URL to avoid clients needing to find
36 | * wp-login.php.
37 | *
38 | * @param WP_REST_Request $request Request object.
39 | * @return WP_REST_Response Response object.
40 | */
41 | function redirect_to_authorize( WP_REST_Request $request ) {
42 | $url = OAuth2\get_authorization_url();
43 |
44 | $query = $request->get_query_params();
45 | if ( ! empty( $query ) ) {
46 | // Pass query arguments along.
47 | $url = add_query_arg(
48 | urlencode_deep( $query ),
49 | $url
50 | );
51 | }
52 |
53 | return new WP_REST_Response( [ 'url' => $url ], 302, [ 'Location' => $url ] );
54 | }
55 |
--------------------------------------------------------------------------------
/docs/introduction/OAuth.md:
--------------------------------------------------------------------------------
1 | # Why OAuth?
2 |
3 | When developing a REST API, there's no shortage of possible authentication options to connect to your site. These options span from simple username and password schemes up to much more complex systems. Why choose OAuth out of all of these?
4 |
5 | OAuth is built around a singular core concept: **delegated authorization**. Unlike traditional username and password systems, or even API keys, OAuth doesn't have a single set of credentials. Instead, it splits the concept of credentials into two: client credentials, and user tokens. Clients register with sites they want to access, but this doesn't give them any inherent access. Users then authorize the client to perform actions on their behalf.
6 |
7 | Decoupling these pieces gives better flexibility and security. If a client is compromised or accidentally leaks credentials, these can be revoked, disconnecting the client from all users. If a single user wants to disconnect the client, they can revoke the user token issued to the client. Combined, this gives both site owners and users control over their data.
8 |
9 | This also crucially avoids the anti-pattern of giving credentials to external applications. In particular, OAuth itself provides **no ability to exchange a username and password for a user token**. This reinforces that users should never give their username and password to other applications. This also helps mitigate phishing exploits.
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | dist: trusty
3 |
4 | language: php
5 |
6 | notifications:
7 | email:
8 | on_success: never
9 | on_failure: change
10 |
11 | branches:
12 | only:
13 | - master
14 |
15 | cache:
16 | directories:
17 | - $HOME/.composer/cache
18 |
19 | matrix:
20 | include:
21 | - php: 7.3
22 | env: WP_VERSION=latest
23 | - php: 7.2
24 | env: WP_VERSION=latest
25 | - php: 7.1
26 | env: WP_VERSION=latest
27 | - php: 7.0
28 | env: WP_VERSION=latest
29 | - php: 7.0
30 | env: WP_VERSION=trunk
31 | - php: 7.0
32 | env: WP_TRAVISCI=phpcs
33 | dist: precise
34 |
35 | before_script:
36 | - export PATH="$HOME/.composer/vendor/bin:$PATH"
37 | - composer install --ignore-platform-reqs --optimize-autoloader --no-interaction --prefer-dist
38 | - |
39 | if [ -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ]; then
40 | phpenv config-rm xdebug.ini
41 | else
42 | echo "xdebug.ini does not exist"
43 | fi
44 | - |
45 | if [[ ! -z "$WP_VERSION" ]] ; then
46 | bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
47 | fi
48 |
49 | script:
50 | - |
51 | if [[ ! -z "$WP_VERSION" ]] ; then
52 | vendor/bin/phpunit
53 | WP_MULTISITE=1 vendor/bin/phpunit
54 | fi
55 | - |
56 | if [[ "$WP_TRAVISCI" == "phpcs" ]] ; then
57 | vendor/bin/phpcs
58 | fi
59 |
--------------------------------------------------------------------------------
/docs/introduction/OAuth-1.md:
--------------------------------------------------------------------------------
1 | # Why OAuth 1.0a?
2 |
3 | If you've done research on OAuth, you might notice OAuth 2.0 exists. Why is the canonical authorization scheme using an older version of OAuth?
4 |
5 | OAuth has a long and storied history behind it. The OAuth concept was born from Twitter (and others) needing delegated authorization for user accounts, primarily for API access. This then continued evolving with feedback from other parties (such as Google), before eventually being standardised as OAuth 1.0a in [RFC 5849](https://tools.ietf.org/html/rfc5849). OAuth was then further evolved and simplified in OAuth 2.0, standardised as [RFC 6749](https://tools.ietf.org/html/rfc6749).
6 |
7 | The primary change from version 1 to 2 was the removal of the complicated signature system. This signature system was designed to ensure only the client can use the user tokens, since it relies on a shared secret. However, every request must be individually signed. Version 2 instead relies on SSL/TLS to handle message authenticity.
8 |
9 | This means that **OAuth 2.0 requires HTTPS**. WordPress however does not. We need to be able to provide authentication for all sites, not just those with HTTPS.
10 |
11 | With the impending changes to the HTTPS playing field with the Let's Encrypt certificate authority, we hope to be able to require SSL in the future and move to OAuth 2.0, but this is not yet feasible.
12 |
13 | (Note: While the OAuth RFC requires SSL for some endpoints, [OAuth 1.0a](http://oauth.net/core/1.0a/) does not. This is a willful violation of the RFC, as we need to support non-SSL sites.)
--------------------------------------------------------------------------------
/.phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 | Generally-applicable sniffs for WordPress plugins.
4 |
5 |
6 | .
7 | /vendor/
8 | /node_modules/
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/bin/release.sh:
--------------------------------------------------------------------------------
1 | # release.sh
2 | #
3 | # Takes a tag to release, and syncs it to WordPress.org
4 |
5 | TAG=$1
6 |
7 | PLUGIN="rest-api-oauth2"
8 | TMPDIR=/tmp/rest-api-oauth2-release-svn
9 | PLUGINDIR="$PWD"
10 | PLUGINSVN="https://plugins.svn.wordpress.org/$PLUGIN"
11 |
12 | # Fail on any error
13 | set -e
14 |
15 | # Is the tag valid?
16 | if [ -z "$TAG" ] || ! git rev-parse "$TAG" > /dev/null; then
17 | echo "Invalid tag. Make sure you tag before trying to release."
18 | exit 1
19 | fi
20 |
21 | if [[ $VERSION == "v*" ]]; then
22 | # Starts with an extra "v", strip for the version
23 | VERSION=${TAG:1}
24 | else
25 | VERSION="$TAG"
26 | fi
27 |
28 | if [ -d "$TMPDIR" ]; then
29 | # Wipe it clean
30 | rm -r "$TMPDIR"
31 | fi
32 |
33 | # Ensure the directory exists first
34 | mkdir "$TMPDIR"
35 |
36 | # Grab an unadulterated copy of SVN
37 | svn co "$PLUGINSVN/trunk" "$TMPDIR" > /dev/null
38 |
39 | # Extract files from the Git tag to there
40 | git archive --format="zip" -0 "$TAG" | tar -C "$TMPDIR" -xf -
41 |
42 | # Switch to build dir
43 | cd "$TMPDIR"
44 |
45 | # Run build tasks
46 | sed -e "s/{{TAG}}/$VERSION/g" < "$PLUGINDIR/bin/readme.txt" > readme.txt
47 |
48 | # Remove special files
49 | rm ".gitignore"
50 | rm "composer.json"
51 | rm "book.json"
52 | rm -r "bin"
53 | rm -r "docs"
54 |
55 | # Add any new files
56 | svn status | grep -v "^.[ \t]*\..*" | grep "^?" | awk '{print $2}' | xargs svn add
57 |
58 | # Pause to allow checking
59 | echo "About to commit $VERSION. Double-check $TMPDIR to make sure everything looks fine."
60 | read -p "Hit Enter to continue."
61 |
62 | # Commit the changes
63 | svn commit -m "Tag $VERSION"
64 |
65 | # tag_ur_it
66 | svn copy "$PLUGINSVN/trunk" "$PLUGINSVN/tags/$VERSION" -m "Tag $VERSION"
67 |
--------------------------------------------------------------------------------
/inc/endpoints/class-authorization.php:
--------------------------------------------------------------------------------
1 | get_response_type_code() === $type ) {
38 | $handler = $type_handler;
39 | break;
40 | }
41 | }
42 | }
43 |
44 | if ( empty( $handler ) ) {
45 | $result = new WP_Error(
46 | 'oauth2.endpoints.authorization.handle_request.invalid_type',
47 | __( 'Invalid response type specified.', 'oauth2' )
48 | );
49 | } else {
50 | $result = $handler->handle_authorisation();
51 | }
52 |
53 | if ( is_wp_error( $result ) ) {
54 | // TODO: Handle it.
55 | wp_die( esc_html( $result->get_error_message() ) );
56 | }
57 | exit;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OAuth 2.0 for WordPress
2 |
3 | Connect applications to your WordPress site without ever giving away your password.
4 |
5 | This plugin uses the OAuth 2 protocol to allow delegated authorization; that is, to allow applications to access a site using a set of secondary credentials. This allows server administrators to control which applications can access the site, as well as allowing users to control which applications have access to their data.
6 |
7 | This plugin only supports WordPress >= 4.8.
8 |
9 | ## Contributors Welcome!
10 |
11 | This plugin works and is in use in several production environments, but the user experience and documentation could be substantially improved. We welcome input and contributions to make this tool better!
12 |
13 |
14 | ## Credits
15 |
16 | This plugin is licensed under the GNU General Public License v2 or later:
17 |
18 | > Copyright 2017 by the contributors.
19 | >
20 | > This program is free software; you can redistribute it and/or modify
21 | > it under the terms of the GNU General Public License as published by
22 | > the Free Software Foundation; either version 2 of the License, or
23 | > (at your option) any later version.
24 | >
25 | > This program is distributed in the hope that it will be useful,
26 | > but WITHOUT ANY WARRANTY; without even the implied warranty of
27 | > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 | > GNU General Public License for more details.
29 | >
30 | > You should have received a copy of the GNU General Public License
31 | > along with this program; if not, write to the Free Software
32 | > Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
33 |
34 | Thanks to the contributors at the WCEU 2017 Contributor Day who were responsible for getting this plugin off the ground and into a usable state: @almirbi, @richardsweeney, @tfrommen.
35 |
--------------------------------------------------------------------------------
/inc/tokens/class-token.php:
--------------------------------------------------------------------------------
1 | user = $user;
36 | $this->key = $key;
37 | $this->value = $value;
38 | }
39 |
40 | /**
41 | * Get the ID for the user that the token represents.
42 | *
43 | * @return int
44 | */
45 | public function get_user_id() {
46 | return $this->user->ID;
47 | }
48 |
49 | /**
50 | * Get the user that the token represents.
51 | *
52 | * @return WP_User
53 | */
54 | public function get_user() {
55 | return $this->user;
56 | }
57 |
58 | /**
59 | * Get the meta prefix.
60 | *
61 | * @return string Meta prefix.
62 | */
63 | abstract protected function get_meta_prefix();
64 |
65 | /**
66 | * Check if the token is valid.
67 | *
68 | * @return bool True if the token is valid, false otherwise.
69 | */
70 | abstract public function is_valid();
71 |
72 | /**
73 | * Get the token's key.
74 | *
75 | * @return string Token
76 | */
77 | public function get_key() {
78 | return $this->key;
79 | }
80 |
81 | /**
82 | * Get the token's value.
83 | *
84 | * @return mixed Token value, specific to the token type.
85 | */
86 | public function get_value() {
87 | return $this->value;
88 | }
89 |
90 | /**
91 | * Get the meta key for the token.
92 | *
93 | * @return string Meta key, including type-specific prefix.
94 | */
95 | public function get_meta_key() {
96 | return $this->get_meta_prefix() . $this->get_key();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/inc/types/class-implicit.php:
--------------------------------------------------------------------------------
1 | issue_token( $user );
42 | if ( is_wp_error( $token ) ) {
43 | return $token;
44 | }
45 |
46 | $redirect_args = [
47 | 'access_token' => $token->get_key(),
48 | 'token_type' => 'bearer',
49 | ];
50 | break;
51 |
52 | case 'cancel':
53 | $redirect_args = [
54 | 'error' => 'access_denied',
55 | ];
56 | break;
57 |
58 | default:
59 | return new WP_Error(
60 | 'oauth2.types.authorization_code.handle_authorisation.invalid_action',
61 | __( 'Invalid form action.', 'oauth2' )
62 | );
63 | }
64 |
65 | if ( ! empty( $data['state'] ) ) {
66 | $redirect_args['state'] = $data['state'];
67 | }
68 |
69 | $redirect_args = $this->filter_redirect_args(
70 | $redirect_args,
71 | 'authorize' === $submit,
72 | $client,
73 | $data
74 | );
75 |
76 | $fragment = build_query( $redirect_args );
77 | $generated_redirect = $redirect_uri . '#' . $fragment;
78 | wp_safe_redirect( $generated_redirect );
79 | exit;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/inc/types/class-authorization-code.php:
--------------------------------------------------------------------------------
1 | generate_authorization_code( $user );
42 | if ( is_wp_error( $code ) ) {
43 | return $code;
44 | }
45 |
46 | $redirect_args = [
47 | 'code' => $code->get_code(),
48 | ];
49 | break;
50 |
51 | case 'cancel':
52 | $redirect_args = [
53 | 'error' => 'access_denied',
54 | ];
55 | break;
56 |
57 | default:
58 | return new WP_Error(
59 | 'oauth2.types.authorization_code.handle_authorisation.invalid_action',
60 | __( 'Invalid form action.', 'oauth2' )
61 | );
62 | }
63 |
64 | if ( ! empty( $data['state'] ) ) {
65 | $redirect_args['state'] = $data['state'];
66 | }
67 |
68 | $redirect_args = $this->filter_redirect_args(
69 | $redirect_args,
70 | 'authorize' === $submit,
71 | $client,
72 | $data
73 | );
74 |
75 | $generated_redirect = add_query_arg( urlencode_deep( $redirect_args ), $redirect_uri );
76 | // phpcs:ignore WordPress.Security.SafeRedirect -- Intentionally external redirect, secured via client registration.
77 | wp_redirect( $generated_redirect );
78 | exit;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/plugin.php:
--------------------------------------------------------------------------------
1 |
8 | * @copyright 2019 Squiz Pty Ltd (ABN 77 084 670 600)
9 | * @license GPL-2.0-or-later
10 | *
11 | * @oauth2
12 | * Plugin Name: OAuth 2 for WordPress
13 | * Plugin URI: https://github.com/WP-API/OAuth2
14 | * Description: Connect apps to your site using OAuth 2.
15 | * Version: 0.2.0
16 | * Author: WordPress Core Contributors (REST API Focus)
17 | * Author URI: https://make.wordpress.org/core/
18 | * License: GPL v2 or later
19 | * License URI: https://www.gnu.org/licenses/gpl-2.0.html
20 | * Text Domain: oauth2
21 | * Domain Path: /languages
22 | * Requires at least: 4.8
23 | * Requires PHP: 5.6
24 | */
25 |
26 | namespace WP\OAuth2;
27 |
28 | // Avoid loading twice if loaded via App Connect.
29 | if ( class_exists( 'WP\\OAuth2\\Client' ) ) {
30 | return;
31 | }
32 |
33 | require __DIR__ . '/inc/namespace.php';
34 | require __DIR__ . '/inc/class-clientinterface.php';
35 | require __DIR__ . '/inc/class-client.php';
36 | require __DIR__ . '/inc/class-personalclient.php';
37 | require __DIR__ . '/inc/class-scopes.php';
38 | require __DIR__ . '/inc/authentication/namespace.php';
39 | require __DIR__ . '/inc/endpoints/namespace.php';
40 | require __DIR__ . '/inc/endpoints/class-authorization.php';
41 | require __DIR__ . '/inc/endpoints/class-token.php';
42 | require __DIR__ . '/inc/tokens/namespace.php';
43 | require __DIR__ . '/inc/tokens/class-token.php';
44 | require __DIR__ . '/inc/tokens/class-access-token.php';
45 | require __DIR__ . '/inc/tokens/class-authorization-code.php';
46 | require __DIR__ . '/inc/types/class-type.php';
47 | require __DIR__ . '/inc/types/class-base.php';
48 | require __DIR__ . '/inc/types/class-authorization-code.php';
49 | require __DIR__ . '/inc/types/class-implicit.php';
50 | require __DIR__ . '/inc/admin/namespace.php';
51 | require __DIR__ . '/inc/admin/profile/namespace.php';
52 | require __DIR__ . '/inc/admin/profile/personaltokens/namespace.php';
53 |
54 | bootstrap();
55 |
--------------------------------------------------------------------------------
/docs/advanced/Desktop.md:
--------------------------------------------------------------------------------
1 | # Desktop/Mobile Clients
2 |
3 | OAuth was originally designed for web applications, so desktop and mobile clients may wish to use the OAuth flow slightly differently. The authorization flow for OAuth requires both a browser and a callback URL for the second leg.
4 |
5 |
6 | ## Callback URL Schemes
7 |
8 | The OAuth plugin supports any valid URL scheme, including custom schemes. This allows using custom schemes for callback URLs, which can then trigger your application. Note that for custom schemes, the authority (user and password) part must be empty, and the host **must not be empty** and must not contain invalid characters (such as `:#?[]`).
9 |
10 | For example, the following URLs are **invalid**:
11 | * `custom-app://`
12 | * `custom-app://?oauth_callback`
13 | * `custom-app://user:pass@oauth_callback`
14 |
15 | The following URLs are **valid**:
16 | * `custom-app://oauth_callback`
17 | * `custom-app://oauth?callback`
18 | * `custom-app://oauth/callback`
19 | * `custom-app://oauth_callback:42`
20 |
21 |
22 | ## Out-of-Band Flow
23 |
24 | For clients without the ability to handle a callback URL, an out-of-band flow can be used. Rather than redirecting the user after authorization, this flow displays the verifier token to the user to copy to the client.
25 |
26 | To trigger the out-of-band flow, the callback URL must be set to `oob`. After the user has authorized the application, they'll be redirected to an internal page on the site which displays the verifier token. This can either be copy-and-pasted into the client (e.g. for command-line applications), or typed in manually.
27 |
28 | With the callback URL set to `oob`, the supplied callback and registered callback must match exactly, and must be `oob`. This means that clients can either have a callback URL **or** out-of-band handling, and cannot work with both.
29 |
30 |
31 | ## Best Practices
32 |
33 | * Clients should use callback URLs if at all possible, with out-of-band flow as a last resort.
34 | * Clients should prefer the system browser rather a built-in browser, as the former typically has allows better usage of saved passwords. Using a built-in browser also gives a dangerous signal to users, as a compromised app could fake a login screen and phish their credentials.
35 |
--------------------------------------------------------------------------------
/docs/basics/Registering.md:
--------------------------------------------------------------------------------
1 | # Registering an Application
2 |
3 | Before you can talk to the server, you need to establish your credentials on the site. This involves registering your application with the site.
4 |
5 | Applications can only be registered by site administrators, and must be registered on each site individually. (We're working on making it possible in the future to register once with a central authority to make this process easier.)
6 |
7 | To register an application, open your site dashboard and head to Users > Applications, then click Add New. You'll need to enter the name of the application, an optional description, and the callback URL. This callback URL is used during the authorization process to redirect users back to after connecting. This URL can be changed later if you don't have a callback endpoint yet.
8 |
9 | ## Callback URLs
10 |
11 | The callback URL is used during the authorization process. After users authorize your application on the site, they'll be redirected back to your callback URL. This callback needs to save the verifier token passed in, which is used in the third leg of the flow. The callback also typically starts the third leg (token exchange) on the server side. (The next section, [the Authorization Flow](Auth-Flow.md), expands more on how the callback URL is used.)
12 |
13 | At the start of the OAuth flow, you pass in your OAuth callback URL for the specific request, which allows you to customise the callback URL for each request as needed. The OAuth plugin requires that your supplied callback URL match the scheme, authority (user and password part), host, port, and path of the registered callback URL. Only the query parameters and fragment (hash part) may differ from your registered URL. (This differs from some OAuth implementations, which allow subpaths of the callback URL.)
14 |
15 | For sites with multiple domains or subdomains (e.g. a WordPress multisite network), the recommended method for handling this is to have a singular "main" callback URL which redirects to the specific site. During the request process, the site ID can then be added to the callback URL as a query parameter.
16 |
17 | [Non-web applications](../advanced/Desktop.md) may wish to use custom URL schemes, or out-of-band handling. Out-of-band handling is triggered by setting the callback URL to the string `oob`. Rather than redirect after authorization, the site will instead display the verifier code to the user, which they then copy-and-paste or otherwise provide to the application.
18 |
--------------------------------------------------------------------------------
/docs/advanced/Web.md:
--------------------------------------------------------------------------------
1 | # Web Clients
2 |
3 | OAuth was originally designed for working with web clients, so the process should be fairly smooth for most developers. There are some use cases that are tricky however, although not impossible to work with.
4 |
5 |
6 | ## Distributed or Multi-Domain Clients
7 |
8 | One issue for clients with multiple domains (such as multisite WordPress installs) is callbacks: clients can only have a single registered callback, with no variation in most of the URL. This can make working with multiple domains tricky.
9 |
10 | This can be easily handled by adding extra query parameters to the callback URL, as these **can** be set on a per-request basis. These can then be handled by your callback URL to pass on to a secondary callback.
11 |
12 | For example, for a WordPress multisite, set the callback URL to a URL on the main site. A `site={id}` parameter can then be added when setting the callback for the request. The callback can then redirect the user's browser to a per-site callback based on this parameter (ensuring to pass along the `oauth_token` and `oauth_verifier` parameters.)
13 |
14 | **Note:** When using this method, be sure to verify the site. Check that the request token being handled was actually created by the site asking for it. If you're using domains instead of site IDs, be *very* careful not to redirect to an unknown domain. Failure to check this can easily lead to CSRF (phishing) attacks.
15 |
16 |
17 | ## In-Browser Clients
18 |
19 | Increasingly with modern JavaScript-based applications, the application may run entirely in the user's browser. OAuth 1 was (unfortunately) not designed for this use case. OAuth 2 goes a long way to correcting this, but as mentioned previously, [we can't use it :(](../introduction/OAuth-1.md)
20 |
21 | This primarily falls down to the application secret. OAuth 1.0a relies on the client secret being secret (duh) as the basis for the authorization flow. This is core to the signature process. Without this being secret, other applications can issue their own tokens as your application. OAuth 2 makes allowances for clients with public secrets with the `implicit` flow.
22 |
23 | The simplest way to handle this is to introduce a minimal server-side component. This can be created from scratch, or a prebuilt server such as [Guardian](http://guardianjs.com/) can be used instead.
24 |
25 |
26 | ## Best Practices
27 |
28 | * Never expose secrets, such as in JS client-side applications. Instead, use a proxy to handle the OAuth authentication.
29 | * Similarly, never expose the verifier to sites outside of your control. The verifier is specifically intended for CSRF mitigation, so be exceedingly careful before passing it on to another URL.
--------------------------------------------------------------------------------
/inc/class-clientinterface.php:
--------------------------------------------------------------------------------
1 | 'POST',
25 | 'callback' => [ $this, 'exchange_token' ],
26 | 'args' => [
27 | 'grant_type' => [
28 | 'required' => true,
29 | 'type' => 'string',
30 | 'validate_callback' => [ $this, 'validate_grant_type' ],
31 | ],
32 | 'client_id' => [
33 | 'required' => true,
34 | 'type' => 'string',
35 | 'validate_callback' => 'rest_validate_request_arg',
36 | ],
37 | 'code' => [
38 | 'required' => true,
39 | 'type' => 'string',
40 | 'validate_callback' => 'rest_validate_request_arg',
41 | ],
42 | ],
43 | ]
44 | );
45 | }
46 |
47 | /**
48 | * Validates the given grant type.
49 | *
50 | * @param string $type Grant type.
51 | *
52 | * @return bool Whether or not the grant type is valid.
53 | */
54 | public function validate_grant_type( $type ) {
55 | return 'authorization_code' === $type;
56 | }
57 |
58 | /**
59 | * Validates the token given in the request, and issues a new token for the user.
60 | *
61 | * @param WP_REST_Request $request Request object.
62 | *
63 | * @return array|WP_Error Token data on success, or error on failure.
64 | */
65 | public function exchange_token( WP_REST_Request $request ) {
66 | $client = OAuth2\get_client( $request['client_id'] );
67 | if ( empty( $client ) ) {
68 | return new WP_Error(
69 | 'oauth2.endpoints.token.exchange_token.invalid_client',
70 | /* translators: %s: client ID */
71 | sprintf( __( 'Client ID %s is invalid.', 'oauth2' ), $request['client_id'] ),
72 | [
73 | 'status' => WP_Http::BAD_REQUEST,
74 | 'client_id' => $request['client_id'],
75 | ]
76 | );
77 | }
78 |
79 | $auth_code = $client->get_authorization_code( $request['code'] );
80 | if ( is_wp_error( $auth_code ) ) {
81 | return $auth_code;
82 | }
83 |
84 | $is_valid = $auth_code->validate();
85 | if ( is_wp_error( $is_valid ) ) {
86 | // Invalid request, but code itself exists, so we should delete
87 | // (and silently ignore errors).
88 | $auth_code->delete();
89 |
90 | return $is_valid;
91 | }
92 |
93 | // Looks valid, delete the code and issue a token.
94 | $user = $auth_code->get_user();
95 | if ( is_wp_error( $user ) ) {
96 | return $user;
97 | }
98 |
99 | $did_delete = $auth_code->delete();
100 | if ( is_wp_error( $did_delete ) ) {
101 | return $did_delete;
102 | }
103 |
104 | $token = $client->issue_token( $user );
105 | if ( is_wp_error( $token ) ) {
106 | return $token;
107 | }
108 |
109 | $data = [
110 | 'access_token' => $token->get_key(),
111 | 'token_type' => 'bearer',
112 | ];
113 | return $data;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/theme/oauth2-authorize.php:
--------------------------------------------------------------------------------
1 |
21 |
22 |
62 |
63 |
';
185 | }
186 | }
187 |
188 | /**
189 | * Handle a revocation.
190 | *
191 | * @param int $user_id
192 | */
193 | function handle_revocation( $user_id ) {
194 | if ( empty( $_POST['oauth2_revoke'] ) ) {
195 | return;
196 | }
197 |
198 | $data = sanitize_text_field( wp_unslash( $_POST['oauth2_revoke'] ) );
199 | if ( strpos( $data, ':' ) === null ) {
200 | return;
201 | }
202 |
203 | // Split out nonce and check it.
204 | list( $nonce, $key ) = explode( ':', $data, 2 );
205 | if ( ! wp_verify_nonce( $nonce, 'oauth2_revoke:' . $key ) ) {
206 | wp_nonce_ays( 'oauth2_revoke' );
207 | die();
208 | }
209 |
210 | $token = Access_Token::get_by_id( $key );
211 | if ( empty( $token ) ) {
212 | wp_safe_redirect( add_query_arg( 'oauth2_revocation_failed', true, get_edit_user_link( $user_id ) ) );
213 | exit;
214 | }
215 |
216 | // Check it's for the right user.
217 | if ( $token->get_user_id() !== $user_id ) {
218 | wp_die();
219 | }
220 |
221 | $result = $token->revoke();
222 | if ( is_wp_error( $result ) ) {
223 | wp_safe_redirect( add_query_arg( 'oauth2_revocation_failed', true, get_edit_user_link( $user_id ) ) );
224 | exit;
225 | }
226 |
227 | // Success, redirect and tell the user.
228 | wp_safe_redirect( add_query_arg( 'oauth2_revoked', $key, get_edit_user_link( $user_id ) ) );
229 | exit;
230 | }
231 |
--------------------------------------------------------------------------------
/inc/class-client.php:
--------------------------------------------------------------------------------
1 | post = $post;
40 | }
41 |
42 | /**
43 | * Get the client's ID.
44 | *
45 | * @return string Client ID.
46 | */
47 | public function get_id() {
48 | return $this->post->post_name;
49 | }
50 |
51 | /**
52 | * Get the client's post ID.
53 | *
54 | * For internal (WordPress) use only. For external use, use get_key()
55 | *
56 | * @return int Client ID.
57 | */
58 | public function get_post_id() {
59 | return $this->post->ID;
60 | }
61 |
62 | /**
63 | * Get the client's name.
64 | *
65 | * @return string HTML string.
66 | */
67 | public function get_name() {
68 | return get_the_title( $this->get_post_id() );
69 | }
70 |
71 | /**
72 | * Get the client's description.
73 | *
74 | * @param boolean $raw True to get raw database value for editing, false to get rendered value for display.
75 | *
76 | * @return string
77 | */
78 | public function get_description( $raw = false ) {
79 | // Replicate the_content()'s filters.
80 | global $post;
81 | $current_post = $post;
82 | $the_post = get_post( $this->get_post_id() );
83 | if ( $raw ) {
84 | // Skip the filtering and globals.
85 | return $the_post->post_content;
86 | }
87 |
88 | // Set up globals so the filters have context.
89 | $post = $the_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
90 | setup_postdata( $post );
91 | $content = get_the_content();
92 |
93 | /** This filter is documented in wp-includes/post-template.php */
94 | $content = apply_filters( 'the_content', $content );
95 | $content = str_replace( ']]>', ']]>', $content );
96 |
97 | // Restore previous post.
98 | $post = $current_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
99 | if ( $post ) {
100 | setup_postdata( $post );
101 | }
102 |
103 | return $content;
104 | }
105 |
106 | /**
107 | * Get the client's type.
108 | *
109 | * @return string Type ID if available, or an empty string.
110 | */
111 | public function get_type() {
112 | return get_post_meta( $this->get_post_id(), static::TYPE_KEY, true );
113 | }
114 |
115 | /**
116 | * Get the Client Secret Key.
117 | *
118 | * @return string The Secret Key if available, or an empty string.
119 | */
120 | public function get_secret() {
121 | return get_post_meta( $this->get_post_id(), static::CLIENT_SECRET_KEY, true );
122 | }
123 |
124 | /**
125 | * Get registered URI for the client.
126 | *
127 | * @return array List of valid redirect URIs.
128 | */
129 | public function get_redirect_uris() {
130 | return (array) get_post_meta( $this->get_post_id(), static::REDIRECT_URI_KEY, true );
131 | }
132 |
133 | /**
134 | * Validate a callback URL.
135 | *
136 | * Based on {@see wp_http_validate_url}, but less restrictive around ports
137 | * and hosts. In particular, it allows any scheme, host or port rather than
138 | * just HTTP with standard ports.
139 | *
140 | * @param string $url URL for the callback.
141 | *
142 | * @return bool True for a valid callback URL, false otherwise.
143 | */
144 | public static function validate_callback( $url ) {
145 | if ( strpos( $url, ':' ) === false ) {
146 | return false;
147 | }
148 |
149 | $parsed_url = wp_parse_url( $url );
150 | if ( ! $parsed_url || empty( $parsed_url['host'] ) ) {
151 | return false;
152 | }
153 |
154 | if ( isset( $parsed_url['user'] ) || isset( $parsed_url['pass'] ) ) {
155 | return false;
156 | }
157 |
158 | if ( false !== strpbrk( $parsed_url['host'], ':#?[]' ) ) {
159 | return false;
160 | }
161 |
162 | return true;
163 | }
164 |
165 | /**
166 | * Check if a redirect URI is valid for the client.
167 | *
168 | * @param string $uri Supplied redirect URI to check.
169 | *
170 | * @return boolean True if the URI is valid, false otherwise.
171 | * @todo Implement this properly :)
172 | *
173 | */
174 | public function check_redirect_uri( $uri ) {
175 | if ( ! $this->validate_callback( $uri ) ) {
176 | return false;
177 | }
178 |
179 | $supplied = wp_parse_url( $uri );
180 | $all_registered = $this->get_redirect_uris();
181 |
182 | foreach ( $all_registered as $registered_uri ) {
183 | $registered = wp_parse_url( $registered_uri );
184 |
185 | // Double-check registered URI is valid.
186 | if ( ! $registered ) {
187 | continue;
188 | }
189 |
190 | // Check all components except query and fragment
191 | $parts = [ 'scheme', 'host', 'port', 'user', 'pass', 'path' ];
192 | $valid = true;
193 | foreach ( $parts as $part ) {
194 | if ( isset( $registered[ $part ] ) !== isset( $supplied[ $part ] ) ) {
195 | $valid = false;
196 | break;
197 | }
198 |
199 | if ( ! isset( $registered[ $part ] ) ) {
200 | continue;
201 | }
202 |
203 | if ( $registered[ $part ] !== $supplied[ $part ] ) {
204 | $valid = false;
205 | break;
206 | }
207 | }
208 |
209 | /**
210 | * Filter whether a callback is counted as valid. (deprecated).
211 | * User rest_oauth_check_callback.
212 | *
213 | * @param boolean $valid True if the callback URL is valid, false otherwise.
214 | * @param string $url Supplied callback URL.
215 | * @param string $registered_uri URI being checked.
216 | * @param Client $client OAuth 2 client object.
217 | */
218 | $valid = apply_filters( 'rest_oauth.check_callback', $valid, $uri, $registered_uri, $this );
219 |
220 | if ( $valid ) {
221 | // Stop checking, we have a match.
222 | return true;
223 | }
224 | }
225 |
226 | return false;
227 | }
228 |
229 | /**
230 | * @param WP_User $user
231 | *
232 | * @return Authorization_Code|WP_Error
233 | */
234 | public function generate_authorization_code( WP_User $user ) {
235 | return Authorization_Code::create( $this, $user );
236 | }
237 |
238 | /**
239 | * Get data stored for an authorization code.
240 | *
241 | * @param string $code Authorization code to fetch.
242 | *
243 | * @return Authorization_Code|WP_Error Data if available, error if invalid code.
244 | */
245 | public function get_authorization_code( $code ) {
246 | return Authorization_Code::get_by_code( $this, $code );
247 | }
248 |
249 | /**
250 | * @return bool|WP_Error
251 | */
252 | public function regenerate_secret() {
253 | $result = update_post_meta( $this->get_post_id(), static::CLIENT_SECRET_KEY, wp_generate_password( static::CLIENT_SECRET_LENGTH, false ) );
254 | if ( ! $result ) {
255 | return new WP_Error( 'oauth2.client.create.failed_meta', __( 'Could not regenerate the client secret.', 'oauth2' ) );
256 | }
257 |
258 | return true;
259 | }
260 |
261 | /**
262 | * Issue token for a user.
263 | *
264 | * @param \WP_User $user
265 | * @param array $meta
266 | *
267 | * @return Access_Token
268 | */
269 | public function issue_token( WP_User $user, $meta = [] ) {
270 | return Tokens\Access_Token::create( $this, $user, $meta );
271 | }
272 |
273 | /**
274 | * Get a client by ID.
275 | *
276 | * @param string $id Client ID.
277 | *
278 | * @return static|null Token if ID is found, null otherwise.
279 | */
280 | public static function get_by_id( $id ) {
281 | $args = [
282 | 'post_type' => static::POST_TYPE,
283 | 'post_status' => 'publish',
284 | 'posts_per_page' => 1,
285 | 'no_found_rows' => true,
286 |
287 | // Query by slug.
288 | 'name' => $id,
289 | ];
290 | $query = new WP_Query( $args );
291 | if ( empty( $query->posts ) ) {
292 | return null;
293 | }
294 |
295 | return new static( $query->posts[0] );
296 | }
297 |
298 | /**
299 | * Get a client by post ID.
300 | *
301 | * @param int $id Client/post ID.
302 | *
303 | * @return static|null Client instance on success, null if invalid/not found.
304 | */
305 | public static function get_by_post_id( $id ) {
306 | $post = get_post( $id );
307 | if ( ! $post ) {
308 | return null;
309 | }
310 |
311 | return new static( $post );
312 | }
313 |
314 | /**
315 | * Create a new client.
316 | *
317 | * @param array $data {
318 | * }
319 | *
320 | * @return WP_Error|Client Client instance on success, error otherwise.
321 | */
322 | public static function create( $data ) {
323 | $client_id = wp_generate_password( static::CLIENT_ID_LENGTH, false );
324 | $post_data = [
325 | 'post_type' => static::POST_TYPE,
326 | 'post_title' => $data['name'],
327 | 'post_content' => $data['description'],
328 | 'post_name' => $client_id,
329 | 'post_status' => 'draft',
330 | ];
331 |
332 | $post_id = wp_insert_post( wp_slash( $post_data ), true );
333 | if ( is_wp_error( $post_id ) ) {
334 | return $post_id;
335 | }
336 |
337 | // Generate ID and secret.
338 | $meta = [
339 | static::REDIRECT_URI_KEY => $data['meta']['callback'],
340 | static::TYPE_KEY => $data['meta']['type'],
341 | static::CLIENT_SECRET_KEY => wp_generate_password( static::CLIENT_SECRET_LENGTH, false ),
342 | ];
343 |
344 | foreach ( $meta as $key => $value ) {
345 | $result = update_post_meta( $post_id, wp_slash( $key ), wp_slash( $value ) );
346 | if ( ! $result ) {
347 | // Failed, rollback.
348 | return new WP_Error( 'oauth2.client.create.failed_meta', __( 'Could not save meta value.', 'oauth2' ) );
349 | }
350 | }
351 |
352 | $post = get_post( $post_id );
353 |
354 | return new static( $post );
355 | }
356 |
357 | /**
358 | * @param array $data
359 | *
360 | * @return WP_Error|Client Client instance on success, error otherwise.
361 | */
362 | public function update( $data ) {
363 | $post_data = [
364 | 'ID' => $this->get_post_id(),
365 | 'post_type' => static::POST_TYPE,
366 | 'post_title' => $data['name'],
367 | 'post_content' => $data['description'],
368 | ];
369 |
370 | $post_id = wp_update_post( wp_slash( $post_data ), true );
371 | if ( is_wp_error( $post_id ) ) {
372 | return $post_id;
373 | }
374 |
375 | $meta = [
376 | static::REDIRECT_URI_KEY => $data['meta']['callback'],
377 | static::TYPE_KEY => $data['meta']['type'],
378 | ];
379 |
380 | foreach ( $meta as $key => $value ) {
381 | update_post_meta( $post_id, wp_slash( $key ), wp_slash( $value ) );
382 | }
383 |
384 | $post = get_post( $post_id );
385 |
386 | return new static( $post );
387 | }
388 |
389 | /**
390 | * Delete the client.
391 | *
392 | * @return bool
393 | */
394 | public function delete() {
395 | return (bool) wp_delete_post( $this->get_post_id(), true );
396 | }
397 |
398 | /**
399 | * Approve a client.
400 | *
401 | * @return bool|WP_Error True if client was updated, error otherwise.
402 | */
403 | public function approve() {
404 | $data = [
405 | 'ID' => $this->get_post_id(),
406 | 'post_status' => 'publish',
407 | ];
408 | $result = wp_update_post( wp_slash( $data ), true );
409 |
410 | return is_wp_error( $result ) ? $result : true;
411 | }
412 |
413 | /**
414 | * Register the underlying post type.
415 | */
416 | public static function register_type() {
417 | register_post_type(
418 | static::POST_TYPE,
419 | [
420 | 'public' => false,
421 | 'hierarchical' => true,
422 | 'capability_type' => [
423 | 'oauth2_client',
424 | 'oauth2_clients',
425 | ],
426 | 'capabilities' => [
427 | 'edit_posts' => 'edit_users',
428 | 'edit_others_posts' => 'edit_users',
429 | 'publish_posts' => 'edit_users',
430 | ],
431 | 'supports' => [
432 | 'title',
433 | 'editor',
434 | 'revisions',
435 | 'author',
436 | 'thumbnail',
437 | ],
438 | ]
439 | );
440 | }
441 | }
442 |
--------------------------------------------------------------------------------
/docs/spec.md:
--------------------------------------------------------------------------------
1 | # OAuth Authentication API
2 | The following document describes the HTTP API for authenticating and authorizing
3 | a remote client with a WordPress installation.
4 |
5 | ## Framework
6 | The WordPress OAuth Authentication API ("OAuth API") is a HTTP-based API based
7 | on the [OAuth 1.0a][RFC5849]. It also builds on the OAuth 1.0a specification
8 | with custom parameters.
9 |
10 | This document describes OAuth API version 0.1.
11 |
12 | ## Terminology
13 | * "access token": A long-lived token used for accessing the site. Grants
14 | permissions to the client based on its scope.
15 | * "client": A software program that accesses the OAuth API and provides services
16 | to a user.
17 | * "request token": A short-lived token used during the OAuth process. Does not
18 | grant any permissions, and can only be used for the authorization steps.
19 | * "site": A WordPress installation providing the OAuth API as a service
20 | * "user": An end-user of an API client. Typically a registered user on the site.
21 |
22 | Note that any relative URLs are taken as relative to the site's base URL.
23 |
24 | ## Motivation
25 | The OAuth API is motivated by three main factors:
26 |
27 | * The user should only ever enter their credentials into the site. Clients
28 | should not only be discouraged from asking for user credentials, but the site
29 | should also avoid providing a way to use them.
30 |
31 | * The API must work on any site. The API must only use features available to the
32 | majority of sites in order to provide a useful utility.
33 |
34 | * The API should be simple to implement in clients. Developers should be able to
35 | create clients by reusing existing libraries, rather than writing full
36 | custom solutions.
37 |
38 | ## Differences from OAuth 1.0a
39 | The OAuth API extends OAuth 1.0a to provide additional functionality. The
40 | following differences apply:
41 |
42 | * The authorization endpoint ("Resource Owner authorization endpoint") MAY
43 | accept a `wp_scope` parameter, based on the OAuth 2.0 `scope` parameter.
44 | (See Step 2: Authorization)
45 |
46 | ## Step 0: Assessing Availability
47 | Before beginning the authorization process, clients SHOULD assess whether the
48 | site supports it. Due to the customizable nature of sites, this is not
49 | guaranteed, as the OAuth API can be disabled or replaced.
50 |
51 | To fulfill this requirement, the OAuth API interfaces with the WordPress JSON
52 | REST API ("WP API"). Most clients using the OAuth API are expected to have the
53 | ability to access the WP API.
54 |
55 | The OAuth API exposes information on itself via the index endpoint of the WP
56 | API, typically available at `/wp-json/`. The WP API is discoverable via the
57 | RSD mechanism, and OAuth API clients using this data SHOULD use the RSD
58 | mechanism, as described by the WP API documentation.
59 |
60 | ### Request
61 | The client sends a HTTP GET request to the index endpoint of the WP API. The
62 | location of the index endpoint is out of scope of this document, and is handled
63 | by the WP API documentation.
64 |
65 | ### Response
66 | The WP API index endpoint returns a JSON object of data relating to the site.
67 | The OAuth API exposes data through the `authentication` value in the `oauth1`
68 | property value.
69 |
70 | The `oauth1` value ("API Description object") is a JSON object with the
71 | following properties defined:
72 |
73 | * `request` An absolute URL giving the location of the "Temporary Credential
74 | Request endpoint" (see Step 1: Request Tokens)
75 | * `authorize`: An absolute URL giving the location of the "Resource Owner
76 | Authorization endpoint" (see Step 2: Authorization)
77 | * `access`: An absolute URL giving the location of the "Token Request endpoint"
78 | (see Step 3: Access Tokens)
79 | * `version`: A version string indicating the version of the OAuth API supported
80 | by the site.
81 |
82 | ## Step 1: Request Tokens
83 | The first step to the authorization process is to obtain a request token. This
84 | step asks the site to issue a temporary token, used only for the authorization
85 | process. This token is a short-expiry token which is not yet linked to a user.
86 |
87 | This request follows the [Temporary Credentials][oauth-request] section of
88 | [RFC5948][].
89 |
90 | ### Request
91 | The client sends a HTTP POST request to `/oauth1/request` (the "Temporary
92 | Credential Request endpoint"). This URL is also available via the API
93 | Description object as the `request` property, and clients SHOULD use the URL
94 | from the API Description object instead of hardcoding the URL.
95 |
96 | This request should match the format as described in the OAuth 1.0a
97 | specification, section 2.1.
98 |
99 | This request can also contain the following parameters as an extension on top of
100 | the OAuth 1.0a parameters:
101 |
102 | * `wp_scope`: This is a space- or comma-separated field in the style of OAuth
103 | 2.0's scope field. This represents a narrowing of the available permissions to
104 | the client. See Authorization Scope. This parameter is OPTIONAL, and defaults
105 | to "*" (all permissions).
106 |
107 | ### Response
108 | The OAuth API returns a URL-encoded response of the OAuth request token data, as
109 | described in the OAuth 1.0a specification, section 2.1.
110 |
111 | ## Step 2: Authorization
112 | The second step to the authorization process is to request authorization from
113 | the user. This step sends the user to the site, where the user then
114 | authenticates and grants the requested permissions to the client. This
115 | acceptance is stored with the request data on the site.
116 |
117 | This request follows the [Resource Owner Authorization][oauth-authorize] section
118 | of [RFC5849][], with additions.
119 |
120 | ### Request
121 | The client sends the user to `/oauth1/authorize` (the "Resource Owner
122 | Authorization endpoint"). This URL is also available via the API Description
123 | object as the `authorize` property, and clients SHOULD use the URL from the API
124 | Description object instead of hardcoding the URL.
125 |
126 | This request should match the format as described in the OAuth 1.0a
127 | specification, section 2.2.
128 |
129 | This request can also contain the following parameters as an extension on top of
130 | the OAuth 1.0a parameters:
131 |
132 | * `wp_scope`: This is a space- or comma-separated field in the style of OAuth
133 | 2.0's scope field. This represents a narrowing of the available permissions to
134 | the client. See Authorization Scope. This parameter is OPTIONAL, and defaults
135 | to either the `wp_scope` parameter as specified in the Request Token request,
136 | or "*" (all permissions) otherwise.
137 |
138 | ### Response
139 | The site will redirect the user back to the `oauth_callback` as provided in the
140 | Authorization step. The `oauth_token` and `oauth_verifier` parameters will be
141 | appended to the callback URL as per the OAuth 1.0a standard.
142 |
143 | In addition, a `wp_scope` parameter will be appended describing the actual scope
144 | granted (see Authorization Scope).
145 |
146 | ## Step 3: Access Tokens
147 | The third step to the authorization process is to use the now-authorized request
148 | token to request an access token. This step asks the site to grant the client an
149 | access token to use in future requests as authentication, using the request
150 | token.
151 |
152 | This request follows the [Token Credentials][oauth-access] section of
153 | [RFC5849][].
154 |
155 | ### Request
156 | The client sends the user to `/oauth1/access` (the "Token Request" endpoint).
157 | This URL is also available via the API Description object as the "access"
158 | property, and clients SHOULD use the URL from the API Description object instead
159 | of hardcoding the URL.
160 |
161 | This request should match the format as described in the OAuth 1.0a
162 | specification, section 2.3.
163 |
164 | ### Response
165 | The OAuth API returns a URL-encoded response of the OAuth access token data, as
166 | described in the OAuth 1.0a specification, section 2.3.
167 |
168 | ## Authenticated Requests
169 | ...
170 |
171 | ## Authorization Scope
172 | The OAuth API supports an additional parameter during both the Request Token
173 | request and Authorization request. This `wp_scope` parameter is a list of
174 | delimited strings of requested scopes. Scopes SHOULD be delimited by U+0020
175 | SPACE characters, URL-encoded as `%20`. Clients MAY use U+0020 SPACE characters,
176 | URL-encoded as `+`, or U+002C COMMA characters, URL-encoded as `%2c`.
177 |
178 | The OAuth API will also return the `wp_scope` parameter to the callback URL
179 | during the Authorization step, as a list of space-delimited strings of granted
180 | scopes (U+0020 SPACE characters are encoded as `%20`). This response parameter
181 | indicates the scope granted to the client for the token. This granted scope is
182 | strictly equal to or less permissive than the requested scope; that is, clients
183 | will never be granted additional permissions from those requested, but users may
184 | restrict the client's scope further.
185 |
186 | The default scope for clients that do not specify the `wp_scope` parameter is
187 | `*`, indicating all permissions will be granted. This permission grants the
188 | ability to perform any action the user has the capability to perform, including
189 | any future capabilities they may be granted. This scope SHOULD be used
190 | sparingly, as it presents a large attack surface.
191 |
192 | ### Available Scopes
193 | The following scopes are available:
194 |
195 | * `read`: Ability to read any public site data, or private data that the user
196 | has access to (such as privately published posts).
197 |
198 | Maps to:
199 | * `read`
200 | * `read_private_*` (requires Editor or above)
201 |
202 | * `edit`: Ability to edit any public site data, or private data that the user
203 | has access to. Implies `read`.
204 |
205 | Requires Contributor or above.
206 |
207 | Maps to:
208 | * `edit_*`
209 | * `delete_*`
210 | * `upload_files` (requires Author or above)
211 | * `moderate_comments` (requires Editor or above)
212 | * `manage_categories` (requires Editor or above)
213 | * `edit_others_*` (requires Editor or above)
214 | * `edit_private_*` (requires Editor or above)
215 | * `edit_published_*` (requires Editor or above)
216 | * `delete_others_*` (requires Editor or above)
217 | * `delete_private_*` (requires Editor or above)
218 | * `delete_published_*` (requires Editor or above)
219 |
220 | * `user.read`: Ability to read most user data, with the exception of the user's
221 | email address.
222 |
223 | * `user.email`: Ability to read the user's email address. Use of the user's
224 | email address should conform to all local laws (for both the client and site)
225 | with regards to spam. Implies `user.read`.
226 |
227 | * `user.edit`: Ability to edit any user data. Implies `user.read`
228 | and `user.email`.
229 |
230 | * `admin.read`: Ability to read admin-only data.
231 |
232 | Requires Admin or Super Admin.
233 |
234 | Maps to:
235 | * `list_users`
236 |
237 | * `admin.edit`: Ability to edit admin-only data.
238 |
239 | Requires Admin or Super Admin.
240 |
241 | Maps to:
242 | * `manage_options`
243 | * `install_plugins`
244 | * `update_plugins`
245 | * `install_themes`
246 | * `switch_themes`
247 | * `update_themes`
248 | * `edit_theme_options`
249 | * `update_core`
250 | * `edit_dashboard`
251 |
252 | * `admin.users`: Ability to administrate users.
253 |
254 | Requires Admin or Super Admin. Implies `user.edit`.
255 |
256 | Maps to:
257 | * `list_users`
258 | * `create_users`
259 | * `edit_users`
260 | * `promote_users`
261 | * `remove_users`
262 | * `delete_users`
263 |
264 | * `admin.import`: Ability to import data.
265 |
266 | Requires Admin or Super Admin. Implies `edit`.
267 |
268 | Maps to:
269 | * `import`
270 |
271 | * `admin.export`: Ability to export data.
272 |
273 | Requires Admin or Super Admin. Implies `read`.
274 |
275 | Maps to:
276 | * `export`
277 |
278 | For most applications, `read` and `user.read` are appropriate. For any
279 | applications which need access to information about the current user,
280 | `user.read` is recommended.
281 |
282 | Any permissions requested that are not available to the current user will cause
283 | an error to be returned to the client. Note that for permissions like `edit`,
284 | users without the `upload_files` capability (e.g.) will **not** cause an error,
285 | as the permission encompasses other capabilities. A user without the `edit_*`
286 | capability **will** cause an error, however.
287 |
288 | [RFC5849]: http://tools.ietf.org/html/rfc5849
289 | [oauth-request]: http://tools.ietf.org/html/rfc5849#section-2.1
290 | [oauth-authorize]: http://tools.ietf.org/html/rfc5849#section-2.2
291 | [oauth-access]: http://tools.ietf.org/html/rfc5849#section-2.3
292 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5 | 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
6 |
7 | Everyone is permitted to copy and distribute verbatim copies
8 | of this license document, but changing it is not allowed.
9 |
10 | Preamble
11 |
12 | The licenses for most software are designed to take away your
13 | freedom to share and change it. By contrast, the GNU General Public
14 | License is intended to guarantee your freedom to share and change free
15 | software--to make sure the software is free for all its users. This
16 | General Public License applies to most of the Free Software
17 | Foundation's software and to any other program whose authors commit to
18 | using it. (Some other Free Software Foundation software is covered by
19 | the GNU Library General Public License instead.) You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | this service if you wish), that you receive source code or can get it
26 | if you want it, that you can change the software or use pieces of it
27 | in new free programs; and that you know you can do these things.
28 |
29 | To protect your rights, we need to make restrictions that forbid
30 | anyone to deny you these rights or to ask you to surrender the rights.
31 | These restrictions translate to certain responsibilities for you if you
32 | distribute copies of the software, or if you modify it.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must give the recipients all the rights that
36 | you have. You must make sure that they, too, receive or can get the
37 | source code. And you must show them these terms so they know their
38 | rights.
39 |
40 | We protect your rights with two steps: (1) copyright the software, and
41 | (2) offer you this license which gives you legal permission to copy,
42 | distribute and/or modify the software.
43 |
44 | Also, for each author's protection and ours, we want to make certain
45 | that everyone understands that there is no warranty for this free
46 | software. If the software is modified by someone else and passed on, we
47 | want its recipients to know that what they have is not the original, so
48 | that any problems introduced by others will not reflect on the original
49 | authors' reputations.
50 |
51 | Finally, any free program is threatened constantly by software
52 | patents. We wish to avoid the danger that redistributors of a free
53 | program will individually obtain patent licenses, in effect making the
54 | program proprietary. To prevent this, we have made it clear that any
55 | patent must be licensed for everyone's free use or not licensed at all.
56 |
57 | The precise terms and conditions for copying, distribution and
58 | modification follow.
59 |
60 | GNU GENERAL PUBLIC LICENSE
61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
62 |
63 | 0. This License applies to any program or other work which contains
64 | a notice placed by the copyright holder saying it may be distributed
65 | under the terms of this General Public License. The "Program", below,
66 | refers to any such program or work, and a "work based on the Program"
67 | means either the Program or any derivative work under copyright law:
68 | that is to say, a work containing the Program or a portion of it,
69 | either verbatim or with modifications and/or translated into another
70 | language. (Hereinafter, translation is included without limitation in
71 | the term "modification".) Each licensee is addressed as "you".
72 |
73 | Activities other than copying, distribution and modification are not
74 | covered by this License; they are outside its scope. The act of
75 | running the Program is not restricted, and the output from the Program
76 | is covered only if its contents constitute a work based on the
77 | Program (independent of having been made by running the Program).
78 | Whether that is true depends on what the Program does.
79 |
80 | 1. You may copy and distribute verbatim copies of the Program's
81 | source code as you receive it, in any medium, provided that you
82 | conspicuously and appropriately publish on each copy an appropriate
83 | copyright notice and disclaimer of warranty; keep intact all the
84 | notices that refer to this License and to the absence of any warranty;
85 | and give any other recipients of the Program a copy of this License
86 | along with the Program.
87 |
88 | You may charge a fee for the physical act of transferring a copy, and
89 | you may at your option offer warranty protection in exchange for a fee.
90 |
91 | 2. You may modify your copy or copies of the Program or any portion
92 | of it, thus forming a work based on the Program, and copy and
93 | distribute such modifications or work under the terms of Section 1
94 | above, provided that you also meet all of these conditions:
95 |
96 | a) You must cause the modified files to carry prominent notices
97 | stating that you changed the files and the date of any change.
98 |
99 | b) You must cause any work that you distribute or publish, that in
100 | whole or in part contains or is derived from the Program or any
101 | part thereof, to be licensed as a whole at no charge to all third
102 | parties under the terms of this License.
103 |
104 | c) If the modified program normally reads commands interactively
105 | when run, you must cause it, when started running for such
106 | interactive use in the most ordinary way, to print or display an
107 | announcement including an appropriate copyright notice and a
108 | notice that there is no warranty (or else, saying that you provide
109 | a warranty) and that users may redistribute the program under
110 | these conditions, and telling the user how to view a copy of this
111 | License. (Exception: if the Program itself is interactive but
112 | does not normally print such an announcement, your work based on
113 | the Program is not required to print an announcement.)
114 |
115 | These requirements apply to the modified work as a whole. If
116 | identifiable sections of that work are not derived from the Program,
117 | and can be reasonably considered independent and separate works in
118 | themselves, then this License, and its terms, do not apply to those
119 | sections when you distribute them as separate works. But when you
120 | distribute the same sections as part of a whole which is a work based
121 | on the Program, the distribution of the whole must be on the terms of
122 | this License, whose permissions for other licensees extend to the
123 | entire whole, and thus to each and every part regardless of who wrote it.
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 |
--------------------------------------------------------------------------------
/inc/admin/namespace.php:
--------------------------------------------------------------------------------
1 | value, or wp_parse_args string.
39 | *
40 | * @return string Requested URL.
41 | */
42 | function get_url( $params = [] ) {
43 | $url = admin_url( 'users.php' );
44 | $params = [ 'page' => BASE_SLUG ] + wp_parse_args( $params );
45 |
46 | return add_query_arg( urlencode_deep( $params ), $url );
47 | }
48 |
49 | /**
50 | * Get the current page action.
51 | *
52 | * @return string One of 'add', 'edit', 'delete', or '' for default (list)
53 | */
54 | function get_page_action() {
55 | return isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
56 | }
57 |
58 | /**
59 | * Load data for our page.
60 | */
61 | function load() {
62 | switch ( get_page_action() ) {
63 | case 'add':
64 | case 'edit':
65 | render_edit_page();
66 | break;
67 |
68 | case 'delete':
69 | handle_delete();
70 | break;
71 |
72 | case 'regenerate':
73 | handle_regenerate();
74 | break;
75 |
76 | case 'approve':
77 | handle_approve();
78 | break;
79 |
80 | default:
81 | global $wp_list_table;
82 |
83 | $wp_list_table = new ListTable(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
84 |
85 | $wp_list_table->prepare_items();
86 |
87 | return;
88 | }
89 | }
90 |
91 | /**
92 | *
93 | */
94 | function dispatch() {
95 | switch ( get_page_action() ) {
96 | case 'add':
97 | case 'edit':
98 | case 'delete':
99 | case 'approve':
100 | break;
101 |
102 | default:
103 | render();
104 | break;
105 | }
106 | }
107 |
108 | /**
109 | * Render the list page.
110 | */
111 | function render() {
112 | global $wp_list_table;
113 |
114 | ?>
115 |