@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3/**
4 * Consolidates Phabricator application cookies, including registration
5 * and session management.
6 *
7 * @task clientid Client ID Cookie
8 * @task next Next URI Cookie
9 */
10final class PhabricatorCookies extends Phobject {
11
12 /**
13 * Stores the login username for password authentication. This is just a
14 * display value for convenience, used to prefill the login form. It is not
15 * authoritative.
16 */
17 const COOKIE_USERNAME = 'phusr';
18
19
20 /**
21 * Stores the user's current session ID. This is authoritative and establishes
22 * the user's identity.
23 */
24 const COOKIE_SESSION = 'phsid';
25
26
27 /**
28 * Stores a secret used during new account registration to prevent an attacker
29 * from tricking a victim into registering an account which is linked to
30 * credentials the attacker controls.
31 */
32 const COOKIE_REGISTRATION = 'phreg';
33
34
35 /**
36 * Stores a secret used during OAuth2 handshakes to prevent various attacks
37 * where an attacker hands a victim a URI corresponding to the middle of an
38 * OAuth2 workflow and we might otherwise do something sketchy. Particularly,
39 * this corresponds to the OAuth2 "code".
40 */
41 const COOKIE_CLIENTID = 'phcid';
42
43
44 /**
45 * Stores the URI to redirect the user to after login. This allows users to
46 * visit a path like `/feed/`, be prompted to login, and then be redirected
47 * back to `/feed/` after the workflow completes.
48 */
49 const COOKIE_NEXTURI = 'next_uri';
50
51
52 /**
53 * Stores a hint that the user should be moved directly into high security
54 * after upgrading a partial login session. This is used during password
55 * recovery to avoid a double-prompt.
56 */
57 const COOKIE_HISEC = 'jump_to_hisec';
58
59
60 /**
61 * Stores an invite code.
62 */
63 const COOKIE_INVITE = 'invite';
64
65
66 /**
67 * Stores a workflow completion across a redirect-after-POST following a
68 * form submission. This can be used to show "Changes Saved" messages.
69 */
70 const COOKIE_SUBMIT = 'phfrm';
71
72
73/* -( Client ID Cookie )--------------------------------------------------- */
74
75
76 /**
77 * Set the client ID cookie. This is a random cookie used like a CSRF value
78 * during authentication workflows.
79 *
80 * @param AphrontRequest $request Request to modify.
81 * @return void
82 * @task clientid
83 */
84 public static function setClientIDCookie(AphrontRequest $request) {
85
86 // NOTE: See T3471 for some discussion. Some browsers and browser extensions
87 // can make duplicate requests, so we overwrite this cookie only if it is
88 // not present in the request. The cookie lifetime is limited by making it
89 // temporary and clearing it when users log out.
90
91 $value = $request->getCookie(self::COOKIE_CLIENTID);
92 if (!phutil_nonempty_string($value)) {
93 $request->setTemporaryCookie(
94 self::COOKIE_CLIENTID,
95 Filesystem::readRandomCharacters(16));
96 }
97 }
98
99
100/* -( Next URI Cookie )---------------------------------------------------- */
101
102
103 /**
104 * Set the Next URI cookie. We only write the cookie if it wasn't recently
105 * written, to avoid writing over a real URI with a bunch of "humans.txt"
106 * stuff. See T3793 for discussion.
107 *
108 * @param AphrontRequest $request Request to write to.
109 * @param string $next_uri URI to write.
110 * @param bool $force (optional) Write this cookie even if we
111 * have a fresh cookie already.
112 * @return void
113 *
114 * @task next
115 */
116 public static function setNextURICookie(
117 AphrontRequest $request,
118 $next_uri,
119 $force = false) {
120
121 if (!$force) {
122 $cookie_value = $request->getCookie(self::COOKIE_NEXTURI);
123 list($set_at, $current_uri) = self::parseNextURICookie($cookie_value);
124
125 // If the cookie was set within the last 2 minutes, don't overwrite it.
126 // Primarily, this prevents browser requests for resources which do not
127 // exist (like "humans.txt" and various icons) from overwriting a normal
128 // URI like "/feed/".
129 if ($set_at > (time() - 120)) {
130 return;
131 }
132 }
133
134 $new_value = time().','.$next_uri;
135 $request->setTemporaryCookie(self::COOKIE_NEXTURI, $new_value);
136 }
137
138
139 /**
140 * Read the URI out of the Next URI cookie.
141 *
142 * @param AphrontRequest $request Request to examine.
143 * @return string|null Next URI cookie's URI value.
144 *
145 * @task next
146 */
147 public static function getNextURICookie(AphrontRequest $request) {
148 $cookie_value = $request->getCookie(self::COOKIE_NEXTURI);
149 list($set_at, $next_uri) = self::parseNextURICookie($cookie_value);
150
151 return $next_uri;
152 }
153
154
155 /**
156 * Parse a Next URI cookie into its components.
157 *
158 * @param string $cookie Raw cookie value.
159 * @return list<int,string>|null List of timestamp and URI, or null if the
160 * cookie is empty or null.
161 *
162 * @task next
163 */
164 private static function parseNextURICookie($cookie) {
165 // Old cookies look like: /uri
166 // New cookies look like: timestamp,/uri
167
168 if (!phutil_nonempty_string($cookie)) {
169 return null;
170 }
171
172 if (strpos($cookie, ',') !== false) {
173 list($timestamp, $uri) = explode(',', $cookie, 2);
174 return array((int)$timestamp, $uri);
175 }
176
177 return array(0, $cookie);
178 }
179
180}