Merge branch 'master' into api

This commit is contained in:
Jan Böhmer 2023-08-26 22:15:12 +02:00
commit 85f3ba6aaa
35 changed files with 1219 additions and 878 deletions

View file

@ -49,6 +49,8 @@ class HomepageController extends AbstractController
public function homepage(Request $request, GitVersionInfo $versionInfo, EntityManagerInterface $entityManager,
UpdateAvailableManager $updateAvailableManager): Response
{
$this->denyAccessUnlessGranted('HAS_ACCESS_PERMISSIONS');
if ($this->isGranted('@tools.lastActivity')) {
$table = $this->dataTable->createFromType(
LogDataTable::class,

View file

@ -160,7 +160,7 @@ class PartController extends AbstractController
public function new(Request $request, EntityManagerInterface $em, TranslatorInterface $translator,
AttachmentSubmitHandler $attachmentSubmitHandler, ProjectBuildPartHelper $projectBuildPartHelper,
#[MapEntity(mapping: ['id' => 'id'])] ?Part $part = null,
#[MapEntity(mapping: ['id' => 'project_id'])] ?Project $project = null): Response
#[MapEntity(mapping: ['project_id' => 'id'])] ?Project $project = null): Response
{
if ($part instanceof Part) {

View file

@ -46,6 +46,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
#[Route(path: '/user')]
class UserController extends BaseAdminController
@ -79,7 +80,8 @@ class UserController extends BaseAdminController
*/
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'user_edit')]
#[Route(path: '/{id}/', requirements: ['id' => '\d+'])]
public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ?string $timestamp = null): Response
public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper,
PermissionSchemaUpdater $permissionSchemaUpdater, ValidatorInterface $validator, ?string $timestamp = null): Response
{
//Do an upgrade of the permission schema if needed (so the user can see the permissions a user get on next request (even if it was not done yet)
$permissionSchemaUpdater->userUpgradeSchemaRecursively($entity);
@ -108,7 +110,7 @@ class UserController extends BaseAdminController
$this->addFlash('success', 'user.edit.reset_success');
} else {
$this->addFlash('danger', 'csfr_invalid');
$this->addFlash('error', 'csfr_invalid');
}
}
@ -120,15 +122,25 @@ class UserController extends BaseAdminController
$permissionPresetsHelper->applyPreset($entity, $preset);
$em->flush();
//Ensure that the user is valid after applying the preset
$errors = $validator->validate($entity);
if (count($errors) > 0) {
$this->addFlash('error', 'validator.noLockout');
//Refresh the entity to remove the changes
$em->refresh($entity);
} else {
$em->flush();
$this->addFlash('success', 'user.edit.permission_success');
$this->addFlash('success', 'user.edit.permission_success');
//We need to stop the execution here, or our permissions changes will be overwritten by the form values
return $this->redirectToRoute('user_edit', ['id' => $entity->getID()]);
//We need to stop the execution here, or our permissions changes will be overwritten by the form values
return $this->redirectToRoute('user_edit', ['id' => $entity->getID()]);
}
} else {
$this->addFlash('error', 'csfr_invalid');
}
$this->addFlash('danger', 'csfr_invalid');
}
return $this->_edit($entity, $request, $em, $timestamp);

View file

@ -0,0 +1,55 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\EventListener;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
#[AsEventListener]
class DisallowSearchEngineIndexingRequestListener
{
private const HEADER_NAME = 'X-Robots-Tag';
private readonly bool $enabled;
public function __construct(#[Autowire(param: 'partdb.demo_mode')] bool $demo_mode)
{
// Disable this listener in demo mode
$this->enabled = !$demo_mode;
}
public function __invoke(ResponseEvent $event): void
{
//Skip if disabled
if (!$this->enabled) {
return;
}
if (!$event->getResponse()->headers->has(self::HEADER_NAME)) {
$event->getResponse()->headers->set(self::HEADER_NAME, 'noindex');
}
}
}

View file

@ -62,6 +62,15 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface
'disabled' => !$this->security->isGranted('@parts_stock.withdraw'),
]);
$builder->add('dontCheckQuantity', CheckboxType::class, [
'label' => 'project.build.dont_check_quantity',
'help' => 'project.build.dont_check_quantity.help',
'required' => false,
'attr' => [
'data-controller' => 'pages--dont-check-quantity-checkbox'
]
]);
$builder->add('comment', TextType::class, [
'label' => 'part.info.withdraw_modal.comment',
'help' => 'part.info.withdraw_modal.comment.hint',
@ -124,6 +133,7 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface
}
$forms['comment']->setData($data->getComment());
$forms['dontCheckQuantity']->setData($data->isDontCheckQuantity());
$forms['addBuildsToBuildsPart']->setData($data->getAddBuildsToBuildsPart());
if (isset($forms['buildsPartLot'])) {
$forms['buildsPartLot']->setData($data->getBuildsPartLot());
@ -150,6 +160,8 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface
}
$data->setComment($forms['comment']->getData());
$data->setDontCheckQuantity($forms['dontCheckQuantity']->getData());
if (isset($forms['buildsPartLot'])) {
$lot = $forms['buildsPartLot']->getData();
if (!$lot) { //When the user selected "Create new lot", create a new lot

View file

@ -47,6 +47,8 @@ final class ProjectBuildRequest
private bool $add_build_to_builds_part = false;
private bool $dont_check_quantity = false;
/**
* @param Project $project The project that should be build
* @param int $number_of_builds The number of builds that should be created
@ -283,4 +285,26 @@ final class ProjectBuildRequest
{
return $this->number_of_builds;
}
/**
* If Set to true, the given withdraw amounts are used without any checks for requirements.
* @return bool
*/
public function isDontCheckQuantity(): bool
{
return $this->dont_check_quantity;
}
/**
* Set to true, the given withdraw amounts are used without any checks for requirements.
* @param bool $dont_check_quantity
* @return $this
*/
public function setDontCheckQuantity(bool $dont_check_quantity): ProjectBuildRequest
{
$this->dont_check_quantity = $dont_check_quantity;
return $this;
}
}

View file

@ -0,0 +1,45 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Security\Voter;
use App\Entity\UserSystem\User;
/**
* This voter implements a virtual role, which can be used if the user has any permission set to allowed.
* We use this to restrict access to the homepage.
*/
class HasAccessPermissionsVoter extends ExtendedVoter
{
public const ROLE = "HAS_ACCESS_PERMISSIONS";
protected function voteOnUser(string $attribute, $subject, User $user): bool
{
return $this->resolver->hasAnyPermissionSetToAllowInherited($user);
}
protected function supports(string $attribute, mixed $subject): bool
{
return $attribute === self::ROLE;
}
}

View file

@ -265,8 +265,7 @@ class PKDatastructureImporter
{
$count = $this->importElementsWithCategory($data, Storelocation::class, 'storagelocation');
//Footprints have both attachments and images
$this->importAttachments($data, 'storagelocationimage', Storelocation::class, 'footprint_id', StorelocationAttachment::class);
$this->importAttachments($data, 'storagelocationimage', Storelocation::class, 'storageLocation_id', StorelocationAttachment::class);
return $count;
}

View file

@ -23,7 +23,9 @@ declare(strict_types=1);
namespace App\Services\System;
use Psr\Log\LoggerInterface;
use Shivas\VersioningBundle\Service\VersionManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
@ -41,7 +43,8 @@ class UpdateAvailableManager
public function __construct(private readonly HttpClientInterface $httpClient,
private readonly CacheInterface $updateCache, private readonly VersionManagerInterface $versionManager,
private readonly bool $check_for_updates)
private readonly bool $check_for_updates, private readonly LoggerInterface $logger,
#[Autowire(param: 'kernel.debug')] private readonly bool $is_dev_mode)
{
}
@ -107,17 +110,34 @@ class UpdateAvailableManager
return $this->updateCache->get(self::CACHE_KEY, function (ItemInterface $item) {
$item->expiresAfter(self::CACHE_TTL);
$response = $this->httpClient->request('GET', self::API_URL);
$result = $response->toArray();
$tag_name = $result['tag_name'];
try {
$response = $this->httpClient->request('GET', self::API_URL);
$result = $response->toArray();
$tag_name = $result['tag_name'];
// Remove the leading 'v' from the tag name
$version = substr($tag_name, 1);
// Remove the leading 'v' from the tag name
$version = substr($tag_name, 1);
return [
'version' => $version,
'url' => $result['html_url'],
];
return [
'version' => $version,
'url' => $result['html_url'],
];
} catch (\Exception $e) {
//When we are in dev mode, throw the exception, otherwise just silently log it
if ($this->is_dev_mode) {
throw $e;
}
//In the case of an error, try it again after half of the cache time
$item->expiresAfter(self::CACHE_TTL / 2);
$this->logger->error('Checking for updates failed: ' . $e->getMessage());
return [
'version' => '0.0.1',
'url' => 'update-checking-error'
];
}
});
}
}

View file

@ -271,6 +271,27 @@ class PermissionManager
}
}
/**
* This function checks if the given user has any permission set to allow, either directly or inherited.
* @param User $user
* @return bool
*/
public function hasAnyPermissionSetToAllowInherited(User $user): bool
{
//Iterate over all permissions
foreach ($this->permission_structure['perms'] as $perm_key => $permission) {
//Iterate over all operations of the permission
foreach ($permission['operations'] as $op_key => $op) {
//Check if the user has the permission set to allow
if ($this->inherit($user, $perm_key, $op_key) === true) {
return true;
}
}
}
return false;
}
protected function generatePermissionStructure()
{
$cache = new ConfigCache($this->cache_file, $this->kernel_debug_enabled);

View file

@ -69,12 +69,12 @@ class ValidProjectBuildRequestValidator extends ConstraintValidator
->addViolation();
}
if ($withdraw_sum > $needed_amount) {
if ($withdraw_sum > $needed_amount && $value->isDontCheckQuantity() === false) {
$this->buildViolationForLot($lot, 'validator.project_build.lot_bigger_than_needed')
->addViolation();
}
if ($withdraw_sum < $needed_amount) {
if ($withdraw_sum < $needed_amount && $value->isDontCheckQuantity() === false) {
$this->buildViolationForLot($lot, 'validator.project_build.lot_smaller_than_needed')
->addViolation();
}