├── library ├── keys │ ├── index.php │ ├── .htaccess │ ├── public_key.pem │ └── private_key.pem ├── OAuth2 │ ├── ResponseType │ │ ├── ResponseTypeInterface.php │ │ ├── AuthorizationCodeInterface.php │ │ ├── AccessTokenInterface.php │ │ ├── AuthorizationCode.php │ │ ├── CryptoToken.php │ │ ├── JwtAccessToken.php │ │ └── AccessToken.php │ ├── OpenID │ │ ├── ResponseType │ │ │ ├── CodeIdTokenInterface.php │ │ │ ├── IdTokenTokenInterface.php │ │ │ ├── TokenIdTokenInterface.php │ │ │ ├── CodeIdToken.php │ │ │ ├── TokenIdToken.php │ │ │ ├── IdTokenToken.php │ │ │ ├── IdTokenInterface.php │ │ │ ├── AuthorizationCodeInterface.php │ │ │ ├── AuthorizationCode.php │ │ │ └── IdToken.php │ │ ├── Controller │ │ │ ├── AuthorizeControllerInterface.php │ │ │ ├── UserInfoControllerInterface.php │ │ │ ├── UserInfoController.php │ │ │ └── AuthorizeController.php │ │ ├── Storage │ │ │ ├── UserClaimsInterface.php │ │ │ └── AuthorizationCodeInterface.php │ │ └── GrantType │ │ │ └── AuthorizationCode.php │ ├── Storage │ │ ├── CryptoTokenInterface.php │ │ ├── JwtAccessTokenInterface.php │ │ ├── PublicKeyInterface.php │ │ ├── ScopeInterface.php │ │ ├── ClientCredentialsInterface.php │ │ ├── UserCredentialsInterface.php │ │ ├── JwtBearerInterface.php │ │ ├── AccessTokenInterface.php │ │ ├── CryptoToken.php │ │ ├── ClientInterface.php │ │ ├── RefreshTokenInterface.php │ │ ├── JwtAccessToken.php │ │ ├── AuthorizationCodeInterface.php │ │ └── Redis.php │ ├── Encryption │ │ ├── EncryptionInterface.php │ │ ├── FirebaseJwt.php │ │ └── Jwt.php │ ├── RequestInterface.php │ ├── ClientAssertionType │ │ ├── ClientAssertionTypeInterface.php │ │ └── HttpBasic.php │ ├── TokenType │ │ ├── Mac.php │ │ ├── TokenTypeInterface.php │ │ └── Bearer.php │ ├── GrantType │ │ ├── GrantTypeInterface.php │ │ ├── ClientCredentials.php │ │ ├── UserCredentials.php │ │ ├── AuthorizationCode.php │ │ ├── RefreshToken.php │ │ └── JwtBearer.php │ ├── ResponseInterface.php │ ├── Controller │ │ ├── ResourceControllerInterface.php │ │ ├── TokenControllerInterface.php │ │ ├── AuthorizeControllerInterface.php │ │ └── ResourceController.php │ ├── ScopeInterface.php │ ├── Autoloader.php │ ├── Scope.php │ └── Request.php ├── content │ ├── create-new-client.php │ └── edit-client.php ├── class-wo-table.php └── class-wo-api.php ├── assets ├── images │ ├── cer.png │ ├── logo.png │ └── openid-config-json.png ├── js │ └── admin.js └── css │ └── admin.css ├── includes ├── ajax │ └── class-wo-ajax.php ├── actions.php ├── admin │ └── page-server-status.php ├── filters.php └── functions.php ├── wp-oauth.php └── wp-oauth-main.php /library/keys/index.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | interface CryptoTokenInterface extends AccessTokenInterface 12 | { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /library/OAuth2/Encryption/EncryptionInterface.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | interface JwtAccessTokenInterface extends AccessTokenInterface 12 | { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /library/OAuth2/RequestInterface.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | interface PublicKeyInterface 12 | { 13 | public function getPublicKey($client_id = null); 14 | public function getPrivateKey($client_id = null); 15 | public function getEncryptionAlgorithm($client_id = null); 16 | } 17 | -------------------------------------------------------------------------------- /library/OAuth2/TokenType/Mac.php: -------------------------------------------------------------------------------- 1 | $response = new OAuth2\Response(); 14 | * > $userInfoController->handleUserInfoRequest( 15 | * > OAuth2\Request::createFromGlobals(), 16 | * > $response; 17 | * > $response->send(); 18 | * 19 | */ 20 | interface UserInfoControllerInterface 21 | { 22 | public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response); 23 | } 24 | -------------------------------------------------------------------------------- /library/OAuth2/ResponseInterface.php: -------------------------------------------------------------------------------- 1 | false 8 | ); 9 | 10 | /** loop though all the ajax events and add then as needed */ 11 | foreach ( $ajax_events as $ajax_event => $nopriv ) { 12 | add_action( 'wp_ajax_wo_' . $ajax_event, 'wo_ajax_'.$ajax_event ); 13 | if ( $nopriv ) 14 | add_action( 'wp_ajax_nopriv_wo_' . $ajax_event, 'wo_ajax_'.$ajax_event ); 15 | } 16 | 17 | function wo_ajax_remove_client () { 18 | global $wpdb; 19 | $action = $wpdb->delete( "{$wpdb->prefix}oauth_clients", array( 'client_id' => $_POST['data']) ); 20 | if($action){ 21 | print "1"; 22 | }else{ 23 | print "System Error: Could not remove the client from the server."; 24 | } 25 | exit; 26 | } -------------------------------------------------------------------------------- /library/OAuth2/OpenID/ResponseType/CodeIdToken.php: -------------------------------------------------------------------------------- 1 | authCode = $authCode; 13 | $this->idToken = $idToken; 14 | } 15 | 16 | public function getAuthorizeResponse($params, $user_id = null) 17 | { 18 | $result = $this->authCode->getAuthorizeResponse($params, $user_id); 19 | $id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce']); 20 | $result[1]['query']['id_token'] = $id_token; 21 | 22 | return $result; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /library/OAuth2/OpenID/ResponseType/TokenIdToken.php: -------------------------------------------------------------------------------- 1 | accessToken = $accessToken; 15 | $this->idToken = $idToken; 16 | } 17 | 18 | public function getAuthorizeResponse($params, $user_id = null) 19 | { 20 | $result = $this->accessToken->getAuthorizeResponse($params, $user_id); 21 | $access_token = $result[1]['fragment']['access_token']; 22 | $id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce'], null, $access_token); 23 | $result[1]['fragment']['id_token'] = $id_token; 24 | 25 | return $result; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/OAuth2/Controller/ResourceControllerInterface.php: -------------------------------------------------------------------------------- 1 | if (!$resourceController->verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) { 15 | * > $response->send(); // authorization failed 16 | * > die(); 17 | * > } 18 | * > return json_encode($resource); // valid token! Send the stuff! 19 | * 20 | */ 21 | interface ResourceControllerInterface 22 | { 23 | public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null); 24 | 25 | public function getAccessTokenData(RequestInterface $request, ResponseInterface $response); 26 | } 27 | -------------------------------------------------------------------------------- /library/OAuth2/OpenID/ResponseType/IdTokenToken.php: -------------------------------------------------------------------------------- 1 | accessToken = $accessToken; 15 | $this->idToken = $idToken; 16 | } 17 | 18 | public function getAuthorizeResponse($params, $user_id = null) 19 | { 20 | $result = $this->accessToken->getAuthorizeResponse($params, $user_id); 21 | $access_token = $result[1]['fragment']['access_token']; 22 | $id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce'], null, $access_token); 23 | $result[1]['fragment']['id_token'] = $id_token; 24 | 25 | return $result; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/OAuth2/Controller/TokenControllerInterface.php: -------------------------------------------------------------------------------- 1 | $tokenController->handleTokenRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response()); 15 | * > $response->send(); 16 | * 17 | */ 18 | interface TokenControllerInterface 19 | { 20 | /** 21 | * handleTokenRequest 22 | * 23 | * @param $request 24 | * OAuth2\RequestInterface - The current http request 25 | * @param $response 26 | * OAuth2\ResponseInterface - An instance of OAuth2\ResponseInterface to contain the response data 27 | * 28 | */ 29 | public function handleTokenRequest(RequestInterface $request, ResponseInterface $response); 30 | 31 | public function grantAccessToken(RequestInterface $request, ResponseInterface $response); 32 | } 33 | -------------------------------------------------------------------------------- /library/OAuth2/ScopeInterface.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | interface AuthorizationCodeInterface extends ResponseTypeInterface 10 | { 11 | /** 12 | * @return 13 | * TRUE if the grant type requires a redirect_uri, FALSE if not 14 | */ 15 | public function enforceRedirect(); 16 | 17 | /** 18 | * Handle the creation of the authorization code. 19 | * 20 | * @param $client_id client identifier related to the authorization code 21 | * @param $user_id user id associated with the authorization code 22 | * @param $redirect_uri an absolute URI to which the authorization server will redirect the 23 | * user-agent to when the end-user authorization step is completed. 24 | * @param $scope OPTIONAL scopes to be stored in space-separated string. 25 | * 26 | * @see http://tools.ietf.org/html/rfc6749#section-4 27 | * @ingroup oauth2_section_4 28 | */ 29 | public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null); 30 | } 31 | -------------------------------------------------------------------------------- /library/OAuth2/OpenID/ResponseType/IdTokenInterface.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface 12 | { 13 | /** 14 | * Handle the creation of the authorization code. 15 | * 16 | * @param $client_id Client identifier related to the authorization code 17 | * @param $user_id User ID associated with the authorization code 18 | * @param $redirect_uri An absolute URI to which the authorization server will redirect the 19 | * user-agent to when the end-user authorization step is completed. 20 | * @param $scope OPTIONAL Scopes to be stored in space-separated string. 21 | * @param $id_token OPTIONAL The OpenID Connect id_token. 22 | * 23 | * @see http://tools.ietf.org/html/rfc6749#section-4 24 | * @ingroup oauth2_section_4 25 | */ 26 | public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null); 27 | } 28 | -------------------------------------------------------------------------------- /library/OAuth2/Autoloader.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT License 10 | */ 11 | class Autoloader 12 | { 13 | private $dir; 14 | 15 | public function __construct($dir = null) 16 | { 17 | if (is_null($dir)) { 18 | $dir = dirname(__FILE__).'/..'; 19 | } 20 | $this->dir = $dir; 21 | } 22 | /** 23 | * Registers OAuth2\Autoloader as an SPL autoloader. 24 | */ 25 | public static function register($dir = null) 26 | { 27 | ini_set('unserialize_callback_func', 'spl_autoload_call'); 28 | spl_autoload_register(array(new self($dir), 'autoload')); 29 | } 30 | 31 | /** 32 | * Handles autoloading of classes. 33 | * 34 | * @param string $class A class name. 35 | * 36 | * @return boolean Returns true if the class has been loaded 37 | */ 38 | public function autoload($class) 39 | { 40 | if (0 !== strpos($class, 'OAuth2')) { 41 | return; 42 | } 43 | 44 | if (file_exists($file = $this->dir.'/'.str_replace('\\', '/', $class).'.php')) { 45 | require $file; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /includes/actions.php: -------------------------------------------------------------------------------- 1 | 6 | * @package WordPress OAuth Server 7 | */ 8 | 9 | /** 10 | * Invalidate any token and refresh tokens during password reset 11 | * @param object $user WP_User Object 12 | * @param String $new_pass New Password 13 | * @return Void 14 | * 15 | * @since 3.1.8 16 | */ 17 | function wo_password_reset_action( $user, $new_pass ) { 18 | global $wpdb; 19 | $wpdb->delete( "{$wpdb->prefix}oauth_access_tokens", array( "user_id" => $user->ID ) ); 20 | $wpdb->delete( "{$wpdb->prefix}oauth_refresh_tokens", array( "user_id" => $user->ID ) ); 21 | } 22 | add_action( 'password_reset', 'wo_password_reset_action', 10, 2 ); 23 | 24 | /** 25 | * [wo_profile_update_action description] 26 | * @param int $user_id WP User ID 27 | * @return Void 28 | */ 29 | function wo_profile_update_action( $user_id ) { 30 | if ( ! isset( $_POST['pass1'] ) || '' == $_POST['pass1'] ) { 31 | return; 32 | } 33 | global $wpdb; 34 | $wpdb->delete( "{$wpdb->prefix}oauth_access_tokens", array( "user_id" => $user_id ) ); 35 | $wpdb->delete( "{$wpdb->prefix}oauth_refresh_tokens", array( "user_id" => $user_id ) ); 36 | } 37 | add_action( 'profile_update', 'wo_profile_update_action' ); -------------------------------------------------------------------------------- /library/OAuth2/ResponseType/AccessTokenInterface.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | interface AccessTokenInterface extends ResponseTypeInterface 10 | { 11 | /** 12 | * Handle the creation of access token, also issue refresh token if supported / desirable. 13 | * 14 | * @param $client_id client identifier related to the access token. 15 | * @param $user_id user ID associated with the access token 16 | * @param $scope OPTONAL scopes to be stored in space-separated string. 17 | * @param bool $includeRefreshToken if true, a new refresh_token will be added to the response 18 | * 19 | * @see http://tools.ietf.org/html/rfc6749#section-5 20 | * @ingroup oauth2_section_5 21 | */ 22 | public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true); 23 | 24 | /** 25 | * Handle the revoking of refresh tokens, and access tokens if supported / desirable 26 | * 27 | * @param $token 28 | * @param $tokenTypeHint 29 | * @return mixed 30 | * 31 | * @todo v2.0 include this method in interface. Omitted to maintain BC in v1.x 32 | */ 33 | //public function revokeToken($token, $tokenTypeHint); 34 | } 35 | -------------------------------------------------------------------------------- /library/OAuth2/Storage/ScopeInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface ScopeInterface 13 | { 14 | /** 15 | * Check if the provided scope exists. 16 | * 17 | * @param $scope 18 | * A space-separated string of scopes. 19 | * 20 | * @return 21 | * TRUE if it exists, FALSE otherwise. 22 | */ 23 | public function scopeExists($scope); 24 | 25 | /** 26 | * The default scope to use in the event the client 27 | * does not request one. By returning "false", a 28 | * request_error is returned by the server to force a 29 | * scope request by the client. By returning "null", 30 | * opt out of requiring scopes 31 | * 32 | * @param $client_id 33 | * An optional client id that can be used to return customized default scopes. 34 | * 35 | * @return 36 | * string representation of default scope, null if 37 | * scopes are not defined, or false to force scope 38 | * request by the client 39 | * 40 | * ex: 41 | * 'default' 42 | * ex: 43 | * null 44 | */ 45 | public function getDefaultScope($client_id = null); 46 | } 47 | -------------------------------------------------------------------------------- /library/OAuth2/Encryption/FirebaseJwt.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class FirebaseJwt implements EncryptionInterface 10 | { 11 | public function __construct() 12 | { 13 | if (!class_exists('\JWT')) { 14 | throw new \ErrorException('firebase/php-jwt must be installed to use this feature. You can do this by running "composer require firebase/php-jwt"'); 15 | } 16 | } 17 | 18 | public function encode($payload, $key, $alg = 'HS256', $keyId = null) 19 | { 20 | return \JWT::encode($payload, $key, $alg, $keyId); 21 | } 22 | 23 | public function decode($jwt, $key = null, $allowedAlgorithms = null) 24 | { 25 | try { 26 | 27 | //Maintain BC: Do not verify if no algorithms are passed in. 28 | if (!$allowedAlgorithms) { 29 | $key = null; 30 | } 31 | 32 | return (array)\JWT::decode($jwt, $key, $allowedAlgorithms); 33 | } catch (\Exception $e) { 34 | return false; 35 | } 36 | } 37 | 38 | public function urlSafeB64Encode($data) 39 | { 40 | return \JWT::urlsafeB64Encode($data); 41 | } 42 | 43 | public function urlSafeB64Decode($b64) 44 | { 45 | return \JWT::urlsafeB64Decode($b64); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /library/OAuth2/OpenID/Storage/UserClaimsInterface.php: -------------------------------------------------------------------------------- 1 | value format. 34 | * 35 | * @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims 36 | */ 37 | public function getUserClaims($user_id, $scope); 38 | } 39 | -------------------------------------------------------------------------------- /library/OAuth2/Storage/ClientCredentialsInterface.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | interface ClientCredentialsInterface extends ClientInterface 12 | { 13 | 14 | /** 15 | * Make sure that the client credentials is valid. 16 | * 17 | * @param $client_id 18 | * Client identifier to be check with. 19 | * @param $client_secret 20 | * (optional) If a secret is required, check that they've given the right one. 21 | * 22 | * @return 23 | * TRUE if the client credentials are valid, and MUST return FALSE if it isn't. 24 | * @endcode 25 | * 26 | * @see http://tools.ietf.org/html/rfc6749#section-3.1 27 | * 28 | * @ingroup oauth2_section_3 29 | */ 30 | public function checkClientCredentials($client_id, $client_secret = null); 31 | 32 | /** 33 | * Determine if the client is a "public" client, and therefore 34 | * does not require passing credentials for certain grant types 35 | * 36 | * @param $client_id 37 | * Client identifier to be check with. 38 | * 39 | * @return 40 | * TRUE if the client is public, and FALSE if it isn't. 41 | * @endcode 42 | * 43 | * @see http://tools.ietf.org/html/rfc6749#section-2.3 44 | * @see https://github.com/bshaffer/oauth2-server-php/issues/257 45 | * 46 | * @ingroup oauth2_section_2 47 | */ 48 | public function isPublicClient($client_id); 49 | } 50 | -------------------------------------------------------------------------------- /library/OAuth2/Controller/AuthorizeControllerInterface.php: -------------------------------------------------------------------------------- 1 | $user_id = $this->somehowDetermineUserId(); 16 | * > $is_authorized = $this->somehowDetermineUserAuthorization(); 17 | * > $response = new OAuth2\Response(); 18 | * > $authorizeController->handleAuthorizeRequest( 19 | * > OAuth2\Request::createFromGlobals(), 20 | * > $response, 21 | * > $is_authorized, 22 | * > $user_id); 23 | * > $response->send(); 24 | * 25 | */ 26 | interface AuthorizeControllerInterface 27 | { 28 | /** 29 | * List of possible authentication response types. 30 | * The "authorization_code" mechanism exclusively supports 'code' 31 | * and the "implicit" mechanism exclusively supports 'token'. 32 | * 33 | * @var string 34 | * @see http://tools.ietf.org/html/rfc6749#section-4.1.1 35 | * @see http://tools.ietf.org/html/rfc6749#section-4.2.1 36 | */ 37 | const RESPONSE_TYPE_AUTHORIZATION_CODE = 'code'; 38 | const RESPONSE_TYPE_ACCESS_TOKEN = 'token'; 39 | 40 | public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null); 41 | 42 | public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response); 43 | } 44 | -------------------------------------------------------------------------------- /assets/js/admin.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | 3 | /** intiate jQuery tabs */ 4 | $("#wo_tabs").tabs({ 5 | beforeActivate: function (event, ui) { 6 | var scrollTop = $(window).scrollTop(); 7 | window.location.hash = ui.newPanel.selector; 8 | $(window).scrollTop(scrollTop); 9 | } 10 | }); 11 | 12 | /** 13 | * Create New Client Form Submission Hook 14 | * @param {[type]} e [description] 15 | * @return {[type]} [description] 16 | */ 17 | $('#create-new-client').submit(function(e){ 18 | e.preventDefault(); 19 | var formData = $(this).serialize(); 20 | var data = { 21 | 'action': 'wo_create_new_client', 22 | 'data': formData 23 | }; 24 | jQuery.post(ajaxurl, data, function(response) { 25 | if(response != '1') 26 | { 27 | alert(response); 28 | } 29 | else 30 | { 31 | /** reload for the time being */ 32 | location.reload(); 33 | } 34 | }); 35 | }); 36 | 37 | 38 | })(jQuery); 39 | 40 | /** 41 | * Remove a Client 42 | */ 43 | function wo_remove_client (client_id) 44 | { 45 | if(!confirm("Are you sure you want to delete this client?")) 46 | return; 47 | 48 | var data = { 49 | 'action': 'wo_remove_client', 50 | 'data': client_id 51 | }; 52 | jQuery.post(ajaxurl, data, function(response) { 53 | if(response != '1') 54 | { 55 | alert(response); 56 | } 57 | else 58 | { 59 | jQuery("#record_"+client_id+"").remove(); 60 | } 61 | }); 62 | } 63 | 64 | /** 65 | * Update a Client 66 | * @param {[type]} form [description] 67 | * @return {[type]} [description] 68 | */ 69 | function wo_update_client(form){ 70 | alert('Submit the form'); 71 | } -------------------------------------------------------------------------------- /library/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface 14 | { 15 | /** 16 | * Take the provided authorization code values and store them somewhere. 17 | * 18 | * This function should be the storage counterpart to getAuthCode(). 19 | * 20 | * If storage fails for some reason, we're not currently checking for 21 | * any sort of success/failure, so you should bail out of the script 22 | * and provide a descriptive fail message. 23 | * 24 | * Required for OAuth2::GRANT_TYPE_AUTH_CODE. 25 | * 26 | * @param $code authorization code to be stored. 27 | * @param $client_id client identifier to be stored. 28 | * @param $user_id user identifier to be stored. 29 | * @param string $redirect_uri redirect URI(s) to be stored in a space-separated string. 30 | * @param int $expires expiration to be stored as a Unix timestamp. 31 | * @param string $scope OPTIONAL scopes to be stored in space-separated string. 32 | * @param string $id_token OPTIONAL the OpenID Connect id_token. 33 | * 34 | * @ingroup oauth2_section_4 35 | */ 36 | public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null); 37 | } 38 | -------------------------------------------------------------------------------- /library/OAuth2/OpenID/GrantType/AuthorizationCode.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class AuthorizationCode extends BaseAuthorizationCode { 17 | public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) { 18 | $includeRefreshToken = true; 19 | 20 | $config = get_option("wo_options"); 21 | /* 22 | if ( isset( $this->authCode['id_token'] ) ) { 23 | 24 | // Issue a refresh token when "offline_access" is presented 25 | // http://openid.net/specs/openid-connect-core-1_0-17.html#OfflineAccess 26 | // 27 | // The document states that a server "MAY" issue a refresh token outside of the "offline_access" 28 | // and since there is a paramter "always_issue_refresh_token" we can hook into that 29 | $scopes = explode(' ', trim($scope)); 30 | if(in_array('offline_access', $scopes) || $config['refresh_tokens_enabled']){ 31 | $includeRefreshToken = true; 32 | } 33 | } 34 | */ 35 | 36 | $token = $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken); 37 | if ( isset( $this->authCode['id_token'] ) ) { 38 | $token['id_token'] = $this->authCode['id_token']; 39 | } 40 | 41 | $this->storage->expireAuthorizationCode( $this->authCode['code'] ); 42 | 43 | return $token; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /library/keys/private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAoM/l/PBbzTkr82INZ3L6d/wi78nioUSew6cGRj9pidy3Mupc 3 | 2NMSs7dveE0U8F6JqiW0q3bsKKUC8bQnIrolPWLVuGQEkHCfjsSixgBj8gxFNUKn 4 | f1uMamfu+CB8PQVFBFkia2AIypkHdH2O0eHrkRhMLIyO4nxGo+yYYX064HMtB7tM 5 | YbitirBZHG2gv6XhdJnKnt2H0lBz7ESMI4V+Aosnw8w8eutQQnpgXNl9BgOlxX74 6 | HoRt/GlUxnDOqMs3EKm9/dpchbnTy9JGnMN+NuJts+YIP552i4KleIXXrdg6ybTb 7 | JLRwpKmRIj6WwD+dJOfmtkZ6azqlc0+TDbIb7QIDAQABAoIBAAo4TDCAAgWP3Zgx 8 | IhLx/rMDZiEBHpMLLl/WzJJIU2e8jDQDKvVorKaZQM9PbsY769nRXaMMAsQugHpl 9 | 7ZrKY7V8A4MdcCDR14IWQxX8Tl3Co99Xphd09P6KLmi8f8jM/e7hz0stpkFh6lRf 10 | 6mSyS+tDtQubvzTrmv2t+p0vfT9oWKhUDE3GpV7CsnlriRpOuVtFbxkFlPa8OPQ+ 11 | cYvtKmTzhe933ByV2rEJhA6YqR/YJhfeNOHwZon7VBtyXq98DGJIVNrIydLSL1WX 12 | rDXF1zGbgfbNml4yf8KRuX8p/GHwRmuc6211Sf8y5HTHFinjuWlJxAQ/ZnXbjSFb 13 | 2/EyqaECgYEAzhqSCZdNEBXjemtSy8XWwpHDwSybneX4IBiGg2OSNK1ENNG9379j 14 | ftAvt4eS0aiLhFnVXw8tBJdMddKUChq/xCsqUM9J3pYvjxsT9GG/LjEFdu1ElIrs 15 | lyHuZPBUAYc8Ydwwtv+QnF9u5RrWvl76cqAiSfGm0ObXrCwVCDPXekkCgYEAx75X 16 | 36i9qUs+/sTlU1tNc3Yt+FTGL7o9bNCFj6RPGEb2AdILrwGaLdWbDKULMFfrsBQu 17 | EcR1zqpEwYZdV/S0+enw0foIvaMT4MM+bPCz+v1mPaAedd6PdIV0SVQLBM6czwn0 18 | ENJtESbfW1ig41J2z5yb+2w2RLMJmt/I0Hyi9IUCgYBz91lSUjKHKXm8KOUGSQQE 19 | qFW3vKy21G2fSY1uIjlisFcfTSCD48FM2kDvCDZhB2+xAYbgHL7cxXC9HsYzRUe/ 20 | TfZaT7glqOWLpNW+fL1AdU640tnyppRmmS3015C852XfPjCe1v98LOpNuKM5rGFI 21 | 27dPWcvd4PVbJ9aRrfHBGQKBgECRThSVlmGgWke+3Ca67+lv/WI1/S62dF61dUc+ 22 | pPbDEYj7Hh2/VdiVIR7QRzKkaSKtE0tZB3/72Gf6iDgDeXED533o5mRuz+ErHAXZ 23 | NUTgHumy1cXiYNsYvMNrcHhoVZYzsHqzmuAdgbwkhTWWlaN9C4sVquFAQs2Wo3KJ 24 | p5+FAoGAFjfoUhWgV2KyoxBSA4ufH/t9CdxlVvRXJL/+AS0vn+JR+O22Ofz/P7y9 25 | SFhd51sptUF3exINOOyTLC/Ai393lp5khYt3iKHGV+CdPxrsBFFxPklxgxSUX8aN 26 | 6B8HQibNEoo0e1honS/xBenNwKJnhNnyyws7PFEWhlctEj/EvO0= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /library/OAuth2/Storage/UserCredentialsInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface UserCredentialsInterface 13 | { 14 | /** 15 | * Grant access tokens for basic user credentials. 16 | * 17 | * Check the supplied username and password for validity. 18 | * 19 | * You can also use the $client_id param to do any checks required based 20 | * on a client, if you need that. 21 | * 22 | * Required for OAuth2::GRANT_TYPE_USER_CREDENTIALS. 23 | * 24 | * @param $username 25 | * Username to be check with. 26 | * @param $password 27 | * Password to be check with. 28 | * 29 | * @return 30 | * TRUE if the username and password are valid, and FALSE if it isn't. 31 | * Moreover, if the username and password are valid, and you want to 32 | * 33 | * @see http://tools.ietf.org/html/rfc6749#section-4.3 34 | * 35 | * @ingroup oauth2_section_4 36 | */ 37 | public function checkUserCredentials($username, $password); 38 | 39 | /** 40 | * @return 41 | * ARRAY the associated "user_id" and optional "scope" values 42 | * This function MUST return FALSE if the requested user does not exist or is 43 | * invalid. "scope" is a space-separated list of restricted scopes. 44 | * @code 45 | * return array( 46 | * "user_id" => USER_ID, // REQUIRED user_id to be stored with the authorization code or access token 47 | * "scope" => SCOPE // OPTIONAL space-separated list of restricted scopes 48 | * ); 49 | * @endcode 50 | */ 51 | public function getUserDetails($username); 52 | } 53 | -------------------------------------------------------------------------------- /library/OAuth2/OpenID/Controller/UserInfoController.php: -------------------------------------------------------------------------------- 1 | tokenType = $tokenType; 30 | $this->tokenStorage = $tokenStorage; 31 | $this->userClaimsStorage = $userClaimsStorage; 32 | 33 | $this->config = array_merge(array( 34 | 'www_realm' => 'Service', 35 | ), $config); 36 | 37 | if (is_null($scopeUtil)) { 38 | $scopeUtil = new Scope(); 39 | } 40 | $this->scopeUtil = $scopeUtil; 41 | } 42 | 43 | public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response) 44 | { 45 | if (!$this->verifyResourceRequest($request, $response, 'openid')) { 46 | return; 47 | } 48 | 49 | $token = $this->getToken(); 50 | $claims = $this->userClaimsStorage->getUserClaims($token['user_id'], $token['scope']); 51 | // The sub Claim MUST always be returned in the UserInfo Response. 52 | // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse 53 | $claims += array( 54 | 'sub' => $token['user_id'], 55 | ); 56 | $response->addParameters($claims); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /includes/admin/page-server-status.php: -------------------------------------------------------------------------------- 1 | 7 |
10 | The following information is helpful when debugging or reporting an issue. Please note that the 11 | information provided here is a reference only. 12 |
13 || Plugin Build: | 16 |17 | version, '-') ? _WO()->version . " You are using a development version of the plugin." : _WO()->version;?> 18 | | 19 |
|---|---|
| PHP Version (): | 23 |24 | = 0 ? " OK" : " Failed - Please upgrade PHP to 5.4 or greater.";?> 25 | | 26 |
| Apache Version: | 30 |31 | apache_get_version() not enabled.'; ?> 32 | | 33 |
| Running CGI: | 37 |38 | OK" : " Notice - Header 'Authorization Basic' may not work as expected.";?> 39 | | 40 |
| Certificates Generated: | 44 |45 | No Certificates Found" : "Certificates Found"?> 46 | | 47 |
| License: | 51 |52 | Standard" : "Licensed"?> 53 | | 54 |
27 | * return array(
28 | * "redirect_uri" => REDIRECT_URI, // REQUIRED redirect_uri registered for the client
29 | * "client_id" => CLIENT_ID, // OPTIONAL the client id
30 | * "grant_types" => GRANT_TYPES, // OPTIONAL an array of restricted grant types
31 | * "user_id" => USER_ID, // OPTIONAL the user identifier associated with this client
32 | * "scope" => SCOPE, // OPTIONAL the scopes allowed for this client
33 | * );
34 | *
35 | *
36 | * @ingroup oauth2_section_4
37 | */
38 | public function getClientDetails($client_id);
39 |
40 | /**
41 | * Get the scope associated with this client
42 | *
43 | * @return
44 | * STRING the space-delineated scope list for the specified client_id
45 | */
46 | public function getClientScope($client_id);
47 |
48 | /**
49 | * Check restricted grant types of corresponding client identifier.
50 | *
51 | * If you want to restrict clients to certain grant types, override this
52 | * function.
53 | *
54 | * @param $client_id
55 | * Client identifier to be check with.
56 | * @param $grant_type
57 | * Grant type to be check with
58 | *
59 | * @return
60 | * TRUE if the grant type is supported by this client identifier, and
61 | * FALSE if it isn't.
62 | *
63 | * @ingroup oauth2_section_4
64 | */
65 | public function checkRestrictedGrantType($client_id, $grant_type);
66 | }
67 |
--------------------------------------------------------------------------------
/library/OAuth2/Storage/RefreshTokenInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface RefreshTokenInterface
13 | {
14 | /**
15 | * Grant refresh access tokens.
16 | *
17 | * Retrieve the stored data for the given refresh token.
18 | *
19 | * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN.
20 | *
21 | * @param $refresh_token
22 | * Refresh token to be check with.
23 | *
24 | * @return
25 | * An associative array as below, and NULL if the refresh_token is
26 | * invalid:
27 | * - refresh_token: Refresh token identifier.
28 | * - client_id: Client identifier.
29 | * - user_id: User identifier.
30 | * - expires: Expiration unix timestamp, or 0 if the token doesn't expire.
31 | * - scope: (optional) Scope values in space-separated string.
32 | *
33 | * @see http://tools.ietf.org/html/rfc6749#section-6
34 | *
35 | * @ingroup oauth2_section_6
36 | */
37 | public function getRefreshToken($refresh_token);
38 |
39 | /**
40 | * Take the provided refresh token values and store them somewhere.
41 | *
42 | * This function should be the storage counterpart to getRefreshToken().
43 | *
44 | * If storage fails for some reason, we're not currently checking for
45 | * any sort of success/failure, so you should bail out of the script
46 | * and provide a descriptive fail message.
47 | *
48 | * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN.
49 | *
50 | * @param $refresh_token
51 | * Refresh token to be stored.
52 | * @param $client_id
53 | * Client identifier to be stored.
54 | * @param $user_id
55 | * User identifier to be stored.
56 | * @param $expires
57 | * Expiration timestamp to be stored. 0 if the token doesn't expire.
58 | * @param $scope
59 | * (optional) Scopes to be stored in space-separated string.
60 | *
61 | * @ingroup oauth2_section_6
62 | */
63 | public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null);
64 |
65 | /**
66 | * Expire a used refresh token.
67 | *
68 | * This is not explicitly required in the spec, but is almost implied.
69 | * After granting a new refresh token, the old one is no longer useful and
70 | * so should be forcibly expired in the data store so it can't be used again.
71 | *
72 | * If storage fails for some reason, we're not currently checking for
73 | * any sort of success/failure, so you should bail out of the script
74 | * and provide a descriptive fail message.
75 | *
76 | * @param $refresh_token
77 | * Refresh token to be expirse.
78 | *
79 | * @ingroup oauth2_section_6
80 | */
81 | public function unsetRefreshToken($refresh_token);
82 | }
83 |
--------------------------------------------------------------------------------
/library/OAuth2/ResponseType/AuthorizationCode.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class AuthorizationCode implements AuthorizationCodeInterface
12 | {
13 | protected $storage;
14 | protected $config;
15 |
16 | public function __construct(AuthorizationCodeStorageInterface $storage, array $config = array())
17 | {
18 | $this->storage = $storage;
19 | $this->config = array_merge(array(
20 | 'enforce_redirect' => false,
21 | 'auth_code_lifetime' => 30,
22 | ), $config);
23 | }
24 |
25 | public function getAuthorizeResponse($params, $user_id = null)
26 | {
27 | // build the URL to redirect to
28 | $result = array('query' => array());
29 |
30 | $params += array('scope' => null, 'state' => null);
31 |
32 | $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope']);
33 |
34 | if (isset($params['state'])) {
35 | $result['query']['state'] = $params['state'];
36 | }
37 |
38 | return array($params['redirect_uri'], $result);
39 | }
40 |
41 | /**
42 | * Handle the creation of the authorization code.
43 | *
44 | * @param $client_id
45 | * Client identifier related to the authorization code
46 | * @param $user_id
47 | * User ID associated with the authorization code
48 | * @param $redirect_uri
49 | * An absolute URI to which the authorization server will redirect the
50 | * user-agent to when the end-user authorization step is completed.
51 | * @param $scope
52 | * (optional) Scopes to be stored in space-separated string.
53 | *
54 | * @see http://tools.ietf.org/html/rfc6749#section-4
55 | * @ingroup oauth2_section_4
56 | */
57 | public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null)
58 | {
59 | $code = $this->generateAuthorizationCode();
60 | $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope);
61 |
62 | return $code;
63 | }
64 |
65 | /**
66 | * @return
67 | * TRUE if the grant type requires a redirect_uri, FALSE if not
68 | */
69 | public function enforceRedirect()
70 | {
71 | return $this->config['enforce_redirect'];
72 | }
73 |
74 | /**
75 | * Generates an unique auth code.
76 | *
77 | * Implementing classes may want to override this function to implement
78 | * other auth code generation schemes.
79 | *
80 | * @return
81 | * An unique auth code.
82 | *
83 | * @ingroup oauth2_section_4
84 | *
85 | * @since 3.1.94 The function has been converted to use wp_generate_password
86 | */
87 | protected function generateAuthorizationCode()
88 | {
89 | $tokenLen = 40;
90 | return strtolower(wp_generate_password( $tokenLen, false, $extra_special_chars = false ));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/library/OAuth2/Scope.php:
--------------------------------------------------------------------------------
1 | storage = $storage;
30 | }
31 |
32 | /**
33 | * Check if everything in required scope is contained in available scope.
34 | *
35 | * @param $required_scope
36 | * A space-separated string of scopes.
37 | *
38 | * @return
39 | * TRUE if everything in required scope is contained in available scope,
40 | * and FALSE if it isn't.
41 | *
42 | * @see http://tools.ietf.org/html/rfc6749#section-7
43 | *
44 | * @ingroup oauth2_section_7
45 | */
46 | public function checkScope($required_scope, $available_scope)
47 | {
48 | $required_scope = explode(' ', trim($required_scope));
49 | $available_scope = explode(' ', trim($available_scope));
50 |
51 | return (count(array_diff($required_scope, $available_scope)) == 0);
52 | }
53 |
54 | /**
55 | * Check if the provided scope exists in storage.
56 | *
57 | * @param $scope
58 | * A space-separated string of scopes.
59 | *
60 | * @return
61 | * TRUE if it exists, FALSE otherwise.
62 | */
63 | public function scopeExists($scope)
64 | {
65 | // Check reserved scopes first.
66 | $scope = explode(' ', trim($scope));
67 | $reservedScope = $this->getReservedScopes();
68 | $nonReservedScopes = array_diff($scope, $reservedScope);
69 | if (count($nonReservedScopes) == 0) {
70 | return true;
71 | } else {
72 | // Check the storage for non-reserved scopes.
73 | $nonReservedScopes = implode(' ', $nonReservedScopes);
74 |
75 | return $this->storage->scopeExists($nonReservedScopes);
76 | }
77 | }
78 |
79 | public function getScopeFromRequest(RequestInterface $request)
80 | {
81 | // "scope" is valid if passed in either POST or QUERY
82 | return $request->request('scope', $request->query('scope'));
83 | }
84 |
85 | public function getDefaultScope($client_id = null)
86 | {
87 | return $this->storage->getDefaultScope($client_id);
88 | }
89 |
90 | /**
91 | * Get reserved scopes needed by the server.
92 | *
93 | * In case OpenID Connect is used, these scopes must include:
94 | * 'openid', offline_access'.
95 | *
96 | * @return
97 | * An array of reserved scopes.
98 | */
99 | public function getReservedScopes()
100 | {
101 | return array('openid', 'offline_access');
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/library/OAuth2/Storage/JwtAccessToken.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class JwtAccessToken implements JwtAccessTokenInterface
13 | {
14 | protected $publicKeyStorage;
15 | protected $tokenStorage;
16 | protected $encryptionUtil;
17 |
18 | /**
19 | * @param OAuth2\Encryption\PublicKeyInterface $publicKeyStorage the public key encryption to use
20 | * @param OAuth2\Storage\AccessTokenInterface $tokenStorage OPTIONAL persist the access token to another storage. This is useful if
21 | * you want to retain access token grant information somewhere, but
22 | * is not necessary when using this grant type.
23 | * @param OAuth2\Encryption\EncryptionInterface $encryptionUtil OPTIONAL class to use for "encode" and "decode" functions.
24 | */
25 | public function __construct(PublicKeyInterface $publicKeyStorage, AccessTokenInterface $tokenStorage = null, EncryptionInterface $encryptionUtil = null)
26 | {
27 | $this->publicKeyStorage = $publicKeyStorage;
28 | $this->tokenStorage = $tokenStorage;
29 | if (is_null($encryptionUtil)) {
30 | $encryptionUtil = new Jwt;
31 | }
32 | $this->encryptionUtil = $encryptionUtil;
33 | }
34 |
35 | public function getAccessToken($oauth_token)
36 | {
37 | // just decode the token, don't verify
38 | if (!$tokenData = $this->encryptionUtil->decode($oauth_token, null, false)) {
39 | return false;
40 | }
41 |
42 | $client_id = isset($tokenData['aud']) ? $tokenData['aud'] : null;
43 | $public_key = $this->publicKeyStorage->getPublicKey($client_id);
44 | $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
45 |
46 | // now that we have the client_id, verify the token
47 | if (false === $this->encryptionUtil->decode($oauth_token, $public_key, array($algorithm))) {
48 | return false;
49 | }
50 |
51 | // normalize the JWT claims to the format expected by other components in this library
52 | return $this->convertJwtToOAuth2($tokenData);
53 | }
54 |
55 | public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null)
56 | {
57 | if ($this->tokenStorage) {
58 | return $this->tokenStorage->setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope);
59 | }
60 | }
61 |
62 | public function unsetAccessToken($access_token)
63 | {
64 | if ($this->tokenStorage) {
65 | return $this->tokenStorage->unsetAccessToken($access_token);
66 | }
67 | }
68 |
69 |
70 | // converts a JWT access token into an OAuth2-friendly format
71 | protected function convertJwtToOAuth2($tokenData)
72 | {
73 | $keyMapping = array(
74 | 'aud' => 'client_id',
75 | 'exp' => 'expires',
76 | 'sub' => 'user_id'
77 | );
78 |
79 | foreach ($keyMapping as $jwtKey => $oauth2Key) {
80 | if (isset($tokenData[$jwtKey])) {
81 | $tokenData[$oauth2Key] = $tokenData[$jwtKey];
82 | unset($tokenData[$jwtKey]);
83 | }
84 | }
85 |
86 | return $tokenData;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/library/OAuth2/Storage/AuthorizationCodeInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface AuthorizationCodeInterface
13 | {
14 | /**
15 | * The Authorization Code grant type supports a response type of "code".
16 | *
17 | * @var string
18 | * @see http://tools.ietf.org/html/rfc6749#section-1.4.1
19 | * @see http://tools.ietf.org/html/rfc6749#section-4.2
20 | */
21 | const RESPONSE_TYPE_CODE = "code";
22 |
23 | /**
24 | * Fetch authorization code data (probably the most common grant type).
25 | *
26 | * Retrieve the stored data for the given authorization code.
27 | *
28 | * Required for OAuth2::GRANT_TYPE_AUTH_CODE.
29 | *
30 | * @param $code
31 | * Authorization code to be check with.
32 | *
33 | * @return
34 | * An associative array as below, and NULL if the code is invalid
35 | * @code
36 | * return array(
37 | * "client_id" => CLIENT_ID, // REQUIRED Stored client identifier
38 | * "user_id" => USER_ID, // REQUIRED Stored user identifier
39 | * "expires" => EXPIRES, // REQUIRED Stored expiration in unix timestamp
40 | * "redirect_uri" => REDIRECT_URI, // REQUIRED Stored redirect URI
41 | * "scope" => SCOPE, // OPTIONAL Stored scope values in space-separated string
42 | * );
43 | * @endcode
44 | *
45 | * @see http://tools.ietf.org/html/rfc6749#section-4.1
46 | *
47 | * @ingroup oauth2_section_4
48 | */
49 | public function getAuthorizationCode($code);
50 |
51 | /**
52 | * Take the provided authorization code values and store them somewhere.
53 | *
54 | * This function should be the storage counterpart to getAuthCode().
55 | *
56 | * If storage fails for some reason, we're not currently checking for
57 | * any sort of success/failure, so you should bail out of the script
58 | * and provide a descriptive fail message.
59 | *
60 | * Required for OAuth2::GRANT_TYPE_AUTH_CODE.
61 | *
62 | * @param string $code Authorization code to be stored.
63 | * @param mixed $client_id Client identifier to be stored.
64 | * @param mixed $user_id User identifier to be stored.
65 | * @param string $redirect_uri Redirect URI(s) to be stored in a space-separated string.
66 | * @param int $expires Expiration to be stored as a Unix timestamp.
67 | * @param string $scope OPTIONAL Scopes to be stored in space-separated string.
68 | *
69 | * @ingroup oauth2_section_4
70 | */
71 | public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null);
72 |
73 | /**
74 | * once an Authorization Code is used, it must be exipired
75 | *
76 | * @see http://tools.ietf.org/html/rfc6749#section-4.1.2
77 | *
78 | * The client MUST NOT use the authorization code
79 | * more than once. If an authorization code is used more than
80 | * once, the authorization server MUST deny the request and SHOULD
81 | * revoke (when possible) all tokens previously issued based on
82 | * that authorization code
83 | *
84 | */
85 | public function expireAuthorizationCode($code);
86 | }
87 |
--------------------------------------------------------------------------------
/library/OAuth2/GrantType/AuthorizationCode.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class AuthorizationCode implements GrantTypeInterface
15 | {
16 | protected $storage;
17 | protected $authCode;
18 |
19 | /**
20 | * @param OAuth2\Storage\AuthorizationCodeInterface $storage REQUIRED Storage class for retrieving authorization code information
21 | */
22 | public function __construct(AuthorizationCodeInterface $storage)
23 | {
24 | $this->storage = $storage;
25 | }
26 |
27 | public function getQuerystringIdentifier()
28 | {
29 | return 'authorization_code';
30 | }
31 |
32 | public function validateRequest(RequestInterface $request, ResponseInterface $response)
33 | {
34 | if (!$request->request('code')) {
35 | $response->setError(400, 'invalid_request', 'Missing parameter: "code" is required');
36 |
37 | return false;
38 | }
39 |
40 | $code = $request->request('code');
41 | if (!$authCode = $this->storage->getAuthorizationCode($code)) {
42 | $response->setError(400, 'invalid_grant', 'Authorization code doesn\'t exist or is invalid for the client');
43 |
44 | return false;
45 | }
46 |
47 | // Remove id_token if not wanted
48 | $scopes = explode(' ', trim($authCode['scope']));
49 | if( !in_array('openid', $scopes ) ) {
50 | unset( $authCode['id_token'] );
51 | }
52 |
53 | /*
54 | * 4.1.3 - ensure that the "redirect_uri" parameter is present if the "redirect_uri" parameter was included in the initial authorization request
55 | * @uri - http://tools.ietf.org/html/rfc6749#section-4.1.3
56 | */
57 | if (isset($authCode['redirect_uri']) && $authCode['redirect_uri']) {
58 | if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != $authCode['redirect_uri']) {
59 | $response->setError(400, 'redirect_uri_mismatch', "The redirect URI is missing or do not match", "#section-4.1.3");
60 |
61 | return false;
62 | }
63 | }
64 |
65 | if (!isset($authCode['expires'])) {
66 | throw new \Exception('Storage must return authcode with a value for "expires"');
67 | }
68 |
69 | if ($authCode["expires"] < time()) {
70 | $response->setError(400, 'invalid_grant', "The authorization code has expired");
71 |
72 | return false;
73 | }
74 |
75 | if (!isset($authCode['code'])) {
76 | $authCode['code'] = $code; // used to expire the code after the access token is granted
77 | }
78 |
79 | $this->authCode = $authCode;
80 |
81 | return true;
82 | }
83 |
84 | public function getClientId()
85 | {
86 | return $this->authCode['client_id'];
87 | }
88 |
89 | public function getScope()
90 | {
91 | return isset($this->authCode['scope']) ? $this->authCode['scope'] : null;
92 | }
93 |
94 | public function getUserId()
95 | {
96 | return isset($this->authCode['user_id']) ? $this->authCode['user_id'] : null;
97 | }
98 |
99 | public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
100 | {
101 | $token = $accessToken->createAccessToken($client_id, $user_id, $scope);
102 | $this->storage->expireAuthorizationCode($this->authCode['code']);
103 |
104 | return $token;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/library/content/create-new-client.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * @todo Implant proper error handling
7 | */
8 |
9 | /** Block direct and or unauthorized access */
10 | if(!current_user_can('manage_options') || ! wp_verify_nonce($_GET['_wpnonce'], 'wpo-create-client') )
11 | exit('Unauthorized Access');
12 |
13 | /** listen for post back */
14 | if(isset($_POST['_wpnonce']) && wp_verify_nonce( $_POST['_wpnonce'], 'add-new-client')){
15 | if( wo_create_client( $_POST ) ){
16 | print 'Reloading...';
17 | exit;
18 | }
19 | print 'There was an issue creating a new client in the server';
20 | }
21 |
22 | $options = get_option('wo_options');
23 | ?>
24 |
99 |
100 |
25 | * $config = array(
26 | * 'always_issue_new_refresh_token' => true, // whether to issue a new refresh token upon successful token request
27 | * 'unset_refresh_token_after_use' => true // whether to unset the refresh token after after using
28 | * );
29 | *
30 | */
31 | public function __construct(RefreshTokenInterface $storage, $config = array())
32 | {
33 | $this->config = array_merge(array(
34 | 'always_issue_new_refresh_token' => false,
35 | 'unset_refresh_token_after_use' => true
36 | ), $config);
37 |
38 | // to preserve B.C. with v1.6
39 | // @see https://github.com/bshaffer/oauth2-server-php/pull/580
40 | // @todo - remove in v2.0
41 | if (isset($config['always_issue_new_refresh_token']) && !isset($config['unset_refresh_token_after_use'])) {
42 | $this->config['unset_refresh_token_after_use'] = $config['always_issue_new_refresh_token'];
43 | }
44 |
45 | $this->storage = $storage;
46 | }
47 |
48 | public function getQuerystringIdentifier()
49 | {
50 | return 'refresh_token';
51 | }
52 |
53 | public function validateRequest(RequestInterface $request, ResponseInterface $response)
54 | {
55 | if (!$request->request("refresh_token")) {
56 | $response->setError(400, 'invalid_request', 'Missing parameter: "refresh_token" is required');
57 |
58 | return null;
59 | }
60 |
61 | if (!$refreshToken = $this->storage->getRefreshToken($request->request("refresh_token"))) {
62 | $response->setError(400, 'invalid_grant', 'Invalid refresh token');
63 |
64 | return null;
65 | }
66 |
67 | if ($refreshToken['expires'] > 0 && $refreshToken["expires"] < time()) {
68 | $response->setError(400, 'invalid_grant', 'Refresh token has expired');
69 |
70 | return null;
71 | }
72 |
73 | // store the refresh token locally so we can delete it when a new refresh token is generated
74 | $this->refreshToken = $refreshToken;
75 |
76 | return true;
77 | }
78 |
79 | public function getClientId()
80 | {
81 | return $this->refreshToken['client_id'];
82 | }
83 |
84 | public function getUserId()
85 | {
86 | return isset($this->refreshToken['user_id']) ? $this->refreshToken['user_id'] : null;
87 | }
88 |
89 | public function getScope()
90 | {
91 | return isset($this->refreshToken['scope']) ? $this->refreshToken['scope'] : null;
92 | }
93 |
94 | public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
95 | {
96 | /*
97 | * It is optional to force a new refresh token when a refresh token is used.
98 | * However, if a new refresh token is issued, the old one MUST be expired
99 | * @see http://tools.ietf.org/html/rfc6749#section-6
100 | */
101 | $issueNewRefreshToken = $this->config['always_issue_new_refresh_token'];
102 | $unsetRefreshToken = $this->config['unset_refresh_token_after_use'];
103 | $token = $accessToken->createAccessToken($client_id, $user_id, $scope, $issueNewRefreshToken);
104 |
105 | if ($unsetRefreshToken) {
106 | $this->storage->unsetRefreshToken($this->refreshToken['refresh_token']);
107 | }
108 |
109 | return $token;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/library/content/edit-client.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * @todo Although this file works is it is very import that we do it right. Now that we have it working and it
7 | * is secure, we need to start tweaking the file to be more WP compliant. Quality Matters!
8 | *
9 | * @todo Add an additional check to ensure that the form is being loaded by WordPress.
10 | * @todo Load WP core JS and styles for the plugin. It will be more cleaner and not rely on external JS libs.
11 | */
12 |
13 | /** should stop 99% exploits */
14 | if(! current_user_can('manage_options') || !isset($_REQUEST['client_id']) || ! wp_verify_nonce($_REQUEST['_wp_nonce'], 'wpo-edit-client') )
15 | wp_die('');
16 |
17 | global $wpdb;
18 | $client_info = $wpdb->get_row($wpdb->prepare("
19 | SELECT *
20 | FROM {$wpdb->prefix}oauth_clients
21 | WHERE client_id='%s'", array($_GET['client_id']))
22 | );
23 |
24 | /** simple check if the client exists */
25 | if(!$client_info)
26 | exit('Unauthorized Access');
27 |
28 | /** listen for post back */
29 | if(isset($_POST['_wpnonce']) && wp_verify_nonce( $_POST['_wpnonce'], 'edit-client')){
30 | global $wpdb;
31 | $update_client = $wpdb->update("{$wpdb->prefix}oauth_clients",
32 | array(
33 | 'redirect_uri' => $_POST['client-redirect-uri'],
34 | 'name' => $_POST['client-name'],
35 | 'description' => $_POST['client-description']
36 | ),
37 | array(
38 | 'client_id' => $_GET['client_id']
39 | ));
40 |
41 | print 'Reloading...';
42 | exit;
43 | }
44 | ?>
45 |
120 |
121 |
25 | * $config = array(
26 | * 'allow_credentials_in_request_body' => true, // whether to look for credentials in the POST body in addition to the Authorize HTTP Header
27 | * 'allow_public_clients' => true // if true, "public clients" (clients without a secret) may be authenticated
28 | * );
29 | *
30 | */
31 | public function __construct(ClientCredentialsInterface $storage, array $config = array())
32 | {
33 | $this->storage = $storage;
34 | $this->config = array_merge(array(
35 | 'allow_credentials_in_request_body' => true,
36 | 'allow_public_clients' => true,
37 | ), $config);
38 | }
39 |
40 | public function validateRequest(RequestInterface $request, ResponseInterface $response)
41 | {
42 | if (!$clientData = $this->getClientCredentials($request, $response)) {
43 | return false;
44 | }
45 |
46 | if (!isset($clientData['client_id'])) {
47 | throw new \LogicException('the clientData array must have "client_id" set');
48 | }
49 |
50 | if (!isset($clientData['client_secret']) || $clientData['client_secret'] == '') {
51 | if (!$this->config['allow_public_clients']) {
52 | $response->setError(400, 'invalid_client', 'client credentials are required');
53 |
54 | return false;
55 | }
56 |
57 | if (!$this->storage->isPublicClient($clientData['client_id'])) {
58 | $response->setError(400, 'invalid_client', 'This client is invalid or must authenticate using a client secret');
59 |
60 | return false;
61 | }
62 | } elseif ($this->storage->checkClientCredentials($clientData['client_id'], $clientData['client_secret']) === false) {
63 | $response->setError(400, 'invalid_client', 'The client credentials are invalid');
64 |
65 | return false;
66 | }
67 |
68 | $this->clientData = $clientData;
69 |
70 | return true;
71 | }
72 |
73 | public function getClientId()
74 | {
75 | return $this->clientData['client_id'];
76 | }
77 |
78 | /**
79 | * Internal function used to get the client credentials from HTTP basic
80 | * auth or POST data.
81 | *
82 | * According to the spec (draft 20), the client_id can be provided in
83 | * the Basic Authorization header (recommended) or via GET/POST.
84 | *
85 | * @return
86 | * A list containing the client identifier and password, for example
87 | * @code
88 | * return array(
89 | * "client_id" => CLIENT_ID, // REQUIRED the client id
90 | * "client_secret" => CLIENT_SECRET, // OPTIONAL the client secret (may be omitted for public clients)
91 | * );
92 | * @endcode
93 | *
94 | * @see http://tools.ietf.org/html/rfc6749#section-2.3.1
95 | *
96 | * @ingroup oauth2_section_2
97 | */
98 | public function getClientCredentials(RequestInterface $request, ResponseInterface $response = null)
99 | {
100 | if (!is_null($request->headers('PHP_AUTH_USER')) && !is_null($request->headers('PHP_AUTH_PW'))) {
101 | return array('client_id' => $request->headers('PHP_AUTH_USER'), 'client_secret' => $request->headers('PHP_AUTH_PW'));
102 | }
103 |
104 | if ($this->config['allow_credentials_in_request_body']) {
105 | // Using POST for HttpBasic authorization is not recommended, but is supported by specification
106 | if (!is_null($request->request('client_id'))) {
107 | /**
108 | * client_secret can be null if the client's password is an empty string
109 | * @see http://tools.ietf.org/html/rfc6749#section-2.3.1
110 | */
111 |
112 | return array('client_id' => $request->request('client_id'), 'client_secret' => $request->request('client_secret'));
113 | }
114 | }
115 |
116 | if ($response) {
117 | $message = $this->config['allow_credentials_in_request_body'] ? ' or body' : '';
118 | $response->setError(400, 'invalid_client', 'Client credentials were not found in the headers'.$message);
119 | }
120 |
121 | return null;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/library/OAuth2/Encryption/Jwt.php:
--------------------------------------------------------------------------------
1 | generateJwtHeader($payload, $algo);
14 |
15 | $segments = array(
16 | $this->urlSafeB64Encode(json_encode($header)),
17 | $this->urlSafeB64Encode(json_encode($payload))
18 | );
19 |
20 | $signing_input = implode('.', $segments);
21 |
22 | $signature = $this->sign($signing_input, $key, $algo);
23 | $segments[] = $this->urlsafeB64Encode($signature);
24 |
25 | return implode('.', $segments);
26 | }
27 |
28 | public function decode($jwt, $key = null, $allowedAlgorithms = true)
29 | {
30 | if (!strpos($jwt, '.')) {
31 | return false;
32 | }
33 |
34 | $tks = explode('.', $jwt);
35 |
36 | if (count($tks) != 3) {
37 | return false;
38 | }
39 |
40 | list($headb64, $payloadb64, $cryptob64) = $tks;
41 |
42 | if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) {
43 | return false;
44 | }
45 |
46 | if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) {
47 | return false;
48 | }
49 |
50 | $sig = $this->urlSafeB64Decode($cryptob64);
51 |
52 | if ((bool) $allowedAlgorithms) {
53 | if (!isset($header['alg'])) {
54 | return false;
55 | }
56 |
57 | // check if bool arg supplied here to maintain BC
58 | if (is_array($allowedAlgorithms) && !in_array($header['alg'], $allowedAlgorithms)) {
59 | return false;
60 | }
61 |
62 | if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) {
63 | return false;
64 | }
65 | }
66 |
67 | return $payload;
68 | }
69 |
70 | private function verifySignature($signature, $input, $key, $algo = 'HS256')
71 | {
72 | // use constants when possible, for HipHop support
73 | switch ($algo) {
74 | case'HS256':
75 | case'HS384':
76 | case'HS512':
77 | return $this->hash_equals(
78 | $this->sign($input, $key, $algo),
79 | $signature
80 | );
81 |
82 | case 'RS256':
83 | return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256') === 1;
84 |
85 | case 'RS384':
86 | return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1;
87 |
88 | case 'RS512':
89 | return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1;
90 |
91 | default:
92 | throw new \InvalidArgumentException("Unsupported or invalid signing algorithm.");
93 | }
94 | }
95 |
96 | private function sign($input, $key, $algo = 'HS256')
97 | {
98 | switch ($algo) {
99 | case 'HS256':
100 | return hash_hmac('sha256', $input, $key, true);
101 |
102 | case 'HS384':
103 | return hash_hmac('sha384', $input, $key, true);
104 |
105 | case 'HS512':
106 | return hash_hmac('sha512', $input, $key, true);
107 |
108 | case 'RS256':
109 | return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256');
110 |
111 | case 'RS384':
112 | return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384');
113 |
114 | case 'RS512':
115 | return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512');
116 |
117 | default:
118 | throw new \Exception("Unsupported or invalid signing algorithm.");
119 | }
120 | }
121 |
122 | private function generateRSASignature($input, $key, $algo)
123 | {
124 | if (!openssl_sign($input, $signature, $key, $algo)) {
125 | throw new \Exception("Unable to sign data.");
126 | }
127 |
128 | return $signature;
129 | }
130 |
131 | public function urlSafeB64Encode($data)
132 | {
133 | $b64 = base64_encode($data);
134 | $b64 = str_replace(array('+', '/', "\r", "\n", '='),
135 | array('-', '_'),
136 | $b64);
137 |
138 | return $b64;
139 | }
140 |
141 | public function urlSafeB64Decode($b64)
142 | {
143 | $b64 = str_replace(array('-', '_'),
144 | array('+', '/'),
145 | $b64);
146 |
147 | return base64_decode($b64);
148 | }
149 |
150 | /**
151 | * Override to create a custom header
152 | */
153 | protected function generateJwtHeader($payload, $algorithm)
154 | {
155 | return array(
156 | 'typ' => 'JWT',
157 | 'alg' => $algorithm,
158 | );
159 | }
160 |
161 | protected function hash_equals($a, $b)
162 | {
163 | if (function_exists('hash_equals')) {
164 | return hash_equals($a, $b);
165 | }
166 | $diff = strlen($a) ^ strlen($b);
167 | for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
168 | $diff |= ord($a[$i]) ^ ord($b[$i]);
169 | }
170 |
171 | return $diff === 0;
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/wp-oauth.php:
--------------------------------------------------------------------------------
1 |
21 | * @package WordPress OAuth Server
22 | */
23 |
24 | defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
25 |
26 | if (! defined( 'WPOAUTH_FILE' ) ) {
27 | define( 'WPOAUTH_FILE', __FILE__ );
28 | }
29 |
30 | /**
31 | * 5.4 Strict Mode Temp Patch
32 | *
33 | * Since PHP 5.4, WP will through notices due to the way WP calls statically
34 | */
35 | function _wo_server_register_files() {
36 | wp_register_style( 'wo_admin', plugins_url( '/assets/css/admin.css', __FILE__ ) );
37 | wp_register_script( 'wo_admin', plugins_url( '/assets/js/admin.js', __FILE__ ) );
38 | }
39 | add_action( 'wp_loaded', '_wo_server_register_files' );
40 |
41 | require_once( dirname(__FILE__) . '/wp-oauth-main.php' );
42 |
43 | /**
44 | * Adds/registers query vars
45 | * @return void
46 | */
47 | function _wo_server_register_query_vars() {
48 | _wo_server_register_rewrites();
49 |
50 | global $wp;
51 | $wp->add_query_var( 'oauth' );
52 | $wp->add_query_var( 'well-known' );
53 | $wp->add_query_var( 'wpoauthincludes' );
54 | }
55 | add_action( 'init', '_wo_server_register_query_vars' );
56 |
57 | /**
58 | * Registers rewrites for OAuth2 Server
59 | *
60 | * - authorize
61 | * - token
62 | * - .well-known
63 | * - wpoauthincludes
64 | *
65 | * @return void
66 | */
67 | function _wo_server_register_rewrites() {
68 | add_rewrite_rule( '^oauth/(.+)','index.php?oauth=$matches[1]','top' );
69 | add_rewrite_rule( '^.well-known/(.+)','index.php?well-known=$matches[1]','top' );
70 | add_rewrite_rule( '^wpoauthincludes/(.+)','index.php?wpoauthincludes=$matches[1]','top' );
71 | }
72 |
73 | /**
74 | * [template_redirect_intercept description]
75 | * @return [type] [description]
76 | */
77 | function _wo_server_template_redirect_intercept( $template ) {
78 | global $wp_query;
79 |
80 | if ( $wp_query->get( 'oauth' ) || $wp_query->get( 'well-known' ) ) {
81 | require_once dirname( __FILE__ ) . '/library/class-wo-api.php';
82 | exit;
83 | }
84 |
85 | if ( $wp_query->get( 'wpoauthincludes' ) ) {
86 | $allowed_includes = array(
87 | 'create' => dirname( WPOAUTH_FILE ) . '/library/content/create-new-client.php',
88 | 'edit' => dirname( WPOAUTH_FILE ) . '/library/content/edit-client.php'
89 | );
90 | if( array_key_exists( $wp_query->get( 'wpoauthincludes' ), $allowed_includes ) && current_user_can( 'manage_options' ) ) {
91 | require_once $allowed_includes[$wp_query->get( 'wpoauthincludes' )];
92 | }
93 | }
94 |
95 | return $template;
96 | }
97 | add_filter( 'template_include', '_wo_server_template_redirect_intercept', 100);
98 |
99 | /**
100 | * OAuth2 Server Activation
101 | * @param [type] $network_wide [description]
102 | * @return [type] [description]
103 | */
104 | function _wo_server_activation( $network_wide ) {
105 | if ( function_exists( 'is_multisite' ) && is_multisite() && $network_wide ) {
106 | $mu_blogs = wp_get_sites();
107 | foreach ( $mu_blogs as $mu_blog ) {
108 | switch_to_blog( $mu_blog['blog_id'] );
109 | _wo_server_register_rewrites();
110 | flush_rewrite_rules();
111 | }
112 | restore_current_blog();
113 | } else {
114 | _wo_server_register_rewrites();
115 | flush_rewrite_rules();
116 | }
117 | }
118 | register_activation_hook( __FILE__, '_wo_server_activation' );
119 |
120 | /**
121 | * OAuth Server Deactivation
122 | * @param [type] $network_wide [description]
123 | * @return [type] [description]
124 | */
125 | function _wo_server_deactivation( $network_wide ) {
126 | if ( function_exists( 'is_multisite' ) && is_multisite() && $network_wide ) {
127 | $mu_blogs = wp_get_sites();
128 | foreach ( $mu_blogs as $mu_blog ) {
129 | switch_to_blog( $mu_blog['blog_id'] );
130 | flush_rewrite_rules();
131 | }
132 | restore_current_blog();
133 | } else {
134 | flush_rewrite_rules();
135 | }
136 | }
137 | register_deactivation_hook( __FILE__, '_wo_server_deactivation' );
138 |
139 | /**
140 | * @todo Move setup and upgrade inside the function wo_plugin_activate()
141 | */
142 | register_activation_hook(__FILE__, array(new WO_Server, 'setup'));
143 | register_activation_hook(__FILE__, array(new WO_Server, 'upgrade'));
144 |
145 | function wo_admin_notice_upgrade_jump() {
146 | ?>
147 | WP OAuth Server has updated to version 3.2.x. Click here for more information.
149 |
23 | * $config = array(
24 | * 'token_type' => 'bearer', // token type identifier
25 | * 'access_lifetime' => 3600, // time before access token expires
26 | * 'refresh_token_lifetime' => 1209600, // time before refresh token expires
27 | * );
28 | *
29 | */
30 | public function __construct(AccessTokenStorageInterface $tokenStorage, RefreshTokenInterface $refreshStorage = null, array $config = array())
31 | {
32 | $this->tokenStorage = $tokenStorage;
33 | $this->refreshStorage = $refreshStorage;
34 |
35 | $this->config = array_merge(array(
36 | 'token_type' => 'bearer',
37 | 'access_lifetime' => 3600,
38 | 'refresh_token_lifetime' => 1209600,
39 | ), $config);
40 | }
41 |
42 | public function getAuthorizeResponse($params, $user_id = null)
43 | {
44 | // build the URL to redirect to
45 | $result = array('query' => array());
46 |
47 | $params += array('scope' => null, 'state' => null);
48 |
49 | /*
50 | * a refresh token MUST NOT be included in the fragment
51 | *
52 | * @see http://tools.ietf.org/html/rfc6749#section-4.2.2
53 | */
54 | $includeRefreshToken = false;
55 | $result["fragment"] = $this->createAccessToken($params['client_id'], $user_id, $params['scope'], $includeRefreshToken);
56 |
57 | if (isset($params['state'])) {
58 | $result["fragment"]["state"] = $params['state'];
59 | }
60 |
61 | return array($params['redirect_uri'], $result);
62 | }
63 |
64 | /**
65 | * Handle the creation of access token, also issue refresh token if supported / desirable.
66 | *
67 | * @param $client_id client identifier related to the access token.
68 | * @param $user_id user ID associated with the access token
69 | * @param $scope OPTIONAL scopes to be stored in space-separated string.
70 | * @param bool $includeRefreshToken if true, a new refresh_token will be added to the response
71 | *
72 | * @see http://tools.ietf.org/html/rfc6749#section-5
73 | * @ingroup oauth2_section_5
74 | */
75 | public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
76 | {
77 | $token = array(
78 | "access_token" => $this->generateAccessToken(),
79 | "expires_in" => (int) $this->config['access_lifetime'],
80 | "token_type" => $this->config['token_type'],
81 | "scope" => $scope
82 | );
83 |
84 | $this->tokenStorage->setAccessToken($token["access_token"], $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);
85 |
86 | /*
87 | * Issue a refresh token also, if we support them
88 | *
89 | * Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface
90 | * is supplied in the constructor
91 | */
92 | if ($includeRefreshToken && $this->refreshStorage) {
93 | $token["refresh_token"] = $this->generateRefreshToken();
94 | $expires = 0;
95 | if ($this->config['refresh_token_lifetime'] > 0) {
96 | $expires = time() + $this->config['refresh_token_lifetime'];
97 | }
98 | $this->refreshStorage->setRefreshToken($token['refresh_token'], $client_id, $user_id, $expires, $scope);
99 | }
100 |
101 | return $token;
102 | }
103 |
104 | /**
105 | * Generates an unique access token.
106 | *
107 | * Implementing classes may want to override this function to implement
108 | * other access token generation schemes.
109 | *
110 | * @return
111 | * An unique access token.
112 | *
113 | * @ingroup oauth2_section_4
114 | */
115 | protected function generateAccessToken () {
116 | $tokenLen = 40;
117 | return strtolower(wp_generate_password( $tokenLen, false, $extra_special_chars = false ));
118 | }
119 |
120 | /**
121 | * Generates an unique refresh token
122 | *
123 | * Implementing classes may want to override this function to implement
124 | * other refresh token generation schemes.
125 | *
126 | * @return
127 | * An unique refresh.
128 | *
129 | * @ingroup oauth2_section_4
130 | * @see OAuth2::generateAccessToken()
131 | */
132 | protected function generateRefreshToken()
133 | {
134 | return $this->generateAccessToken(); // let's reuse the same scheme for token generation
135 | }
136 |
137 | /**
138 | * Handle the revoking of refresh tokens, and access tokens if supported / desirable
139 | * RFC7009 specifies that "If the server is unable to locate the token using
140 | * the given hint, it MUST extend its search across all of its supported token types"
141 | *
142 | * @param $token
143 | * @param null $tokenTypeHint
144 | * @return boolean
145 | */
146 | public function revokeToken($token, $tokenTypeHint = null)
147 | {
148 | if ($tokenTypeHint == 'refresh_token') {
149 | if ($this->refreshStorage && $revoked = $this->refreshStorage->unsetRefreshToken($token)) {
150 | return true;
151 | }
152 | }
153 |
154 | /** @TODO remove in v2 */
155 | if (!method_exists($this->tokenStorage, 'unsetAccessToken')) {
156 | throw new \RuntimeException(
157 | sprintf('Token storage %s must implement unsetAccessToken method', get_class($this->tokenStorage)
158 | ));
159 | }
160 |
161 | $revoked = $this->tokenStorage->unsetAccessToken($token);
162 |
163 | // if a typehint is supplied and fails, try other storages
164 | // @see https://tools.ietf.org/html/rfc7009#section-2.1
165 | if (!$revoked && $tokenTypeHint != 'refresh_token') {
166 | if ($this->refreshStorage) {
167 | $revoked = $this->refreshStorage->unsetRefreshToken($token);
168 | }
169 | }
170 |
171 | return $revoked;
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/includes/functions.php:
--------------------------------------------------------------------------------
1 |
6 | * @package WordPress OAuth Server
7 | *
8 | * @todo Convert entire file into library ( class )
9 | */
10 |
11 | // Hook into core filters
12 | require_once dirname(__FILE__) . '/filters.php' ;
13 |
14 | // Hook into core actions
15 | require_once( dirname(__FILE__) . '/actions.php' );
16 |
17 | /**
18 | * Generates a 40 Character key is generated by default but should be adjustable in the admin
19 | * @return [type] [description]
20 | *
21 | * @todo Allow more characters to be added to the character list to provide complex keys
22 | */
23 | function wo_gen_key( $length = 40 ) {
24 |
25 | // Gather the settings
26 | $options = get_option("wo_options");
27 | $user_defined_length = (int) $options["client_id_length"];
28 |
29 | // If user setting is larger than 0, then define it
30 | if ( $user_defined_length > 0 ) {
31 | $length = $user_defined_length;
32 | }
33 |
34 | $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
35 | $randomString = '';
36 |
37 | for ($i = 0; $i < $length; $i++) {
38 | $randomString .= $characters[rand(0, strlen($characters) - 1)];
39 | }
40 |
41 | return $randomString;
42 | }
43 |
44 | /**
45 | * Blowfish Encryptions
46 | * @param [type] $input [description]
47 | * @param integer $rounds [description]
48 | * @return [type] [description]
49 | *
50 | * REQUIRES ATLEAST 5.3.x
51 | */
52 | function wo_crypt($input, $rounds = 7) {
53 | $salt = "";
54 | $salt_chars = array_merge(range('A', 'Z'), range('a', 'z'), range(0, 9));
55 | for ($i = 0; $i < 22; $i++) {
56 | $salt .= $salt_chars[array_rand($salt_chars)];
57 | }
58 | return crypt($input, sprintf('$2a$%02d$', $rounds) . $salt);
59 | }
60 |
61 | /**
62 | * Check if there is more than one client in the system
63 | *
64 | * @return boolean [description]
65 | */
66 | function has_a_client (){
67 | global $wpdb;
68 | $count = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}oauth_clients");
69 | //print_r($count);
70 | if (count($count) >= 1)
71 | return true;
72 | }
73 |
74 | /**
75 | * Get the client IP multiple ways since REMOTE_ADDR is not always the best way to do so
76 | * @return [type] [description]
77 | */
78 | function client_ip(){
79 | $ipaddress = '';
80 | if (getenv('HTTP_CLIENT_IP'))
81 | $ipaddress = getenv('HTTP_CLIENT_IP');
82 | else if(getenv('HTTP_X_FORWARDED_FOR'))
83 | $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
84 | else if(getenv('HTTP_X_FORWARDED'))
85 | $ipaddress = getenv('HTTP_X_FORWARDED');
86 | else if(getenv('HTTP_FORWARDED_FOR'))
87 | $ipaddress = getenv('HTTP_FORWARDED_FOR');
88 | else if(getenv('HTTP_FORWARDED'))
89 | $ipaddress = getenv('HTTP_FORWARDED');
90 | else if(getenv('REMOTE_ADDR'))
91 | $ipaddress = getenv('REMOTE_ADDR');
92 | else
93 | $ipaddress = 'UNKNOWN';
94 |
95 | return $ipaddress;
96 | }
97 |
98 | /**
99 | * Valid the license
100 | * @param [type] $l [description]
101 | * @return [type] [description]
102 | */
103 | function _vl() {
104 | $options = get_option('wo_options');
105 | return @$options['license_status'] == 'valid' ? true : false;
106 | }
107 |
108 | function license_status (){
109 | $options = get_option('wo_options');
110 | $status = isset($options['license_status']) ? $options['license_status'] : '';
111 | switch($status){
112 | case 'invalid':
113 | echo 'Invalid. Activate your license now.';
114 | break;
115 | case 'valid':
116 | echo 'Valid';
117 | break;
118 | }
119 |
120 | }
121 |
122 | /**
123 | * Cron Tasks That the plugin should run daily
124 | * @return [type] [description]
125 | */
126 | add_action( 'wo_daily_tasks_hook', 'wo_daily_tasks' );
127 | function wo_daily_tasks () {
128 | $options = get_option( 'wo_options' );
129 | if( @$options['license_status'] == 'valid' ){
130 | $api_params = array(
131 | 'edd_action'=> 'activate_license',
132 | 'license' => $options['license'],
133 | 'item_name' => urlencode('WP OAuth License'),
134 | 'url' => home_url()
135 | );
136 | $response = wp_remote_get( add_query_arg( $api_params, 'https://wp-oauth.com' ) );
137 | if ( !is_wp_error( $response ) ){
138 | $license_data = json_decode( wp_remote_retrieve_body( $response ) );
139 | if(@$options['license_status'] == 'valid' && $license_data->license != 'valid') {
140 | wp_mail( get_option('admin_email'), 'Issues found with WP OAuth Server', 'Recent checks show that your license key status for WordPress OAuth Server has been changed.');
141 | $options['license'] = '';
142 | $options['license_status'] = '';
143 | }
144 | update_option('wo_options', $options);
145 | }
146 | }
147 | }
148 |
149 | /**
150 | * WordPress OAuth Server Firewall
151 | * Called if and license is valid and the firewall is enabled
152 | */
153 | add_action('wo_before_api', 'wordpress_oauth_firewall_init', 10);
154 | function wordpress_oauth_firewall_init() {
155 | $options = get_option('wo_options');
156 | if(!_vl())
157 | return;
158 |
159 | if(isset($options['firewall_block_all_incomming']) && $options['firewall_block_all_incomming']){
160 | $remote_addr = client_ip();
161 | $whitelist = str_replace(' ', '',$options['firewall_ip_whitelist']); // remove all whitespace
162 | $whitelist_array = explode(',', $whitelist);
163 | if(in_array($remote_addr, $whitelist_array))
164 | return;
165 |
166 | header('Content-Type: application/json');
167 | $response = array(
168 | 'error' => 'Unauthorized'
169 | );
170 | print json_encode($response);
171 | exit;
172 | }
173 | }
174 |
175 | /**
176 | * Return the private key for signing
177 | * @since 3.0.5
178 | * @return [type] [description]
179 | */
180 | function get_private_server_key () {
181 | $keys = apply_filters('wo_server_keys', null);
182 | return file_get_contents( $keys['private'] );
183 | }
184 |
185 | /**
186 | * Returns the public key
187 | * @return [type] [description]
188 | * @since 3.1.0
189 | */
190 | function get_public_server_key () {
191 | $keys = apply_filters('wo_server_keys', null);
192 | return file_get_contents( $keys['public'] );
193 | }
194 |
195 | /**
196 | * Returns the set ALGO that is to be used for the server to encode
197 | *
198 | * @todo Possibly set this to be adjusted somewhere. The it_token calls for it to be set by each
199 | * client as a pref but we need to keep this simple.
200 | *
201 | * @since 3.1.93
202 | * @return String Type of algorithm used for encoding and decoding.
203 | */
204 | function wo_get_algorithm (){
205 | return 'RS256';
206 | }
207 |
208 | /**
209 | * Check to see if there is certificates that have been generated
210 | * @return boolean [description]
211 | */
212 | function wo_has_certificates (){
213 | return file_exists( dirname(WPOAUTH_FILE) . '/library/keys/public_key.pem' )
214 | && file_exists( dirname(WPOAUTH_FILE) . '/library/keys/private_key.pem' );
215 | }
216 |
217 | /**
218 | * [wo_create_client description]
219 | * @param [type] $user [description]
220 | * @return [type] [description]
221 | *
222 | * @todo Add role and permissions check
223 | */
224 | function wo_create_client( $user=null ){
225 |
226 | do_action('wo_before_create_client', array( $user ) );
227 | if(! current_user_can( 'manage_options' ) )
228 | return false;
229 |
230 | $new_client_id = wo_gen_key();
231 | $new_client_secret = wo_gen_key();
232 |
233 | // Insert the user into the system
234 | global $wpdb;
235 | return $wpdb->insert("{$wpdb->prefix}oauth_clients",
236 | array(
237 | 'client_id' => $new_client_id,
238 | 'client_secret' => $new_client_secret,
239 | 'redirect_uri' => empty($user['client-redirect-uri']) ? '' : $user['client-redirect-uri'],
240 | 'name' => empty($user['client-name']) ? 'No Name' : $user['client-name'],
241 | 'description' => empty($user['client-description']) ? '' : $user['client-description']
242 | ));
243 | }
--------------------------------------------------------------------------------
/library/OAuth2/GrantType/JwtBearer.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
22 | {
23 | private $jwt;
24 |
25 | protected $storage;
26 | protected $audience;
27 | protected $jwtUtil;
28 | protected $allowedAlgorithms;
29 |
30 | /**
31 | * Creates an instance of the JWT bearer grant type.
32 | *
33 | * @param OAuth2\Storage\JWTBearerInterface|JwtBearerInterface $storage A valid storage interface that implements storage hooks for the JWT bearer grant type.
34 | * @param string $audience The audience to validate the token against. This is usually the full URI of the OAuth token requests endpoint.
35 | * @param EncryptionInterface|OAuth2\Encryption\JWT $jwtUtil OPTONAL The class used to decode, encode and verify JWTs.
36 | * @param array $config
37 | */
38 | public function __construct(JwtBearerInterface $storage, $audience, EncryptionInterface $jwtUtil = null, array $config = array())
39 | {
40 | $this->storage = $storage;
41 | $this->audience = $audience;
42 |
43 | if (is_null($jwtUtil)) {
44 | $jwtUtil = new Jwt();
45 | }
46 |
47 | $this->config = array_merge(array(
48 | 'allowed_algorithms' => array('RS256', 'RS384', 'RS512')
49 | ), $config);
50 |
51 | $this->jwtUtil = $jwtUtil;
52 |
53 | $this->allowedAlgorithms = $this->config['allowed_algorithms'];
54 | }
55 |
56 | /**
57 | * Returns the grant_type get parameter to identify the grant type request as JWT bearer authorization grant.
58 | *
59 | * @return
60 | * The string identifier for grant_type.
61 | *
62 | * @see OAuth2\GrantType\GrantTypeInterface::getQuerystringIdentifier()
63 | */
64 | public function getQuerystringIdentifier()
65 | {
66 | return 'urn:ietf:params:oauth:grant-type:jwt-bearer';
67 | }
68 |
69 | /**
70 | * Validates the data from the decoded JWT.
71 | *
72 | * @return
73 | * TRUE if the JWT request is valid and can be decoded. Otherwise, FALSE is returned.
74 | *
75 | * @see OAuth2\GrantType\GrantTypeInterface::getTokenData()
76 | */
77 | public function validateRequest(RequestInterface $request, ResponseInterface $response)
78 | {
79 | if (!$request->request("assertion")) {
80 | $response->setError(400, 'invalid_request', 'Missing parameters: "assertion" required');
81 |
82 | return null;
83 | }
84 |
85 | // Store the undecoded JWT for later use
86 | $undecodedJWT = $request->request('assertion');
87 |
88 | // Decode the JWT
89 | $jwt = $this->jwtUtil->decode($request->request('assertion'), null, false);
90 |
91 | if (!$jwt) {
92 | $response->setError(400, 'invalid_request', "JWT is malformed");
93 |
94 | return null;
95 | }
96 |
97 | // ensure these properties contain a value
98 | // @todo: throw malformed error for missing properties
99 | $jwt = array_merge(array(
100 | 'scope' => null,
101 | 'iss' => null,
102 | 'sub' => null,
103 | 'aud' => null,
104 | 'exp' => null,
105 | 'nbf' => null,
106 | 'iat' => null,
107 | 'jti' => null,
108 | 'typ' => null,
109 | ), $jwt);
110 |
111 | if (!isset($jwt['iss'])) {
112 | $response->setError(400, 'invalid_grant', "Invalid issuer (iss) provided");
113 |
114 | return null;
115 | }
116 |
117 | if (!isset($jwt['sub'])) {
118 | $response->setError(400, 'invalid_grant', "Invalid subject (sub) provided");
119 |
120 | return null;
121 | }
122 |
123 | if (!isset($jwt['exp'])) {
124 | $response->setError(400, 'invalid_grant', "Expiration (exp) time must be present");
125 |
126 | return null;
127 | }
128 |
129 | // Check expiration
130 | if (ctype_digit($jwt['exp'])) {
131 | if ($jwt['exp'] <= time()) {
132 | $response->setError(400, 'invalid_grant', "JWT has expired");
133 |
134 | return null;
135 | }
136 | } else {
137 | $response->setError(400, 'invalid_grant', "Expiration (exp) time must be a unix time stamp");
138 |
139 | return null;
140 | }
141 |
142 | // Check the not before time
143 | if ($notBefore = $jwt['nbf']) {
144 | if (ctype_digit($notBefore)) {
145 | if ($notBefore > time()) {
146 | $response->setError(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time");
147 |
148 | return null;
149 | }
150 | } else {
151 | $response->setError(400, 'invalid_grant', "Not Before (nbf) time must be a unix time stamp");
152 |
153 | return null;
154 | }
155 | }
156 |
157 | // Check the audience if required to match
158 | if (!isset($jwt['aud']) || ($jwt['aud'] != $this->audience)) {
159 | $response->setError(400, 'invalid_grant', "Invalid audience (aud)");
160 |
161 | return null;
162 | }
163 |
164 | // Check the jti (nonce)
165 | // @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-13#section-4.1.7
166 | if (isset($jwt['jti'])) {
167 | $jti = $this->storage->getJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']);
168 |
169 | //Reject if jti is used and jwt is still valid (exp parameter has not expired).
170 | if ($jti && $jti['expires'] > time()) {
171 | $response->setError(400, 'invalid_grant', "JSON Token Identifier (jti) has already been used");
172 |
173 | return null;
174 | } else {
175 | $this->storage->setJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']);
176 | }
177 | }
178 |
179 | // Get the iss's public key
180 | // @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-4.1.1
181 | if (!$key = $this->storage->getClientKey($jwt['iss'], $jwt['sub'])) {
182 | $response->setError(400, 'invalid_grant', "Invalid issuer (iss) or subject (sub) provided");
183 |
184 | return null;
185 | }
186 |
187 | // Verify the JWT
188 | if (!$this->jwtUtil->decode($undecodedJWT, $key, $this->allowedAlgorithms)) {
189 | $response->setError(400, 'invalid_grant', "JWT failed signature verification");
190 |
191 | return null;
192 | }
193 |
194 | $this->jwt = $jwt;
195 |
196 | return true;
197 | }
198 |
199 | public function getClientId()
200 | {
201 | return $this->jwt['iss'];
202 | }
203 |
204 | public function getUserId()
205 | {
206 | return $this->jwt['sub'];
207 | }
208 |
209 | public function getScope()
210 | {
211 | return null;
212 | }
213 |
214 | /**
215 | * Creates an access token that is NOT associated with a refresh token.
216 | * If a subject (sub) the name of the user/account we are accessing data on behalf of.
217 | *
218 | * @see OAuth2\GrantType\GrantTypeInterface::createAccessToken()
219 | */
220 | public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
221 | {
222 | $includeRefreshToken = false;
223 |
224 | return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/library/OAuth2/Request.php:
--------------------------------------------------------------------------------
1 | initialize($query, $request, $attributes, $cookies, $files, $server, $content, $headers);
37 | }
38 |
39 | /**
40 | * Sets the parameters for this request.
41 | *
42 | * This method also re-initializes all properties.
43 | *
44 | * @param array $query The GET parameters
45 | * @param array $request The POST parameters
46 | * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
47 | * @param array $cookies The COOKIE parameters
48 | * @param array $files The FILES parameters
49 | * @param array $server The SERVER parameters
50 | * @param string $content The raw body data
51 | *
52 | * @api
53 | */
54 | public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null, array $headers = null)
55 | {
56 | $this->request = $request;
57 | $this->query = $query;
58 | $this->attributes = $attributes;
59 | $this->cookies = $cookies;
60 | $this->files = $files;
61 | $this->server = $server;
62 | $this->content = $content;
63 | $this->headers = is_null($headers) ? $this->getHeadersFromServer($this->server) : $headers;
64 | }
65 |
66 | public function query($name, $default = null)
67 | {
68 | return isset($this->query[$name]) ? $this->query[$name] : $default;
69 | }
70 |
71 | public function request($name, $default = null)
72 | {
73 | return isset($this->request[$name]) ? $this->request[$name] : $default;
74 | }
75 |
76 | public function server($name, $default = null)
77 | {
78 | return isset($this->server[$name]) ? $this->server[$name] : $default;
79 | }
80 |
81 | public function headers($name, $default = null)
82 | {
83 | $headers = array_change_key_case($this->headers);
84 | $name = strtolower($name);
85 |
86 | return isset($headers[$name]) ? $headers[$name] : $default;
87 | }
88 |
89 | public function getAllQueryParameters()
90 | {
91 | return $this->query;
92 | }
93 |
94 | /**
95 | * Returns the request body content.
96 | *
97 | * @param Boolean $asResource If true, a resource will be returned
98 | *
99 | * @return string|resource The request body content or a resource to read the body stream.
100 | */
101 | public function getContent($asResource = false)
102 | {
103 | if (false === $this->content || (true === $asResource && null !== $this->content)) {
104 | throw new \LogicException('getContent() can only be called once when using the resource return type.');
105 | }
106 |
107 | if (true === $asResource) {
108 | $this->content = false;
109 |
110 | if( !isset( $HTTP_RAW_POST_DATA ) ) {
111 | return fopen('php://input', 'rb');
112 | }else{
113 | return $HTTP_RAW_POST_DATA;
114 | }
115 | }
116 |
117 | if (null === $this->content) {
118 | if( !isset( $HTTP_RAW_POST_DATA ) ) {
119 | $this->content = file_get_contents('php://input');
120 | }else{
121 | $this->content = $HTTP_RAW_POST_DATA;
122 | }
123 | }
124 |
125 | return $this->content;
126 | }
127 |
128 | private function getHeadersFromServer($server)
129 | {
130 | $headers = array();
131 | foreach ($server as $key => $value) {
132 | if (0 === strpos($key, 'HTTP_')) {
133 | $headers[substr($key, 5)] = $value;
134 | }
135 | // CONTENT_* are not prefixed with HTTP_
136 | elseif (in_array($key, array('CONTENT_LENGTH', 'CONTENT_MD5', 'CONTENT_TYPE'))) {
137 | $headers[$key] = $value;
138 | }
139 | }
140 |
141 | if (isset($server['PHP_AUTH_USER'])) {
142 | $headers['PHP_AUTH_USER'] = $server['PHP_AUTH_USER'];
143 | $headers['PHP_AUTH_PW'] = isset($server['PHP_AUTH_PW']) ? $server['PHP_AUTH_PW'] : '';
144 | } else {
145 | /*
146 | * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
147 | * For this workaround to work, add this line to your .htaccess file:
148 | * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
149 | *
150 | * A sample .htaccess file:
151 | * RewriteEngine On
152 | * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
153 | * RewriteCond %{REQUEST_FILENAME} !-f
154 | * RewriteRule ^(.*)$ app.php [QSA,L]
155 | */
156 |
157 | $authorizationHeader = null;
158 | if (isset($server['HTTP_AUTHORIZATION'])) {
159 | $authorizationHeader = $server['HTTP_AUTHORIZATION'];
160 | } elseif (isset($server['REDIRECT_HTTP_AUTHORIZATION'])) {
161 | $authorizationHeader = $server['REDIRECT_HTTP_AUTHORIZATION'];
162 | } elseif (function_exists('apache_request_headers')) {
163 | $requestHeaders = (array) apache_request_headers();
164 |
165 | // Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
166 | $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
167 |
168 | if (isset($requestHeaders['Authorization'])) {
169 | $authorizationHeader = trim($requestHeaders['Authorization']);
170 | }
171 | }
172 |
173 | if (null !== $authorizationHeader) {
174 | $headers['AUTHORIZATION'] = $authorizationHeader;
175 | // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
176 | if (0 === stripos($authorizationHeader, 'basic')) {
177 | $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)));
178 | if (count($exploded) == 2) {
179 | list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
180 | }
181 | }
182 | }
183 | }
184 |
185 | // PHP_AUTH_USER/PHP_AUTH_PW
186 | if (isset($headers['PHP_AUTH_USER'])) {
187 | $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
188 | }
189 |
190 | return $headers;
191 | }
192 |
193 | /**
194 | * Creates a new request with values from PHP's super globals.
195 | *
196 | * @return Request A new request
197 | *
198 | * @api
199 | */
200 | public static function createFromGlobals()
201 | {
202 | $class = get_called_class();
203 | $request = new $class($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);
204 |
205 | $contentType = $request->server('CONTENT_TYPE', '');
206 | $requestMethod = $request->server('REQUEST_METHOD', 'GET');
207 | if (0 === strpos($contentType, 'application/x-www-form-urlencoded')
208 | && in_array(strtoupper($requestMethod), array('PUT', 'DELETE'))
209 | ) {
210 | parse_str($request->getContent(), $data);
211 | $request->request = $data;
212 | } elseif (0 === strpos($contentType, 'application/json')
213 | && in_array(strtoupper($requestMethod), array('POST', 'PUT', 'DELETE'))
214 | ) {
215 | $data = json_decode($request->getContent(), true);
216 | $request->request = $data;
217 | }
218 |
219 | return $request;
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/library/class-wo-api.php:
--------------------------------------------------------------------------------
1 | 'temporarily_unavailable'));
19 | $response->send();
20 | exit;
21 | }
22 |
23 | global $wp_query;
24 | $method = $wp_query->get( 'oauth' );
25 | $well_known = $wp_query->get( 'well-known' );
26 | $storage = new OAuth2\Storage\Wordpressdb();
27 | $config = array(
28 | 'use_crypto_tokens' => false,
29 | 'store_encrypted_token_string' => false,
30 | 'use_openid_connect' => $o['use_openid_connect'] == '' ? false : $o['use_openid_connect'],
31 | 'issuer' => site_url( null, 'https' ), // Must be HTTPS
32 | 'id_lifetime' => $o['id_token_lifetime'] == '' ? 3600 : $o['id_token_lifetime'],
33 | 'access_lifetime' => $o['access_token_lifetime'] == '' ? 3600 : $o['access_token_lifetime'],
34 | 'refresh_token_lifetime' => $o['refresh_token_lifetime'] == '' ? 86400 : $o['refresh_token_lifetime'],
35 | 'www_realm' => 'Service',
36 | 'token_param_name' => 'access_token',
37 | 'token_bearer_header_name' => 'Bearer',
38 | 'enforce_state' => $o['enforce_state'] == '1' ? true : false,
39 | 'require_exact_redirect_uri' => $o['require_exact_redirect_uri'] == '1' ? true : false,
40 | 'allow_implicit' => $o['implicit_enabled'] == '1' ? true : false,
41 | 'allow_credentials_in_request_body' => true, // Must be set to true for openID to work in most cases
42 | 'allow_public_clients' => false,
43 | 'always_issue_new_refresh_token' => true,
44 | 'redirect_status_code' => 302
45 | );
46 | $server = new OAuth2\Server( $storage, $config );
47 |
48 | /*
49 | |--------------------------------------------------------------------------
50 | | SUPPORTED GRANT TYPES
51 | |--------------------------------------------------------------------------
52 | |
53 | | Authorization Code will always be on. This may be a bug or a f@#$ up on
54 | | my end. None the less, these are controlled in the server settings page.
55 | |
56 | */
57 | $support_grant_types = array();
58 | if ( '1' == $o['auth_code_enabled'] ) {
59 | $server->addGrantType( new OAuth2\GrantType\AuthorizationCode( $storage ) );
60 | }
61 | if ( '1' == $o['client_creds_enabled'] ) {
62 | $server->addGrantType(new OAuth2\GrantType\ClientCredentials( $storage ) );
63 | }
64 | if ( '1' == $o['user_creds_enabled'] ) {
65 | $server->addGrantType(new OAuth2\GrantType\UserCredentials( $storage ) );
66 | }
67 | if ( '1' == $o['refresh_tokens_enabled'] ) {
68 | $server->addGrantType( new OAuth2\GrantType\RefreshToken( $storage, $config ) );
69 | }
70 | if ( '1' == $o['use_openid_connect'] ) {
71 | $server->addGrantType( new OAuth2\OpenID\GrantType\AuthorizationCode( $storage, $config ) );
72 | }
73 |
74 | /*
75 | |--------------------------------------------------------------------------
76 | | DEFAULT SCOPES
77 | |--------------------------------------------------------------------------
78 | |
79 | | Supported scopes can be added to the plugin by modifying the wo_scopes.
80 | | Until further notice, the default scope is 'basic'. Plans are in place to
81 | | allow this scope to be adjusted.
82 | |
83 | */
84 | $defaultScope = 'basic';
85 | $supportedScopes = apply_filters( 'wo_scopes', null, 20 );
86 |
87 | $memory = new OAuth2\Storage\Memory( array(
88 | 'default_scope' => $defaultScope,
89 | 'supported_scopes' => $supportedScopes,
90 | ) );
91 | $scopeUtil = new OAuth2\Scope( $memory );
92 | $server->setScopeUtil( $scopeUtil );
93 |
94 | /*
95 | |--------------------------------------------------------------------------
96 | | TOKEN CATCH
97 | |--------------------------------------------------------------------------
98 | |
99 | | The following code is ran when a request is made to the server using the
100 | | Authorization Code (implicit) Grant Type as well as request tokens
101 | |
102 | */
103 | if ( $method == 'token' ) {
104 | do_action( 'wo_before_token_method', array( $_REQUEST ) );
105 | $server->handleTokenRequest( OAuth2\Request::createFromGlobals() )->send();
106 | exit;
107 | }
108 |
109 | /*
110 | |--------------------------------------------------------------------------
111 | | AUTHORIZATION CODE CATCH
112 | |--------------------------------------------------------------------------
113 | |
114 | | The following code is ran when a request is made to the server using the
115 | | Authorization Code (not implicit) Grant Type.
116 | |
117 | | 1. Check if the user is logged in (redirect if not)
118 | | 2. Validate the request (client_id, redirect_uri)
119 | | 3. Create the authorization request using the authentication user's user_id
120 | |
121 | */
122 | if ( $method == 'authorize' ) {
123 | do_action( 'wo_before_authorize_method', array( $_REQUEST ) );
124 | $request = OAuth2\Request::createFromGlobals();
125 | $response = new OAuth2\Response();
126 | if (! $server->validateAuthorizeRequest( $request, $response ) ) {
127 | $response->send();
128 | exit;
129 | }
130 |
131 | if (! is_user_logged_in() ) {
132 | wp_redirect( wp_login_url( $_SERVER['REQUEST_URI'] ) );
133 | exit;
134 | }
135 |
136 | $server->handleAuthorizeRequest($request, $response, true, get_current_user_id());
137 | $response->send();
138 | exit;
139 | }
140 |
141 | /*
142 | |--------------------------------------------------------------------------
143 | | PUBLIC KEY
144 | |--------------------------------------------------------------------------
145 | |
146 | | Presents the generic public key for signing.
147 | | @since 3.0.5
148 | */
149 | if ( $well_known == 'keys' ) {
150 | $keys = apply_filters( 'wo_server_keys', null);
151 | $publicKey = openssl_pkey_get_public( file_get_contents( $keys['public'] ) );
152 | $publicKey = openssl_pkey_get_details( $publicKey );
153 | $response = new OAuth2\Response( array(
154 | 'keys' => array(
155 | array(
156 | 'kty' => 'RSA',
157 | 'alg' => 'RS256',
158 | 'use' => 'sig',
159 | 'n' => base64_encode( $publicKey['rsa']['n'] ),
160 | 'e' => base64_encode( $publicKey['rsa']['e'] )
161 | )
162 | )
163 | ));
164 | $response->send();
165 | exit;
166 | }
167 |
168 | /*
169 | |--------------------------------------------------------------------------
170 | | OpenID Discovery
171 | |--------------------------------------------------------------------------
172 | |
173 | */
174 | if ( $well_known == 'openid-configuration' ) {
175 | $openid_configuration = array(
176 | 'issuer' => site_url( null, 'https' ),
177 | 'authorization_endpoint' => site_url( '/oauth/authorize' ),
178 | 'token_endpoint' => site_url( 'oauth/token' ),
179 | 'userinfo_endpoint' => site_url( '/oauth/me' ),
180 | 'jwks_uri' => site_url( '/.well-known/keys' ),
181 | 'response_types_supported' => array( 'code', 'id_token', 'token id_token', 'code id_token' ),
182 | 'subject_types_supported' => array( 'public' ),
183 | 'id_token_signing_alg_values_supported' => array( 'RS256' )
184 | );
185 | $response = new OAuth2\Response( $openid_configuration );
186 | $response->send();
187 | exit;
188 | }
189 |
190 | /*
191 | |--------------------------------------------------------------------------
192 | | EXTENDABLE RESOURCE SERVER METHODS
193 | |--------------------------------------------------------------------------
194 | |
195 | | Below this line is part of the developer API. Do not edit directly.
196 | | Refer to the developer documentation for extending the WordPress OAuth
197 | | Server plugin core functionality.
198 | |
199 | | @todo Document and tighten up error messages. All error messages will soon be
200 | | controlled through apply_filters so start planning for a filter error list to
201 | | allow for developers to customize error messages.
202 | |
203 | */
204 | $ext_methods = apply_filters( "wo_endpoints", null );
205 |
206 | // Check to see if the method exists in the filter
207 | if ( array_key_exists( $method, $ext_methods ) ) {
208 |
209 | // If the method is is set to public, lets just run the method without
210 | if( isset( $ext_methods[$method]['public'] ) && $ext_methods[$method]['public'] ){
211 | call_user_func_array($ext_methods[$method]['func'], $_REQUEST);
212 | exit;
213 | }
214 |
215 | $response = new OAuth2\Response();
216 | if ( ! $server->verifyResourceRequest( OAuth2\Request::createFromGlobals() ) ) {
217 | $response->setError(400, 'invalid_request', 'Missing or invalid parameter(s)');
218 | $response->send();
219 | exit;
220 | }
221 | $token = $server->getAccessTokenData( OAuth2\Request::createFromGlobals() );
222 | if ( is_null( $token ) ) {
223 | $server->getResponse()->send();
224 | exit;
225 | }
226 |
227 | do_action('wo_endpoint_user_authenticated', array( $token ) );
228 | call_user_func_array( $ext_methods[$method]['func'], array( $token ) );
229 |
230 | exit;
231 | }
232 |
233 | /**
234 | * Server error response. End of line
235 | * @since 3.1.0
236 | */
237 | $response = new OAuth2\Response();
238 | $response->setError(400, 'invalid_request', 'Unknown request');
239 | $response->send();
240 | exit;
--------------------------------------------------------------------------------
/wp-oauth-main.php:
--------------------------------------------------------------------------------
1 |
7 | * @package WordPress OAuth Server
8 | */
9 |
10 | defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
11 |
12 | class WO_Server {
13 |
14 | /** Plugin Version */
15 | public $version = "3.1.98";
16 |
17 | /** Server Instance */
18 | public static $_instance = null;
19 |
20 | /** Default Settings */
21 | protected $defualt_settings = array(
22 | "enabled" => 1,
23 | "client_id_length" => 30,
24 | "auth_code_enabled" => 1,
25 | "client_creds_enabled" => 0,
26 | "user_creds_enabled" => 0,
27 | "refresh_tokens_enabled" => 0,
28 | "implicit_enabled" => 0,
29 | "require_exact_redirect_uri" => 0,
30 | "enforce_state" => 0,
31 | "refresh_token_lifetime" => 864000, // 10 Days
32 | "access_token_lifetime" => 86400, // 24 Hours
33 | "use_openid_connect" => 0,
34 | "id_lifetime" => 3600
35 | );
36 |
37 | function __construct() {
38 |
39 | if ( ! defined( 'WOABSPATH' ) ) {
40 | define( 'WOABSPATH', dirname( __FILE__ ) );
41 | }
42 |
43 | if ( ! defined( 'WOURI' ) ) {
44 | define( 'WOURI', plugins_url( '/', __FILE__) );
45 | }
46 |
47 | if ( function_exists( '__autoload' ) ) {
48 | spl_autoload_register( '__autoload' );
49 | }
50 | spl_autoload_register( array( $this, 'autoload') );
51 |
52 |
53 | add_filter( 'determine_current_user', array($this, '_wo_authenicate_bypass'), 21);
54 | add_action("init", array(__CLASS__, "includes"));
55 |
56 | }
57 |
58 | /**
59 | * Awesomeness for 3rd party support
60 | *
61 | * Filter; determine_current_user
62 | * Other Filter: check_authentication
63 | *
64 | * This creates a hook in the determine_current_user filter that can check for a valid access_token
65 | * and user services like WP JSON API and WP REST API.
66 | * @param [type] $user_id User ID to
67 | *
68 | * @author Mauro Constantinescu Modified slightly but still a contribution to the project.
69 | */
70 | public function _wo_authenicate_bypass( $user_id ) {
71 | if ( $user_id && $user_id > 0 )
72 | return (int) $user_id;
73 |
74 | $o = get_option( 'wo_options' );
75 | if ( $o['enabled'] == 0 )
76 | return (int) $user_id;
77 |
78 | require_once( dirname( WPOAUTH_FILE ) . '/library/OAuth2/Autoloader.php');
79 | OAuth2\Autoloader::register();
80 | $server = new OAuth2\Server( new OAuth2\Storage\Wordpressdb() );
81 | $request = OAuth2\Request::createFromGlobals();
82 | if ( $server->verifyResourceRequest( $request ) ) {
83 | $token = $server->getAccessTokenData( $request );
84 | if ( isset( $token['user_id'] ) && $token['user_id'] > 0 ) {
85 | return (int) $token['user_id'];
86 | }elseif( isset( $token['user_id'] ) && $token['user_id'] === 0 ) {
87 |
88 | }
89 | }
90 | }
91 |
92 | /**
93 | * populate the instance if the plugin for extendability
94 | * @return object plugin instance
95 | */
96 | public static function instance() {
97 | if ( is_null( self::$_instance ) ) {
98 | self::$_instance = new self();
99 | }
100 |
101 | return self::$_instance;
102 | }
103 |
104 | /**
105 | * setup plugin class autoload
106 | * @return void
107 | */
108 | public function autoload( $class ) {
109 | $path = null;
110 | $class = strtolower( $class );
111 | $file = 'class-' . str_replace( '_', '-', $class ) . '.php';
112 |
113 | if ( strpos( $class, "wo_" ) === 0 ) {
114 | $path = dirname( __FILE__ ) . '/library/' . trailingslashit( substr( str_replace( '_', '-', $class ), 18 ) );
115 | }
116 |
117 | if ( $path && is_readable( $path . $file ) ) {
118 | include_once $path . $file;
119 | return;
120 | }
121 | }
122 |
123 | /**
124 | * plugin includes called during load of plugin
125 | * @return void
126 | */
127 | public static function includes() {
128 | require_once dirname( __FILE__ ) . '/includes/functions.php';
129 | require_once dirname( __FILE__ ) . '/includes/admin-options.php';
130 | //require_once dirname( __FILE__ ) . '/includes/rewrites.php';
131 |
132 | /** include the ajax class if DOING_AJAX is defined */
133 | if (defined('DOING_AJAX')) {
134 | require_once dirname(__FILE__) . '/includes/ajax/class-wo-ajax.php';
135 | }
136 |
137 | /** Daily Crons */
138 | if ( ! wp_next_scheduled( 'wo_daily_tasks_hook' ) ) {
139 | wp_schedule_event( time(), 'hourly', 'wo_daily_tasks_hook' );
140 | }
141 | }
142 |
143 | /**
144 | * plugin setup. this is only ran on activation
145 | * @return [type] [description]
146 | */
147 | public function setup() {
148 | $options = get_option( "wo_options" );
149 | if (! isset( $options["enabled"] ) ) {
150 | update_option( "wo_options", $this->defualt_settings );
151 | }
152 |
153 | $this->install();
154 | }
155 |
156 | /**
157 | * plugin update check
158 | * @return [type] [description]
159 | */
160 | public function install() {
161 |
162 | /** Install the required tables in the database */
163 | global $wpdb;
164 |
165 | $charset_collate = '';
166 |
167 | /** Set charset to current wp option */
168 | if (!empty($wpdb->charset)) {
169 | $charset_collate = "DEFAULT CHARACTER SET {$wpdb->charset}";
170 | }
171 |
172 | /** Set collate to current wp option */
173 | if (!empty($wpdb->collate)) {
174 | $charset_collate .= " COLLATE {$wpdb->collate}";
175 | }
176 |
177 | /** Update the version in the database */
178 | update_option("wpoauth_version", $this->version);
179 |
180 | $sql1 = "
181 | CREATE TABLE IF NOT EXISTS {$wpdb->prefix}oauth_clients (
182 | client_id VARCHAR(80) NOT NULL,
183 | client_secret VARCHAR(80) NOT NULL,
184 | redirect_uri VARCHAR(2000),
185 | grant_types VARCHAR(80),
186 | scope VARCHAR(4000),
187 | user_id VARCHAR(80),
188 | name VARCHAR(80),
189 | description LONGTEXT,
190 | PRIMARY KEY (client_id)
191 | );
192 | ";
193 |
194 | $sql2 = "
195 | CREATE TABLE IF NOT EXISTS {$wpdb->prefix}oauth_access_tokens (
196 | id INT NOT NULL AUTO_INCREMENT,
197 | access_token VARCHAR(4000) NOT NULL,
198 | client_id VARCHAR(80) NOT NULL,
199 | user_id VARCHAR(80),
200 | expires TIMESTAMP NOT NULL,
201 | scope VARCHAR(4000),
202 | PRIMARY KEY (id)
203 | );
204 | ";
205 |
206 | $sql3 = "
207 | CREATE TABLE IF NOT EXISTS {$wpdb->prefix}oauth_refresh_tokens (
208 | refresh_token VARCHAR(40) NOT NULL,
209 | client_id VARCHAR(80) NOT NULL,
210 | user_id VARCHAR(80),
211 | expires TIMESTAMP NOT NULL,
212 | scope VARCHAR(4000),
213 | PRIMARY KEY (refresh_token)
214 | );
215 | ";
216 |
217 | $sql4 = "
218 | CREATE TABLE IF NOT EXISTS {$wpdb->prefix}oauth_authorization_codes (
219 | authorization_code VARCHAR(40) NOT NULL,
220 | client_id VARCHAR(80) NOT NULL,
221 | user_id VARCHAR(80),
222 | redirect_uri VARCHAR(2000),
223 | expires TIMESTAMP NOT NULL,
224 | scope VARCHAR(4000),
225 | id_token VARCHAR(3000),
226 | PRIMARY KEY (authorization_code)
227 | );
228 | ";
229 |
230 | $sql5 = "
231 | CREATE TABLE IF NOT EXISTS {$wpdb->prefix}oauth_scopes (
232 | scope VARCHAR(80) NOT NULL,
233 | is_default BOOLEAN,
234 | PRIMARY KEY (scope)
235 | );
236 | ";
237 |
238 | $sql6 = "
239 | CREATE TABLE IF NOT EXISTS {$wpdb->prefix}oauth_jwt (
240 | client_id VARCHAR(80) NOT NULL,
241 | subject VARCHAR(80),
242 | public_key VARCHAR(2000) NOT NULL,
243 | PRIMARY KEY (client_id)
244 | );
245 | ";
246 |
247 | $sql7 = "
248 | CREATE TABLE IF NOT EXISTS {$wpdb->prefix}oauth_public_keys (
249 | client_id VARCHAR(80),
250 | public_key VARCHAR(2000),
251 | private_key VARCHAR(2000),
252 | encryption_algorithm VARCHAR(100) DEFAULT 'RS256',
253 | PRIMARY KEY (client_id)
254 | );
255 | ";
256 |
257 | require_once ABSPATH . 'wp-admin/includes/upgrade.php';
258 | dbDelta($sql1);
259 | dbDelta($sql2);
260 | dbDelta($sql3);
261 | dbDelta($sql4);
262 | dbDelta($sql5);
263 | dbDelta($sql6);
264 | dbDelta($sql7);
265 |
266 | /**
267 | * Create certificates for signing
268 | *
269 | */
270 | if( function_exists( 'openssl_pkey_new' ) ){
271 | $res = openssl_pkey_new( array(
272 | "private_key_bits" => 2048,
273 | "private_key_type" => OPENSSL_KEYTYPE_RSA,
274 | ));
275 | openssl_pkey_export( $res, $privKey );
276 | file_put_contents(dirname( WPOAUTH_FILE) . '/library/keys/private_key.pem', $privKey);
277 |
278 | $pubKey = openssl_pkey_get_details($res);
279 | $pubKey = $pubKey["key"];
280 | file_put_contents(dirname(WPOAUTH_FILE) . '/library/keys/public_key.pem', $pubKey);
281 |
282 | // Update plugin version
283 | $plugin_data = get_plugin_data( WPOAUTH_FILE );
284 | $plugin_version = $plugin_data['Version'];
285 | update_option( 'wpoauth_version', $plugin_version );
286 | }
287 |
288 | }
289 |
290 | /**
291 | * Upgrade method
292 | *
293 | */
294 | public function upgrade () {
295 | $options = get_option( 'wo_options' );
296 |
297 | // added 3.0.4
298 | if( ! isset( $options['access_token_lifetime'] ) ) {
299 | $options['access_token_lifetime'] = 3600;
300 | }
301 |
302 | // added 3.0.4
303 | if( ! isset( $options['refresh_token_lifetime'] ) ) {
304 | $options['refresh_token_lifetime'] = 86400;
305 | }
306 |
307 | // added 3.0.5
308 | if( ! isset( $options['id_token_lifetime'] ) ) {
309 | $options['id_token_lifetime'] = 3600;
310 | }
311 |
312 | // added 3.0.5
313 | if( ! isset( $options['use_openid_connect'] ) ) {
314 | $options['use_openid_connect'] = 3600;
315 | }
316 |
317 | update_option( 'wo_options', $options );
318 | }
319 |
320 | }
321 |
322 | function _WO() {
323 | return WO_Server::instance();
324 | }
325 | $GLOBAL['WO'] = _WO();
--------------------------------------------------------------------------------
/library/OAuth2/Storage/Redis.php:
--------------------------------------------------------------------------------
1 |
14 | * $storage = new OAuth2\Storage\Redis($redis);
15 | * $storage->setClientDetails($client_id, $client_secret, $redirect_uri);
16 | *
17 | */
18 | class Redis implements AuthorizationCodeInterface,
19 | AccessTokenInterface,
20 | ClientCredentialsInterface,
21 | UserCredentialsInterface,
22 | RefreshTokenInterface,
23 | JwtBearerInterface,
24 | ScopeInterface,
25 | OpenIDAuthorizationCodeInterface
26 | {
27 |
28 | private $cache;
29 |
30 | /* The redis client */
31 | protected $redis;
32 |
33 | /* Configuration array */
34 | protected $config;
35 |
36 | /**
37 | * Redis Storage!
38 | *
39 | * @param \Predis\Client $redis
40 | * @param array $config
41 | */
42 | public function __construct($redis, $config=array())
43 | {
44 | $this->redis = $redis;
45 | $this->config = array_merge(array(
46 | 'client_key' => 'oauth_clients:',
47 | 'access_token_key' => 'oauth_access_tokens:',
48 | 'refresh_token_key' => 'oauth_refresh_tokens:',
49 | 'code_key' => 'oauth_authorization_codes:',
50 | 'user_key' => 'oauth_users:',
51 | 'jwt_key' => 'oauth_jwt:',
52 | 'scope_key' => 'oauth_scopes:',
53 | ), $config);
54 | }
55 |
56 | protected function getValue($key)
57 | {
58 | if ( isset($this->cache[$key]) ) {
59 | return $this->cache[$key];
60 | }
61 | $value = $this->redis->get($key);
62 | if ( isset($value) ) {
63 | return json_decode($value, true);
64 | } else {
65 | return false;
66 | }
67 | }
68 |
69 | protected function setValue($key, $value, $expire=0)
70 | {
71 | $this->cache[$key] = $value;
72 | $str = json_encode($value);
73 | if ($expire > 0) {
74 | $seconds = $expire - time();
75 | $ret = $this->redis->setex($key, $seconds, $str);
76 | } else {
77 | $ret = $this->redis->set($key, $str);
78 | }
79 |
80 | // check that the key was set properly
81 | // if this fails, an exception will usually thrown, so this step isn't strictly necessary
82 | return is_bool($ret) ? $ret : $ret->getPayload() == 'OK';
83 | }
84 |
85 | protected function expireValue($key)
86 | {
87 | unset($this->cache[$key]);
88 |
89 | return $this->redis->del($key);
90 | }
91 |
92 | /* AuthorizationCodeInterface */
93 | public function getAuthorizationCode($code)
94 | {
95 | return $this->getValue($this->config['code_key'] . $code);
96 | }
97 |
98 | public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
99 | {
100 | return $this->setValue(
101 | $this->config['code_key'] . $authorization_code,
102 | compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'),
103 | $expires
104 | );
105 | }
106 |
107 | public function expireAuthorizationCode($code)
108 | {
109 | $key = $this->config['code_key'] . $code;
110 | unset($this->cache[$key]);
111 |
112 | return $this->expireValue($key);
113 | }
114 |
115 | /* UserCredentialsInterface */
116 | public function checkUserCredentials($username, $password)
117 | {
118 | $user = $this->getUserDetails($username);
119 |
120 | return $user && $user['password'] === $password;
121 | }
122 |
123 | public function getUserDetails($username)
124 | {
125 | return $this->getUser($username);
126 | }
127 |
128 | public function getUser($username)
129 | {
130 | if (!$userInfo = $this->getValue($this->config['user_key'] . $username)) {
131 | return false;
132 | }
133 |
134 | // the default behavior is to use "username" as the user_id
135 | return array_merge(array(
136 | 'user_id' => $username,
137 | ), $userInfo);
138 | }
139 |
140 | public function setUser($username, $password, $first_name = null, $last_name = null)
141 | {
142 | return $this->setValue(
143 | $this->config['user_key'] . $username,
144 | compact('username', 'password', 'first_name', 'last_name')
145 | );
146 | }
147 |
148 | /* ClientCredentialsInterface */
149 | public function checkClientCredentials($client_id, $client_secret = null)
150 | {
151 | if (!$client = $this->getClientDetails($client_id)) {
152 | return false;
153 | }
154 |
155 | return isset($client['client_secret'])
156 | && $client['client_secret'] == $client_secret;
157 | }
158 |
159 | public function isPublicClient($client_id)
160 | {
161 | if (!$client = $this->getClientDetails($client_id)) {
162 | return false;
163 | }
164 |
165 | return empty($client['client_secret']);
166 | }
167 |
168 | /* ClientInterface */
169 | public function getClientDetails($client_id)
170 | {
171 | return $this->getValue($this->config['client_key'] . $client_id);
172 | }
173 |
174 | public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null)
175 | {
176 | return $this->setValue(
177 | $this->config['client_key'] . $client_id,
178 | compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id')
179 | );
180 | }
181 |
182 | public function checkRestrictedGrantType($client_id, $grant_type)
183 | {
184 | $details = $this->getClientDetails($client_id);
185 | if (isset($details['grant_types'])) {
186 | $grant_types = explode(' ', $details['grant_types']);
187 |
188 | return in_array($grant_type, (array) $grant_types);
189 | }
190 |
191 | // if grant_types are not defined, then none are restricted
192 | return true;
193 | }
194 |
195 | /* RefreshTokenInterface */
196 | public function getRefreshToken($refresh_token)
197 | {
198 | return $this->getValue($this->config['refresh_token_key'] . $refresh_token);
199 | }
200 |
201 | public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null)
202 | {
203 | return $this->setValue(
204 | $this->config['refresh_token_key'] . $refresh_token,
205 | compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'),
206 | $expires
207 | );
208 | }
209 |
210 | public function unsetRefreshToken($refresh_token)
211 | {
212 | return $this->expireValue($this->config['refresh_token_key'] . $refresh_token);
213 | }
214 |
215 | /* AccessTokenInterface */
216 | public function getAccessToken($access_token)
217 | {
218 | return $this->getValue($this->config['access_token_key'].$access_token);
219 | }
220 |
221 | public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null)
222 | {
223 | return $this->setValue(
224 | $this->config['access_token_key'].$access_token,
225 | compact('access_token', 'client_id', 'user_id', 'expires', 'scope'),
226 | $expires
227 | );
228 | }
229 |
230 | public function unsetAccessToken($access_token)
231 | {
232 | return $this->expireValue($this->config['access_token_key'] . $access_token);
233 | }
234 |
235 | /* ScopeInterface */
236 | public function scopeExists($scope)
237 | {
238 | $scope = explode(' ', $scope);
239 |
240 | $result = $this->getValue($this->config['scope_key'].'supported:global');
241 |
242 | $supportedScope = explode(' ', (string) $result);
243 |
244 | return (count(array_diff($scope, $supportedScope)) == 0);
245 | }
246 |
247 | public function getDefaultScope($client_id = null)
248 | {
249 | if (is_null($client_id) || !$result = $this->getValue($this->config['scope_key'].'default:'.$client_id)) {
250 | $result = $this->getValue($this->config['scope_key'].'default:global');
251 | }
252 |
253 | return $result;
254 | }
255 |
256 | public function setScope($scope, $client_id = null, $type = 'supported')
257 | {
258 | if (!in_array($type, array('default', 'supported'))) {
259 | throw new \InvalidArgumentException('"$type" must be one of "default", "supported"');
260 | }
261 |
262 | if (is_null($client_id)) {
263 | $key = $this->config['scope_key'].$type.':global';
264 | } else {
265 | $key = $this->config['scope_key'].$type.':'.$client_id;
266 | }
267 |
268 | return $this->setValue($key, $scope);
269 | }
270 |
271 | /*JWTBearerInterface */
272 | public function getClientKey($client_id, $subject)
273 | {
274 | if (!$jwt = $this->getValue($this->config['jwt_key'] . $client_id)) {
275 | return false;
276 | }
277 |
278 | if (isset($jwt['subject']) && $jwt['subject'] == $subject) {
279 | return $jwt['key'];
280 | }
281 |
282 | return null;
283 | }
284 |
285 | public function setClientKey($client_id, $key, $subject = null)
286 | {
287 | return $this->setValue($this->config['jwt_key'] . $client_id, array(
288 | 'key' => $key,
289 | 'subject' => $subject
290 | ));
291 | }
292 |
293 | public function getClientScope($client_id)
294 | {
295 | if (!$clientDetails = $this->getClientDetails($client_id)) {
296 | return false;
297 | }
298 |
299 | if (isset($clientDetails['scope'])) {
300 | return $clientDetails['scope'];
301 | }
302 |
303 | return null;
304 | }
305 |
306 | public function getJti($client_id, $subject, $audience, $expiration, $jti)
307 | {
308 | //TODO: Needs redis implementation.
309 | throw new \Exception('getJti() for the Redis driver is currently unimplemented.');
310 | }
311 |
312 | public function setJti($client_id, $subject, $audience, $expiration, $jti)
313 | {
314 | //TODO: Needs redis implementation.
315 | throw new \Exception('setJti() for the Redis driver is currently unimplemented.');
316 | }
317 | }
318 |
--------------------------------------------------------------------------------