From 240ccda762734828c728085cf9b500f169da8bd5 Mon Sep 17 00:00:00 2001 From: William Tam Date: Tue, 5 Sep 2023 16:50:52 -0400 Subject: [PATCH 1/4] v0.3.2.0 enable PKCE --- assets/js/loginform_helpers.js | 50 +++++++++++++++++++-- classes/class-PBS-LAAS-client.php | 15 ++++--- classes/class-pbs-passport-authenticate.php | 2 +- pbs-passport-authenticate.php | 2 +- templates/oauthcallback.php | 7 ++- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/assets/js/loginform_helpers.js b/assets/js/loginform_helpers.js index ce5e65c..568e055 100644 --- a/assets/js/loginform_helpers.js +++ b/assets/js/loginform_helpers.js @@ -1,5 +1,40 @@ jQuery(document).ready(function($) { + // Helper functions for PKCE // + + // Generate a secure random string using the browser crypto functions + function generateRandomString() { + var array = new Uint32Array(28); + window.crypto.getRandomValues(array); + return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join(''); + } + + // Calculate the SHA256 hash of the input text. + // Returns a promise that resolves to an ArrayBuffer + function sha256(plain) { + const encoder = new TextEncoder(); + const data = encoder.encode(plain); + return window.crypto.subtle.digest('SHA-256', data); + } + + // Base64-urlencodes the input string + function base64urlencode(str) { + // Convert the ArrayBuffer to string using Uint8 array to convert to what btoa accepts. + // btoa accepts chars only within ascii 0-255 and base64 encodes them. + // Then convert the base64 encoded to base64url encoded + // (replace + with -, replace / with _, trim trailing =) + return btoa(String.fromCharCode.apply(null, new Uint8Array(str))) + .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); + } + + // Return the base64-urlencoded sha256 hash for the PKCE challenge + async function pkceChallengeFromVerifier(v) { + hashed = await sha256(v); + return base64urlencode(hashed); + } + + + function setPBSOAuthRememberMe() { var rememberme = 'false'; if ( $("input[name='pbsoauth_rememberme']").prop("checked") ){ @@ -16,16 +51,25 @@ jQuery(document).ready(function($) { setPBSOAuthRememberMe(); }); - /* set a loginprovider cookie when the person chooses one */ - $(".passport-login-wrap li a").click(function(event) { + /* Various things to do when someone clicks on a login link */ + $(".passport-login-wrap li a").on("click", async function(event) { event.preventDefault(); + // set the loginprovider cookie var logintype = $(this).closest('li').attr("class"); if (logintype) { document.cookie='pbsoauth_loginprovider=' + logintype + ';domain=' + window.location.hostname + ';path=/'; window.dataLayer = window.dataLayer || []; dataLayer.push({ 'event': 'login', 'method': logintype }); } - window.location.href = $(this).attr('href'); + // generate and append the codeverifier + var code_verifier = generateRandomString(); + document.cookie='pkce_code_verifier=' + code_verifier + ';domain=' + window.location.hostname + ';path=/'; + var code_challenge = await pkceChallengeFromVerifier(code_verifier); + var encoded_code_challenge = encodeURIComponent(code_challenge); + var appended_href = $(this).attr('href') + "&code_challenge=" + encoded_code_challenge + "&code_challenge_method=S256"; + + // send them along their way + window.location.href = appended_href; }); }); diff --git a/classes/class-PBS-LAAS-client.php b/classes/class-PBS-LAAS-client.php index eb710ba..5b385f9 100644 --- a/classes/class-PBS-LAAS-client.php +++ b/classes/class-PBS-LAAS-client.php @@ -33,7 +33,7 @@ Public Methods: -authenticate($code, $rememberme) +authenticate($code, $rememberme, $nonce='', $code_exchange='') Takes an oAuth grant code and use it to get access and refresh tokens from PBS then stores tokens and userinfo in encrypted session variables. the second arg determines if this info is stored in encryped cookies for longer term. @@ -130,13 +130,13 @@ private function build_curl_handle($url) { - public function authenticate($code= '', $rememberme='', $nonce=''){ + public function authenticate($code= '', $rememberme='', $nonce='', $code_verifier= ''){ $this->checknonce = $nonce; $this->rememberme = $rememberme; - $tokeninfo = $this->get_code_response($code); + $tokeninfo = $this->get_code_response($code, $code_verifier); if (! isset($tokeninfo["access_token"]) ) { $tokeninfo['messages'] = 'broke on code response'; return $tokeninfo; @@ -162,8 +162,8 @@ public function authenticate($code= '', $rememberme='', $nonce=''){ } - public function code_exchange($code= ''){ - $tokeninfo = $this->get_code_response($code); + public function code_exchange($code= '', $code_verifier= ''){ + $tokeninfo = $this->get_code_response($code, $code_verifier); if (! isset($tokeninfo["access_token"]) ) { $tokeninfo['messages'] = 'broke on code exchange'; return $tokeninfo; @@ -245,7 +245,7 @@ public function logout() { } - private function get_code_response($code=''){ + private function get_code_response($code='', $code_verifier=''){ $url = $this->oauthroot . 'token/'; $postfields = array( 'code' => $code, @@ -254,6 +254,9 @@ private function get_code_response($code=''){ 'client_secret' => $this->client_secret, 'grant_type' => 'authorization_code' ); + if (!empty($code_verifier)) { + $postfields['code_verifier'] = $code_verifier; + } $ch = $this->build_curl_handle($url); //construct the curl request diff --git a/classes/class-pbs-passport-authenticate.php b/classes/class-pbs-passport-authenticate.php index 1fafee8..469f095 100644 --- a/classes/class-pbs-passport-authenticate.php +++ b/classes/class-pbs-passport-authenticate.php @@ -23,7 +23,7 @@ public function __construct($file) { $this->assets_url = esc_url( trailingslashit( plugins_url( '/assets/', $file ) ) ); $this->token = 'pbs_passport_authenticate'; $this->defaults = get_option($this->token); - $this->version = '0.3.1.5'; + $this->version = '0.3.2.0'; // Load public-facing style sheet and JavaScript. add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); diff --git a/pbs-passport-authenticate.php b/pbs-passport-authenticate.php index 6ad6bd5..0fc354f 100644 --- a/pbs-passport-authenticate.php +++ b/pbs-passport-authenticate.php @@ -1,7 +1,7 @@ authenticate($code, $rememberme, $nonce); + $userinfo = $laas_client->authenticate($code, $rememberme, $nonce, $code_verifier); } // now we either have userinfo or null. From fb130834a90039d44e09b6b74e2c80ebfafc2dd6 Mon Sep 17 00:00:00 2001 From: William Tam Date: Wed, 6 Sep 2023 15:28:29 -0400 Subject: [PATCH 2/4] enable donor portal link --- assets/js/jquery.pids.js | 2 +- classes/class-pbs-passport-authenticate-settings.php | 1 + templates/userinfo.php | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/assets/js/jquery.pids.js b/assets/js/jquery.pids.js index edc5338..3851ec1 100644 --- a/assets/js/jquery.pids.js +++ b/assets/js/jquery.pids.js @@ -195,7 +195,7 @@ jQuery(document).ready(function($) { if ($(".pbs_passport_authenticate div.messages-new")[0]){ // new version of messages with drop down menu... if (user.thumbnail_URL) {thumbimage = "";} - welcomestring = '' + thumbimage + '' + user.first_name + ' '; + welcomestring = '' + thumbimage + '' + user.first_name + ' '; $('.pbs_passport_authenticate div.messages-new').html(welcomestring); if (typeof makeNavObserverPids == 'function') { diff --git a/classes/class-pbs-passport-authenticate-settings.php b/classes/class-pbs-passport-authenticate-settings.php index 1d92840..93f95c9 100644 --- a/classes/class-pbs-passport-authenticate-settings.php +++ b/classes/class-pbs-passport-authenticate-settings.php @@ -59,6 +59,7 @@ public function register_settings() { add_settings_field( 'join_url', 'Join/Donate URL', array( $this, 'settings_field'), 'pbs_passport_authenticate', 'general_settings', array('setting' => 'pbs_passport_authenticate', 'field' => 'join_url', 'class' => 'regular-text', 'label' => 'Link to the specific donate form people should be directed to from the login screen.' ) ); add_settings_field( 'watch_url', 'Watch Programs URL', array( $this, 'settings_field'), 'pbs_passport_authenticate', 'general_settings', array('setting' => 'pbs_passport_authenticate', 'field' => 'watch_url', 'class' => 'regular-text', 'label' => 'Link to your watch programs landing page.' ) ); add_settings_field( 'landing_page_url', 'Post-Login Landing Page URL', array( $this, 'settings_field'), 'pbs_passport_authenticate', 'general_settings', array('setting' => 'pbs_passport_authenticate', 'field' => 'landing_page_url', 'class' => 'regular-text', 'label' => 'URL a member is sent to after successfully logging in if not clicking in from a video page (or some other page that sets the "login_referrer" cookie). Defaults to your site home page.' ) ); + add_settings_field( 'donor_portal_url', 'Donor Portal URL', array( $this, 'settings_field'), 'pbs_passport_authenticate', 'general_settings', array('setting' => 'pbs_passport_authenticate', 'field' => 'donor_portal_url', 'class' => 'regular-text', 'label' => 'URL for your Donor Portal. Leave blank if you do not have a donor portal.' ) ); add_settings_section('pbslaas_settings', 'PBS LAAS settings', array( $this, 'settings_section_callback'), 'pbs_passport_authenticate'); diff --git a/templates/userinfo.php b/templates/userinfo.php index 0d286ae..581224a 100644 --- a/templates/userinfo.php +++ b/templates/userinfo.php @@ -47,7 +47,7 @@ $station_nice_name = $defaults['station_nice_name']; $join_url = $defaults['join_url']; $watch_url = $defaults['watch_url']; - + $donor_portal_url = trim($defaults['donor_portal_url']); echo "
MEMBER: " . $userinfo['first_name'] . " " . $userinfo['last_name'] . "
"; @@ -62,6 +62,7 @@ echo "

$station_nice_name Passport

"; if (!empty($watch_url)) {echo "";} + if (!empty($donor_portal_url)) {echo "";} } @@ -119,6 +120,7 @@ echo "
MEMBER: " . $userinfo['first_name'] . " " . $userinfo['last_name'] . "
"; echo "

STATUS: Expired

"; if (!empty($join_url)) {echo "

Your $station_nice_name Passport membership has expired. Please renew your $station_nice_name membership to continue enjoying $station_nice_name Passport content.

";} + if (!empty($donor_portal_url)) {echo "";} } From bdcabc70884ddce4959f8eb32d073a26d7dfd89f Mon Sep 17 00:00:00 2001 From: William Tam Date: Tue, 26 Sep 2023 11:50:16 -0400 Subject: [PATCH 3/4] Revert "v0.3.2.0 enable PKCE" This reverts commit 240ccda762734828c728085cf9b500f169da8bd5. --- assets/js/loginform_helpers.js | 50 ++------------------- classes/class-PBS-LAAS-client.php | 15 +++---- classes/class-pbs-passport-authenticate.php | 2 +- pbs-passport-authenticate.php | 2 +- templates/oauthcallback.php | 7 +-- 5 files changed, 12 insertions(+), 64 deletions(-) diff --git a/assets/js/loginform_helpers.js b/assets/js/loginform_helpers.js index 568e055..ce5e65c 100644 --- a/assets/js/loginform_helpers.js +++ b/assets/js/loginform_helpers.js @@ -1,40 +1,5 @@ jQuery(document).ready(function($) { - // Helper functions for PKCE // - - // Generate a secure random string using the browser crypto functions - function generateRandomString() { - var array = new Uint32Array(28); - window.crypto.getRandomValues(array); - return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join(''); - } - - // Calculate the SHA256 hash of the input text. - // Returns a promise that resolves to an ArrayBuffer - function sha256(plain) { - const encoder = new TextEncoder(); - const data = encoder.encode(plain); - return window.crypto.subtle.digest('SHA-256', data); - } - - // Base64-urlencodes the input string - function base64urlencode(str) { - // Convert the ArrayBuffer to string using Uint8 array to convert to what btoa accepts. - // btoa accepts chars only within ascii 0-255 and base64 encodes them. - // Then convert the base64 encoded to base64url encoded - // (replace + with -, replace / with _, trim trailing =) - return btoa(String.fromCharCode.apply(null, new Uint8Array(str))) - .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); - } - - // Return the base64-urlencoded sha256 hash for the PKCE challenge - async function pkceChallengeFromVerifier(v) { - hashed = await sha256(v); - return base64urlencode(hashed); - } - - - function setPBSOAuthRememberMe() { var rememberme = 'false'; if ( $("input[name='pbsoauth_rememberme']").prop("checked") ){ @@ -51,25 +16,16 @@ jQuery(document).ready(function($) { setPBSOAuthRememberMe(); }); - /* Various things to do when someone clicks on a login link */ - $(".passport-login-wrap li a").on("click", async function(event) { + /* set a loginprovider cookie when the person chooses one */ + $(".passport-login-wrap li a").click(function(event) { event.preventDefault(); - // set the loginprovider cookie var logintype = $(this).closest('li').attr("class"); if (logintype) { document.cookie='pbsoauth_loginprovider=' + logintype + ';domain=' + window.location.hostname + ';path=/'; window.dataLayer = window.dataLayer || []; dataLayer.push({ 'event': 'login', 'method': logintype }); } - // generate and append the codeverifier - var code_verifier = generateRandomString(); - document.cookie='pkce_code_verifier=' + code_verifier + ';domain=' + window.location.hostname + ';path=/'; - var code_challenge = await pkceChallengeFromVerifier(code_verifier); - var encoded_code_challenge = encodeURIComponent(code_challenge); - var appended_href = $(this).attr('href') + "&code_challenge=" + encoded_code_challenge + "&code_challenge_method=S256"; - - // send them along their way - window.location.href = appended_href; + window.location.href = $(this).attr('href'); }); }); diff --git a/classes/class-PBS-LAAS-client.php b/classes/class-PBS-LAAS-client.php index 5b385f9..eb710ba 100644 --- a/classes/class-PBS-LAAS-client.php +++ b/classes/class-PBS-LAAS-client.php @@ -33,7 +33,7 @@ Public Methods: -authenticate($code, $rememberme, $nonce='', $code_exchange='') +authenticate($code, $rememberme) Takes an oAuth grant code and use it to get access and refresh tokens from PBS then stores tokens and userinfo in encrypted session variables. the second arg determines if this info is stored in encryped cookies for longer term. @@ -130,13 +130,13 @@ private function build_curl_handle($url) { - public function authenticate($code= '', $rememberme='', $nonce='', $code_verifier= ''){ + public function authenticate($code= '', $rememberme='', $nonce=''){ $this->checknonce = $nonce; $this->rememberme = $rememberme; - $tokeninfo = $this->get_code_response($code, $code_verifier); + $tokeninfo = $this->get_code_response($code); if (! isset($tokeninfo["access_token"]) ) { $tokeninfo['messages'] = 'broke on code response'; return $tokeninfo; @@ -162,8 +162,8 @@ public function authenticate($code= '', $rememberme='', $nonce='', $code_verifie } - public function code_exchange($code= '', $code_verifier= ''){ - $tokeninfo = $this->get_code_response($code, $code_verifier); + public function code_exchange($code= ''){ + $tokeninfo = $this->get_code_response($code); if (! isset($tokeninfo["access_token"]) ) { $tokeninfo['messages'] = 'broke on code exchange'; return $tokeninfo; @@ -245,7 +245,7 @@ public function logout() { } - private function get_code_response($code='', $code_verifier=''){ + private function get_code_response($code=''){ $url = $this->oauthroot . 'token/'; $postfields = array( 'code' => $code, @@ -254,9 +254,6 @@ private function get_code_response($code='', $code_verifier=''){ 'client_secret' => $this->client_secret, 'grant_type' => 'authorization_code' ); - if (!empty($code_verifier)) { - $postfields['code_verifier'] = $code_verifier; - } $ch = $this->build_curl_handle($url); //construct the curl request diff --git a/classes/class-pbs-passport-authenticate.php b/classes/class-pbs-passport-authenticate.php index 469f095..1fafee8 100644 --- a/classes/class-pbs-passport-authenticate.php +++ b/classes/class-pbs-passport-authenticate.php @@ -23,7 +23,7 @@ public function __construct($file) { $this->assets_url = esc_url( trailingslashit( plugins_url( '/assets/', $file ) ) ); $this->token = 'pbs_passport_authenticate'; $this->defaults = get_option($this->token); - $this->version = '0.3.2.0'; + $this->version = '0.3.1.5'; // Load public-facing style sheet and JavaScript. add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); diff --git a/pbs-passport-authenticate.php b/pbs-passport-authenticate.php index 0fc354f..6ad6bd5 100644 --- a/pbs-passport-authenticate.php +++ b/pbs-passport-authenticate.php @@ -1,7 +1,7 @@ authenticate($code, $rememberme, $nonce, $code_verifier); + $userinfo = $laas_client->authenticate($code, $rememberme, $nonce); } // now we either have userinfo or null. From b409f5350e9c9e03cf2473a83e5e7121628c2dcf Mon Sep 17 00:00:00 2001 From: William Tam Date: Fri, 8 Dec 2023 12:42:03 -0500 Subject: [PATCH 4/4] donor portal link support --- assets/js/jquery.pids.js | 12 ++++++++++-- classes/class-pbs-passport-authenticate.php | 5 +++-- pbs-passport-authenticate.php | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/assets/js/jquery.pids.js b/assets/js/jquery.pids.js index cca045c..e10a077 100644 --- a/assets/js/jquery.pids.js +++ b/assets/js/jquery.pids.js @@ -8,6 +8,7 @@ jQuery(document).ready(function($) { var station_call_letters_lc = 'wnet'; var learnmorelink = '/passport/'; var vppalink = '/pbsoauth/vppa/'; + var portallink = ''; if (typeof pbs_passport_authenticate_args !== "undefined"){ authenticate_script = pbs_passport_authenticate_args.laas_authenticate_script; @@ -21,6 +22,9 @@ jQuery(document).ready(function($) { if (typeof pbs_passport_authenticate_args.vppalink !== "undefined"){ vppalink = pbs_passport_authenticate_args.vppalink; } + if (typeof pbs_passport_authenticate_args.portallink !== "undefined"){ + portallink = pbs_passport_authenticate_args.portallink; + } } /* in case the loginform url has no protocol */ @@ -197,8 +201,12 @@ jQuery(document).ready(function($) { if ($(".pbs_passport_authenticate div.messages-new")[0]){ // new version of messages with drop down menu... - if (user.thumbnail_URL) {thumbimage = "";} - welcomestring = '' + thumbimage + '' + user.first_name + ' '; + if (user.thumbnail_URL) {thumbimage = "";} + portal_list_entry = '
  • YOUR ACCOUNT
  • '; + if (portallink.length > 0) { + portal_list_entry= '
  • MEMBER PORTAL
  • '; + } + welcomestring = '' + thumbimage + '' + user.first_name + ' '; $('.pbs_passport_authenticate div.messages-new').html(welcomestring); if (typeof makeNavObserverPids == 'function') { diff --git a/classes/class-pbs-passport-authenticate.php b/classes/class-pbs-passport-authenticate.php index 4c5d2d5..213b7ba 100644 --- a/classes/class-pbs-passport-authenticate.php +++ b/classes/class-pbs-passport-authenticate.php @@ -23,7 +23,7 @@ public function __construct($file) { $this->assets_url = esc_url( trailingslashit( plugins_url( '/assets/', $file ) ) ); $this->token = 'pbs_passport_authenticate'; $this->defaults = get_option($this->token); - $this->version = '0.3.1.5'; + $this->version = '0.3.2.0'; // Load public-facing style sheet and JavaScript. add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); @@ -118,9 +118,10 @@ public function do_shortcode( $atts ) { $args['userinfolink'] = site_url('pbsoauth/userinfo/'); $args['vppalink'] = site_url('pbsoauth/vppa/'); $args['station_call_letters_lc'] = strtolower($defaults['station_call_letters']); + $args['portallink'] = !empty($defaults['donor_portal_url']) ? $defaults['donor_portal_url'] : ''; $json_args = json_encode($args); $button = '
    '; - $jsonblock = ''; + $jsonblock = ''; $style = ''; $return = ''; if ($render == 'all'){ diff --git a/pbs-passport-authenticate.php b/pbs-passport-authenticate.php index 6ad6bd5..037ad5a 100644 --- a/pbs-passport-authenticate.php +++ b/pbs-passport-authenticate.php @@ -1,7 +1,7 @@