changeTagsManager = $changeTagsManager; $this->parserFactory = $parserFactory; $this->permManager = $permManager; $this->restrictedActions = $restrictedActions; } /** * @param AbstractFilter $newFilter * @param AbstractFilter $originalFilter * @param User $user * @return Status */ public function checkAll( AbstractFilter $newFilter, AbstractFilter $originalFilter, User $user ) : Status { // TODO We might consider not bailing at the first error, so we can show all errors at the first attempt $syntaxStatus = $this->checkValidSyntax( $newFilter ); if ( !$syntaxStatus->isGood() ) { return $syntaxStatus; } $requiredFieldsStatus = $this->checkRequiredFields( $newFilter ); if ( !$requiredFieldsStatus->isGood() ) { return $requiredFieldsStatus; } $conflictStatus = $this->checkConflictingFields( $newFilter ); if ( !$conflictStatus->isGood() ) { return $conflictStatus; } $actions = $newFilter->getActions(); if ( isset( $actions['tag'] ) ) { $validTagsStatus = $this->checkAllTags( $actions['tag'] ); if ( !$validTagsStatus->isGood() ) { return $validTagsStatus; } } $messagesStatus = $this->checkEmptyMessages( $newFilter ); if ( !$messagesStatus->isGood() ) { return $messagesStatus; } if ( isset( $actions['throttle'] ) ) { $throttleStatus = $this->checkThrottleParameters( $actions['throttle'] ); if ( !$throttleStatus->isGood() ) { return $throttleStatus; } } $globalPermStatus = $this->checkGlobalFilterEditPermission( $user, $newFilter, $originalFilter ); if ( !$globalPermStatus->isGood() ) { return $globalPermStatus; } $globalFilterMsgStatus = $this->checkMessagesOnGlobalFilters( $newFilter ); if ( !$globalFilterMsgStatus->isGood() ) { return $globalFilterMsgStatus; } $restrictedActionsStatus = $this->checkRestrictedActions( $user, $newFilter, $originalFilter ); if ( !$restrictedActionsStatus->isGood() ) { return $restrictedActionsStatus; } return Status::newGood(); } /** * @param AbstractFilter $filter * @return Status */ public function checkValidSyntax( AbstractFilter $filter ) : Status { $ret = Status::newGood(); $parser = $this->parserFactory->newParser(); $syntaxerr = $parser->checkSyntax( $filter->getRules() ); if ( $syntaxerr !== true ) { $ret->error( 'abusefilter-edit-badsyntax', $syntaxerr[0] ); } return $ret; } /** * @param AbstractFilter $filter * @return Status */ public function checkRequiredFields( AbstractFilter $filter ) : Status { $ret = Status::newGood(); $missing = []; if ( $filter->getRules() === '' ) { $missing[] = new Message( 'abusefilter-edit-field-conditions' ); } if ( trim( $filter->getName() ) === '' ) { $missing[] = new Message( 'abusefilter-edit-field-description' ); } if ( count( $missing ) !== 0 ) { $ret->error( 'abusefilter-edit-missingfields', Message::listParam( $missing, 'comma' ) ); } return $ret; } /** * @param AbstractFilter $filter * @return Status */ public function checkConflictingFields( AbstractFilter $filter ) : Status { $ret = Status::newGood(); // Don't allow setting as deleted an active filter if ( $filter->isEnabled() && $filter->isDeleted() ) { $ret->error( 'abusefilter-edit-deleting-enabled' ); } return $ret; } /** * @param string[] $tags * @return Status */ public function checkAllTags( array $tags ) : Status { $ret = Status::newGood(); if ( count( $tags ) === 0 ) { $ret->error( 'tags-create-no-name' ); return $ret; } foreach ( $tags as $tag ) { $curStatus = $this->isAllowedTag( $tag ); if ( !$curStatus->isGood() ) { // TODO Consider merging return $curStatus; } } return $ret; } /** * Check whether a filter is allowed to use a tag * * @param string $tag Tag name * @return Status */ public function isAllowedTag( string $tag ) : Status { $tagNameStatus = ChangeTags::isTagNameValid( $tag ); if ( !$tagNameStatus->isGood() ) { return $tagNameStatus; } $canAddStatus = ChangeTags::canAddTagsAccompanyingChange( [ $tag ] ); if ( $canAddStatus->isGood() ) { return Status::newGood(); } if ( $tag === 'abusefilter-condition-limit' ) { return Status::newFatal( 'abusefilter-tag-reserved' ); } // note: these are both local and global $alreadyDefinedTags = $this->changeTagsManager->getTagsDefinedByFilters(); if ( in_array( $tag, $alreadyDefinedTags, true ) ) { return Status::newGood(); } $canCreateTagStatus = ChangeTags::canCreateTag( $tag ); if ( $canCreateTagStatus->isGood() ) { return Status::newGood(); } return Status::newFatal( 'abusefilter-edit-bad-tags' ); } /** * @todo Consider merging with checkRequiredFields * @param AbstractFilter $filter * @return Status */ public function checkEmptyMessages( AbstractFilter $filter ) : Status { $ret = Status::newGood(); $actions = $filter->getActions(); // TODO: Check and report both together if ( isset( $actions['warn'] ) && $actions['warn'][0] === '' ) { $ret->error( 'abusefilter-edit-invalid-warn-message' ); } elseif ( isset( $actions['disallow'] ) && $actions['disallow'][0] === '' ) { $ret->error( 'abusefilter-edit-invalid-disallow-message' ); } return $ret; } /** * Validate throttle parameters * * @param array $params Throttle parameters * @return Status */ public function checkThrottleParameters( array $params ) : Status { list( $throttleCount, $throttlePeriod ) = explode( ',', $params[1], 2 ); $throttleGroups = array_slice( $params, 2 ); $validGroups = [ 'ip', 'user', 'range', 'creationdate', 'editcount', 'site', 'page' ]; $ret = Status::newGood(); if ( preg_match( '/^[1-9][0-9]*$/', $throttleCount ) === 0 ) { $ret->error( 'abusefilter-edit-invalid-throttlecount' ); } elseif ( preg_match( '/^[1-9][0-9]*$/', $throttlePeriod ) === 0 ) { $ret->error( 'abusefilter-edit-invalid-throttleperiod' ); } elseif ( !$throttleGroups ) { $ret->error( 'abusefilter-edit-empty-throttlegroups' ); } else { $valid = true; // Groups should be unique in three ways: no direct duplicates like 'user' and 'user', // no duplicated subgroups, not even shuffled ('ip,user' and 'user,ip') and no duplicates // within subgroups ('user,ip,user') $uniqueGroups = []; $uniqueSubGroups = true; // Every group should be valid, and subgroups should have valid groups inside foreach ( $throttleGroups as $group ) { if ( strpos( $group, ',' ) !== false ) { $subGroups = explode( ',', $group ); if ( $subGroups !== array_unique( $subGroups ) ) { $uniqueSubGroups = false; break; } foreach ( $subGroups as $subGroup ) { if ( !in_array( $subGroup, $validGroups ) ) { $valid = false; break 2; } } sort( $subGroups ); $uniqueGroups[] = implode( ',', $subGroups ); } else { if ( !in_array( $group, $validGroups ) ) { $valid = false; break; } $uniqueGroups[] = $group; } } if ( !$valid ) { $ret->error( 'abusefilter-edit-invalid-throttlegroups' ); } elseif ( !$uniqueSubGroups || $uniqueGroups !== array_unique( $uniqueGroups ) ) { $ret->error( 'abusefilter-edit-duplicated-throttlegroups' ); } } return $ret; } /** * @param User $user * @param AbstractFilter $newFilter * @param AbstractFilter $originalFilter * @return Status */ public function checkGlobalFilterEditPermission( User $user, AbstractFilter $newFilter, AbstractFilter $originalFilter ) : Status { if ( !$this->permManager->canEditFilter( $user, $newFilter ) || !$this->permManager->canEditFilter( $user, $originalFilter ) ) { return Status::newFatal( 'abusefilter-edit-notallowed-global' ); } return Status::newGood(); } /** * @param AbstractFilter $filter * @return Status */ public function checkMessagesOnGlobalFilters( AbstractFilter $filter ) : Status { $ret = Status::newGood(); $actions = $filter->getActions(); if ( $filter->isGlobal() && ( ( isset( $actions['warn'] ) && $actions['warn'][0] !== 'abusefilter-warning' ) || ( isset( $actions['disallow'] ) && $actions['disallow'][0] !== 'abusefilter-disallowed' ) ) ) { $ret->error( 'abusefilter-edit-notallowed-global-custom-msg' ); } return $ret; } /** * @param UserIdentity $user * @param AbstractFilter $newFilter * @param AbstractFilter $originalFilter * @return Status */ public function checkRestrictedActions( UserIdentity $user, AbstractFilter $newFilter, AbstractFilter $originalFilter ) : Status { $ret = Status::newGood(); $allEnabledActions = array_merge( $newFilter->getActions(), $originalFilter->getActions() ); if ( array_intersect_key( array_flip( $this->restrictedActions ), $allEnabledActions ) && !$this->permManager->canEditFilterWithRestrictedActions( $user ) ) { $ret->error( 'abusefilter-edit-restricted' ); } return $ret; } }