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

Modernize user activity logs (ApplicationSearch, policies)

Summary:
Ref T4398. Ref T4842. I want to let users review their own account activity, partly as a general security measure and partly to make some of the multi-factor stuff easier to build and debug.

To support this, implement modern policies and application search.

I also removed the "old" and "new" columns from this output, since they had limited utility and revealed email addresses to administrators for some actions. We don't let administrators access email addresses from other UIs, and the value of doing so here seems very small.

Test Plan: Used interface to issue a bunch of queries against user logs, got reasonable/expected results.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: keir, epriestley

Maniphest Tasks: T4842, T4398

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

+443 -203
+18 -10
resources/celerity/map.php
··· 7 7 return array( 8 8 'names' => 9 9 array( 10 - 'core.pkg.css' => '038433b1', 10 + 'core.pkg.css' => '97f7fd44', 11 11 'core.pkg.js' => '417722ff', 12 12 'darkconsole.pkg.js' => 'ca8671ce', 13 13 'differential.pkg.css' => '12c11318', ··· 25 25 'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d', 26 26 'rsrc/css/aphront/list-filter-view.css' => 'ef989c67', 27 27 'rsrc/css/aphront/multi-column.css' => '12f65921', 28 - 'rsrc/css/aphront/notification.css' => '6901121e', 28 + 'rsrc/css/aphront/notification.css' => 'ef2c9b34', 29 29 'rsrc/css/aphront/pager-view.css' => '2e3539af', 30 30 'rsrc/css/aphront/panel-view.css' => '5846dfa2', 31 31 'rsrc/css/aphront/phabricator-nav-view.css' => '80e60fc1', ··· 454 454 'rsrc/js/core/behavior-form.js' => 'a9aaba0c', 455 455 'rsrc/js/core/behavior-gesture.js' => 'fe2e0ba4', 456 456 'rsrc/js/core/behavior-global-drag-and-drop.js' => '8fd76bab', 457 + 'rsrc/js/core/behavior-high-security-warning.js' => '8fc1c918', 457 458 'rsrc/js/core/behavior-history-install.js' => '7ee2b591', 458 459 'rsrc/js/core/behavior-hovercard.js' => '9c808199', 459 460 'rsrc/js/core/behavior-keyboard-pager.js' => 'b657bdf8', ··· 567 568 'javelin-behavior-global-drag-and-drop' => '8fd76bab', 568 569 'javelin-behavior-harbormaster-reorder-steps' => '957a7fde', 569 570 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 571 + 'javelin-behavior-high-security-warning' => '8fc1c918', 570 572 'javelin-behavior-history-install' => '7ee2b591', 571 573 'javelin-behavior-icon-composer' => '8ef9ab58', 572 574 'javelin-behavior-konami' => '5bc2cb21', ··· 701 703 'phabricator-menu-item' => '0f386ef4', 702 704 'phabricator-nav-view-css' => '80e60fc1', 703 705 'phabricator-notification' => '0c6946e7', 704 - 'phabricator-notification-css' => '6901121e', 706 + 'phabricator-notification-css' => 'ef2c9b34', 705 707 'phabricator-notification-menu-css' => 'fc9a363c', 706 708 'phabricator-object-selector-css' => '029a133d', 707 709 'phabricator-phtize' => 'd254d646', ··· 1196 1198 2 => 'javelin-util', 1197 1199 3 => 'phabricator-shaped-request', 1198 1200 ), 1199 - '62e18640' => 1200 - array( 1201 - 0 => 'javelin-install', 1202 - 1 => 'javelin-util', 1203 - 2 => 'javelin-dom', 1204 - 3 => 'javelin-typeahead-normalizer', 1205 - ), 1206 1201 '6453c869' => 1207 1202 array( 1208 1203 0 => 'javelin-install', ··· 1235 1230 array( 1236 1231 0 => 'javelin-behavior', 1237 1232 1 => 'javelin-dom', 1233 + ), 1234 + '62e18640' => 1235 + array( 1236 + 0 => 'javelin-install', 1237 + 1 => 'javelin-util', 1238 + 2 => 'javelin-dom', 1239 + 3 => 'javelin-typeahead-normalizer', 1238 1240 ), 1239 1241 '75903ee1' => 1240 1242 array( ··· 1366 1368 0 => 'javelin-behavior', 1367 1369 1 => 'javelin-stratcom', 1368 1370 2 => 'javelin-dom', 1371 + ), 1372 + '8fc1c918' => 1373 + array( 1374 + 0 => 'javelin-behavior', 1375 + 1 => 'javelin-uri', 1376 + 2 => 'phabricator-notification', 1369 1377 ), 1370 1378 '8fd76bab' => 1371 1379 array(
+14 -2
src/__phutil_library_map__.php
··· 1816 1816 'PhabricatorPeopleHovercardEventListener' => 'applications/people/event/PhabricatorPeopleHovercardEventListener.php', 1817 1817 'PhabricatorPeopleLdapController' => 'applications/people/controller/PhabricatorPeopleLdapController.php', 1818 1818 'PhabricatorPeopleListController' => 'applications/people/controller/PhabricatorPeopleListController.php', 1819 + 'PhabricatorPeopleLogQuery' => 'applications/people/query/PhabricatorPeopleLogQuery.php', 1820 + 'PhabricatorPeopleLogSearchEngine' => 'applications/people/query/PhabricatorPeopleLogSearchEngine.php', 1819 1821 'PhabricatorPeopleLogsController' => 'applications/people/controller/PhabricatorPeopleLogsController.php', 1820 1822 'PhabricatorPeopleNewController' => 'applications/people/controller/PhabricatorPeopleNewController.php', 1821 1823 'PhabricatorPeoplePHIDTypeExternal' => 'applications/people/phid/PhabricatorPeoplePHIDTypeExternal.php', ··· 4651 4653 0 => 'PhabricatorPeopleController', 4652 4654 1 => 'PhabricatorApplicationSearchResultsControllerInterface', 4653 4655 ), 4654 - 'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController', 4656 + 'PhabricatorPeopleLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4657 + 'PhabricatorPeopleLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 4658 + 'PhabricatorPeopleLogsController' => 4659 + array( 4660 + 0 => 'PhabricatorPeopleController', 4661 + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', 4662 + ), 4655 4663 'PhabricatorPeopleNewController' => 'PhabricatorPeopleController', 4656 4664 'PhabricatorPeoplePHIDTypeExternal' => 'PhabricatorPHIDType', 4657 4665 'PhabricatorPeoplePHIDTypeUser' => 'PhabricatorPHIDType', ··· 5118 5126 'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase', 5119 5127 'PhabricatorUserEmail' => 'PhabricatorUserDAO', 5120 5128 'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase', 5121 - 'PhabricatorUserLog' => 'PhabricatorUserDAO', 5129 + 'PhabricatorUserLog' => 5130 + array( 5131 + 0 => 'PhabricatorUserDAO', 5132 + 1 => 'PhabricatorPolicyInterface', 5133 + ), 5122 5134 'PhabricatorUserPreferences' => 'PhabricatorUserDAO', 5123 5135 'PhabricatorUserProfile' => 'PhabricatorUserDAO', 5124 5136 'PhabricatorUserProfileEditor' => 'PhabricatorApplicationTransactionEditor',
+2 -1
src/applications/people/application/PhabricatorApplicationPeople.php
··· 40 40 return array( 41 41 '/people/' => array( 42 42 '(query/(?P<key>[^/]+)/)?' => 'PhabricatorPeopleListController', 43 - 'logs/' => 'PhabricatorPeopleLogsController', 43 + 'logs/(?:query/(?P<queryKey>[^/]+)/)?' 44 + => 'PhabricatorPeopleLogsController', 44 45 'approve/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleApproveController', 45 46 '(?P<via>disapprove)/(?P<id>[1-9]\d*)/' 46 47 => 'PhabricatorPeopleDisableController',
+58 -170
src/applications/people/controller/PhabricatorPeopleLogsController.php
··· 1 1 <?php 2 2 3 - final class PhabricatorPeopleLogsController 4 - extends PhabricatorPeopleController { 5 - 6 - public function processRequest() { 7 - $request = $this->getRequest(); 8 - $user = $request->getUser(); 9 - 10 - $filter_activity = $request->getStr('activity'); 11 - $filter_ip = $request->getStr('ip'); 12 - $filter_session = $request->getStr('session'); 13 - 14 - $filter_user = $request->getArr('user', array()); 15 - $filter_actor = $request->getArr('actor', array()); 16 - 17 - $user_value = array(); 18 - $actor_value = array(); 19 - 20 - $phids = array_merge($filter_user, $filter_actor); 21 - if ($phids) { 22 - $handles = $this->loadViewerHandles($phids); 23 - if ($filter_user) { 24 - $filter_user = reset($filter_user); 25 - $user_value = array($handles[$filter_user]); 26 - } 27 - 28 - if ($filter_actor) { 29 - $filter_actor = reset($filter_actor); 30 - $actor_value = array($handles[$filter_actor]); 31 - } 32 - } 33 - 34 - $form = new AphrontFormView(); 35 - $form 36 - ->setUser($user) 37 - ->appendChild( 38 - id(new AphrontFormTokenizerControl()) 39 - ->setLabel(pht('Filter Actor')) 40 - ->setName('actor') 41 - ->setLimit(1) 42 - ->setValue($actor_value) 43 - ->setDatasource('/typeahead/common/accounts/')) 44 - ->appendChild( 45 - id(new AphrontFormTokenizerControl()) 46 - ->setLabel(pht('Filter User')) 47 - ->setName('user') 48 - ->setLimit(1) 49 - ->setValue($user_value) 50 - ->setDatasource('/typeahead/common/accounts/')) 51 - ->appendChild( 52 - id(new AphrontFormSelectControl()) 53 - ->setLabel(pht('Show Activity')) 54 - ->setName('activity') 55 - ->setValue($filter_activity) 56 - ->setOptions( 57 - array( 58 - '' => pht('All Activity'), 59 - 'admin' => pht('Admin Activity'), 60 - ))) 61 - ->appendChild( 62 - id(new AphrontFormTextControl()) 63 - ->setLabel(pht('Filter IP')) 64 - ->setName('ip') 65 - ->setValue($filter_ip) 66 - ->setCaption( 67 - pht('Enter an IP (or IP prefix) to show only activity by that '. 68 - 'remote address.'))) 69 - ->appendChild( 70 - id(new AphrontFormTextControl()) 71 - ->setLabel(pht('Filter Session')) 72 - ->setName('session') 73 - ->setValue($filter_session)) 74 - ->appendChild( 75 - id(new AphrontFormSubmitControl()) 76 - ->setValue(pht('Filter Logs'))); 77 - 78 - $log_table = new PhabricatorUserLog(); 79 - $conn_r = $log_table->establishConnection('r'); 3 + final class PhabricatorPeopleLogsController extends PhabricatorPeopleController 4 + implements PhabricatorApplicationSearchResultsControllerInterface { 80 5 81 - $where_clause = array(); 82 - $where_clause[] = '1 = 1'; 83 - 84 - if ($filter_user) { 85 - $where_clause[] = qsprintf( 86 - $conn_r, 87 - 'userPHID = %s', 88 - $filter_user); 89 - } 90 - 91 - if ($filter_actor) { 92 - $where_clause[] = qsprintf( 93 - $conn_r, 94 - 'actorPHID = %s', 95 - $filter_actor); 96 - } 97 - 98 - if ($filter_activity == 'admin') { 99 - $where_clause[] = qsprintf( 100 - $conn_r, 101 - 'action NOT IN (%Ls)', 102 - array( 103 - PhabricatorUserLog::ACTION_LOGIN, 104 - PhabricatorUserLog::ACTION_LOGOUT, 105 - PhabricatorUserLog::ACTION_LOGIN_FAILURE, 106 - )); 107 - } 108 - 109 - if ($filter_ip) { 110 - $where_clause[] = qsprintf( 111 - $conn_r, 112 - 'remoteAddr LIKE %>', 113 - $filter_ip); 114 - } 6 + private $queryKey; 115 7 116 - if ($filter_session) { 117 - $where_clause[] = qsprintf( 118 - $conn_r, 119 - 'session = %s', 120 - $filter_session); 121 - } 8 + public function willProcessRequest(array $data) { 9 + $this->queryKey = idx($data, 'queryKey'); 10 + } 122 11 123 - $where_clause = '('.implode(') AND (', $where_clause).')'; 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $controller = id(new PhabricatorApplicationSearchController($request)) 15 + ->setQueryKey($this->queryKey) 16 + ->setSearchEngine(new PhabricatorPeopleLogSearchEngine()) 17 + ->setNavigation($this->buildSideNavView()); 124 18 125 - $pager = new AphrontPagerView(); 126 - $pager->setURI($request->getRequestURI(), 'page'); 127 - $pager->setOffset($request->getInt('page')); 128 - $pager->setPageSize(500); 19 + return $this->delegateToController($controller); 20 + } 129 21 130 - $logs = $log_table->loadAllWhere( 131 - '(%Q) ORDER BY dateCreated DESC LIMIT %d, %d', 132 - $where_clause, 133 - $pager->getOffset(), 134 - $pager->getPageSize() + 1); 22 + public function renderResultsList( 23 + array $logs, 24 + PhabricatorSavedQuery $query) { 25 + assert_instances_of($logs, 'PhabricatorUserLog'); 135 26 136 - $logs = $pager->sliceResults($logs); 27 + $request = $this->getRequest(); 28 + $viewer = $request->getUser(); 137 29 138 30 $phids = array(); 139 31 foreach ($logs as $log) { ··· 142 34 } 143 35 $phids = array_keys($phids); 144 36 $handles = $this->loadViewerHandles($phids); 37 + 38 + $action_map = PhabricatorUserLog::getActionTypeMap(); 145 39 146 40 $rows = array(); 147 41 foreach ($logs as $log) { 42 + 43 + $ip_href = $this->getApplicationURI( 44 + 'logs/?ip='.$log->getRemoteAddr()); 45 + 46 + $session_href = $this->getApplicationURI( 47 + 'logs/?sessions='.$log->getSession()); 48 + 49 + $action = $log->getAction(); 50 + $action_name = idx($action_map, $action, $action); 51 + 148 52 $rows[] = array( 149 - phabricator_date($log->getDateCreated(), $user), 150 - phabricator_time($log->getDateCreated(), $user), 151 - $log->getAction(), 152 - $log->getActorPHID() ? $handles[$log->getActorPHID()]->getName() : null, 53 + phabricator_date($log->getDateCreated(), $viewer), 54 + phabricator_time($log->getDateCreated(), $viewer), 55 + $action_name, 56 + $log->getActorPHID() 57 + ? $handles[$log->getActorPHID()]->getName() 58 + : null, 153 59 $handles[$log->getUserPHID()]->getName(), 154 - json_encode($log->getOldValue(), true), 155 - json_encode($log->getNewValue(), true), 156 60 phutil_tag( 157 61 'a', 158 62 array( 159 - 'href' => $request 160 - ->getRequestURI() 161 - ->alter('ip', $log->getRemoteAddr()), 63 + 'href' => $ip_href, 162 64 ), 163 65 $log->getRemoteAddr()), 164 66 phutil_tag( 165 67 'a', 166 68 array( 167 - 'href' => $request 168 - ->getRequestURI() 169 - ->alter('session', $log->getSession()), 69 + 'href' => $session_href, 170 70 ), 171 - $log->getSession()), 71 + substr($log->getSession(), 0, 6)), 172 72 ); 173 73 } 174 74 ··· 180 80 pht('Action'), 181 81 pht('Actor'), 182 82 pht('User'), 183 - pht('Old'), 184 - pht('New'), 185 83 pht('IP'), 186 84 pht('Session'), 187 85 )); ··· 189 87 array( 190 88 '', 191 89 'right', 90 + 'wide', 192 91 '', 193 92 '', 194 93 '', 195 - 'wrap', 196 - 'wrap', 197 - '', 198 - 'wide', 94 + 'n', 199 95 )); 200 96 201 - $panel = new AphrontPanelView(); 202 - $panel->setHeader(pht('Activity Logs')); 203 - $panel->setNoBackground(); 204 - $panel->appendChild($table); 205 - $panel->appendChild($pager); 97 + return id(new PHUIObjectBoxView()) 98 + ->setHeaderText(pht('User Activity Logs')) 99 + ->appendChild($table); 100 + } 101 + 102 + 103 + public function buildSideNavView() { 104 + $nav = new AphrontSideNavFilterView(); 105 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 206 106 207 - $filter = new AphrontListFilterView(); 208 - $filter->appendChild($form); 209 - $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); 210 - $crumbs->addTextCrumb(pht('Activity Logs'), '/people/logs/'); 107 + $viewer = $this->getRequest()->getUser(); 211 108 212 - $nav = $this->buildSideNavView(); 213 - $nav->selectFilter('logs'); 214 - $nav->appendChild( 215 - array( 216 - $filter, 217 - $panel, 218 - )); 219 - $nav->setCrumbs($crumbs); 109 + id(new PhabricatorPeopleLogSearchEngine()) 110 + ->setViewer($viewer) 111 + ->addNavigationItems($nav->getMenu()); 220 112 221 - return $this->buildApplicationPage( 222 - $nav, 223 - array( 224 - 'title' => pht('Activity Logs'), 225 - 'device' => true, 226 - )); 113 + return $nav; 227 114 } 115 + 228 116 }
+112
src/applications/people/query/PhabricatorPeopleLogQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorPeopleLogQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $actorPHIDs; 7 + private $userPHIDs; 8 + private $relatedPHIDs; 9 + private $sessionKeys; 10 + private $actions; 11 + private $remoteAddressPrefix; 12 + 13 + public function withActorPHIDs(array $actor_phids) { 14 + $this->actorPHIDs = $actor_phids; 15 + return $this; 16 + } 17 + 18 + public function withUserPHIDs(array $user_phids) { 19 + $this->userPHIDs = $user_phids; 20 + return $this; 21 + } 22 + 23 + public function withRelatedPHIDs(array $related_phids) { 24 + $this->relatedPHIDs = $related_phids; 25 + return $this; 26 + } 27 + 28 + public function withSessionKeys(array $session_keys) { 29 + $this->sessionKeys = $session_keys; 30 + return $this; 31 + } 32 + 33 + public function withActions(array $actions) { 34 + $this->actions = $actions; 35 + return $this; 36 + } 37 + 38 + public function withRemoteAddressPrefix($remote_address_prefix) { 39 + $this->remoteAddressPrefix = $remote_address_prefix; 40 + return $this; 41 + } 42 + 43 + public function loadPage() { 44 + $table = new PhabricatorUserLog(); 45 + $conn_r = $table->establishConnection('r'); 46 + 47 + $data = queryfx_all( 48 + $conn_r, 49 + 'SELECT * FROM %T %Q %Q %Q', 50 + $table->getTableName(), 51 + $this->buildWhereClause($conn_r), 52 + $this->buildOrderClause($conn_r), 53 + $this->buildLimitClause($conn_r)); 54 + 55 + return $table->loadAllFromArray($data); 56 + } 57 + 58 + private function buildWhereClause($conn_r) { 59 + $where = array(); 60 + 61 + if ($this->actorPHIDs !== null) { 62 + $where[] = qsprintf( 63 + $conn_r, 64 + 'actorPHID IN (%Ls)', 65 + $this->actorPHIDs); 66 + } 67 + 68 + if ($this->userPHIDs !== null) { 69 + $where[] = qsprintf( 70 + $conn_r, 71 + 'userPHID IN (%Ls)', 72 + $this->userPHIDs); 73 + } 74 + 75 + if ($this->relatedPHIDs !== null) { 76 + $where[] = qsprintf( 77 + $conn_r, 78 + 'actorPHID IN (%Ls) OR userPHID IN (%Ls)', 79 + $this->relatedPHIDs); 80 + } 81 + 82 + if ($this->sessionKeys !== null) { 83 + $where[] = qsprintf( 84 + $conn_r, 85 + 'session IN (%Ls)', 86 + $this->sessionKeys); 87 + } 88 + 89 + if ($this->actions !== null) { 90 + $where[] = qsprintf( 91 + $conn_r, 92 + 'action IN (%Ls)', 93 + $this->actions); 94 + } 95 + 96 + if ($this->remoteAddressPrefix !== null) { 97 + $where[] = qsprintf( 98 + $conn_r, 99 + 'remoteAddr LIKE %>', 100 + $this->remoteAddressPrefix); 101 + } 102 + 103 + $where[] = $this->buildPagingClause($conn_r); 104 + 105 + return $this->formatWhereClause($where); 106 + } 107 + 108 + public function getQueryApplicationClass() { 109 + return 'PhabricatorApplicationPeople'; 110 + } 111 + 112 + }
+157
src/applications/people/query/PhabricatorPeopleLogSearchEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorPeopleLogSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + public function getPageSize(PhabricatorSavedQuery $saved) { 7 + return 500; 8 + } 9 + 10 + public function buildSavedQueryFromRequest(AphrontRequest $request) { 11 + $saved = new PhabricatorSavedQuery(); 12 + 13 + $saved->setParameter( 14 + 'userPHIDs', 15 + $this->readUsersFromRequest($request, 'users')); 16 + 17 + $saved->setParameter( 18 + 'actorPHIDs', 19 + $this->readUsersFromRequest($request, 'actors')); 20 + 21 + $saved->setParameter( 22 + 'actions', 23 + $this->readListFromRequest($request, 'actions')); 24 + 25 + $saved->setParameter( 26 + 'ip', 27 + $request->getStr('ip')); 28 + 29 + $saved->setParameter( 30 + 'sessions', 31 + $this->readListFromRequest($request, 'sessions')); 32 + 33 + return $saved; 34 + } 35 + 36 + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 37 + $query = id(new PhabricatorPeopleLogQuery()); 38 + 39 + $actor_phids = $saved->getParameter('actorPHIDs', array()); 40 + if ($actor_phids) { 41 + $query->withActorPHIDs($actor_phids); 42 + } 43 + 44 + $user_phids = $saved->getParameter('userPHIDs', array()); 45 + if ($user_phids) { 46 + $query->withUserPHIDs($user_phids); 47 + } 48 + 49 + $actions = $saved->getParameter('actions', array()); 50 + if ($actions) { 51 + $query->withActions($actions); 52 + } 53 + 54 + $remote_prefix = $saved->getParameter('ip'); 55 + if (strlen($remote_prefix)) { 56 + $query->withRemoteAddressprefix($remote_prefix); 57 + } 58 + 59 + $sessions = $saved->getParameter('sessions', array()); 60 + if ($sessions) { 61 + $query->withSessionKeys($sessions); 62 + } 63 + 64 + return $query; 65 + } 66 + 67 + public function buildSearchForm( 68 + AphrontFormView $form, 69 + PhabricatorSavedQuery $saved) { 70 + 71 + $actor_phids = $saved->getParameter('actorPHIDs', array()); 72 + $user_phids = $saved->getParameter('userPHIDs', array()); 73 + 74 + $all_phids = array_merge( 75 + $actor_phids, 76 + $user_phids); 77 + 78 + if ($all_phids) { 79 + $handles = id(new PhabricatorHandleQuery()) 80 + ->setViewer($this->requireViewer()) 81 + ->withPHIDs($all_phids) 82 + ->execute(); 83 + } else { 84 + $handles = array(); 85 + } 86 + 87 + $actor_handles = array_select_keys($handles, $actor_phids); 88 + $user_handles = array_select_keys($handles, $user_phids); 89 + 90 + $actions = $saved->getParameter('actions', array()); 91 + $remote_prefix = $saved->getParameter('ip'); 92 + $sessions = $saved->getParameter('sessions', array()); 93 + 94 + $actions = array_fuse($actions); 95 + $action_control = id(new AphrontFormCheckboxControl()) 96 + ->setLabel(pht('Actions')); 97 + $action_types = PhabricatorUserLog::getActionTypeMap(); 98 + foreach ($action_types as $type => $label) { 99 + $action_control->addCheckbox( 100 + 'actions[]', 101 + $type, 102 + $label, 103 + isset($actions[$label])); 104 + } 105 + 106 + $form 107 + ->appendChild( 108 + id(new AphrontFormTokenizerControl()) 109 + ->setDatasource('/typeahead/common/accounts/') 110 + ->setName('actors') 111 + ->setLabel(pht('Actors')) 112 + ->setValue($actor_handles)) 113 + ->appendChild( 114 + id(new AphrontFormTokenizerControl()) 115 + ->setDatasource('/typeahead/common/accounts/') 116 + ->setName('users') 117 + ->setLabel(pht('Users')) 118 + ->setValue($user_handles)) 119 + ->appendChild($action_control) 120 + ->appendChild( 121 + id(new AphrontFormTextControl()) 122 + ->setLabel(pht('Filter IP')) 123 + ->setName('ip') 124 + ->setValue($remote_prefix)) 125 + ->appendChild( 126 + id(new AphrontFormTextControl()) 127 + ->setLabel(pht('Sessions')) 128 + ->setName('sessions') 129 + ->setValue(implode(', ', $sessions))); 130 + 131 + } 132 + 133 + protected function getURI($path) { 134 + return '/people/logs/'.$path; 135 + } 136 + 137 + public function getBuiltinQueryNames() { 138 + $names = array( 139 + 'all' => pht('All'), 140 + ); 141 + 142 + return $names; 143 + } 144 + 145 + public function buildSavedQueryFromBuiltin($query_key) { 146 + $query = $this->newSavedQuery(); 147 + $query->setQueryKey($query_key); 148 + 149 + switch ($query_key) { 150 + case 'all': 151 + return $query; 152 + } 153 + 154 + return parent::buildSavedQueryFromBuiltin($query_key); 155 + } 156 + 157 + }
+82 -20
src/applications/people/storage/PhabricatorUserLog.php
··· 1 1 <?php 2 2 3 - final class PhabricatorUserLog extends PhabricatorUserDAO { 3 + final class PhabricatorUserLog extends PhabricatorUserDAO 4 + implements PhabricatorPolicyInterface { 4 5 5 6 const ACTION_LOGIN = 'login'; 6 7 const ACTION_LOGOUT = 'logout'; ··· 35 36 protected $remoteAddr; 36 37 protected $session; 37 38 39 + public static function getActionTypeMap() { 40 + return array( 41 + self::ACTION_LOGIN => pht('Login'), 42 + self::ACTION_LOGIN_FAILURE => pht('Login Failure'), 43 + self::ACTION_LOGOUT => pht('Logout'), 44 + self::ACTION_RESET_PASSWORD => pht('Reset Password'), 45 + self::ACTION_CREATE => pht('Create Account'), 46 + self::ACTION_EDIT => pht('Edit Account'), 47 + self::ACTION_ADMIN => pht('Add/Remove Administrator'), 48 + self::ACTION_SYSTEM_AGENT => pht('Add/Remove System Agent'), 49 + self::ACTION_DISABLE => pht('Enable/Disable'), 50 + self::ACTION_APPROVE => pht('Approve Registration'), 51 + self::ACTION_DELETE => pht('Delete User'), 52 + self::ACTION_CONDUIT_CERTIFICATE 53 + => pht('Conduit: Read Certificate'), 54 + self::ACTION_CONDUIT_CERTIFICATE_FAILURE 55 + => pht('Conduit: Read Certificate Failure'), 56 + self::ACTION_EMAIL_PRIMARY => pht('Email: Change Primary'), 57 + self::ACTION_EMAIL_ADD => pht('Email: Add Address'), 58 + self::ACTION_EMAIL_REMOVE => pht('Email: Remove Address'), 59 + self::ACTION_CHANGE_PASSWORD => pht('Change Password'), 60 + self::ACTION_CHANGE_USERNAME => pht('Change Username'), 61 + ); 62 + } 63 + 64 + 38 65 public static function initializeNewLog( 39 66 PhabricatorUser $actor = null, 40 67 $object_phid, ··· 44 71 45 72 if ($actor) { 46 73 $log->setActorPHID($actor->getPHID()); 74 + if ($actor->hasSession()) { 75 + $session = $actor->getSession(); 76 + 77 + // NOTE: This is a hash of the real session value, so it's safe to 78 + // store it directly in the logs. 79 + $log->setSession($session->getSessionKey()); 80 + } 47 81 } 48 82 49 83 $log->setUserPHID((string)$object_phid); 50 84 $log->setAction($action); 85 + 86 + $log->remoteAddr = idx($_SERVER, 'REMOTE_ADDR', ''); 51 87 52 88 return $log; 53 89 } ··· 62 98 } 63 99 64 100 public function save() { 65 - if (!$this->remoteAddr) { 66 - $this->remoteAddr = idx($_SERVER, 'REMOTE_ADDR', ''); 67 - } 68 - if (!$this->session) { 69 - // TODO: This is not correct if there's a cookie prefix. This object 70 - // should take an AphrontRequest. 71 - // TODO: Maybe record session kind, or drop this for anonymous sessions? 72 - $this->setSession(idx($_COOKIE, PhabricatorCookies::COOKIE_SESSION)); 73 - } 74 101 $this->details['host'] = php_uname('n'); 75 102 $this->details['user_agent'] = AphrontRequest::getHTTPHeader('User-Agent'); 76 103 77 104 return parent::save(); 78 105 } 79 106 80 - public function setSession($session) { 81 - // Store the hash of the session, not the actual session key, so that 82 - // seeing the logs doesn't compromise all the sessions which appear in 83 - // them. This just prevents casual leaks, like in a screenshot. 84 - if (strlen($session)) { 85 - $this->session = PhabricatorHash::digest($session); 86 - } 87 - return $this; 88 - } 89 - 90 107 public function getConfiguration() { 91 108 return array( 92 109 self::CONFIG_SERIALIZATION => array( ··· 95 112 'details' => self::SERIALIZATION_JSON, 96 113 ), 97 114 ) + parent::getConfiguration(); 115 + } 116 + 117 + 118 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 119 + 120 + 121 + public function getCapabilities() { 122 + return array( 123 + PhabricatorPolicyCapability::CAN_VIEW, 124 + ); 125 + } 126 + 127 + public function getPolicy($capability) { 128 + switch ($capability) { 129 + case PhabricatorPolicyCapability::CAN_VIEW: 130 + return PhabricatorPolicies::POLICY_NOONE; 131 + } 132 + } 133 + 134 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 135 + if ($viewer->getIsAdmin()) { 136 + return true; 137 + } 138 + 139 + $viewer_phid = $viewer->getPHID(); 140 + if ($viewer_phid) { 141 + $user_phid = $this->getUserPHID(); 142 + if ($viewer_phid == $user_phid) { 143 + return true; 144 + } 145 + 146 + $actor_phid = $this->getActorPHID(); 147 + if ($viewer_phid == $actor_phid) { 148 + return true; 149 + } 150 + } 151 + 152 + return false; 153 + } 154 + 155 + public function describeAutomaticCapability($capability) { 156 + return array( 157 + pht('Users can view their activity and activity that affects them.'), 158 + pht('Administrators can always view all activity.'), 159 + ); 98 160 } 99 161 100 162 }