Browse Source

keyring upstream

windhamdavid 9 years ago
parent
commit
0f8896ff53

+ 33 - 2
app/plugins/keyring/admin-ui.php

@@ -105,6 +105,29 @@ class Keyring_Admin_UI {
 				Keyring::message( __( 'Could not delete that connection!', 'keyring' ) );
 		}
 
+		// Handle test request. Will default back to "tokens" later
+		if ( isset( $_REQUEST['action'] ) && 'test' == $_REQUEST['action'] ) {
+			if ( !isset( $_REQUEST['nonce'] ) || !wp_verify_nonce( $_REQUEST['nonce'], 'keyring-test-' . $_REQUEST['service'] . '-' . $_REQUEST['token'] ) ) {
+				Keyring::error( __( 'Invalid/missing testing nonce.', 'keyring' ) );
+				exit;
+			}
+
+			// If the test_connection() method exists, call it for this service/connection
+			$service = $this->keyring->get_service_by_name( $_REQUEST['service'] );
+			if ( method_exists( $service, 'test_connection' ) ) {
+				$service->set_token( $this->keyring->get_token_store()->get_token( array( 'id' => $_REQUEST['token'], 'type' => 'request' ) ) );
+
+				if ( $service->test_connection() ) {
+					Keyring::message( __( 'This connection is working correctly.', 'keyring' ) );
+				} else {
+					Keyring::message( __( 'This connection is <strong>NOT</strong> working correctly.', 'keyring' ) );
+				}
+			} else {
+				Keyring::message( __( 'This service does not currently support connection testing.', 'keyring' ) );
+			}
+
+		}
+
 		// Set up our defaults
 		$service = '';
 		if ( !empty( $_REQUEST['service'] ) )
@@ -250,8 +273,16 @@ class Keyring_Connections_List_Table extends WP_List_Table {
 	}
 
 	function column_actions( $row ) {
-		$kr_nonce = wp_create_nonce( 'keyring-delete' );
+		$kr_delete_nonce = wp_create_nonce( 'keyring-delete' );
 		$delete_nonce = wp_create_nonce( 'keyring-delete-' . $row->get_service()->get_name() . '-' . $row->get_uniq_id() );
-		echo '<a href="' . Keyring_Util::admin_url( false, array( 'action' => 'delete', 'service' => $row->get_service()->get_name(), 'token' => $row->get_uniq_id(), 'kr_nonce' => $kr_nonce, 'nonce' => $delete_nonce ) ) . '" title="' . esc_attr( __( 'Delete', 'keyring' ) ) . '" class="delete">Delete</a>';
+
+		$kr_test_nonce = wp_create_nonce( 'keyring-test' );
+		$test_nonce = wp_create_nonce( 'keyring-test-' . $row->get_service()->get_name() . '-' . $row->get_uniq_id() );
+
+		echo '<span class="row-actions">';
+		echo '<span class="trash"><a href="' . Keyring_Util::admin_url( false, array( 'action' => 'delete', 'service' => $row->get_service()->get_name(), 'token' => $row->get_uniq_id(), 'kr_nonce' => $kr_delete_nonce, 'nonce' => $delete_nonce ) ) . '" title="' . esc_attr( __( 'Delete', 'keyring' ) ) . '" class="delete">Delete</a></span>';
+		echo ' | ';
+		echo '<a href="' . Keyring_Util::admin_url( false, array( 'action' => 'test', 'service' => $row->get_service()->get_name(), 'token' => $row->get_uniq_id(), 'kr_nonce' => $kr_test_nonce, 'nonce' => $test_nonce ) ) . '" title="' . esc_attr( __( 'Test', 'keyring' ) ) . '" class="test">Test</a>';
+		echo '</span>';
 	}
 }

+ 3 - 3
app/plugins/keyring/includes/services/core/http-basic.php

@@ -104,11 +104,11 @@ class Keyring_Service_HTTP_Basic extends Keyring_Service {
 		}
 
 		// Load up the request token that got us here and globalize it
-		if ( $_REQUEST['state'] ) {
+		if ( isset( $_REQUEST['state'] ) ) {
 			global $keyring_request_token;
-			$state = (int) $_REQUEST['state'];
+			$state = preg_replace( '/[^\x20-\x7E]/', '', $_GET['state'] );
 			$keyring_request_token = $this->store->get_token( array( 'id' => $state, 'type' => 'request' ) );
-			Keyring_Util::debug( 'HTTP Basic Loaded Request Token ' . $_REQUEST['state'] );
+			Keyring_Util::debug( 'HTTP Basic Loaded Request Token ' . $state );
 			Keyring_Util::debug( $keyring_request_token );
 
 			// Remove request token, don't need it any more.

+ 33 - 34
app/plugins/keyring/includes/services/core/oauth1.php

@@ -75,32 +75,26 @@ class Keyring_Service_OAuth1 extends Keyring_Service {
 		);
 		$request_token     = apply_filters( 'keyring_request_token', $request_token, $this );
 		$request_token_id  = $this->store_token( $request_token );
+		$scope             = apply_filters( 'keyring_' . $this->get_name() . '_request_scope', false );
+
 		Keyring_Util::debug( 'OAuth1 Stored Request token ' . $request_token_id );
-		$request_token_url = add_query_arg(
-			'oauth_callback',
-			urlencode(
-				add_query_arg(
-					'state',
-					$request_token_id,
-					$this->callback_url
-				)
+		$request_token_url = add_query_arg( array(
+				'oauth_callback' =>
+					urlencode(
+						add_query_arg(
+							array(
+								'state' => $request_token_id,
+							),
+							$this->callback_url
+						)
+					),
+				'scope' => $scope,
 			),
 			$this->request_token_url
 		);
 
 		// Set up OAuth request
-		$req = OAuthRequest::from_consumer_and_token(
-			$this->consumer,
-			null,
-			$this->request_token_method,
-			$request_token_url,
-			null
-		);
-		$req->sign_request(
-			$this->signature_method,
-			$this->consumer,
-			null
-		);
+		$req = $this->prepare_request( null, $this->request_token_method, $request_token_url, false );
 
 		$query = '';
 		$parsed = parse_url( (string) $req );
@@ -203,9 +197,9 @@ class Keyring_Service_OAuth1 extends Keyring_Service {
 		// Load up the request token that got us here and globalize it
 		if ( isset( $_GET['state'] ) ) {
 			global $keyring_request_token;
-			$state = (int) $_GET['state'];
+			$state = preg_replace( '/[^\x20-\x7E]/', '', $_GET['state'] );
 			$keyring_request_token = $this->store->get_token( array( 'id' => $state, 'type' => 'request' ) );
-			Keyring_Util::debug( 'OAuth1 Loaded Request Token ' . $_GET['state'] );
+			Keyring_Util::debug( 'OAuth1 Loaded Request Token ' . $state );
 			Keyring_Util::debug( $keyring_request_token );
 
 			$secret = $keyring_request_token->token['oauth_token_secret'];
@@ -289,18 +283,7 @@ class Keyring_Service_OAuth1 extends Keyring_Service {
 			}
 		}
 
-		$req = OAuthRequest::from_consumer_and_token(
-			$this->consumer,
-			$token,
-			$method,
-			$url,
-			$sign_vars
-		);
-		$req->sign_request(
-			$this->signature_method,
-			$this->consumer,
-			$token
-		);
+		$req = $this->prepare_request( $token, $method, $url, $sign_vars );
 		$request_url = (string) $req;
 
 		if ( $this->token && $this->authorization_header ) {
@@ -362,6 +345,22 @@ class Keyring_Service_OAuth1 extends Keyring_Service {
 		}
 	}
 
+	function prepare_request( $token, $method, $url, $sign_vars = false ) {
+		$req = OAuthRequest::from_consumer_and_token(
+			$this->consumer,
+			$token,
+			$method,
+			$url,
+			$sign_vars
+		);
+		$req->sign_request(
+			$this->signature_method,
+			$this->consumer,
+			$token
+		);
+		return $req;
+	}
+
 	function get_display( Keyring_Access_Token $token ) {
 		return (string) $token->token->key;
 	}

+ 2 - 2
app/plugins/keyring/includes/services/core/oauth2.php

@@ -81,9 +81,9 @@ class Keyring_Service_OAuth2 extends Keyring_Service_OAuth1 {
 
 		// Load up the request token that got us here and globalize it
 		global $keyring_request_token;
-		$state = (int) $_GET['state'];
+		$state = preg_replace( '/[^\x20-\x7E]/', '', $_GET['state'] );
 		$keyring_request_token = $this->store->get_token( array( 'id' => $state, 'type' => 'request' ) );
-		Keyring_Util::debug( 'OAuth2 Loaded Request Token ' . $_GET['state'] );
+		Keyring_Util::debug( 'OAuth2 Loaded Request Token ' . $state );
 		Keyring_Util::debug( $keyring_request_token );
 
 		if ( !$keyring_request_token ) {

+ 4 - 4
app/plugins/keyring/includes/services/extended/facebook.php

@@ -17,7 +17,7 @@ class Keyring_Service_Facebook extends Keyring_Service_OAuth2 {
 			add_filter( 'keyring_facebook_basic_ui_intro', array( $this, 'basic_ui_intro' ) );
 		}
 
-		$this->set_endpoint( 'authorize',     'https://www.facebook.com/dialog/oauth',        'GET' );
+		$this->set_endpoint( 'authorize',    'https://www.facebook.com/dialog/oauth',         'GET' );
 		$this->set_endpoint( 'access_token', 'https://graph.facebook.com/oauth/access_token', 'GET' );
 		$this->set_endpoint( 'self',         'https://graph.facebook.com/me',                 'GET' );
 
@@ -38,10 +38,10 @@ class Keyring_Service_Facebook extends Keyring_Service_OAuth2 {
 	function basic_ui_intro() {
 		echo '<p>' . __( "If you haven't already, you'll need to set up an app on Facebook:", 'keyring' ) . '</p>';
 		echo '<ol>';
-		echo '<li>' . __( "Click <strong>+ Create New App</strong> at the top-right of <a href='https://developers.facebook.com/apps'>this page</a>", 'keyring' ) . '</li>';
-		echo '<li>' . __( "Enter a name for your app (maybe the name of your website?) and click <strong>Continue</strong> (ignore the other settings)", 'keyring' ) . '</li>';
+		echo '<li>' . sprintf( __( "Click <strong>+ Create New App</strong> at the top-right of <a href='%s'>this page</a>", 'keyring' ), 'https://developers.facebook.com/apps' ) . '</li>';
+		echo '<li>' . __( "Enter a name for your app (maybe the name of your website?) and a Category, click <strong>Continue</strong> (you can skip optional things)", 'keyring' ) . '</li>';
 		echo '<li>' . __( "Enter whatever is in the CAPTCHA and click <strong>Continue</strong>", 'keyring' ) . '</li>';
-		echo '<li>' . sprintf( __( "Put your domain name in the <strong>App Domains</strong> box. That value is probably <code>%s</code>", 'keyring' ), $_SERVER['HTTP_HOST'] ) . '</li>';
+		echo '<li>' . sprintf( __( "Click <strong>Settings</strong> on the left and then <strong>Advanced</strong> at the top of that page. Under <strong>Valid OAuth redirect URIs</strong>, enter your domain name. That value is probably <code>%s</code>", 'keyring' ), $_SERVER['HTTP_HOST'] ) . '</li>';
 		echo '<li>' . sprintf( __( "Click the <strong>Website with Facebook Login</strong> box and enter the URL to your website, which is probably <code>%s</code>", 'keyring' ), get_bloginfo( 'url' ) ) . '</li>';
 		echo '<li>' . __( "Click <strong>Save Changes</strong>", 'keyring' ) . '</li>';
 		echo '</ol>';

+ 1 - 1
app/plugins/keyring/includes/services/extended/flickr.php

@@ -33,7 +33,7 @@ class Keyring_Service_Flickr extends Keyring_Service_OAuth1 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . __( "To connect to Flickr, you'll need to <a href='http://www.flickr.com/services/apps/create/apply/?'>create an application at Flickr.com</a>. If this is a personal website then you can use a non-commercial key (which will be approved automatically).", 'keyring' ) . '</p>';
+		echo '<p>' . sprintf( __( 'To connect to Flickr, you\'ll need to <a href="%s">create an application at Flickr.com</a>. If this is a personal website then you can use a non-commercial key (which will be approved automatically).', 'keyring' ), 'http://www.flickr.com/services/apps/create/apply/?' ) . '</p>';
 		echo '<p>' . __( "Once you've created your app, enter the API <strong>Key</strong> and <strong>Secret</strong> below (App ID is not required for Flickr apps).", 'keyring' ) . '</p>';
 	}
 

+ 1 - 1
app/plugins/keyring/includes/services/extended/foursquare.php

@@ -35,7 +35,7 @@ class Keyring_Service_Foursquare extends Keyring_Service_OAuth2 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . __( "If you haven't already, you'll need to <a href='https://foursquare.com/developers/register'>create a new app at Foursquare</a>. You should only need to worry about these settings:", 'keyring' ) . '</p>';
+		echo '<p>' . sprintf( __( 'If you haven\'t already, you\'ll need to <a href="%s">create a new app at Foursquare</a>. You should only need to worry about these settings:', 'keyring' ), 'https://foursquare.com/developers/register' ) . '</p>';
 		echo '<ol>';
 		echo '<li>' . __( "<strong>Your app name</strong>: enter whatever you like, maybe your website's name?", 'keyring' ) . '</li>';
 		echo '<li>' . sprintf( __( "<strong>Download / welcome page url</strong>: just enter your website's URL, <code>%s</code>", 'keyring' ), get_bloginfo( 'url' ) ) . '</li>';

+ 5 - 4
app/plugins/keyring/includes/services/extended/google-contacts.php

@@ -54,15 +54,15 @@ class Keyring_Service_GoogleContacts extends Keyring_Service_OAuth2 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . __( "Google controls access to all of their APIs through their API Console. <a href='https://code.google.com/apis/console'>Go to the console</a> and click the project dropdown just under the logo in the upper left of the screen. Click <strong>Create&hellip;</strong> to create a new project. Enter a name and then click <strong>Create project</strong>. You don't technically need access to any of the additional APIs, but if you want to, then feel free to enable them", 'keyring' ) . '</p>';
+		echo '<p>' . sprintf( __( "Google controls access to all of their APIs through their API Console. <a href='%s'>Go to the console</a> and click the project dropdown just under the logo in the upper left of the screen. Click <strong>Create&hellip;</strong> to create a new project. Enter a name and then click <strong>Create project</strong>. You don't technically need access to any of the additional APIs, but if you want to, then feel free to enable them", 'keyring' ), 'https://code.google.com/apis/console' ) . '</p>';
 		echo '<p>' . __( "Now you need to set up an OAuth Client ID.", 'keyring' ) . '</p>';
 		echo '<ol>';
 		echo '<li>' . __( "Click <strong>API Access</strong> in the menu on the left.", 'keyring' ) . '</li>';
 		echo '<li>' . __( "Click the big blue button labelled <strong>Create an OAuth 2.0 client ID&hellip;</strong>", '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>' . __( "Leave the Application type set to <strong>Web application</strong>", 'keyring' ) . '</li>';
-		echo '<li>' . __( "Next to <strong>Your site or hostname</strong>, click <strong>(more options)</strong> <code>%s</code>", 'keyring' ) . '</li>';
-		echo '<li>' . sprintf( __( "In the <strong>Authorized Redirect URIs</strong> box, enter the URL <code>%s</code>", 'keyring' ), Keyring_Util::admin_url( 'google', array( 'action' => 'verify' ) ) ) . '</li>';
+		echo '<li>' . __( "Next to <strong>Your site or hostname</strong>, click <strong>(more options)</strong>", 'keyring' ) . '</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>' . 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>' . __( "Click <strong>Create client ID</strong> when you're done", 'keyring' ) . '</li>';
 		echo '</ol>';
@@ -102,6 +102,7 @@ class Keyring_Service_GoogleContacts extends Keyring_Service_OAuth2 {
 						'action'   => 'verify',
 						'kr_nonce' => $kr_nonce,
 						'nonce'    => $nonce,
+						'state'    => $request['state'],
 						'code'     => $request['code'], // Auth code from successful response (maybe)
 					)
 				)
@@ -176,7 +177,7 @@ class Keyring_Service_GoogleContacts extends Keyring_Service_OAuth2 {
 		echo apply_filters( 'keyring_' . $this->get_name() . '_basic_ui_intro', '' );
 
 		if ( ! $redirect_uri )
-			$redirect_uri = Keyring_Util::admin_url( 'google', array( 'action' => 'verify' ) );
+			$redirect_uri = Keyring_Util::admin_url( $this->get_name(), array( 'action' => 'verify' ) );
 
 		// Output basic form for collecting key/secret
 		echo '<form method="post" action="">';

+ 3 - 3
app/plugins/keyring/includes/services/extended/instagram.php

@@ -18,9 +18,9 @@ class Keyring_Service_Instagram extends Keyring_Service_OAuth2 {
 			add_filter( 'keyring_instagram_basic_ui_intro', array( $this, 'basic_ui_intro' ) );
 		}
 
-		$this->set_endpoint( 'authorize',    'https://api.instagram.com/oauth/authorize/', 'GET' );
+		$this->set_endpoint( 'authorize',    'https://api.instagram.com/oauth/authorize/',   'GET'  );
 		$this->set_endpoint( 'access_token', 'https://api.instagram.com/oauth/access_token', 'POST' );
-		$this->set_endpoint( 'self',         'https://api.Instagram.com/v2/users/self',   'GET' );
+		$this->set_endpoint( 'self',         'https://api.instagram.com/v2/users/self',      'GET'  );
 
 		$creds = $this->get_credentials();
 		$this->app_id  = $creds['app_id'];
@@ -35,7 +35,7 @@ class Keyring_Service_Instagram extends Keyring_Service_OAuth2 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . sprintf( __( "To get started, <a href='http://instagram.com/developer/clients/register/'>register an OAuth client on Instagram</a>. The most important setting is the <strong>OAuth redirect_uri</strong>, which should be set to <code>%s</code>. You can set the other values to whatever you like.", 'keyring' ), Keyring_Util::admin_url( 'instagram', array( 'action' => 'verify' ) ) ) . '</p>';
+		echo '<p>' . sprintf( __( 'To get started, <a href="%1$s">register an OAuth client on Instagram</a>. The most important setting is the <strong>OAuth redirect_uri</strong>, which should be set to <code>%2$s</code>. You can set the other values to whatever you like.', 'keyring' ), 'http://instagram.com/developer/clients/register/', Keyring_Util::admin_url( 'instagram', array( 'action' => 'verify' ) ) ) . '</p>';
 		echo '<p>' . __( "Once you've saved those changes, copy the <strong>CLIENT ID</strong> value into the <strong>API Key</strong> field, and the <strong>CLIENT SECRET</strong> value into the <strong>API Secret</strong> field and click save (you don't need an App ID value for Instagram).", 'keyring' ) . '</p>';
 	}
 

+ 1 - 1
app/plugins/keyring/includes/services/extended/instapaper.php

@@ -35,7 +35,7 @@ class Keyring_Service_Instapaper extends Keyring_Service_OAuth1 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . __( "To use the Instapaper API, you need to get manually approved. <a href='http://www.instapaper.com/main/request_oauth_consumer_token'>Apply here</a>, then wait for a reply email.", 'keyring' ) . '</p>';
+		echo '<p>' . sprintf( __( 'To use the Instapaper API, you need to get manually approved. <a href="%s">Apply here</a>, then wait for a reply email.', 'keyring' ), 'http://www.instapaper.com/main/request_oauth_consumer_token' ) . '</p>';
 		echo '<p>' . __( "Once you get approved, you'll get an email back with your details. Copy the <strong>OAuth consumer key</strong> value into the <strong>API Key</strong> field, and the <strong>OAuth consumer secret</strong> value into the <strong>API Secret</strong> field and click save (you don't need an App ID value for Instapaper).", 'keyring' ) . '</p>';
 	}
 

+ 9 - 2
app/plugins/keyring/includes/services/extended/linkedin.php

@@ -21,7 +21,7 @@ class Keyring_Service_LinkedIn extends Keyring_Service_OAuth1 {
 		}
 
 		$this->set_endpoint( 'request_token', 'https://api.linkedin.com/uas/oauth/requestToken', 'POST' );
-		$this->set_endpoint( 'authorize',     'https://api.linkedin.com/uas/oauth/authorize',    'GET'  );
+		$this->set_endpoint( 'authorize',     'https://api.linkedin.com/uas/oauth/authenticate', 'GET'  );
 		$this->set_endpoint( 'access_token',  'https://api.linkedin.com/uas/oauth/accessToken',  'GET'  );
 
 		$creds = $this->get_credentials();
@@ -31,10 +31,12 @@ class Keyring_Service_LinkedIn extends Keyring_Service_OAuth1 {
 
 		$this->consumer = new OAuthConsumer( $this->key, $this->secret, $this->callback_url );
 		$this->signature_method = new OAuthSignatureMethod_HMAC_SHA1;
+
+		add_filter( 'keyring_linkedin_request_scope', array( $this, 'member_permissions' ) );
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . __( "To connect to LinkedIn, you'll first need to <a href='https://www.linkedin.com/secure/developer?newapp='>create an app</a>. A lot of the details are required, but they're not actually important to the operation of your app, since Keyring will override any important settings.", 'keyring' ) . '</p>';
+		echo '<p>' . sprintf( __( "To connect to LinkedIn, you'll first need to <a href='%s'>create an app</a>. A lot of the details are required, but they're not actually important to the operation of your app, since Keyring will override any important settings.", 'keyring' ), 'https://www.linkedin.com/secure/developer?newapp=' ) . '</p>';
 		echo '<p>' . __( "Once you've created your app, go down to the <strong>OAuth Keys</strong> section and copy the <strong>API Key</strong> value into the <strong>API Key</strong> field below, and the <strong>Secret Key</strong> value into the <strong>API Secret</strong> field and click save (you don't need an App ID value for LinkedIn).", 'keyring' ) . '</p>';
 	}
 
@@ -45,6 +47,11 @@ class Keyring_Service_LinkedIn extends Keyring_Service_OAuth1 {
 			return json_decode( $response );
 	}
 
+	function member_permissions( $permissions = '' ) {
+		$permissions = 'rw_nus+r_basicprofile';
+		return $permissions;
+	}
+
 	function build_token_meta( $token ) {
 		// Set the token so that we can make requests using it
 		$this->set_token(

+ 27 - 3
app/plugins/keyring/includes/services/extended/moves.php

@@ -37,8 +37,8 @@ class Keyring_Service_Moves extends Keyring_Service_OAuth2 {
 
 		$this->set_endpoint( 'authorize',    'https://api.moves-app.com/oauth/v1/authorize',    'GET'  );
 		$this->set_endpoint( 'access_token', 'https://api.moves-app.com/oauth/v1/access_token', 'POST' );
-		$this->set_endpoint( 'verify_token', 'https://api.moves-app.com/oauth/v1/tokeninfo',    'GET' );
-		$this->set_endpoint( 'profile',      'https://api.moves-app.com/api/v1/user/profile',   'GET'  );
+		$this->set_endpoint( 'verify_token', 'https://api.moves-app.com/oauth/v1/tokeninfo',    'GET'  );
+		$this->set_endpoint( 'profile',      'https://api.moves-app.com/api/1.1/user/profile',  'GET'  );
 
 		$creds = $this->get_credentials();
 		$this->app_id  = $creds['app_id'];
@@ -48,6 +48,10 @@ class Keyring_Service_Moves extends Keyring_Service_OAuth2 {
 		$this->consumer = new OAuthConsumer( $this->key, $this->secret, $this->callback_url );
 		$this->signature_method = new OAuthSignatureMethod_HMAC_SHA1;
 
+		// Moves requires an exact match on Redirect URI, which means we can't send any nonces
+		$this->callback_url = remove_query_arg( array( 'nonce', 'kr_nonce' ), $this->callback_url );
+		add_action( 'pre_keyring_moves_verify', array( $this, 'redirect_incoming_verify' ) );
+
 		$this->authorization_header    = 'Bearer';
 		$this->authorization_parameter = false;
 
@@ -55,7 +59,7 @@ class Keyring_Service_Moves extends Keyring_Service_OAuth2 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . __( "Head over and <a href='https://dev.moves-app.com/apps/new'>create a new application</a> on Moves-app which you'll use to connect.", 'keyring' ) . '</p>';
+		echo '<p>' . sprintf( __( 'Head over and <a href="%s">create a new application</a> on Moves-app which you\'ll use to connect.', 'keyring' ), 'https://dev.moves-app.com/apps/new' ) . '</p>';
 		echo '<p>' . sprintf( __( "Once it's created, click the <strong>Development</strong> tab. Your <strong>App ID</strong> and <strong>API Key</strong> are both shown on that page as <strong>Client ID</strong>. Enter your <strong>Client secret</strong> in the <strong>API Secret</strong> box. On that tab there is also a <strong>Redirect URI</strong> box, which you should set to <code>%s</code>.", 'keyring' ), Keyring_Util::admin_url( self::NAME, array( 'action' => 'verify' ) ) ) . '</p>';
 	}
 
@@ -64,6 +68,26 @@ class Keyring_Service_Moves extends Keyring_Service_OAuth2 {
 		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(
 			'user_id'       => $token['user_id'],

+ 1 - 1
app/plugins/keyring/includes/services/extended/runkeeper.php

@@ -37,7 +37,7 @@ class Keyring_Service_RunKeeper extends Keyring_Service_OAuth2 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . __( "You'll need to <a href='http://runkeeper.com/partner/applications/registerForm'>register a new application</a> on RunKeeper so that you can connect. Be sure to check the <strong>Read Health Information</strong> option under <strong>Permissions Requests</strong> (and explain why you want to read that data). You will also be required to set an <strong>Estimated Date of Publication</strong>.", 'keyring' ) . '</p>';
+		echo '<p>' . sprintf( __( 'You\'ll need to <a href="%s">register a new application</a> on RunKeeper so that you can connect. Be sure to check the <strong>Read Health Information</strong> option under <strong>Permissions Requests</strong> (and explain why you want to read that data). You will also be required to set an <strong>Estimated Date of Publication</strong>.', 'keyring' ), 'http://runkeeper.com/partner/applications/register' ) . '</p>';
 		echo '<p>' . __( "Once you've registered your application, click the <strong>Application Keys and URLs</strong> next to it, and copy the <strong>Client ID</strong> into the <strong>API Key</strong> field below, and the <strong>Client Secret</strong> value into <strong>API Secret</strong>.", 'keyring' ) . '</p>';
 	}
 

+ 1 - 1
app/plugins/keyring/includes/services/extended/tripit.php

@@ -37,7 +37,7 @@ class Keyring_Service_TripIt extends Keyring_Service_OAuth1 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . __( "If you haven't created an app on TripIt yet, <a href='https://www.tripit.com/developer/create'>create one now</a>. Make sure you set it to being a 'Web application or widget', and other than that the settings are all up to you.", 'keyring' ) . '</p>';
+		echo '<p>' . sprintf( __( 'If you haven\'t created an app on TripIt yet, <a href="%s">create one now</a>. Make sure you select \'Full API Application\' and \'Web application or widget\'; other than that the settings are all up to you.', 'keyring' ), 'https://www.tripit.com/developer/create' ) . '</p>';
 		echo '<p>' . __( "Once you've created your app, you will see a yellow box at the top of the page, where you can get your <strong>API Key</strong> and <strong>API Secret</strong>, to enter below (you don't need an App ID value for TripIt).", 'keyring' ) . '</p>';
 	}
 

+ 2 - 2
app/plugins/keyring/includes/services/extended/tumblr.php

@@ -18,7 +18,7 @@ class Keyring_Service_Tumblr extends Keyring_Service_OAuth1 {
 		}
 
 		$this->set_endpoint( 'request_token', 'http://www.tumblr.com/oauth/request_token', 'POST' );
-		$this->set_endpoint( 'authorize',     'http://www.tumblr.com/oauth/authorize',     'GET' );
+		$this->set_endpoint( 'authorize',     'http://www.tumblr.com/oauth/authorize',     'GET'  );
 		$this->set_endpoint( 'access_token',  'http://www.tumblr.com/oauth/access_token',  'POST' );
 
 		$creds = $this->get_credentials();
@@ -34,7 +34,7 @@ class Keyring_Service_Tumblr extends Keyring_Service_OAuth1 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . sprintf( __( "To get started, <a href='http://www.tumblr.com/oauth/register'>register an application with Tumblr</a>. The <strong>Default callback URL</strong> should be set to <code>%s</code>, and you can enter whatever you like in the other fields.", 'keyring' ), Keyring_Util::admin_url( 'tumblr', array( 'action' => 'verify' ) ) ) . '</p>';
+		echo '<p>' . sprintf( __( 'To get started, <a href="%1$s">register an application with Tumblr</a>. The <strong>Default callback URL</strong> should be set to <code>%2$s</code>, and you can enter whatever you like in the other fields.', 'keyring' ), 'http://www.tumblr.com/oauth/register', Keyring_Util::admin_url( 'tumblr', array( 'action' => 'verify' ) ) ) . '</p>';
 		echo '<p>' . __( "Once you've created your app, copy the <strong>OAuth Consumer Key</strong> into the <strong>API Key</strong> field below. Click the <strong>Show secret key</strong> link, and then copy the <strong>Secret Key</strong> value into the <strong>API Secret</strong> field below. You don't need an App ID value for Tumblr.", 'keyring' ) . '</p>';
 	}
 

+ 2 - 2
app/plugins/keyring/includes/services/extended/twitter.php

@@ -21,7 +21,7 @@ class Keyring_Service_Twitter extends Keyring_Service_OAuth1 {
 		$this->authorization_realm  = "twitter.com";
 
 		$this->set_endpoint( 'request_token', 'https://twitter.com/oauth/request_token', 'POST' );
-		$this->set_endpoint( 'authorize',     'https://twitter.com/oauth/authorize',     'GET' );
+		$this->set_endpoint( 'authorize',     'https://twitter.com/oauth/authorize',     'GET'  );
 		$this->set_endpoint( 'access_token',  'https://twitter.com/oauth/access_token',  'POST' );
 		$this->set_endpoint( 'verify',        'https://api.twitter.com/1.1/account/verify_credentials.json', 'GET' );
 
@@ -37,7 +37,7 @@ class Keyring_Service_Twitter extends Keyring_Service_OAuth1 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . sprintf( __( "If you haven't already, you'll need to <a href='https://dev.twitter.com/apps/new'>create an app on Twitter</a> (log in using your normal Twitter account). Make sure you enter something for the <strong>Callback URL</strong>, even though Keyring will override it with the correct value. Just enter your homepage, e.g. <code>%s</code>.", 'keyring' ), get_bloginfo( 'url' ) ) . '</p>';
+		echo '<p>' . sprintf( __( 'If you haven\'t already, you\'ll need to <a href="%1$s">create an app on Twitter</a> (log in using your normal Twitter account). Make sure you enter something for the <strong>Callback URL</strong>, even though Keyring will override it with the correct value. Just enter your homepage, e.g. <code>%2$s</code>.', 'keyring' ), 'https://dev.twitter.com/apps/new', get_bloginfo( 'url' ) ) . '</p>';
 		echo '<p>' . __( "Once you've created an app, copy and paste your <strong>Consumer key</strong> and <strong>Consumer secret</strong> (from under the <strong>OAuth settings</strong> section of your app's details) into the boxes below. You don't need an App ID for Twitter.", 'keyring' ) . '</p>';
 	}
 

+ 3 - 3
app/plugins/keyring/includes/services/extended/yahoo.php

@@ -17,8 +17,8 @@ class Keyring_Service_Yahoo extends Keyring_Service_OAuth1 {
 			add_filter( 'keyring_yahoo_basic_ui_intro', array( $this, 'basic_ui_intro' ) );
 		}
 
-		$this->set_endpoint( 'request_token', 'https://api.login.yahoo.com/oauth/v2/get_request_token', 'GET' );
-		$this->set_endpoint( 'authorize',     'https://api.login.yahoo.com/oauth/v2/request_auth',      'GET' );
+		$this->set_endpoint( 'request_token', 'https://api.login.yahoo.com/oauth/v2/get_request_token', 'GET'  );
+		$this->set_endpoint( 'authorize',     'https://api.login.yahoo.com/oauth/v2/request_auth',      'GET'  );
 		$this->set_endpoint( 'access_token',  'https://api.login.yahoo.com/oauth/v2/get_token',         'POST' );
 
 		$creds = $this->get_credentials();
@@ -31,7 +31,7 @@ class Keyring_Service_Yahoo extends Keyring_Service_OAuth1 {
 	}
 
 	function basic_ui_intro() {
-		echo '<p>' . sprintf( __( "To connect to Yahoo!, you need to <a href='https://developer.apps.yahoo.com/dashboard/createKey.html'>Create a new project</a>. Make sure you set the <strong>Access Scope</strong> to <strong>This app requires access to private user data</strong>. When you select that, you will be asked for an <strong>Application Domain</strong>, which should probably be set to <code>http://%s</code>. Which APIs you request access for will depend on how Keyring will be used on this site. Common ones will be <strong>Contacts</strong>, <strong>Social Directory</strong>, <strong>Status</strong>, and <strong>Updates</strong>.", 'keyring' ), $_SERVER['HTTP_HOST'] ) . '</p>';
+		echo '<p>' . sprintf( __( 'To connect to Yahoo!, you need to <a href="%1$s">Create a new project</a>. Make sure you set the <strong>Access Scope</strong> to <strong>This app requires access to private user data</strong>. When you select that, you will be asked for an <strong>Application Domain</strong>, which should probably be set to <code>http://%2$s</code>. Which APIs you request access for will depend on how Keyring will be used on this site. Common ones will be <strong>Contacts</strong>, <strong>Social Directory</strong>, <strong>Status</strong>, and <strong>Updates</strong>.', 'keyring' ), 'https://developer.apps.yahoo.com/dashboard/createKey.html', $_SERVER['HTTP_HOST'] ) . '</p>';
 		echo '<p>' . __( "Once you've created your project, copy and paste your <strong>Consumer key</strong> and <strong>Consumer secret</strong> (from under the <strong>Authentication Information: OAuth</strong> section of your app's details) into the boxes below. You don't need an App ID for Yahoo!.", 'keyring' ) . '</p>';
 	}
 

+ 2 - 2
app/plugins/keyring/includes/stores/singlestore.php

@@ -39,7 +39,7 @@ class Keyring_SingleStore extends Keyring_Store {
 			'post_type'   => 'kr_' . $token->type() . '_token',
 			'meta_key'    => 'service',
 			'meta_value'  => $token->get_name(),
-			'author_id'   => get_current_user_id(),
+			'author'      => get_current_user_id(),
 			's'           => serialize( $token->token ), // Search the post content for this token
 			'exact'       => true, // Require exact content match
 		) );
@@ -105,7 +105,7 @@ class Keyring_SingleStore extends Keyring_Store {
 		$query = array(
 			'numberposts' => -1, // all
 			'post_type'   => 'kr_' . $args['type'] . '_token',
-			'author_id'   => $args['user_id'],
+			'author'      => $args['user_id'],
 		);
 
 		// Get tokens for a specific service

+ 15 - 10
app/plugins/keyring/keyring.php

@@ -3,9 +3,10 @@
 Plugin Name: Keyring
 Plugin URI: http://dentedreality.com.au/projects/wp-keyring/
 Description: Keyring helps you manage your keys. It provides a generic, very hookable framework for connecting to remote systems and managing your access tokens, username/password combos etc for those services. On its own it doesn't do much, but it enables other plugins to do things that require authorization to act on your behalf.
-Version: 1.5.1
+Version: 1.6
 Author: Beau Lebens
 Author URI: http://dentedreality.com.au
+License: GPL v2 or newer <https://www.gnu.org/licenses/gpl.txt>
 */
 
 // Define this in your wp-config (and set to true) to enable debugging
@@ -25,7 +26,7 @@ define( 'KEYRING__DEBUG_WARN',   2 );
 define( 'KEYRING__DEBUG_ERROR',  3 );
 
 // Indicates Keyring is installed/active so that other plugins can detect it
-define( 'KEYRING__VERSION', '1.5.1' );
+define( 'KEYRING__VERSION', '1.6' );
 
 /**
  * Core Keyring class that handles UI and the general flow of requesting access tokens etc
@@ -38,6 +39,7 @@ class Keyring {
 	protected $store               = false;
 	protected $errors              = array();
 	protected $messages            = array();
+	protected $token_store         = '';
 	var $admin_page                = 'keyring';
 
 	function __construct() {
@@ -45,9 +47,10 @@ class Keyring {
 			require_once dirname( __FILE__ ) . '/admin-ui.php';
 			Keyring_Admin_UI::init();
 
-			add_filter( 'keyring_admin_url', function( $url ) {
-				return admin_url( 'tools.php?page=' . Keyring::init()->admin_page );
-			} );
+			add_filter( 'keyring_admin_url', function( $url, $params ) {
+				$url = admin_url( 'tools.php?page=' . Keyring::init()->admin_page );
+				return add_query_arg( $params, $url );
+			}, 10, 2 );
 		}
 
 		// This is used internally to create URLs, and also to know when to
@@ -82,7 +85,9 @@ class Keyring {
 		// Load stores early so we can confirm they're loaded correctly
 		require_once dirname( __FILE__ ) . '/store.php';
 		do_action( 'keyring_load_token_stores' );
-		if ( !defined( 'KEYRING__TOKEN_STORE' ) || !class_exists( KEYRING__TOKEN_STORE ) || !in_array( 'Keyring_Store', class_parents( KEYRING__TOKEN_STORE ) ) )
+		$keyring = Keyring::init();
+		$keyring->token_store = apply_filters( 'keyring_token_store', defined( 'KEYRING__TOKEN_STORE' ) ? KEYRING__TOKEN_STORE : false );
+		if ( !class_exists( $keyring->token_store ) || !in_array( 'Keyring_Store', class_parents( $keyring->token_store ) ) )
 			wp_die( sprintf( __( 'Invalid <code>KEYRING__TOKEN_STORE</code> specified. Please make sure <code>KEYRING__TOKEN_STORE</code> is set to a valid classname for handling token storage in <code>%s</code> (or <code>wp-config.php</code>)', 'keyring' ), __FILE__ ) );
 
 		// Load base token and service definitions + core services
@@ -173,7 +178,7 @@ class Keyring {
 		$keyring = Keyring::init();
 
 		if ( !$keyring->store )
-			$keyring->store = call_user_func( array( KEYRING__TOKEN_STORE, 'init' ) );
+			$keyring->store = call_user_func( array( $keyring->token_store, 'init' ) );
 
 		return $keyring->store;
 	}
@@ -251,15 +256,15 @@ class Keyring_Util {
 	 * @return URL to Keyring admin UI (main listing, or specific service verify process)
 	 */
 	static function admin_url( $service = false, $params = array() ) {
-		$url = apply_filters( 'keyring_admin_url', admin_url( '' ) );
+		$url = admin_url();
 
 		if ( $service )
-			$url = add_query_arg( array( 'service' => $service ), $url );
+			$params['service'] = $service;
 
 		if ( count( $params ) )
 			$url = add_query_arg( $params, $url );
 
-		return $url;
+		return apply_filters( 'keyring_admin_url', $url, $params );
 	}
 
 	static function connect_to( $service, $for ) {

+ 20 - 3
app/plugins/keyring/readme.txt

@@ -1,10 +1,10 @@
 === Keyring ===
 
 Contributors: beaulebens, mdawaffe, jshreve, automattic
-Tags: authentication, security, oauth, http basic, key, token, authorization, delicious, facebook, flickr, foursquare, google contacts, instagram, instapaper, linkedin, runkeeper, tripit, tumblr, twitter, yahoo, web services
+Tags: authentication, security, oauth, http basic, key, token, authorization, delicious, facebook, flickr, foursquare, google contacts, instagram, instapaper, linkedin, moves, runkeeper, tripit, tumblr, twitter, yahoo, web services
 Requires at least: 3.3
-Tested up to: 3.6
-Stable Tag: 1.5.1
+Tested up to: 3.9
+Stable Tag: 1.6
 
 An authentication framework that handles authorization with external web services.
 
@@ -92,6 +92,23 @@ Keyring just provides a framework for handling connections to external services.
 Add files to includes/services/extended/ that either implement one of the includes/services/core/ service foundations, or start from scratch. Follow one of the existing service definitions for a template, and see service.php in the root of Keyring for some detail on methods you need to define, and optional ones that might make your life easier.
 
 == Changelog ==
+= 1.6 =
+* Enhancement BREAKING: Change the way the keyring_admin_url filter is applied so that it's already got all the parameters etc added to it by the time the filter happens. Makes that filter much more flexible. You probably need to add $params to your filter function, and the add_query_arg() those params onto whatever URL you're returning.
+* Bugfix WARNING: Change the filters in get_credentials() to keyring_service_credentials, since keyring_credentials is in use, and slightly different
+* Enhancement: Allow the token store to be filtered, for divergent implementations on a single installation, props Jamie P
+* Enhancement: Allow filtering of request scope for OAuth1 services
+* Enhancement: Add connection-testing to the default admin UI for most services
+* Enhancement: Abstract out prepare_request() for OAuth1 service
+* Bugfix: Use the correct parameters for filtering results in SingleStore, props Milan D
+* Bugfix: Request correct permissions for LinkedIn, using the new filter
+* Bugfix: Get Google Contacts working properly. Props Simon W for spotting 2 errors.
+* Bugfix: Update authorize/authenticate URL for LinkedIn. Should move entire service to OAuth2 ideally
+* Bugfix: Don't restrict the 'state' parameter to being an int only (per OAuth2 spec), props Jamie P
+* Bugfix: Ensure to always filter the return value (even if false) from get_credentials(), props Jamie P
+* Bugfix: Fix the Moves service so that it can get past the auth flow again (new restriction on Redirect URIs)
+* Enhancement: Update the Facebook config instructions to match their UI changes
+* Bugfix: Remove unnecessary %s from Google instructions
+
 = 1.5.1 =
 * Remove example OAuth application included within that library. Unnecessary and contains an XSS vulnerability.
 

+ 5 - 5
app/plugins/keyring/service.php

@@ -208,7 +208,7 @@ abstract class Keyring_Service {
 			$creds = $this->_get_credentials();
 
 			if ( !is_null( $creds ) )
-				return apply_filters( 'keyring_credentials', $creds, $this->get_name() );
+				return apply_filters( 'keyring_service_credentials', $creds, $this->get_name() );
 		}
 
 		// Then check for generic constants
@@ -226,17 +226,17 @@ abstract class Keyring_Service {
 				'key'    => constant( 'KEYRING__' . $name . '_KEY' ),
 				'secret' => constant( 'KEYRING__' . $name . '_SECRET' ),
 			);
-			return apply_filters( 'keyring_credentials', $creds, $this->get_name() );
+			return apply_filters( 'keyring_service_credentials', $creds, $this->get_name() );
 		}
 
 		// Last check in the database for a shared store of credentials
-		$all = apply_filters( 'keyring_credentials', get_option( 'keyring_credentials' ) );
+		$creds = false;
+		$all   = apply_filters( 'keyring_credentials', get_option( 'keyring_credentials' ) );
 		if ( !empty( $all[ $this->get_name() ] ) ) {
 			$creds = $all[ $this->get_name() ];
-			return apply_filters( 'keyring_credentials', $creds, $this->get_name() );
 		}
 
-		return false;
+		return apply_filters( 'keyring_service_credentials', $creds, $this->get_name() );
 	}
 
 	/**