/** * The policy enforcement advice. This advices applies the security enforcement interceptor to all methods configured in the policy. * Note: If we have some kind of "run as" functionality in the future, we would have to manipulate the security context * before calling the policy enforcement interceptor * * @Flow\Around("filter(TYPO3\Flow\Security\Authorization\Privilege\Method\MethodPrivilegePointcutFilter)") * @param JoinPointInterface $joinPoint The current joinpoint * @return mixed The result of the target method if it has not been intercepted */ public function enforcePolicy(JoinPointInterface $joinPoint) { if ($this->securityContext->areAuthorizationChecksDisabled() !== true) { $this->policyEnforcementInterceptor->setJoinPoint($joinPoint); $this->policyEnforcementInterceptor->invoke(); } return $joinPoint->getAdviceChain()->proceed($joinPoint); }
/** * The policy enforcement advice. This advices applies the security enforcement interceptor to all methods configured in the policy. * Note: If we have some kind of "run as" functionality in the future, we would have to manipulate the security context * before calling the policy enforcement interceptor * * @Flow\Around("setting(TYPO3.Flow.security.enable) && filter(TYPO3\Flow\Security\Policy\PolicyService)") * @param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current joinpoint * @return mixed The result of the target method if it has not been intercepted */ public function enforcePolicy(\TYPO3\Flow\Aop\JoinPointInterface $joinPoint) { if ($this->securityContext->areAuthorizationChecksDisabled() !== TRUE) { $this->policyEnforcementInterceptor->setJoinPoint($joinPoint); $this->policyEnforcementInterceptor->invoke(); } $result = $joinPoint->getAdviceChain()->proceed($joinPoint); // @TODO Once we use the AfterInvocation again, it needs to be invoked here and its result returned instead. return $result; }
/** * Gets the SQL query part to add to a query. * * @param ClassMetaData $targetEntity Metadata object for the target entity to be filtered * @param string $targetTableAlias The target table alias used in the current query * @return string The constraint SQL if there is available, empty string otherwise */ public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) { $this->initializeDependencies(); /* * TODO: Instead of checking for class account we could introduce some interface for white listing entities from entity security checks * Problem with checking the Account is, that this filter calls getRoles() on the security context while accounts are not * yet fully initialized. By this we get a half built account object that will end up in access denied exception, * as it has no roles (and other properties) set */ if ($this->securityContext->areAuthorizationChecksDisabled() || $targetEntity->getName() === \TYPO3\Flow\Security\Account::class) { return ''; } if (!$this->securityContext->isInitialized()) { if (!$this->securityContext->canBeInitialized()) { return ''; } $this->securityContext->initialize(); } // This is needed to include the current context of roles into query cache identifier $this->setParameter('__contextHash', $this->securityContext->getContextHash(), 'string'); $sqlConstraints = array(); $grantedConstraints = array(); $deniedConstraints = array(); foreach ($this->securityContext->getRoles() as $role) { $entityPrivileges = $role->getPrivilegesByType(\TYPO3\Flow\Security\Authorization\Privilege\Entity\EntityPrivilegeInterface::class); /** @var EntityPrivilegeInterface $privilege */ foreach ($entityPrivileges as $privilege) { if (!$privilege->matchesEntityType($targetEntity->getName())) { continue; } $sqlConstraint = $privilege->getSqlConstraint($targetEntity, $targetTableAlias); if ($sqlConstraint === null) { continue; } $sqlConstraints[] = ' NOT (' . $sqlConstraint . ')'; if ($privilege->isGranted()) { $grantedConstraints[] = ' NOT (' . $sqlConstraint . ')'; } elseif ($privilege->isDenied()) { $deniedConstraints[] = ' NOT (' . $sqlConstraint . ')'; } } } $grantedConstraints = array_diff($grantedConstraints, $deniedConstraints); $effectiveConstraints = array_diff($sqlConstraints, $grantedConstraints); if (count($effectiveConstraints) > 0) { return ' (' . implode(') AND (', $effectiveConstraints) . ') '; } return ''; }
/** * Checks, if the current policy allows the retrieval of the object fetched by getObjectDataByIdentifier() * * @Flow\Around("within(TYPO3\Flow\Persistence\PersistenceManagerInterface) && method(.*->getObjectByIdentifier()) && setting(TYPO3.Flow.security.enable)") * @param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current joinpoint * @return array The object data of the original object, or NULL if access is not permitted */ public function checkAccessAfterFetchingAnObjectByIdentifier(JoinPointInterface $joinPoint) { $result = $joinPoint->getAdviceChain()->proceed($joinPoint); if ($this->securityContext->areAuthorizationChecksDisabled() === TRUE || $this->policyService->hasPolicyEntriesForEntities() === FALSE) { return $result; } if ($this->securityContext->isInitialized() === FALSE) { if ($this->securityContext->canBeInitialized() === TRUE) { $this->securityContext->initialize(); } else { return $result; } } $authenticatedRoles = $this->securityContext->getRoles(); $entityType = $this->reflectionService->getClassNameByObject($result); if ($this->policyService->hasPolicyEntryForEntityType($entityType, $authenticatedRoles)) { if ($this->policyService->isGeneralAccessForEntityTypeGranted($entityType, $authenticatedRoles) === FALSE) { return NULL; } $policyConstraintsDefinition = $this->policyService->getResourcesConstraintsForEntityTypeAndRoles($entityType, $authenticatedRoles); if ($this->checkConstraintDefinitionsOnResultObject($policyConstraintsDefinition, $result) === FALSE) { return NULL; } } return $result; }
/** * Matches a \TYPO3\Flow\Mvc\RequestInterface against the configured CSRF pattern rules and * searches for invalid csrf tokens. If this returns TRUE, the request is invalid! * * @param RequestInterface $request The request that should be matched * @return boolean TRUE if the pattern matched, FALSE otherwise * @throws AuthenticationRequiredException */ public function matchRequest(RequestInterface $request) { if (!$request instanceof ActionRequest || $request->getHttpRequest()->isMethodSafe()) { $this->systemLogger->log('CSRF: No token required, safe request', LOG_DEBUG); return false; } if ($this->authenticationManager->isAuthenticated() === false) { $this->systemLogger->log('CSRF: No token required, not authenticated', LOG_DEBUG); return false; } if ($this->securityContext->areAuthorizationChecksDisabled() === true) { $this->systemLogger->log('CSRF: No token required, authorization checks are disabled', LOG_DEBUG); return false; } $controllerClassName = $this->objectManager->getClassNameByObjectName($request->getControllerObjectName()); $actionMethodName = $request->getControllerActionName() . 'Action'; if (!$this->hasPolicyEntryForMethod($controllerClassName, $actionMethodName)) { $this->systemLogger->log(sprintf('CSRF: No token required, method %s::%s() is not restricted by a policy.', $controllerClassName, $actionMethodName), LOG_DEBUG); return false; } if ($this->reflectionService->isMethodTaggedWith($controllerClassName, $actionMethodName, 'skipcsrfprotection')) { $this->systemLogger->log(sprintf('CSRF: No token required, method %s::%s() is tagged with a "skipcsrfprotection" annotation', $controllerClassName, $actionMethodName), LOG_DEBUG); return false; } $httpRequest = $request->getHttpRequest(); if ($httpRequest->hasHeader('X-Flow-Csrftoken')) { $csrfToken = $httpRequest->getHeader('X-Flow-Csrftoken'); } else { $internalArguments = $request->getMainRequest()->getInternalArguments(); $csrfToken = isset($internalArguments['__csrfToken']) ? $internalArguments['__csrfToken'] : null; } if (empty($csrfToken)) { $this->systemLogger->log(sprintf('CSRF: token was empty but a valid token is required for %s::%s()', $controllerClassName, $actionMethodName), LOG_DEBUG); return true; } if (!$this->securityContext->hasCsrfProtectionTokens()) { throw new AuthenticationRequiredException(sprintf('CSRF: No CSRF tokens in security context, possible session timeout. A valid token is required for %s::%s()', $controllerClassName, $actionMethodName), 1317309673); } if ($this->securityContext->isCsrfProtectionTokenValid($csrfToken) === false) { $this->systemLogger->log(sprintf('CSRF: token was invalid but a valid token is required for %s::%s()', $controllerClassName, $actionMethodName), LOG_DEBUG); return true; } $this->systemLogger->log(sprintf('CSRF: Successfully verified token for %s::%s()', $controllerClassName, $actionMethodName), LOG_DEBUG); return false; }
/** * Advices the dispatch method so that illegal action requests are blocked before * invoking any controller. * * The "request" referred to within this method is an ActionRequest or some other * dispatchable request implementing RequestInterface. Note that we don't deal * with HTTP requests here. * * @Flow\Around("setting(TYPO3.Flow.security.enable) && method(TYPO3\Flow\Mvc\Dispatcher->dispatch())") * @param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current joinpoint * @return mixed Result of the advice chain * @throws \Exception|\TYPO3\Flow\Security\Exception\AccessDeniedException * @throws \Exception|\TYPO3\Flow\Security\Exception\AuthenticationRequiredException */ public function blockIllegalRequestsAndForwardToAuthenticationEntryPoints(JoinPointInterface $joinPoint) { $request = $joinPoint->getMethodArgument('request'); if (!$request instanceof ActionRequest || $this->securityContext->areAuthorizationChecksDisabled()) { return $joinPoint->getAdviceChain()->proceed($joinPoint); } try { $this->firewall->blockIllegalRequests($request); return $joinPoint->getAdviceChain()->proceed($joinPoint); } catch (AuthenticationRequiredException $exception) { $response = $joinPoint->getMethodArgument('response'); $entryPointFound = FALSE; /** @var $token \TYPO3\Flow\Security\Authentication\TokenInterface */ foreach ($this->securityContext->getAuthenticationTokens() as $token) { $entryPoint = $token->getAuthenticationEntryPoint(); if ($entryPoint !== NULL) { $entryPointFound = TRUE; if ($entryPoint instanceof WebRedirect) { $this->securityLogger->log('Redirecting to authentication entry point', LOG_INFO, $entryPoint->getOptions()); } else { $this->securityLogger->log('Starting authentication with entry point of type ' . get_class($entryPoint), LOG_INFO); } $this->securityContext->setInterceptedRequest($request->getMainRequest()); $entryPoint->startAuthentication($request->getHttpRequest(), $response); } } if ($entryPointFound === FALSE) { $this->securityLogger->log('No authentication entry point found for active tokens, therefore cannot authenticate or redirect to authentication automatically.', LOG_NOTICE); throw $exception; } } catch (AccessDeniedException $exception) { $this->securityLogger->log('Access denied', LOG_WARNING); throw $exception; } return NULL; }
/** * Matches a \TYPO3\Flow\Mvc\RequestInterface against the configured CSRF pattern rules and * searches for invalid csrf tokens. If this returns TRUE, the request is invalid! * * @param \TYPO3\Flow\Mvc\RequestInterface $request The request that should be matched * @return boolean TRUE if the pattern matched, FALSE otherwise * @throws \TYPO3\Flow\Security\Exception\AuthenticationRequiredException */ public function matchRequest(\TYPO3\Flow\Mvc\RequestInterface $request) { if (!$request instanceof ActionRequest || $request->getHttpRequest()->isMethodSafe()) { $this->systemLogger->log('No CSRF required, safe request', LOG_DEBUG); return FALSE; } if ($this->authenticationManager->isAuthenticated() === FALSE) { $this->systemLogger->log('No CSRF required, not authenticated', LOG_DEBUG); return FALSE; } if ($this->securityContext->areAuthorizationChecksDisabled() === TRUE) { $this->systemLogger->log('No CSRF required, authorization checks are disabled', LOG_DEBUG); return FALSE; } $controllerClassName = $this->objectManager->getClassNameByObjectName($request->getControllerObjectName()); $actionName = $request->getControllerActionName() . 'Action'; if (!$this->policyService->hasPolicyEntryForMethod($controllerClassName, $actionName)) { $this->systemLogger->log(sprintf('CSRF protection filter: allowed %s request without requiring CSRF token because action "%s" in controller "%s" is not restricted by a policy.', $request->getHttpRequest()->getMethod(), $actionName, $controllerClassName), LOG_NOTICE); return FALSE; } if ($this->reflectionService->isMethodTaggedWith($controllerClassName, $actionName, 'skipcsrfprotection')) { return FALSE; } $httpRequest = $request->getHttpRequest(); if ($httpRequest->hasHeader('X-Flow-Csrftoken')) { $csrfToken = $httpRequest->getHeader('X-Flow-Csrftoken'); } else { $internalArguments = $request->getMainRequest()->getInternalArguments(); $csrfToken = isset($internalArguments['__csrfToken']) ? $internalArguments['__csrfToken'] : NULL; } if (empty($csrfToken)) { $this->systemLogger->log('CSRF token was empty', LOG_DEBUG); return TRUE; } if (!$this->securityContext->hasCsrfProtectionTokens()) { throw new \TYPO3\Flow\Security\Exception\AuthenticationRequiredException('No tokens in security context, possible session timeout', 1317309673); } if ($this->securityContext->isCsrfProtectionTokenValid($csrfToken) === FALSE) { $this->systemLogger->log('CSRF token was invalid', LOG_DEBUG); return TRUE; } // the CSRF token was necessary and is valid return FALSE; }