diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ return array( 'names' => array( - 'core.pkg.css' => '038433b1', + 'core.pkg.css' => '97f7fd44', 'core.pkg.js' => '417722ff', 'darkconsole.pkg.js' => 'ca8671ce', 'differential.pkg.css' => '12c11318', @@ -25,7 +25,7 @@ 'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d', 'rsrc/css/aphront/list-filter-view.css' => 'ef989c67', 'rsrc/css/aphront/multi-column.css' => '12f65921', - 'rsrc/css/aphront/notification.css' => '6901121e', + 'rsrc/css/aphront/notification.css' => 'ef2c9b34', 'rsrc/css/aphront/pager-view.css' => '2e3539af', 'rsrc/css/aphront/panel-view.css' => '5846dfa2', 'rsrc/css/aphront/phabricator-nav-view.css' => '80e60fc1', @@ -454,6 +454,7 @@ 'rsrc/js/core/behavior-form.js' => 'a9aaba0c', 'rsrc/js/core/behavior-gesture.js' => 'fe2e0ba4', 'rsrc/js/core/behavior-global-drag-and-drop.js' => '8fd76bab', + 'rsrc/js/core/behavior-high-security-warning.js' => '8fc1c918', 'rsrc/js/core/behavior-history-install.js' => '7ee2b591', 'rsrc/js/core/behavior-hovercard.js' => '9c808199', 'rsrc/js/core/behavior-keyboard-pager.js' => 'b657bdf8', @@ -567,6 +568,7 @@ 'javelin-behavior-global-drag-and-drop' => '8fd76bab', 'javelin-behavior-harbormaster-reorder-steps' => '957a7fde', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', + 'javelin-behavior-high-security-warning' => '8fc1c918', 'javelin-behavior-history-install' => '7ee2b591', 'javelin-behavior-icon-composer' => '8ef9ab58', 'javelin-behavior-konami' => '5bc2cb21', @@ -701,7 +703,7 @@ 'phabricator-menu-item' => '0f386ef4', 'phabricator-nav-view-css' => '80e60fc1', 'phabricator-notification' => '0c6946e7', - 'phabricator-notification-css' => '6901121e', + 'phabricator-notification-css' => 'ef2c9b34', 'phabricator-notification-menu-css' => 'fc9a363c', 'phabricator-object-selector-css' => '029a133d', 'phabricator-phtize' => 'd254d646', @@ -1196,13 +1198,6 @@ 2 => 'javelin-util', 3 => 'phabricator-shaped-request', ), - '62e18640' => - array( - 0 => 'javelin-install', - 1 => 'javelin-util', - 2 => 'javelin-dom', - 3 => 'javelin-typeahead-normalizer', - ), '6453c869' => array( 0 => 'javelin-install', @@ -1236,6 +1231,13 @@ 0 => 'javelin-behavior', 1 => 'javelin-dom', ), + '62e18640' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-dom', + 3 => 'javelin-typeahead-normalizer', + ), '75903ee1' => array( 0 => 'javelin-behavior', @@ -1367,6 +1369,12 @@ 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), + '8fc1c918' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-uri', + 2 => 'phabricator-notification', + ), '8fd76bab' => array( 0 => 'javelin-behavior', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1816,6 +1816,8 @@ 'PhabricatorPeopleHovercardEventListener' => 'applications/people/event/PhabricatorPeopleHovercardEventListener.php', 'PhabricatorPeopleLdapController' => 'applications/people/controller/PhabricatorPeopleLdapController.php', 'PhabricatorPeopleListController' => 'applications/people/controller/PhabricatorPeopleListController.php', + 'PhabricatorPeopleLogQuery' => 'applications/people/query/PhabricatorPeopleLogQuery.php', + 'PhabricatorPeopleLogSearchEngine' => 'applications/people/query/PhabricatorPeopleLogSearchEngine.php', 'PhabricatorPeopleLogsController' => 'applications/people/controller/PhabricatorPeopleLogsController.php', 'PhabricatorPeopleNewController' => 'applications/people/controller/PhabricatorPeopleNewController.php', 'PhabricatorPeoplePHIDTypeExternal' => 'applications/people/phid/PhabricatorPeoplePHIDTypeExternal.php', @@ -4651,7 +4653,13 @@ 0 => 'PhabricatorPeopleController', 1 => 'PhabricatorApplicationSearchResultsControllerInterface', ), - 'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController', + 'PhabricatorPeopleLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorPeopleLogSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorPeopleLogsController' => + array( + 0 => 'PhabricatorPeopleController', + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', + ), 'PhabricatorPeopleNewController' => 'PhabricatorPeopleController', 'PhabricatorPeoplePHIDTypeExternal' => 'PhabricatorPHIDType', 'PhabricatorPeoplePHIDTypeUser' => 'PhabricatorPHIDType', @@ -5118,7 +5126,11 @@ 'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorUserEmail' => 'PhabricatorUserDAO', 'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase', - 'PhabricatorUserLog' => 'PhabricatorUserDAO', + 'PhabricatorUserLog' => + array( + 0 => 'PhabricatorUserDAO', + 1 => 'PhabricatorPolicyInterface', + ), 'PhabricatorUserPreferences' => 'PhabricatorUserDAO', 'PhabricatorUserProfile' => 'PhabricatorUserDAO', 'PhabricatorUserProfileEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/people/application/PhabricatorApplicationPeople.php b/src/applications/people/application/PhabricatorApplicationPeople.php --- a/src/applications/people/application/PhabricatorApplicationPeople.php +++ b/src/applications/people/application/PhabricatorApplicationPeople.php @@ -40,7 +40,8 @@ return array( '/people/' => array( '(query/(?P[^/]+)/)?' => 'PhabricatorPeopleListController', - 'logs/' => 'PhabricatorPeopleLogsController', + 'logs/(?:query/(?P[^/]+)/)?' + => 'PhabricatorPeopleLogsController', 'approve/(?P[1-9]\d*)/' => 'PhabricatorPeopleApproveController', '(?Pdisapprove)/(?P[1-9]\d*)/' => 'PhabricatorPeopleDisableController', diff --git a/src/applications/people/controller/PhabricatorPeopleLogsController.php b/src/applications/people/controller/PhabricatorPeopleLogsController.php --- a/src/applications/people/controller/PhabricatorPeopleLogsController.php +++ b/src/applications/people/controller/PhabricatorPeopleLogsController.php @@ -1,139 +1,31 @@ getRequest(); - $user = $request->getUser(); - - $filter_activity = $request->getStr('activity'); - $filter_ip = $request->getStr('ip'); - $filter_session = $request->getStr('session'); - - $filter_user = $request->getArr('user', array()); - $filter_actor = $request->getArr('actor', array()); - - $user_value = array(); - $actor_value = array(); - - $phids = array_merge($filter_user, $filter_actor); - if ($phids) { - $handles = $this->loadViewerHandles($phids); - if ($filter_user) { - $filter_user = reset($filter_user); - $user_value = array($handles[$filter_user]); - } - - if ($filter_actor) { - $filter_actor = reset($filter_actor); - $actor_value = array($handles[$filter_actor]); - } - } - - $form = new AphrontFormView(); - $form - ->setUser($user) - ->appendChild( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Filter Actor')) - ->setName('actor') - ->setLimit(1) - ->setValue($actor_value) - ->setDatasource('/typeahead/common/accounts/')) - ->appendChild( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Filter User')) - ->setName('user') - ->setLimit(1) - ->setValue($user_value) - ->setDatasource('/typeahead/common/accounts/')) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Show Activity')) - ->setName('activity') - ->setValue($filter_activity) - ->setOptions( - array( - '' => pht('All Activity'), - 'admin' => pht('Admin Activity'), - ))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Filter IP')) - ->setName('ip') - ->setValue($filter_ip) - ->setCaption( - pht('Enter an IP (or IP prefix) to show only activity by that '. - 'remote address.'))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Filter Session')) - ->setName('session') - ->setValue($filter_session)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Filter Logs'))); - - $log_table = new PhabricatorUserLog(); - $conn_r = $log_table->establishConnection('r'); - - $where_clause = array(); - $where_clause[] = '1 = 1'; - - if ($filter_user) { - $where_clause[] = qsprintf( - $conn_r, - 'userPHID = %s', - $filter_user); - } - - if ($filter_actor) { - $where_clause[] = qsprintf( - $conn_r, - 'actorPHID = %s', - $filter_actor); - } - - if ($filter_activity == 'admin') { - $where_clause[] = qsprintf( - $conn_r, - 'action NOT IN (%Ls)', - array( - PhabricatorUserLog::ACTION_LOGIN, - PhabricatorUserLog::ACTION_LOGOUT, - PhabricatorUserLog::ACTION_LOGIN_FAILURE, - )); - } + private $queryKey; - if ($filter_ip) { - $where_clause[] = qsprintf( - $conn_r, - 'remoteAddr LIKE %>', - $filter_ip); - } - - if ($filter_session) { - $where_clause[] = qsprintf( - $conn_r, - 'session = %s', - $filter_session); - } + public function willProcessRequest(array $data) { + $this->queryKey = idx($data, 'queryKey'); + } - $where_clause = '('.implode(') AND (', $where_clause).')'; + public function processRequest() { + $request = $this->getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($this->queryKey) + ->setSearchEngine(new PhabricatorPeopleLogSearchEngine()) + ->setNavigation($this->buildSideNavView()); - $pager = new AphrontPagerView(); - $pager->setURI($request->getRequestURI(), 'page'); - $pager->setOffset($request->getInt('page')); - $pager->setPageSize(500); + return $this->delegateToController($controller); + } - $logs = $log_table->loadAllWhere( - '(%Q) ORDER BY dateCreated DESC LIMIT %d, %d', - $where_clause, - $pager->getOffset(), - $pager->getPageSize() + 1); + public function renderResultsList( + array $logs, + PhabricatorSavedQuery $query) { + assert_instances_of($logs, 'PhabricatorUserLog'); - $logs = $pager->sliceResults($logs); + $request = $this->getRequest(); + $viewer = $request->getUser(); $phids = array(); foreach ($logs as $log) { @@ -143,32 +35,40 @@ $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); + $action_map = PhabricatorUserLog::getActionTypeMap(); + $rows = array(); foreach ($logs as $log) { + + $ip_href = $this->getApplicationURI( + 'logs/?ip='.$log->getRemoteAddr()); + + $session_href = $this->getApplicationURI( + 'logs/?sessions='.$log->getSession()); + + $action = $log->getAction(); + $action_name = idx($action_map, $action, $action); + $rows[] = array( - phabricator_date($log->getDateCreated(), $user), - phabricator_time($log->getDateCreated(), $user), - $log->getAction(), - $log->getActorPHID() ? $handles[$log->getActorPHID()]->getName() : null, + phabricator_date($log->getDateCreated(), $viewer), + phabricator_time($log->getDateCreated(), $viewer), + $action_name, + $log->getActorPHID() + ? $handles[$log->getActorPHID()]->getName() + : null, $handles[$log->getUserPHID()]->getName(), - json_encode($log->getOldValue(), true), - json_encode($log->getNewValue(), true), phutil_tag( 'a', array( - 'href' => $request - ->getRequestURI() - ->alter('ip', $log->getRemoteAddr()), + 'href' => $ip_href, ), $log->getRemoteAddr()), phutil_tag( 'a', array( - 'href' => $request - ->getRequestURI() - ->alter('session', $log->getSession()), + 'href' => $session_href, ), - $log->getSession()), + substr($log->getSession(), 0, 6)), ); } @@ -180,8 +80,6 @@ pht('Action'), pht('Actor'), pht('User'), - pht('Old'), - pht('New'), pht('IP'), pht('Session'), )); @@ -189,40 +87,30 @@ array( '', 'right', + 'wide', '', '', '', - 'wrap', - 'wrap', - '', - 'wide', + 'n', )); - $panel = new AphrontPanelView(); - $panel->setHeader(pht('Activity Logs')); - $panel->setNoBackground(); - $panel->appendChild($table); - $panel->appendChild($pager); + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('User Activity Logs')) + ->appendChild($table); + } - $filter = new AphrontListFilterView(); - $filter->appendChild($form); - $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); - $crumbs->addTextCrumb(pht('Activity Logs'), '/people/logs/'); - $nav = $this->buildSideNavView(); - $nav->selectFilter('logs'); - $nav->appendChild( - array( - $filter, - $panel, - )); - $nav->setCrumbs($crumbs); + public function buildSideNavView() { + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('Activity Logs'), - 'device' => true, - )); + $viewer = $this->getRequest()->getUser(); + + id(new PhabricatorPeopleLogSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); + + return $nav; } + } diff --git a/src/applications/people/query/PhabricatorPeopleLogQuery.php b/src/applications/people/query/PhabricatorPeopleLogQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/people/query/PhabricatorPeopleLogQuery.php @@ -0,0 +1,112 @@ +actorPHIDs = $actor_phids; + return $this; + } + + public function withUserPHIDs(array $user_phids) { + $this->userPHIDs = $user_phids; + return $this; + } + + public function withRelatedPHIDs(array $related_phids) { + $this->relatedPHIDs = $related_phids; + return $this; + } + + public function withSessionKeys(array $session_keys) { + $this->sessionKeys = $session_keys; + return $this; + } + + public function withActions(array $actions) { + $this->actions = $actions; + return $this; + } + + public function withRemoteAddressPrefix($remote_address_prefix) { + $this->remoteAddressPrefix = $remote_address_prefix; + return $this; + } + + public function loadPage() { + $table = new PhabricatorUserLog(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + private function buildWhereClause($conn_r) { + $where = array(); + + if ($this->actorPHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'actorPHID IN (%Ls)', + $this->actorPHIDs); + } + + if ($this->userPHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'userPHID IN (%Ls)', + $this->userPHIDs); + } + + if ($this->relatedPHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'actorPHID IN (%Ls) OR userPHID IN (%Ls)', + $this->relatedPHIDs); + } + + if ($this->sessionKeys !== null) { + $where[] = qsprintf( + $conn_r, + 'session IN (%Ls)', + $this->sessionKeys); + } + + if ($this->actions !== null) { + $where[] = qsprintf( + $conn_r, + 'action IN (%Ls)', + $this->actions); + } + + if ($this->remoteAddressPrefix !== null) { + $where[] = qsprintf( + $conn_r, + 'remoteAddr LIKE %>', + $this->remoteAddressPrefix); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPeople'; + } + +} diff --git a/src/applications/people/query/PhabricatorPeopleLogSearchEngine.php b/src/applications/people/query/PhabricatorPeopleLogSearchEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/people/query/PhabricatorPeopleLogSearchEngine.php @@ -0,0 +1,157 @@ +setParameter( + 'userPHIDs', + $this->readUsersFromRequest($request, 'users')); + + $saved->setParameter( + 'actorPHIDs', + $this->readUsersFromRequest($request, 'actors')); + + $saved->setParameter( + 'actions', + $this->readListFromRequest($request, 'actions')); + + $saved->setParameter( + 'ip', + $request->getStr('ip')); + + $saved->setParameter( + 'sessions', + $this->readListFromRequest($request, 'sessions')); + + return $saved; + } + + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + $query = id(new PhabricatorPeopleLogQuery()); + + $actor_phids = $saved->getParameter('actorPHIDs', array()); + if ($actor_phids) { + $query->withActorPHIDs($actor_phids); + } + + $user_phids = $saved->getParameter('userPHIDs', array()); + if ($user_phids) { + $query->withUserPHIDs($user_phids); + } + + $actions = $saved->getParameter('actions', array()); + if ($actions) { + $query->withActions($actions); + } + + $remote_prefix = $saved->getParameter('ip'); + if (strlen($remote_prefix)) { + $query->withRemoteAddressprefix($remote_prefix); + } + + $sessions = $saved->getParameter('sessions', array()); + if ($sessions) { + $query->withSessionKeys($sessions); + } + + return $query; + } + + public function buildSearchForm( + AphrontFormView $form, + PhabricatorSavedQuery $saved) { + + $actor_phids = $saved->getParameter('actorPHIDs', array()); + $user_phids = $saved->getParameter('userPHIDs', array()); + + $all_phids = array_merge( + $actor_phids, + $user_phids); + + if ($all_phids) { + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($this->requireViewer()) + ->withPHIDs($all_phids) + ->execute(); + } else { + $handles = array(); + } + + $actor_handles = array_select_keys($handles, $actor_phids); + $user_handles = array_select_keys($handles, $user_phids); + + $actions = $saved->getParameter('actions', array()); + $remote_prefix = $saved->getParameter('ip'); + $sessions = $saved->getParameter('sessions', array()); + + $actions = array_fuse($actions); + $action_control = id(new AphrontFormCheckboxControl()) + ->setLabel(pht('Actions')); + $action_types = PhabricatorUserLog::getActionTypeMap(); + foreach ($action_types as $type => $label) { + $action_control->addCheckbox( + 'actions[]', + $type, + $label, + isset($actions[$label])); + } + + $form + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource('/typeahead/common/accounts/') + ->setName('actors') + ->setLabel(pht('Actors')) + ->setValue($actor_handles)) + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource('/typeahead/common/accounts/') + ->setName('users') + ->setLabel(pht('Users')) + ->setValue($user_handles)) + ->appendChild($action_control) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Filter IP')) + ->setName('ip') + ->setValue($remote_prefix)) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Sessions')) + ->setName('sessions') + ->setValue(implode(', ', $sessions))); + + } + + protected function getURI($path) { + return '/people/logs/'.$path; + } + + public function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + +} diff --git a/src/applications/people/storage/PhabricatorUserLog.php b/src/applications/people/storage/PhabricatorUserLog.php --- a/src/applications/people/storage/PhabricatorUserLog.php +++ b/src/applications/people/storage/PhabricatorUserLog.php @@ -1,6 +1,7 @@ pht('Login'), + self::ACTION_LOGIN_FAILURE => pht('Login Failure'), + self::ACTION_LOGOUT => pht('Logout'), + self::ACTION_RESET_PASSWORD => pht('Reset Password'), + self::ACTION_CREATE => pht('Create Account'), + self::ACTION_EDIT => pht('Edit Account'), + self::ACTION_ADMIN => pht('Add/Remove Administrator'), + self::ACTION_SYSTEM_AGENT => pht('Add/Remove System Agent'), + self::ACTION_DISABLE => pht('Enable/Disable'), + self::ACTION_APPROVE => pht('Approve Registration'), + self::ACTION_DELETE => pht('Delete User'), + self::ACTION_CONDUIT_CERTIFICATE + => pht('Conduit: Read Certificate'), + self::ACTION_CONDUIT_CERTIFICATE_FAILURE + => pht('Conduit: Read Certificate Failure'), + self::ACTION_EMAIL_PRIMARY => pht('Email: Change Primary'), + self::ACTION_EMAIL_ADD => pht('Email: Add Address'), + self::ACTION_EMAIL_REMOVE => pht('Email: Remove Address'), + self::ACTION_CHANGE_PASSWORD => pht('Change Password'), + self::ACTION_CHANGE_USERNAME => pht('Change Username'), + ); + } + + public static function initializeNewLog( PhabricatorUser $actor = null, $object_phid, @@ -44,11 +71,20 @@ if ($actor) { $log->setActorPHID($actor->getPHID()); + if ($actor->hasSession()) { + $session = $actor->getSession(); + + // NOTE: This is a hash of the real session value, so it's safe to + // store it directly in the logs. + $log->setSession($session->getSessionKey()); + } } $log->setUserPHID((string)$object_phid); $log->setAction($action); + $log->remoteAddr = idx($_SERVER, 'REMOTE_ADDR', ''); + return $log; } @@ -62,31 +98,12 @@ } public function save() { - if (!$this->remoteAddr) { - $this->remoteAddr = idx($_SERVER, 'REMOTE_ADDR', ''); - } - if (!$this->session) { - // TODO: This is not correct if there's a cookie prefix. This object - // should take an AphrontRequest. - // TODO: Maybe record session kind, or drop this for anonymous sessions? - $this->setSession(idx($_COOKIE, PhabricatorCookies::COOKIE_SESSION)); - } $this->details['host'] = php_uname('n'); $this->details['user_agent'] = AphrontRequest::getHTTPHeader('User-Agent'); return parent::save(); } - public function setSession($session) { - // Store the hash of the session, not the actual session key, so that - // seeing the logs doesn't compromise all the sessions which appear in - // them. This just prevents casual leaks, like in a screenshot. - if (strlen($session)) { - $this->session = PhabricatorHash::digest($session); - } - return $this; - } - public function getConfiguration() { return array( self::CONFIG_SERIALIZATION => array( @@ -97,4 +114,49 @@ ) + parent::getConfiguration(); } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::POLICY_NOONE; + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + if ($viewer->getIsAdmin()) { + return true; + } + + $viewer_phid = $viewer->getPHID(); + if ($viewer_phid) { + $user_phid = $this->getUserPHID(); + if ($viewer_phid == $user_phid) { + return true; + } + + $actor_phid = $this->getActorPHID(); + if ($viewer_phid == $actor_phid) { + return true; + } + } + + return false; + } + + public function describeAutomaticCapability($capability) { + return array( + pht('Users can view their activity and activity that affects them.'), + pht('Administrators can always view all activity.'), + ); + } + }