376 lines
9.2 KiB
PHP
376 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace BlueSpice\PermissionManager;
|
|
|
|
use BlueSpice\ExtensionAttributeBasedRegistry;
|
|
use BlueSpice\Permission\IRole;
|
|
use BlueSpice\Permission\RoleManager;
|
|
use InvalidArgumentException;
|
|
use ManualLogEntry;
|
|
use MediaWiki\Config\Config;
|
|
use MediaWiki\Context\RequestContext;
|
|
use MediaWiki\HookContainer\HookContainer;
|
|
use MediaWiki\MediaWikiServices;
|
|
use MediaWiki\Message\Message;
|
|
use MediaWiki\SpecialPage\SpecialPage;
|
|
use MWStake\MediaWiki\Component\DynamicConfig\DynamicConfigManager;
|
|
use Psr\Log\LoggerInterface;
|
|
use Throwable;
|
|
use UnexpectedValueException;
|
|
|
|
class PermissionManager {
|
|
/** @var RoleManager */
|
|
private $roleManager;
|
|
/** @var Config */
|
|
private $config;
|
|
/** @var LoggerInterface */
|
|
private $logger;
|
|
/** @var HookContainer */
|
|
private $hookContainer;
|
|
/** @var MediaWikiServices */
|
|
private $services;
|
|
/** @var array */
|
|
protected $groups = [];
|
|
/** @var ExtensionAttributeBasedRegistry */
|
|
protected $presets;
|
|
/** @var DynamicConfigManager */
|
|
private DynamicConfigManager $dynamicConfigManager;
|
|
|
|
/**
|
|
* @param RoleManager $roleManager
|
|
* @param MediaWikiServices $services
|
|
* @param Config $config
|
|
* @param LoggerInterface $logger
|
|
* @param HookContainer $hookContainer
|
|
* @param DynamicConfigManager $dynamicConfigManager
|
|
*/
|
|
public function __construct(
|
|
RoleManager $roleManager,
|
|
MediaWikiServices $services,
|
|
Config $config,
|
|
LoggerInterface $logger,
|
|
HookContainer $hookContainer,
|
|
DynamicConfigManager $dynamicConfigManager
|
|
) {
|
|
$this->roleManager = $roleManager;
|
|
$this->config = $config;
|
|
$this->logger = $logger;
|
|
$this->hookContainer = $hookContainer;
|
|
$this->services = $services;
|
|
$this->dynamicConfigManager = $dynamicConfigManager;
|
|
|
|
$this->presets = new ExtensionAttributeBasedRegistry(
|
|
'BlueSpicePermissionManagerPermissionPresets'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Enable role system
|
|
*/
|
|
public function enableRoleSystem() {
|
|
$this->roleManager->enableRoleSystem();
|
|
}
|
|
|
|
/**
|
|
* Apply permission preset
|
|
*/
|
|
public function applyCurrentPreset() {
|
|
try {
|
|
$preset = $this->getActivePreset();
|
|
$preset->apply();
|
|
$this->services->getHookContainer()->run( 'BSPermissionManagerAfterApplyPreset', [
|
|
$preset, $this->roleManager
|
|
] );
|
|
} catch ( Throwable $exception ) {
|
|
$this->logger->critical(
|
|
'Exception while applying preset: {error}',
|
|
[
|
|
'error' => $exception->getMessage()
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return IPreset
|
|
*/
|
|
public function getActivePreset() {
|
|
$activePreset = $this->config->get( 'PermissionManagerActivePreset' );
|
|
$preset = $this->getPreset( $activePreset );
|
|
if ( $preset === null ) {
|
|
$this->logger->critical(
|
|
'Attempted to apply unrecognized preset: {preset}',
|
|
[
|
|
'preset' => $activePreset
|
|
]
|
|
);
|
|
throw new UnexpectedValueException(
|
|
"Permission preset \"$activePreset\" is not registered or not allowed"
|
|
);
|
|
}
|
|
|
|
return $preset;
|
|
}
|
|
|
|
/**
|
|
* Get names of available presets
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getAvailablePresets() {
|
|
return array_intersect(
|
|
$this->presets->getAllKeys(),
|
|
$this->config->get( 'PermissionManagerAllowedPresets' )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get preset instance
|
|
*
|
|
* @param string $name
|
|
* @return IPreset|null
|
|
*/
|
|
public function getPreset( $name ) {
|
|
if ( !in_array( $name, $this->config->get( 'PermissionManagerAllowedPresets' ) ) ) {
|
|
return null;
|
|
}
|
|
if ( !$this->presets->getValue( $name, null ) ) {
|
|
return null;
|
|
}
|
|
|
|
$callable = $this->presets->getValue( $name );
|
|
$preset = call_user_func( $callable );
|
|
|
|
if ( !$preset instanceof IPreset ) {
|
|
$this->logger->critical(
|
|
'Preset expected to be an instance of {preset_class}, {actual} given',
|
|
[
|
|
'preset_class' => IPreset::class,
|
|
'actual' => get_class( $preset )
|
|
]
|
|
);
|
|
return null;
|
|
}
|
|
|
|
return $preset;
|
|
}
|
|
|
|
/**
|
|
* Get all permissions assigned to given role
|
|
*
|
|
* @param IRole|string $role
|
|
* @param bool $includeDesc
|
|
* @return array
|
|
*/
|
|
public function getRolePermissions( $role, $includeDesc = false ) {
|
|
if ( $role instanceof IRole ) {
|
|
$role = $role->getName();
|
|
}
|
|
$role = $this->roleManager->getRole( $role );
|
|
if ( $role instanceof IRole === false ) {
|
|
return [];
|
|
}
|
|
|
|
$permissions = $role->getPermissions();
|
|
if ( !$includeDesc ) {
|
|
return $permissions;
|
|
}
|
|
|
|
$permissionsAndDescs = [];
|
|
foreach ( $permissions as $permission ) {
|
|
$permissionsAndDescs[ $permission ] =
|
|
wfMessage( "right-$permission" )->text();
|
|
}
|
|
|
|
return $permissionsAndDescs;
|
|
}
|
|
|
|
/**
|
|
* @return RoleManager
|
|
*/
|
|
public function getRoleManager() {
|
|
return $this->roleManager;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
public function saveRoles( $data ) {
|
|
if ( !isset( $data ) || !isset( $data['groupRoles'] ) || !isset( $data['roleLockdown'] ) ) {
|
|
throw new InvalidArgumentException();
|
|
}
|
|
|
|
$groupRoles = $data['groupRoles'];
|
|
$roleLockdown = $data['roleLockdown'];
|
|
|
|
$status = $this->hookContainer->run(
|
|
'BsPermissionManager::beforeSaveRoles', [ &$groupRoles, &$roleLockdown ]
|
|
);
|
|
|
|
if ( !$status ) {
|
|
throw new \RuntimeException( 'Hook aborted' );
|
|
}
|
|
|
|
return $this->persistRoles( $groupRoles, $roleLockdown );
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getNamespaceRolesLockdown() {
|
|
return $this->config->get( 'NamespaceRolesLockdown' );
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getRoleDependencyTree() {
|
|
$roles = $this->roleManager->getRoleNames();
|
|
$tree = [];
|
|
foreach ( $roles as $roleName ) {
|
|
$role = $this->roleManager->getRole( $roleName );
|
|
if ( !$role instanceof \BlueSpice\Permission\IRole ) {
|
|
continue;
|
|
}
|
|
if ( empty( $role->getRequiredPermissions() ) ) {
|
|
continue;
|
|
}
|
|
$requiredPermissions = $role->getRequiredPermissions();
|
|
$neededRoles = [];
|
|
foreach ( $requiredPermissions as $permission ) {
|
|
$rolesWithPermission = $this->roleManager->getRolesWithPermission( $permission );
|
|
if ( in_array( $roleName, $rolesWithPermission ) ) {
|
|
continue;
|
|
}
|
|
$neededRoles[$permission] = $rolesWithPermission;
|
|
}
|
|
$tree[$roleName] = $neededRoles;
|
|
}
|
|
return $tree;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param array $groupRoles
|
|
* @param array $roleLockdown
|
|
* @return array
|
|
*/
|
|
private function persistRoles( $groupRoles, $roleLockdown ) {
|
|
if ( $this->services->getReadOnlyMode()->isReadOnly() ) {
|
|
return [
|
|
'success' => false,
|
|
'message' => wfMessage( 'bs-readonly', $this->services->getReadOnlyMode()->getReason() )->text()
|
|
];
|
|
}
|
|
|
|
$roleMatrixDiff = new RoleMatrixDiff( $this->config, $groupRoles, $roleLockdown );
|
|
$globalDiff = $roleMatrixDiff->getGlobalDiff();
|
|
$nsDiff = $roleMatrixDiff->getNsDiff();
|
|
|
|
$status = $this->dynamicConfigManager->storeConfig(
|
|
$this->dynamicConfigManager->getConfigObject( 'bs-permissionmanager-roles' ),
|
|
[ 'groupRoles' => $groupRoles, 'roleLockdown' => $roleLockdown ]
|
|
);
|
|
if ( $status ) {
|
|
$this->doLog( $globalDiff, $nsDiff );
|
|
return [ 'success' => true ];
|
|
} else {
|
|
return [
|
|
'success' => false,
|
|
'message' => Message::newFromKey(
|
|
'bs-permissionmanager-write-config-file-error',
|
|
'bs-permissionmanager-roles'
|
|
)->text()
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param array $globalDiff
|
|
* @param array $nsDiff
|
|
*/
|
|
private function doLog( $globalDiff, $nsDiff ) {
|
|
foreach ( $globalDiff as $group => $roles ) {
|
|
$addedRoles = [];
|
|
$removedRoles = [];
|
|
foreach ( $roles as $role => $added ) {
|
|
if ( $added ) {
|
|
$addedRoles[] = $role;
|
|
} else {
|
|
$removedRoles[] = $role;
|
|
}
|
|
}
|
|
if ( !empty( $addedRoles ) ) {
|
|
$this->insertLog( 'global-add', [
|
|
'4::diffGroup' => $group,
|
|
'5::diffRoles' => implode( ',', $addedRoles ),
|
|
'6::roleCount' => count( $addedRoles )
|
|
] );
|
|
}
|
|
if ( !empty( $removedRoles ) ) {
|
|
$this->insertLog( 'global-remove', [
|
|
'4::diffGroup' => $group,
|
|
'5::diffRoles' => implode( ',', $removedRoles ),
|
|
'6::roleCount' => count( $removedRoles )
|
|
] );
|
|
}
|
|
}
|
|
|
|
foreach ( $nsDiff as $group => $namespaces ) {
|
|
foreach ( $namespaces as $ns => $roles ) {
|
|
$namespaceInfo = $this->services->getNamespaceInfo();
|
|
$nsCanonical = $namespaceInfo->getCanonicalName( $ns );
|
|
if ( $ns === NS_MAIN ) {
|
|
$nsCanonical = wfMessage( 'bs-ns_main' )->text();
|
|
}
|
|
$addedRoles = [];
|
|
$removedRoles = [];
|
|
foreach ( $roles as $role => $added ) {
|
|
if ( $added ) {
|
|
$addedRoles[] = $role;
|
|
} else {
|
|
$removedRoles[] = $role;
|
|
}
|
|
}
|
|
if ( !empty( $addedRoles ) ) {
|
|
$this->insertLog( 'ns-add', [
|
|
'4::diffGroup' => $group,
|
|
'5::diffRoles' => implode( ',', $addedRoles ),
|
|
'6::roleCount' => count( $addedRoles ),
|
|
'7::ns' => $nsCanonical
|
|
] );
|
|
}
|
|
if ( !empty( $removedRoles ) ) {
|
|
$this->insertLog( 'ns-remove', [
|
|
'4::diffGroup' => $group,
|
|
'5::diffRoles' => implode( ',', $removedRoles ),
|
|
'6::roleCount' => count( $removedRoles ),
|
|
'7::ns' => $nsCanonical
|
|
] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $type
|
|
* @param array $params
|
|
*/
|
|
private function insertLog( $type, $params ) {
|
|
$targetTitle = SpecialPage::getTitleFor( 'PermissionManager' );
|
|
$user = RequestContext::getMain()->getUser();
|
|
|
|
$logger = new ManualLogEntry( 'bs-permission-manager', $type );
|
|
$logger->setPerformer( $user );
|
|
$logger->setTarget( $targetTitle );
|
|
$logger->setParameters( $params );
|
|
$logger->insert( $this->services->getDBLoadBalancer()->getConnection( DB_PRIMARY ) );
|
|
}
|
|
}
|