Recientemente comenzamos a usar Doctrine 2.2 y partes de Zend Framework 2 en un esfuerzo por mejorar la organización, reducir la duplicación, entre otras cosas. Hoy, comencé a lanzar ideas para implementar una capa de servicio para actuar como intermediario entre nuestros controladores y las entidades de Doctrine.Acceso a los datos y seguridad en la capa de servicio (Doctrine & ZF)
En este momento, la mayoría de nuestra lógica reside en el controlador. Además, usamos un asistente de acción para probar ciertos permisos; sin embargo, se me ocurrió un nuevo enfoque después de implementar Zend \ Di. Empecé a crear modelos de servicio específicos de la entidad, que usan Zend \ Di para inyectar una instancia de EntityManager y los permisos del usuario actual.
El código del controlador es el siguiente:
class Project_DeleteController extends Webjawns_Controller_Action
{
public function init()
{
$this->_initJsonContext();
}
public function indexAction()
{
$response = $this->_getAjaxResponse();
$auditId = (int) $this->_getParam('audit_id');
if (!$auditId) {
throw new DomainException('Audit ID required');
}
/* @var $auditService Service\Audit */
$auditService = $this->getDependencyInjector()->get('Service\Audit');
try {
$auditService->delete($auditId);
$response->setStatusSuccess();
} catch (Webjawns\Exception\SecurityException $e) {
$this->_noAuth();
} catch (Webjawns\Exception\Exception $e) {
$response->setStatusFailure($e->getMessage());
}
$response->sendResponse();
}
}
Y un ejemplo de una de nuestras capas de servicios. El constructor toma dos parámetros: uno toma el EntityManager y el otro un objeto Entity \ UserAccess, inyectado por Zend \ Di.
namespace Service;
use Webjawns\Service\Doctrine,
Webjawns\Exception;
class Audit extends AbstractService
{
public function delete($auditId)
{
// Only account admins can delete audits
if (\Webjawns_Acl::ROLE_ACCT_ADMIN != $this->getUserAccess()->getAccessRole()) {
throw new Exception\SecurityException('Only account administrators can delete audits');
}
$audit = $this->get($auditId);
if ($audit->getAuditStatus() !== \Entity\Audit::STATUS_IN_PROGRESS) {
throw new Exception\DomainException('Audits cannot be deleted once submitted for review');
}
$em = $this->getEntityManager();
$em->remove($audit);
$em->flush();
}
/**
* @param integer $auditId
* @return \Entity\Audit
*/
public function get($auditId)
{
/* @var $audit \Entity\Audit */
$audit = $this->getEntityManager()->find('Entity\Audit', $auditId);
if (null === $audit) {
throw new Exception\DomainException('Audit not found');
}
if ($audit->getAccount()->getAccountId() != $this->getUserAccess()->getAccount()->getAccountId()) {
throw new Exception\SecurityException('User and audit accounts do not match');
}
return $audit;
}
}
- Es este un patrón apropiado a utilizar para lo que estamos tratando de lograr?
- ¿Es una buena práctica tener la validación de permisos dentro de la capa de servicio como publicada?
- Según tengo entendido, la lógica de visualización aún reside en el controlador, lo que le da flexibilidad al modelo para usar en varios contextos (JSON, XML, HTML, etc.). ¿Pensamientos?
Estoy contento con la forma en que esto funciona hasta ahora, pero si alguien ve alguna desventaja de cómo estamos haciendo esto, por favor publique sus ideas.
Sólo mis dos peniques en la autenticación.No creo que haya un camino correcto ni un camino equivocado, sin embargo, comencé a poner mi autenticación en la capa de servicio, pero luego la moví a mis controladores. Mi razonamiento era que la capa de servicio es mi API interna y debería usar mi capa de controlador para exponer eso al mundo, por lo tanto, debería decidir quién tiene acceso a qué. Además, si quiero construir herramientas/scripts internos, etc., no es necesario que construya la autenticación en ellos para usar mi capa de servicio. –
Tenga cuidado de no mezclar autenticación y control de acceso. La autenticación puede (¿debería?) Entrar en el módulo, antes de que las clases de dominio entren en la imagen. Solo cuando hayas establecido la identidad del usuario. Ejemplo: if (! $ AuthService-> hasIdentity()) dentro de su modelo de servicio de dominio. – dualmon
@JamieSutherland: No estoy de acuerdo. Los servicios definen la lógica comercial; los controladores son el puente entre las solicitudes y la lógica comercial apropiada. Por ejemplo, solo tendría un servicio único para ordenar un producto, pero podría tener múltiples controladores para solicitudes HTTP, solicitudes de API, etc. Si le preocupa que la ACL solicite información específica (por ejemplo, a través de HTTP, puede esperar una sesión de usuario en la que esperaría una clave secreta para las solicitudes de la API), generalice su implementación de ACL para permitir esto. – moteutsch