V-Beta-1.0.0

Vision is out of alpha !
This commit is contained in:
Xbird
2022-02-02 17:46:29 +01:00
parent 797bf35b47
commit 9f22f5b1ee
2297 changed files with 278438 additions and 76 deletions

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Security;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
class EmailVerifier
{
private $verifyEmailHelper;
private $mailer;
private $entityManager;
public function __construct(VerifyEmailHelperInterface $help, MailerInterface $mailer, EntityManagerInterface $em)
{
$this->verifyEmailHelper = $help;
$this->mailer = $mailer;
$this->entityManager = $em;
}
public function sendEmailConfirmation(string $verifMailRouteName, UserInterface $user, TemplatedEmail $email): void
{
$signatureComponents = $this->verifyEmailHelper->generateSignature(
$verifMailRouteName,
$user->getId(),
$user->getEmail(),
['id' => $user->getId()]
);
$context = $email->getContext();
$context['signedUrl'] = $signatureComponents->getSignedUrl();
$context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
$context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();
$email->context($context);
$this->mailer->send($email);
}
/**
* @throws VerifyEmailExceptionInterface
*/
public function handleEmailConfirmation(Request $request, UserInterface $user): void
{
$this->verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getEmail());
$user->setIsVerified(true);
$this->entityManager->persist($user);
$this->entityManager->flush();
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
class FormAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
private UrlGeneratorInterface $urlGenerator;
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function authenticate(Request $request): PassportInterface
{
$email = $request->request->get('email', '');
$request->getSession()->set(Security::LAST_USERNAME, $email);
return new Passport(
new UserBadge($email),
new PasswordCredentials($request->request->get('password', '')),
[
new CsrfTokenBadge('authenticate', $request->get('_csrf_token')),
new RememberMeBadge(),
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('home'));
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Security;
use App\Entity\User as AppUser;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
class UserChecker implements UserCheckerInterface
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function checkPreAuth(UserInterface $user)
{
if (!$user instanceof AppUser) {
return;
}
}
public function checkPostAuth(UserInterface $user)
{
if (!$user instanceof AppUser) {
return;
}
$token = $this->security->getToken();
$isNoImpersonation = ($token == null || $token->getUser() == $user);
if ($user->getIsDesactivated() && $isNoImpersonation) {
throw new CustomUserMessageAccountStatusException(
//Account desactivated, you can\'t log in with anymore. Contact an admin to restore your access
'exception_user_account_desactivated'
);
}
if (!$user->isVerified() && $isNoImpersonation) {
throw new CustomUserMessageAccountStatusException(
//'Please clic on the validation link you got on your mailbox.'
'exception_user_clic_link_mailbox'
);
}
if ($user->getMainGroup() === null || $user->getMainRank() === null) {
if ($user->hasRole('ROLE_ADMIN')) {
throw new CustomUserMessageAccountStatusException(
'exception_user_no_group_and_or_no_rank_admin'
);
}
throw new CustomUserMessageAccountStatusException(
'exception_user_no_group_and_or_no_rank'
);
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Security\Voter;
use App\Security\Voter\Tools\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class CommentVoter extends VoterInterface
{
protected function supports(string $attribute, $subject): bool
{
return in_array($attribute, ['edit', 'delete'])
&& $subject instanceof \App\Entity\Comment;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
//first, check if user is valid and has permissions
if (!$this->checkUser($token->getUser())) {
return false;
}
//then set prefix
$this->setPermissionsPrefix('comment');
switch ($attribute) {
case 'edit':
return $this->canEdit($subject);
break;
case 'delete':
return $this->canDelete($subject);
break;
}
return false;
}
private function canEdit($subject)
{
//user appartient au groupe du commentaire
if ($subject->getMainGroup() === $this->user->getMainGroup()) {
if ($subject->getCreator() === $this->user) { //c'est son propre commentaire
return $this->hasPermission('EDIT');
}
return $this->hasPermission('EDIT_GROUP');
}
//user n'appartient pas au groupe du commentaire
if ($subject->getMainGroup() !== $this->user->getMainGroup()) {
return $this->hasPermission('EDIT_OTHERGROUP');
}
return false; //false by default
}
private function canDelete($subject)
{
//user appartient au groupe du commentaire
if ($subject->getMainGroup() === $this->user->getMainGroup()) {
if ($subject->getCreator() === $this->user) { //c'est son propre commentaire
return $this->hasPermission('DELETE');
}
return $this->hasPermission('DELETE_GROUP');
}
//user n'appartient pas au groupe du commentaire
if ($subject->getMainGroup() !== $this->user->getMainGroup()) {
return $this->hasPermission('DELETE_OTHERGROUP');
}
return false; //false by default
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Security\Voter;
use App\Security\Voter\Tools\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class DirectoryVoter extends VoterInterface
{
protected function supports(string $attribute, $subject): bool
{
return in_array($attribute, ['create', 'edit', 'delete', 'edit_medical', 'merge'])
&& $subject instanceof \App\Entity\Directory;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
//first, check if user is valid and has permissions
if (!$this->checkUser($token->getUser())) {
return false;
}
switch ($attribute) {
case 'create':
case 'edit':
case 'delete':
case 'merge':
$this->setPermissionsPrefix('directory');
return $this->hasPermission($attribute);
break;
case 'edit_medical':
if (!$this->hasPermission('general_medical_view')) {
return false;
}
$this->setPermissionsPrefix('directory');
return $this->hasPermission($attribute);
break;
}
//finally, check if permission is in permission list of user
return $this->hasPermission($attribute);
}
}

View File

@@ -0,0 +1,178 @@
<?php
namespace App\Security\Voter;
use App\Security\Voter\Tools\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class DocumentVoter extends VoterInterface
{
protected function supports(string $attribute, $subject): bool
{
return in_array($attribute, ['view', 'create', 'edit', 'delete', 'archive'])
&& is_subclass_of($subject, 'App\Entity\Document');
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
//first, check if user is valid and has permissions
if (!$this->checkUser($token->getUser())) {
return false;
}
//if user is in admin mode, bypass
if ($this->user->getAdminMode()) {
return true;
}
//before all, check if user has specials perms
//reset prefix, maybe voter has been used before
$this->setPermissionsPrefix(null);
if ($subject->getNeedLegalAccess() && !$this->hasPermission('general_legal_view')) {
return false;
}
if ($subject->getNeedMedicalAccess() && !$this->hasPermission('general_medical_view')) {
return false;
}
if ($subject->getNeedGroupAdministration() && !$this->hasPermission('group_administrate')) {
return false;
}
//then set prefix
$this->setPermissionsPrefix($subject->getClassShort());
//Check upon attribute
switch ($attribute) {
case 'view':
return $this->canView($subject);
break;
case 'create':
return $this->canCreate($subject);
break;
case 'edit':
//if document is archived, cannot edit
if ($subject->getArchive()) {
return false;
}
return $this->canEdit($subject);
break;
case 'delete':
//if document is archived, cannot delete
if ($subject->getArchive()) {
return false;
}
return $this->canDelete($subject);
break;
case 'archive':
return $this->canArchive($subject);
break;
}
return false;
}
private function canView($subject)
{
//Document is public
if ($subject->getIsPublic()) {
return true;
}
//Document belong to user main group
if ($subject->getMainGroup() === $this->user->getMainGroup()) {
//Document has a restricted subGroup, is the user member of this subgroup ? or maybe he has a bypass perm ?
if (!$subject->getAllowedSubGroups()->isEmpty() && !$this->hasPermission('group_ignore_subgroups', true)) {
return $subject->getAllowedSubGroups()
->exists(function ($key, $value) {
return $this->user->getSubGroups()->contains($value);
});
}
return true;
}
//Document not belong to user main group, is the user main group allowed ?
//if the document as a subGroup, user can't have the subgroup of an other group
if ($subject->getMainGroup() !== $this->user->getMainGroup() && $subject->getAllowedSubGroups()->isEmpty()) {
return $subject->getAllowedGroups()->contains($this->user->getMainGroup());
}
return false; //false by default
}
private function canCreate()
{
return $this->hasPermission('CREATE');
}
private function canEdit($subject)
{
//Document belong to user main group
if ($subject->getMainGroup() === $this->user->getMainGroup()) {
if ($subject->getCreator() === $this->user) { //c'est son propre document
return $this->hasPermission('EDIT');
}
return $this->hasPermission('EDIT_GROUP');
}
//Document not belong to user main group
if ($subject->getMainGroup() !== $this->user->getMainGroup()) {
if ($subject->getAllowedGroups()->contains($this->user->getMainGroup())) {
return $this->hasPermission('EDIT_OTHERGROUP');
}
return false;
}
return false; //false by default
}
private function canDelete($subject)
{
//Document belong to user main group
if ($subject->getMainGroup() === $this->user->getMainGroup()) {
if ($subject->getCreator() === $this->user) { //document belong to user
return $this->hasPermission('DELETE');
}
return $this->hasPermission('DELETE_GROUP');
}
//Document not belong to user main group
if ($subject->getMainGroup() !== $this->user->getMainGroup()) {
if ($subject->getAllowedGroups()->contains($this->user->getMainGroup())) {
return $this->hasPermission('DELETE_OTHERGROUP');
}
return false;
}
return false; //false by default
}
private function canArchive($subject)
{
//Document belong to user main group
if ($subject->getMainGroup() === $this->user->getMainGroup()) {
if ($subject->getCreator() === $this->user) { //document belong to user
return $this->hasPermission('ARCHIVE');
}
return $this->hasPermission('ARCHIVE_GROUP');
}
//Document not belong to user main group
if ($subject->getMainGroup() !== $this->user->getMainGroup()) {
if ($subject->getAllowedGroups()->contains($this->user->getMainGroup())) {
return $this->hasPermission('ARCHIVE_OTHERGROUP');
}
return false;
}
return false; //false by default
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Security\Voter;
use App\Entity\User;
use App\Security\Voter\Tools\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class GroupVoter extends VoterInterface
{
protected function supports(string $attribute, $subject): bool
{
return in_array($attribute, ['administrate', 'motd', 'fire', 'rank', 'employee', 'sanction'])
&& $subject instanceof \App\Entity\Group;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
//first, check if user is valid and has permissions
/**
* @var User $User
*/
$User = $token->getUser();
if (!$this->checkUser($User)) {
return false;
}
//then set prefix
$this->setPermissionsPrefix('group');
if ($User->getMainGroup() != $subject && !$User->getAdminMode()) {
return false;
}
if (!$this->hasPermission('administrate')) {
return false;
} //base permission
switch ($attribute) {
case 'administrate':
return true;
break;
case 'motd':
case 'fire':
case 'employee':
case 'rank':
case 'sanction':
return $this->hasPermission($attribute);
break;
default:
return false;
break;
}
return false;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Security\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class NotificationVoter extends Voter
{
protected function supports(string $attribute, $subject): bool
{
return in_array($attribute, ['delete', 'markread'])
&& $subject instanceof \App\Entity\Notification;
}
/**
* vote based on Notification
*
* @param string $attribute
* @param Notification $subject
* @param TokenInterface $token
* @return boolean
*/
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
$user = $token->getUser();
// if the user is anonymous, do not grant access
if (!$user instanceof UserInterface) {
return false;
}
// ... (check conditions and return true to grant permission) ...
switch ($attribute) {
case 'delete':
case 'markread':
if ($subject->getReceiver() == $user) {
return true;
}
break;
}
return false;
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Security\Voter\Tools;
use App\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
abstract class VoterInterface extends Voter
{
private LoggerInterface $logger;
public User $user;
public array $userpermissions;
public ?string $permissionsPrefix;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
$this->setPermissionsPrefix(null);
}
public function setPermissionsPrefix(?string $prefix)
{
$this->permissionsPrefix = $prefix;
}
public function getPermissionsPrefix(): ?string
{
return $this->permissionsPrefix;
}
public function checkUser(User $User)
{
$this->user = $User;
// if the user is anonymous, do not grant access
if (!$this->user instanceof UserInterface) {
return false;
}
// if the user has no group or no rank, do not grant access
if ($this->user->getMainGroup() == null) {
return false;
}
if ($this->user->getMainRank() == null) {
return false;
}
//if user is in admin mode, bypass
if ($this->user->getAdminMode()) {
return true;
}
$this->userpermissions = $this->user->getMainRank()->getPermissions();
//if user (rank) has no permissions, do not grant access
if ($this->userpermissions == null) {
return false;
}
return true;
}
/**
* Check if user has the permission
*
* @param string $attribute
* @param boolean $ignoreprefix
* @return boolean
*/
public function hasPermission(string $attribute, bool $ignoreprefix = false): bool
{
//if user is in admin mode, bypass
if ($this->user->getAdminMode()) {
return true;
}
$permission = strtolower(
(
$this->getPermissionsPrefix() != null
&& !$ignoreprefix ?
$this->getPermissionsPrefix() . '_' : ''
) . $attribute
);
if (in_array($permission, array_map('strtolower', $this->userpermissions))) {
return true;
}
return false;
}
/**
* Determines if the attribute and subject are supported by this voter.
*
* @param string $attribute An attribute
* @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type
*
* @return bool True if the attribute and subject are supported, false otherwise
*/
abstract protected function supports(string $attribute, $subject);
}