1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +01:00

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
This commit is contained in:
epriestley 2014-04-27 17:31:35 -07:00
parent f42ec84d0c
commit 320be1a1b8
7 changed files with 448 additions and 208 deletions

View file

@ -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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
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 @@ return array(
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 @@ return array(
1 => 'javelin-stratcom',
2 => 'javelin-dom',
),
'8fc1c918' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-uri',
2 => 'phabricator-notification',
),
'8fd76bab' =>
array(
0 => 'javelin-behavior',

View file

@ -1816,6 +1816,8 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
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 @@ phutil_register_library_map(array(
'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase',
'PhabricatorUserEmail' => 'PhabricatorUserDAO',
'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase',
'PhabricatorUserLog' => 'PhabricatorUserDAO',
'PhabricatorUserLog' =>
array(
0 => 'PhabricatorUserDAO',
1 => 'PhabricatorPolicyInterface',
),
'PhabricatorUserPreferences' => 'PhabricatorUserDAO',
'PhabricatorUserProfile' => 'PhabricatorUserDAO',
'PhabricatorUserProfileEditor' => 'PhabricatorApplicationTransactionEditor',

View file

@ -40,7 +40,8 @@ final class PhabricatorApplicationPeople extends PhabricatorApplication {
return array(
'/people/' => array(
'(query/(?P<key>[^/]+)/)?' => 'PhabricatorPeopleListController',
'logs/' => 'PhabricatorPeopleLogsController',
'logs/(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorPeopleLogsController',
'approve/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleApproveController',
'(?P<via>disapprove)/(?P<id>[1-9]\d*)/'
=> 'PhabricatorPeopleDisableController',

View file

@ -1,139 +1,31 @@
<?php
final class PhabricatorPeopleLogsController
extends PhabricatorPeopleController {
final class PhabricatorPeopleLogsController extends PhabricatorPeopleController
implements PhabricatorApplicationSearchResultsControllerInterface {
private $queryKey;
public function willProcessRequest(array $data) {
$this->queryKey = idx($data, 'queryKey');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$controller = id(new PhabricatorApplicationSearchController($request))
->setQueryKey($this->queryKey)
->setSearchEngine(new PhabricatorPeopleLogSearchEngine())
->setNavigation($this->buildSideNavView());
$filter_activity = $request->getStr('activity');
$filter_ip = $request->getStr('ip');
$filter_session = $request->getStr('session');
return $this->delegateToController($controller);
}
$filter_user = $request->getArr('user', array());
$filter_actor = $request->getArr('actor', array());
public function renderResultsList(
array $logs,
PhabricatorSavedQuery $query) {
assert_instances_of($logs, 'PhabricatorUserLog');
$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,
));
}
if ($filter_ip) {
$where_clause[] = qsprintf(
$conn_r,
'remoteAddr LIKE %>',
$filter_ip);
}
if ($filter_session) {
$where_clause[] = qsprintf(
$conn_r,
'session = %s',
$filter_session);
}
$where_clause = '('.implode(') AND (', $where_clause).')';
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'page');
$pager->setOffset($request->getInt('page'));
$pager->setPageSize(500);
$logs = $log_table->loadAllWhere(
'(%Q) ORDER BY dateCreated DESC LIMIT %d, %d',
$where_clause,
$pager->getOffset(),
$pager->getPageSize() + 1);
$logs = $pager->sliceResults($logs);
$request = $this->getRequest();
$viewer = $request->getUser();
$phids = array();
foreach ($logs as $log) {
@ -143,32 +35,40 @@ final class PhabricatorPeopleLogsController
$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 @@ final class PhabricatorPeopleLogsController
pht('Action'),
pht('Actor'),
pht('User'),
pht('Old'),
pht('New'),
pht('IP'),
pht('Session'),
));
@ -189,40 +87,30 @@ final class PhabricatorPeopleLogsController
array(
'',
'right',
'',
'',
'',
'wrap',
'wrap',
'',
'wide',
'',
'',
'',
'n',
));
$panel = new AphrontPanelView();
$panel->setHeader(pht('Activity Logs'));
$panel->setNoBackground();
$panel->appendChild($table);
$panel->appendChild($pager);
$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);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Activity Logs'),
'device' => true,
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('User Activity Logs'))
->appendChild($table);
}
public function buildSideNavView() {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
$viewer = $this->getRequest()->getUser();
id(new PhabricatorPeopleLogSearchEngine())
->setViewer($viewer)
->addNavigationItems($nav->getMenu());
return $nav;
}
}

View file

@ -0,0 +1,112 @@
<?php
final class PhabricatorPeopleLogQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $actorPHIDs;
private $userPHIDs;
private $relatedPHIDs;
private $sessionKeys;
private $actions;
private $remoteAddressPrefix;
public function withActorPHIDs(array $actor_phids) {
$this->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';
}
}

View file

@ -0,0 +1,157 @@
<?php
final class PhabricatorPeopleLogSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getPageSize(PhabricatorSavedQuery $saved) {
return 500;
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$saved->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);
}
}

View file

@ -1,6 +1,7 @@
<?php
final class PhabricatorUserLog extends PhabricatorUserDAO {
final class PhabricatorUserLog extends PhabricatorUserDAO
implements PhabricatorPolicyInterface {
const ACTION_LOGIN = 'login';
const ACTION_LOGOUT = 'logout';
@ -35,6 +36,32 @@ final class PhabricatorUserLog extends PhabricatorUserDAO {
protected $remoteAddr;
protected $session;
public static function getActionTypeMap() {
return array(
self::ACTION_LOGIN => 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 @@ final class PhabricatorUserLog extends PhabricatorUserDAO {
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 @@ final class PhabricatorUserLog extends PhabricatorUserDAO {
}
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 @@ final class PhabricatorUserLog extends PhabricatorUserDAO {
) + 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.'),
);
}
}