PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
jetpack / jetpack_vendor / automattic / jetpack-comments / src / identity / checkpoint / class-passport.php

class-passport.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-beta, at jetpack_vendor/automattic/jetpack-comments/src/identity/checkpoint/class-passport.php

146 lines 4.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The passport: a first-party cookie that lets a commenter back in without the popup.
4 *
5 * @package automattic/jetpack-comments
6 */
7
8 namespace Automattic\Jetpack\Comments;
9
10 /**
11 * Signed with the site's own salt, so it is this site's and nobody else's.
12 */
13 class Passport {
14
15 const COOKIE = 'jetpack_comment_identity';
16 const DISPLAY_COOKIE = 'jetpack_comment_identity_display';
17 const FIELDS = array( 'site_commenter_id', 'provider', 'name', 'email', 'avatar', 'expires_at' );
18
19 /**
20 * Read the passport the browser sent, if it is intact and unexpired.
21 *
22 * @return array|null Keyed by FIELDS.
23 */
24 public static function read() {
25 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verified against its signature below.
26 $cookie = isset( $_COOKIE[ self::COOKIE ] ) ? wp_unslash( $_COOKIE[ self::COOKIE ] ) : '';
27
28 if ( ! is_string( $cookie ) || '' === $cookie || substr_count( $cookie, '.' ) !== 1 ) {
29 return null;
30 }
31
32 list( $encoded, $signature ) = explode( '.', $cookie, 2 );
33
34 if ( ! hash_equals( self::signature( $encoded ), $signature ) ) {
35 return null;
36 }
37
38 $payload = json_decode( (string) base64_decode( strtr( $encoded, '-_', '+/' ) ), true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- base64url is the cookie format.
39
40 if ( ! is_array( $payload ) || empty( $payload['site_commenter_id'] ) || empty( $payload['provider'] ) ) {
41 return null;
42 }
43
44 if ( (int) ( $payload['expires_at'] ?? 0 ) <= time() ) {
45 return null;
46 }
47
48 if ( (int) ( $payload['blog_id'] ?? 0 ) !== Checkpoint::blog_id() ) {
49 return null;
50 }
51
52 $identity = array();
53 foreach ( self::FIELDS as $field ) {
54 $identity[ $field ] = 'expires_at' === $field ? (int) $payload[ $field ] : (string) ( $payload[ $field ] ?? '' );
55 }
56
57 return $identity;
58 }
59
60 /**
61 * Hand the browser a passport.
62 *
63 * @param array $identity From Checkpoint::exchange().
64 * @return void
65 */
66 public static function issue( array $identity ) {
67 $expires = (int) $identity['expires_at'];
68
69 if ( $expires <= time() ) {
70 return;
71 }
72
73 $payload = array();
74
75 foreach ( self::FIELDS as $field ) {
76 $payload[ $field ] = 'expires_at' === $field ? (int) ( $identity[ $field ] ?? 0 ) : (string) ( $identity[ $field ] ?? '' );
77 }
78
79 $payload['blog_id'] = Checkpoint::blog_id();
80
81 $encoded = rtrim( strtr( base64_encode( (string) wp_json_encode( $payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) ), '+/', '-_' ), '=' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- base64url is the cookie format.
82
83 self::set_cookie( self::COOKIE, $encoded . '.' . self::signature( $encoded ), $expires, true );
84
85 // The display cookie is URL-encoded JSON the page's script decodes.
86 $shown = array();
87
88 foreach ( array( 'provider', 'name', 'avatar' ) as $field ) {
89 $shown[ $field ] = (string) ( $identity[ $field ] ?? '' );
90 }
91
92 $shown['blog_id'] = Checkpoint::blog_id();
93
94 self::set_cookie( self::DISPLAY_COOKIE, rawurlencode( (string) wp_json_encode( $shown, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) ), $expires, false );
95 }
96
97 /**
98 * Take the passport back.
99 *
100 * @return void
101 */
102 public static function revoke() {
103 unset( $_COOKIE[ self::COOKIE ], $_COOKIE[ self::DISPLAY_COOKIE ] );
104 self::set_cookie( self::COOKIE, '', time() - YEAR_IN_SECONDS, true );
105 self::set_cookie( self::DISPLAY_COOKIE, '', time() - YEAR_IN_SECONDS, false );
106 }
107
108 /**
109 * HMAC over the encoded payload, keyed with the site's auth salt.
110 *
111 * @param string $encoded The base64url payload.
112 * @return string
113 */
114 private static function signature( $encoded ) {
115 return hash_hmac( 'sha256', 'jetpack-comment-passport|' . $encoded, wp_salt( 'auth' ) );
116 }
117
118 /**
119 * Send a cookie. Raw, so the display value reaches the script exactly as encoded.
120 *
121 * @param string $name Cookie name.
122 * @param string $value Cookie value.
123 * @param int $expires Unix time.
124 * @param bool $httponly Whether to keep it from the page's script.
125 * @return void
126 */
127 private static function set_cookie( $name, $value, $expires, $httponly ) {
128 if ( headers_sent() ) {
129 return;
130 }
131
132 setrawcookie(
133 $name,
134 $value,
135 array(
136 'expires' => $expires,
137 'path' => COOKIEPATH,
138 'domain' => COOKIE_DOMAIN,
139 'secure' => is_ssl(),
140 'httponly' => $httponly,
141 'samesite' => 'Lax',
142 )
143 );
144 }
145 }
146