Skip to content

Commit 988cb55

Browse files
authored
Merge pull request #47 from WP-API/personal-tokens
Add personal access tokens
2 parents 1747fc7 + 8e3458e commit 988cb55

File tree

9 files changed

+608
-17
lines changed

9 files changed

+608
-17
lines changed

inc/admin/profile/namespace.php

+50-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
namespace WP\OAuth2\Admin\Profile;
77

8+
use WP\OAuth2\PersonalClient;
89
use WP\OAuth2\Tokens\Access_Token;
910
use WP_User;
1011

@@ -17,6 +18,8 @@ function bootstrap() {
1718
add_action( 'all_admin_notices', __NAMESPACE__ . '\\output_profile_messages' );
1819
add_action( 'personal_options_update', __NAMESPACE__ . '\\handle_revocation', 10, 1 );
1920
add_action( 'edit_user_profile_update', __NAMESPACE__ . '\\handle_revocation', 10, 1 );
21+
22+
PersonalTokens\bootstrap();
2023
}
2124

2225
/**
@@ -30,6 +33,12 @@ function render_profile_section( WP_User $user ) {
3033
return (bool) $token->get_client();
3134
});
3235

36+
if ( ! IS_PROFILE_PAGE ) {
37+
$personal_url = PersonalTokens\get_page_url( [ 'user_id' => $user->ID ] );
38+
} else {
39+
$personal_url = PersonalTokens\get_page_url();
40+
}
41+
3342
?>
3443
<h2><?php _e( 'Authorized Applications', 'oauth2' ) ?></h2>
3544
<?php if ( ! empty( $tokens ) ) : ?>
@@ -47,9 +56,19 @@ function render_profile_section( WP_User $user ) {
4756
}
4857
?>
4958
</tbody>
59+
<tfoot>
60+
<tr>
61+
<td colspan="2">
62+
<a href="<?php echo esc_url( $personal_url ) ?>">
63+
<?php esc_html_e( 'Create personal access token', 'oauth2' ) ?>
64+
</a>
65+
</td>
66+
</tr>
67+
</tfoot>
5068
</table>
5169
<?php else : ?>
5270
<p class="description"><?php esc_html_e( 'No applications authorized.', 'oauth2' ) ?></p>
71+
<p><a href="<?php echo esc_url( $personal_url ) ?>"><?php esc_html_e( 'Create personal access token', 'oauth2' ) ?></a></p>
5372
<?php endif ?>
5473
<?php
5574
}
@@ -58,7 +77,12 @@ function render_profile_section( WP_User $user ) {
5877
* Render a single row.
5978
*/
6079
function render_token_row( WP_User $user, Access_Token $token ) {
61-
$client = $token->get_client();
80+
$client = $token->get_client();
81+
$is_personal = $client instanceof PersonalClient;
82+
83+
if ( $is_personal ) {
84+
$token_name = $token->get_meta( 'name', __( 'Unknown Token', 'oauth2' ) );
85+
}
6286

6387
$creation_time = $token->get_creation_time();
6488
$details = [
@@ -80,15 +104,24 @@ function render_token_row( WP_User $user, Access_Token $token ) {
80104
$details = apply_filters( 'oauth2.admin.profile.render_token_row.details', $details, $token, $user );
81105

82106
// Build actions.
83-
$button_title = sprintf(
84-
/* translators: %s: app name */
85-
__( 'Revoke access for "%s"', 'oauth2' ),
86-
$client->get_name()
87-
);
107+
if ( $is_personal ) {
108+
$button_title = sprintf(
109+
/* translators: %s: personal token name */
110+
__( 'Revoke personal token "%s"', 'oauth2' ),
111+
esc_html( $token_name )
112+
);
113+
} else {
114+
$button_title = sprintf(
115+
/* translators: %s: app name */
116+
__( 'Revoke access for "%s"', 'oauth2' ),
117+
$client->get_name()
118+
);
119+
}
120+
88121
$actions = [
89122
sprintf(
90123
'<button class="button" name="oauth2_revoke" title="%s" value="%s">%s</button>',
91-
$button_title,
124+
esc_attr( $button_title ),
92125
wp_create_nonce( 'oauth2_revoke:' . $token->get_key() ) . ':' . esc_attr( $token->get_key() ),
93126
esc_html__( 'Revoke', 'oauth2' )
94127
),
@@ -102,10 +135,19 @@ function render_token_row( WP_User $user, Access_Token $token ) {
102135
* @param WP_User $user User whose profile is being rendered.
103136
*/
104137
$actions = apply_filters( 'oauth2.admin.profile.render_token_row.actions', $actions, $token, $user );
138+
139+
$name = sprintf( '<strong>%s</strong>', $client->get_name() );
140+
if ( $is_personal ) {
141+
$name = sprintf(
142+
'<strong>%s</strong> <em>(%s)</em>',
143+
esc_html( $token_name ),
144+
$client->get_name()
145+
);
146+
}
105147
?>
106148
<tr>
107149
<td>
108-
<p><strong><?php echo $client->get_name() ?></strong></p>
150+
<p><?php echo $name ?></p>
109151
<p><?php echo implode( ' | ', $details ) ?></p>
110152
</td>
111153
<td style="vertical-align: middle">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<?php
2+
3+
namespace WP\OAuth2\Admin\Profile\PersonalTokens;
4+
5+
use WP\OAuth2\PersonalClient;
6+
use WP\OAuth2\Tokens\Access_Token;
7+
use WP_Error;
8+
use WP_User;
9+
10+
const ACCESS_TOKENS_PAGE_SLUG = 'oauth2_personal_tokens';
11+
12+
function bootstrap() {
13+
// Personal Access Tokens page.
14+
add_action( 'admin_action_' . ACCESS_TOKENS_PAGE_SLUG, __NAMESPACE__ . '\\render_page' );
15+
}
16+
17+
/**
18+
* Get the token page URL.
19+
*
20+
* @return string
21+
*/
22+
function get_page_url( $args = [] ) {
23+
$url = admin_url( 'profile.php' );
24+
$args['action'] = ACCESS_TOKENS_PAGE_SLUG;
25+
$url = add_query_arg( urlencode_deep( $args ), $url );
26+
return $url;
27+
}
28+
29+
/**
30+
* Bootstrap the profile page.
31+
*
32+
* This sets up the globals for the user page.
33+
*/
34+
function bootstrap_profile_page() {
35+
global $user_id, $submenu_file, $parent_file;
36+
$user_id = null;
37+
if ( ! empty( $_REQUEST['user_id'] ) ) { // WPCS: CSRF OK
38+
$user_id = (int) $_REQUEST['user_id'];
39+
}
40+
41+
$current_user = wp_get_current_user();
42+
if ( ! defined( 'IS_PROFILE_PAGE' ) ) {
43+
define( 'IS_PROFILE_PAGE', $user_id === $current_user->ID );
44+
}
45+
46+
if ( ! $user_id && IS_PROFILE_PAGE ) {
47+
$user_id = $current_user->ID;
48+
}
49+
50+
$user = get_user_by( 'id', $user_id );
51+
if ( empty( $user ) ) {
52+
wp_die( __( 'Invalid user ID.' ) );
53+
}
54+
if ( ! current_user_can( 'edit_user', $user_id ) ) {
55+
wp_die( __( 'Sorry, you are not allowed to edit this user.' ) );
56+
}
57+
58+
if ( current_user_can( 'edit_users' ) && ! IS_PROFILE_PAGE ) {
59+
$submenu_file = 'users.php';
60+
} else {
61+
$submenu_file = 'profile.php';
62+
}
63+
64+
if ( current_user_can( 'edit_users' ) ) {
65+
$parent_file = 'users.php';
66+
} else {
67+
$parent_file = 'profile.php';
68+
}
69+
}
70+
71+
/**
72+
* Render the access token creation page.
73+
*/
74+
function render_page() {
75+
bootstrap_profile_page();
76+
77+
$user = get_user_by( 'id', $GLOBALS['user_id'] );
78+
79+
if ( isset( $_POST['oauth2_action'] ) ) { // WPCS: CSRF OK
80+
$error = handle_page_action( $user );
81+
82+
if ( is_wp_error( $error ) ) {
83+
add_action( 'all_admin_notices', function () use ( $error ) {
84+
echo '<div class="error"><p>' . esc_html( $error->get_error_message() ) . '</p></div>';
85+
} );
86+
}
87+
}
88+
89+
$GLOBALS['title'] = __( 'Personal Access Tokens', 'oauth2' );
90+
require ABSPATH . 'wp-admin/admin-header.php';
91+
92+
$tokens = Access_Token::get_for_user( $user );
93+
$tokens = array_filter( $tokens, function ( Access_Token $token ) {
94+
$client = $token->get_client();
95+
return ! empty( $client ) && $client instanceof PersonalClient;
96+
});
97+
?>
98+
<div class="wrap" id="profile-page">
99+
<h1><?php esc_html_e( 'Create a Personal Access Token', 'oauth2' ) ?></h1>
100+
101+
<p><?php esc_html_e( "The WordPress API allows access to your site by external applications. Personal access tokens allow easy access for personal scripts, command line utilities, or during development. Generally, you shouldn't provide these to applications you don't trust, and you should treat them just like passwords.", 'oauth2' ) ?></p>
102+
103+
<form action="" method="POST">
104+
<table class="form-table">
105+
<tr>
106+
<th scope="row">
107+
<label for="token-name">Token name</label>
108+
</th>
109+
<td>
110+
<input
111+
class="regular-text"
112+
id="token-name"
113+
name="name"
114+
required="required"
115+
type="text"
116+
/>
117+
118+
<p class="description"><?php esc_html_e( 'Give this token a name so you can easily identify it later.', 'oauth2' ) ?></p>
119+
</td>
120+
</tr>
121+
</table>
122+
123+
<input type="hidden" name="oauth2_action" value="create" />
124+
125+
<?php wp_nonce_field( 'oauth2_personal_tokens.create' ) ?>
126+
127+
<p class="buttons">
128+
<button class="button-primary"><?php esc_html_e( 'Generate Token', 'oauth2' ) ?></button>
129+
</p>
130+
</form>
131+
</div>
132+
<?php
133+
require ABSPATH . 'wp-admin/admin-footer.php';
134+
exit;
135+
}
136+
137+
/**
138+
* Handle action from a form.
139+
*/
140+
function handle_page_action( WP_User $user ) {
141+
$action = $_POST['oauth2_action']; // WPCS: CSRF OK
142+
143+
switch ( $action ) {
144+
case 'create':
145+
check_admin_referer( 'oauth2_personal_tokens.create' );
146+
if ( empty( $_POST['name'] ) ) {
147+
return new WP_Error(
148+
'rest_oauth2_missing_name',
149+
__( 'Missing name for personal access token.', 'oauth2' )
150+
);
151+
}
152+
153+
$name = sanitize_text_field( wp_unslash( $_POST['name'] ) );
154+
return handle_create( $user, $name );
155+
156+
default:
157+
return new WP_Error(
158+
'rest_oauth2_invalid_action',
159+
__( 'Invalid action.', 'oauth2' )
160+
);
161+
}
162+
}
163+
164+
/**
165+
* Handle token creation
166+
*/
167+
function handle_create( WP_User $user, $name ) {
168+
$client = PersonalClient::get_instance();
169+
$meta = [
170+
'name' => $name,
171+
];
172+
$token = $client->issue_token( $user, $meta );
173+
174+
render_create_success( $user, $token );
175+
}
176+
177+
function render_create_success( WP_User $user, $token ) {
178+
$GLOBALS['title'] = __( 'Personal Access Tokens', 'oauth2' );
179+
require ABSPATH . 'wp-admin/admin-header.php';
180+
?>
181+
182+
<div class="wrap" id="profile-page">
183+
<h1><?php esc_html_e( 'Token created!', 'oauth2' ) ?></h1>
184+
<p><?php esc_html_e( "Your token has been created. Make sure to copy it now, as it won't be displayed again!", 'oauth2' ) ?></p>
185+
186+
<pre style="font-size: 2em"><?php echo esc_html( $token->get_key() ) ?></pre>
187+
188+
<p><a href="<?php echo esc_url( get_edit_user_link( $user->ID ) ) ?>"><?php esc_html_e( 'Back to profile', 'oauth2' ) ?></a></p>
189+
</div>
190+
191+
<?php
192+
require ABSPATH . 'wp-admin/admin-footer.php';
193+
exit;
194+
}

inc/class-client.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use WP_Query;
1010
use WP_User;
1111

12-
class Client {
12+
class Client implements ClientInterface {
1313
const POST_TYPE = 'oauth2_client';
1414
const CLIENT_SECRET_KEY = '_oauth2_client_secret';
1515
const TYPE_KEY = '_oauth2_client_type';
@@ -259,11 +259,12 @@ public function regenerate_secret() {
259259
* Issue token for a user.
260260
*
261261
* @param \WP_User $user
262+
* @param array $meta
262263
*
263264
* @return Access_Token
264265
*/
265-
public function issue_token( WP_User $user ) {
266-
return Tokens\Access_Token::create( $this, $user );
266+
public function issue_token( WP_User $user, $meta = [] ) {
267+
return Tokens\Access_Token::create( $this, $user, $meta );
267268
}
268269

269270
/**

0 commit comments

Comments
 (0)