/
youtube.php
264 lines (227 loc) · 11.2 KB
/
youtube.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
<?php
/**
* YouTube Service for Keyring.
*
* YouTube API: https://developers.google.com/youtube/v3/sample_requests
* OAuth implementation: https://developers.google.com/youtube/v3/guides/auth/server-side-web-apps
* App registration: https://console.developers.google.com/
*/
class Keyring_Service_YouTube extends Keyring_Service_OAuth2 {
const NAME = 'youtube';
const LABEL = 'YouTube';
const SCOPE = 'https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/userinfo.profile'; // See https://developers.google.com/youtube/v3/guides/auth/server-side-web-apps#identify-access-scopes
const ACCESS_TYPE = 'offline';
function __construct() {
parent::__construct();
// Enable "basic" UI for entering key/secret
if ( ! KEYRING__HEADLESS_MODE ) {
add_action( 'keyring_youtube_manage_ui', array( $this, 'basic_ui' ) );
add_filter( 'keyring_youtube_basic_ui_intro', array( $this, 'basic_ui_intro' ) );
}
$this->set_endpoint( 'authorize', 'https://accounts.google.com/o/oauth2/v2/auth', 'GET' );
$this->set_endpoint( 'access_token', 'https://www.googleapis.com/oauth2/v4/token', 'POST' );
$this->set_endpoint( 'refresh', 'https://www.googleapis.com/oauth2/v4/token', 'POST' );
$this->set_endpoint( 'userinfo', 'https://www.googleapis.com/oauth2/v3/userinfo', 'GET' );
$creds = $this->get_credentials();
$this->redirect_uri = $creds['redirect_uri'];
$this->key = $creds['key'];
$this->secret = $creds['secret'];
$this->authorization_header = 'Bearer';
$this->authorization_parameter = false;
// Need to reset the callback because Google is very strict about where it sends people
if ( ! empty( $creds['redirect_uri'] ) ) {
$this->callback_url = $creds['redirect_uri']; // Allow user to manually enter a redirect URI
} else {
$this->callback_url = remove_query_arg( array( 'nonce', 'kr_nonce' ), $this->callback_url ); // At least strip nonces, since you can't save them in your app config
}
add_filter( 'keyring_youtube_request_token_params', array( $this, 'request_token_params' ) );
add_action( 'pre_keyring_youtube_verify', array( $this, 'redirect_incoming_verify' ) );
}
function basic_ui_intro() {
echo '<p>' . sprintf( __( "Google controls access to all of their APIs through their API Console. <a href='%s'>Go to the Library page in the console</a> and click the <strong>Select a project</strong> dropdown next to the logo in the upper left of the screen. Click the <strong>plus icon</strong> to create a new project. Enter a name and then click <strong>Create</strong>.", 'keyring' ), 'https://console.developers.google.com/apis/library' ) . '</p>';
echo '<p>' . __( "Now you need to enable the YouTube API and setup your OAuth credentials.", 'keyring' );
echo '<ol>';
echo '<li>' . __( "Select your project from the project dropdown.", 'keyring' ) . '</li>';
echo '<li>' . __( "Click <strong>Library</strong> in the menu on the left.", 'keyring' ) . '</li>';
echo '<li>' . __( "Find and click <strong>YouTube API</strong>.", 'keyring' ) . '</li>';
echo '<li>' . __( "Next to the heading, click <strong>Enable</strong>.", 'keyring' ) . '</li>';
echo '<li>' . __( "Click the blue button labelled <strong>Create credentials</strong>.", 'keyring' ) . '</li>';
echo '<li>' . __( "Click <strong>Credential</strong> in the menu on the left.", 'keyring' ) . '</li>';
echo '<li>' . __( "Click the <strong>OAuth consent screen</strong> menu item.", 'keyring' ) . '</li>';
echo '<li>' . __( "You must enter a <strong>Product name</strong>, but you can skip the logo and home page URL.", 'keyring' ) . '</li>';
echo '<li>' . __( "Click Save.", 'keyring' ) . '</li>';
echo '<li>' . __( "Click the <strong>Create credentials</strong> button and select <strong>OAuth client ID</strong>.", 'keyring' ) . '</li>';
echo '<li>' . __( "Select <strong>Web application</strong> and enter a relevant name or just use the default.", 'keyring' ) . '</li>';
echo '<li>' . sprintf( __( "For the <strong>Authorized JavaScript Origins</strong>, enter the URL of your domain, e.g. <code>http://%s</code>.", 'keyring' ), $_SERVER['HTTP_HOST'] ) . '</li>';
echo '<li>' . sprintf( __( "In the <strong>Authorized Redirect URIs</strong> box, enter the URL <code>%s</code>.", 'keyring' ), Keyring_Util::admin_url( $this->get_name(), array( 'action' => 'verify' ) ) ) . '</li>';
echo '<li>' . __( "Click <strong>Create</strong> when you're done.", 'keyring' ) . '</li>';
echo '</ol>';
echo '<p>' . __( "Once you've saved your details, copy the <strong>Client ID</strong> into the <strong>Client ID</strong> field below, and the <strong>Client secret</strong> value into <strong>Client Secret</strong>. The Redirect URI box should fill itself out for you.", 'keyring' ) . '</p>';
}
function _get_credentials() {
if (
defined( 'KEYRING__YOUTUBE_KEY' )
&&
defined( 'KEYRING__YOUTUBE_SECRET' )
) {
return array(
'redirect_uri' => defined( 'KEYRING__YOUTUBE_URI' ) ? constant( 'KEYRING__YOUTUBE_URI' ) : '', // optional
'key' => constant( 'KEYRING__YOUTUBE_KEY' ),
'secret' => constant( 'KEYRING__YOUTUBE_SECRET' ),
);
} else {
return null;
}
}
function request_token_params( $params ) {
$params['scope'] = self::SCOPE;
$params['access_type'] = self::ACCESS_TYPE;
$params['prompt'] = 'consent'; // Required to get a refresh token
return $params;
}
function redirect_incoming_verify( $request ) {
if ( ! isset( $request['kr_nonce'] ) ) {
$kr_nonce = wp_create_nonce( 'keyring-verify' );
$nonce = wp_create_nonce( 'keyring-verify-' . $this->get_name() );
wp_safe_redirect(
Keyring_Util::admin_url(
$this->get_name(),
array(
'action' => 'verify',
'kr_nonce' => $kr_nonce,
'nonce' => $nonce,
'state' => $request['state'],
'code' => $request['code'], // Auth code from successful response (maybe)
)
)
);
exit;
}
}
function build_token_meta( $token ) {
$meta = array(
'refresh_token' => $token['refresh_token'],
'expires' => time() + $token['expires_in'],
);
$this->set_token(
new Keyring_Access_Token(
$this->get_name(),
$token['access_token'],
array()
)
);
$response = $this->request( $this->userinfo_url, array( 'method' => $this->userinfo_method ) );
if ( ! Keyring_Util::is_error( $response ) ) {
$meta['user_id'] = $response->sub;
$meta['name'] = $response->name;
$meta['picture'] = $response->picture;
}
return apply_filters( 'keyring_access_token_meta', $meta, $this->get_name(), $token, array(), $this );
}
function get_display( Keyring_Access_Token $token ) {
return $token->get_meta( 'name' );
}
function maybe_refresh_token() {
// Request a new token, using the refresh_token
$token = $this->get_token();
$meta = $token->get_meta();
if ( empty( $meta['refresh_token'] ) ) {
return false;
}
// Don't refresh if token is valid
if ( ! $token->is_expired( 20 ) ) {
return;
}
$response = wp_remote_post( $this->refresh_url, array(
'method' => $this->refresh_method,
'body' => array(
'client_id' => $this->key,
'client_secret' => $this->secret,
'refresh_token' => $meta['refresh_token'],
'grant_type' => 'refresh_token',
),
) );
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return false;
}
$return = json_decode( wp_remote_retrieve_body( $response ) );
$meta['expires'] = time() + $return->expires_in;
// Build access token
$access_token = new Keyring_Access_Token(
$this->get_name(),
$return->access_token,
$meta,
$this->token->unique_id
);
// Store the updated access token
$access_token = apply_filters( 'keyring_access_token', $access_token, $token );
$id = $this->store->update( $access_token );
// And switch to using it
$this->set_token( $access_token );
}
// Need to potentially refresh token before each request
function request( $url, array $params = array() ) {
$this->maybe_refresh_token();
return parent::request( $url, $params );
}
// Minor modifications from Keyring_Service::basic_ui
function basic_ui() {
if ( ! isset( $_REQUEST['nonce'] ) || ! wp_verify_nonce( $_REQUEST['nonce'], 'keyring-manage-' . $this->get_name() ) ) {
Keyring::error( __( 'Invalid/missing management nonce.', 'keyring' ) );
exit;
}
// Common Header
echo '<div class="wrap">';
echo '<h2>' . __( 'Keyring Service Management', 'keyring' ) . '</h2>';
echo '<p><a href="' . Keyring_Util::admin_url( false, array( 'action' => 'services' ) ) . '">' . __( '← Back', 'keyring' ) . '</a></p>';
echo '<h3>' . sprintf( __( '%s API Credentials', 'keyring' ), esc_html( $this->get_label() ) ) . '</h3>';
// Handle actually saving credentials
if ( isset( $_POST['api_key'] ) && isset( $_POST['api_secret'] ) ) {
// Store credentials against this service
$this->update_credentials( array(
'key' => stripslashes( $_POST['api_key'] ),
'secret' => stripslashes( $_POST['api_secret'] ),
'redirect_uri' => stripslashes( $_POST['redirect_uri'] ),
) );
echo '<div class="updated"><p>' . __( 'Credentials saved.', 'keyring' ) . '</p></div>';
}
$api_key = $api_secret = $redirect_uri = '';
if ( $creds = $this->get_credentials() ) {
$api_key = $creds['key'];
$api_secret = $creds['secret'];
$redirect_uri = $creds['redirect_uri'];
}
echo apply_filters( 'keyring_' . $this->get_name() . '_basic_ui_intro', '' );
if ( ! $redirect_uri ) {
$redirect_uri = Keyring_Util::admin_url( $this->get_name(), array( 'action' => 'verify' ) );
}
// Output basic form for collecting key/secret
echo '<form method="post" action="">';
echo '<input type="hidden" name="service" value="' . esc_attr( $this->get_name() ) . '" />';
echo '<input type="hidden" name="action" value="manage" />';
wp_nonce_field( 'keyring-manage', 'kr_nonce', false );
wp_nonce_field( 'keyring-manage-' . $this->get_name(), 'nonce', false );
echo '<table class="form-table">';
echo '<tr><th scope="row">' . __( 'Client ID', 'keyring' ) . '</th>';
echo '<td><input type="text" name="api_key" value="' . esc_attr( $api_key ) . '" id="api_key" class="regular-text"></td></tr>';
echo '<tr><th scope="row">' . __( 'Client Secret', 'keyring' ) . '</th>';
echo '<td><input type="text" name="api_secret" value="' . esc_attr( $api_secret ) . '" id="api_secret" class="regular-text"></td></tr>';
echo '<tr><th scope="row">' . __( 'Redirect URI', 'keyring' ) . '</th>';
echo '<td><input type="text" name="redirect_uri" value="' . esc_attr( $redirect_uri ) . '" id="redirect_uri" class="regular-text"></td></tr>';
echo '</table>';
echo '<p class="submitbox">';
echo '<input type="submit" name="submit" value="' . __( 'Save Changes', 'keyring' ) . '" id="submit" class="button-primary">';
echo '<a href="' . esc_url( $_SERVER['HTTP_REFERER'] ) . '" class="submitdelete" style="margin-left:2em;">' . __( 'Cancel', 'keyring' ) . '</a>';
echo '</p>';
echo '</form>';
echo '</div>';
}
function test_connection() {
$res = $this->request( $this->userinfo_url, array( 'method' => $this->userinfo_method ) );
if ( ! Keyring_Util::is_error( $res ) ) {
return true;
}
return $res;
}
}
add_action( 'keyring_load_services', array( 'Keyring_Service_YouTube', 'init' ) );