@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

When logged-out users hit a "Login Required" dialog, try to choose a better "next" URI

Summary:
Ref T10004. After a user logs in, we send them to the "next" URI cookie if there is one, but currently don't always do a very good job of selecting a "next" URI, especially if they tried to do something with a dialog before being asked to log in.

In particular, if a logged-out user clicks an action like "Edit Blocking Tasks" on a Maniphest task, the default behavior is to send them to the standalone page for that dialog after they log in. This can be pretty confusing.

See T2691 and D6416 for earlier efforts here. At that time, we added a mechanism to //manually// override the default behavior, and fixed the most common links. This worked, but I'd like to fix the //default// beahvior so we don't need to remember to `setObjectURI()` correctly all over the place.

ApplicationEditor has also introduced new cases which are more difficult to get right. While we could get them right by using the override and being careful about things, this also motivates fixing the default behavior.

Finally, we have better tools for fixing the default behavior now than we did in 2013.

Instead of using manual overrides, have JS include an "X-Phabricator-Via" header in Ajax requests. This is basically like a referrer header, and will contain the page the user's browser is on.

In essentially every case, this should be a very good place (and often the best place) to send them after login. For all pages currently using `setObjectURI()`, it should produce the same behavior by default.

I'll remove the `setObjectURI()` mechanism in the next diff.

Test Plan: Clicked various workflow actions while logged out, saw "next" get set to a reasonable value, was redirected to a sensible, non-confusing page after login (the page with whatever button I clicked on it).

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10004

Differential Revision: https://secure.phabricator.com/D14804

+62 -46
+23 -23
resources/celerity/map.php
··· 8 8 return array( 9 9 'names' => array( 10 10 'core.pkg.css' => '8378907a', 11 - 'core.pkg.js' => 'c60f35d8', 11 + 'core.pkg.js' => '5058979d', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 13 'differential.pkg.css' => '2de124c9', 14 14 'differential.pkg.js' => '6223dd9d', ··· 227 227 'rsrc/externals/javelin/lib/JSON.js' => '69adf288', 228 228 'rsrc/externals/javelin/lib/Leader.js' => '331b1611', 229 229 'rsrc/externals/javelin/lib/Mask.js' => '8a41885b', 230 - 'rsrc/externals/javelin/lib/Quicksand.js' => '4cebc641', 230 + 'rsrc/externals/javelin/lib/Quicksand.js' => '6b8ef10b', 231 231 'rsrc/externals/javelin/lib/Request.js' => '94b750d2', 232 232 'rsrc/externals/javelin/lib/Resource.js' => '44959b73', 233 233 'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692', 234 234 'rsrc/externals/javelin/lib/Router.js' => '29274e2b', 235 235 'rsrc/externals/javelin/lib/Scrollbar.js' => '087e919c', 236 236 'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5', 237 - 'rsrc/externals/javelin/lib/URI.js' => '6eff08aa', 237 + 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 238 238 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', 239 239 'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4', 240 240 'rsrc/externals/javelin/lib/Workflow.js' => '5b2e3e2b', ··· 487 487 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 488 488 'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03', 489 489 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'eeaa9e5a', 490 - 'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593', 490 + 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', 491 491 'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45', 492 492 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 493 493 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', ··· 657 657 'javelin-behavior-project-create' => '065227cc', 658 658 'javelin-behavior-quicksand-blacklist' => '7927a7d3', 659 659 'javelin-behavior-recurring-edit' => '5f1c4d5f', 660 - 'javelin-behavior-refresh-csrf' => '7814b593', 660 + 'javelin-behavior-refresh-csrf' => 'ab2f381b', 661 661 'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf', 662 662 'javelin-behavior-releeph-request-state-change' => 'a0b57eb8', 663 663 'javelin-behavior-releeph-request-typeahead' => 'de2e896f', ··· 690 690 'javelin-leader' => '331b1611', 691 691 'javelin-magical-init' => '3010e992', 692 692 'javelin-mask' => '8a41885b', 693 - 'javelin-quicksand' => '4cebc641', 693 + 'javelin-quicksand' => '6b8ef10b', 694 694 'javelin-reactor' => '2b8de964', 695 695 'javelin-reactor-dom' => 'c90a04fc', 696 696 'javelin-reactor-node-calmer' => '76f4ebed', ··· 710 710 'javelin-typeahead-preloaded-source' => '54f314a0', 711 711 'javelin-typeahead-source' => '2818f5ce', 712 712 'javelin-typeahead-static-source' => '6c0e62fa', 713 - 'javelin-uri' => '6eff08aa', 713 + 'javelin-uri' => 'c989ade3', 714 714 'javelin-util' => '93cc50d6', 715 715 'javelin-vector' => '2caa8fb8', 716 716 'javelin-view' => '0f764c35', ··· 1130 1130 'javelin-request', 1131 1131 'javelin-util', 1132 1132 ), 1133 - '4cebc641' => array( 1134 - 'javelin-install', 1135 - ), 1136 1133 '4e3e79a6' => array( 1137 1134 'javelin-behavior', 1138 1135 'javelin-stratcom', ··· 1302 1299 '69adf288' => array( 1303 1300 'javelin-install', 1304 1301 ), 1302 + '6b8ef10b' => array( 1303 + 'javelin-install', 1304 + ), 1305 1305 '6c0e62fa' => array( 1306 1306 'javelin-install', 1307 1307 'javelin-typeahead-source', ··· 1329 1329 'phabricator-drag-and-drop-file-upload', 1330 1330 'phabricator-textareautils', 1331 1331 ), 1332 - '6eff08aa' => array( 1333 - 'javelin-install', 1334 - 'javelin-util', 1335 - 'javelin-stratcom', 1336 - ), 1337 1332 '70baed2f' => array( 1338 1333 'javelin-install', 1339 1334 'javelin-dom', ··· 1367 1362 'javelin-install', 1368 1363 'javelin-reactor', 1369 1364 'javelin-util', 1370 - ), 1371 - '7814b593' => array( 1372 - 'javelin-request', 1373 - 'javelin-behavior', 1374 - 'javelin-dom', 1375 - 'javelin-router', 1376 - 'javelin-util', 1377 - 'phabricator-busy', 1378 1365 ), 1379 1366 '782ab6e7' => array( 1380 1367 'javelin-behavior', ··· 1642 1629 'javelin-util', 1643 1630 'phabricator-prefab', 1644 1631 ), 1632 + 'ab2f381b' => array( 1633 + 'javelin-request', 1634 + 'javelin-behavior', 1635 + 'javelin-dom', 1636 + 'javelin-router', 1637 + 'javelin-util', 1638 + 'phabricator-busy', 1639 + ), 1645 1640 'ad10aeac' => array( 1646 1641 'javelin-install', 1647 1642 'javelin-util', ··· 1802 1797 'javelin-reactornode', 1803 1798 'javelin-install', 1804 1799 'javelin-util', 1800 + ), 1801 + 'c989ade3' => array( 1802 + 'javelin-install', 1803 + 'javelin-util', 1804 + 'javelin-stratcom', 1805 1805 ), 1806 1806 'ca3f91eb' => array( 1807 1807 'javelin-behavior',
+4
src/aphront/AphrontRequest.php
··· 236 236 return 'X-Phabricator-Csrf'; 237 237 } 238 238 239 + public static function getViaHeaderName() { 240 + return 'X-Phabricator-Via'; 241 + } 242 + 239 243 public function validateCSRF() { 240 244 $token_name = self::getCSRFTokenName(); 241 245 $token = $this->getStr($token_name);
+17 -7
src/applications/auth/controller/PhabricatorAuthStartController.php
··· 214 214 $request->getRequestURI()); 215 215 } 216 216 217 - $dialog = new AphrontDialogView(); 218 - $dialog->setUser($viewer); 219 - $dialog->setTitle(pht('Login Required')); 220 - $dialog->appendChild(pht('You must login to continue.')); 221 - $dialog->addSubmitButton(pht('Login')); 222 - $dialog->addCancelButton('/'); 217 + // Often, users end up here by clicking a disabled action link in the UI 218 + // (for example, they might click "Edit Blocking Tasks" on a Maniphest 219 + // task page). After they log in we want to send them back to that main 220 + // object page if we can, since it's confusing to end up on a standalone 221 + // page with only a dialog (particularly if that dialog is another error, 222 + // like a policy exception). 223 223 224 - return id(new AphrontDialogResponse())->setDialog($dialog); 224 + $via_header = AphrontRequest::getViaHeaderName(); 225 + $via_uri = AphrontRequest::getHTTPHeader($via_header); 226 + if (strlen($via_uri)) { 227 + PhabricatorCookies::setNextURICookie($request, $via_uri, $force = true); 228 + } 229 + 230 + return $this->newDialog() 231 + ->setTitle(pht('Login Required')) 232 + ->appendParagraph(pht('You must login to continue.')) 233 + ->addSubmitButton(pht('Login')) 234 + ->addCancelButton('/'); 225 235 } 226 236 227 237
+2 -1
src/view/page/PhabricatorStandardPageView.php
··· 261 261 'refresh-csrf', 262 262 array( 263 263 'tokenName' => AphrontRequest::getCSRFTokenName(), 264 - 'header' => AphrontRequest::getCSRFHeaderName(), 264 + 'header' => AphrontRequest::getCSRFHeaderName(), 265 + 'viaHeader' => AphrontRequest::getViaHeaderName(), 265 266 'current' => $current_token, 266 267 )); 267 268
+2 -14
webroot/rsrc/externals/javelin/lib/Quicksand.js
··· 50 50 JX.Stratcom.listen('history:change', null, self._onchange); 51 51 52 52 self._started = true; 53 - var path = self._getRelativeURI(window.location); 53 + var path = JX.$U(window.location).getRelativeURI(); 54 54 self._id = window.history.state || 0; 55 55 var id = self._id; 56 56 self._onpage = id; ··· 157 157 } 158 158 159 159 // Set up the new state and fire a request to fetch the page data. 160 - var path = self._getRelativeURI(uri); 160 + var path = JX.$U(uri).getRelativeURI(); 161 161 var id = ++self._id; 162 162 163 163 self._history.push({path: path, id: id}); ··· 293 293 // Redraw the page. 294 294 self._draw(false); 295 295 } 296 - }, 297 - 298 - 299 - /** 300 - * Get just the relative part of a URI, for History operations. 301 - */ 302 - _getRelativeURI: function(uri) { 303 - return JX.$U(uri) 304 - .setProtocol(null) 305 - .setPort(null) 306 - .setDomain(null) 307 - .toString(); 308 296 }, 309 297 310 298
+8
webroot/rsrc/externals/javelin/lib/URI.js
··· 178 178 return this._domain; 179 179 }, 180 180 181 + getRelativeURI: function() { 182 + return JX.$U(this.toString()) 183 + .setProtocol(null) 184 + .setPort(null) 185 + .setDomain(null) 186 + .toString(); 187 + }, 188 + 181 189 toString : function() { 182 190 if (__DEV__) { 183 191 if (this.getPath() && this.getPath().charAt(0) != '/') {
+6 -1
webroot/rsrc/js/core/behavior-refresh-csrf.js
··· 50 50 51 51 // Additionally, add the CSRF token as an HTTP header to every AJAX request. 52 52 JX.Request.listen('open', function(r) { 53 - r.getTransport().setRequestHeader(config.header, current_token); 53 + var via = JX.$U(window.location).getRelativeURI(); 54 + 55 + var xport = r.getTransport(); 56 + 57 + xport.setRequestHeader(config.header, current_token); 58 + xport.setRequestHeader(config.viaHeader, via); 54 59 }); 55 60 56 61 // Does this type of routable show the "Busy" spinner?