Release 2.18 (#4878)

This commit is contained in:
Kevin Papst
2024-06-16 13:15:49 +02:00
committed by GitHub
parent 8792a1df09
commit 987b46bf8f
46 changed files with 1768 additions and 814 deletions

View File

@@ -26,6 +26,22 @@ final class ApiRequestMatcher implements RequestMatcherInterface
return false;
}
// ------------------------------------------------------------------------------------
// the next two checks are primarily here to make sure to return proper error messages
// let's use this firewall if a Bearer token is set in the header
// other cases like "bearer" are rejected earlier
if (($auth = $request->headers->get('Authorization')) !== null && str_starts_with($auth, 'Bearer ')) {
return true;
}
// let's use this firewall if the deprecated username & token combination is available
if ($request->headers->has(TokenAuthenticator::HEADER_USERNAME) &&
$request->headers->has(TokenAuthenticator::HEADER_TOKEN)) {
return true;
}
// ------------------------------------------------------------------------------------
// checking for a previous session allows us to skip the API firewall and token access handler
// we simply re-use the existing session when doing API calls from the frontend.
// it is not necessary to check headers. if there is no valid session, we should always use this firewall

View File

@@ -31,7 +31,7 @@ final class ConfigurationController extends BaseApiController
* Returns the timesheet configuration
*/
#[OA\Response(response: 200, description: 'Returns the instance specific timesheet configuration', content: new OA\JsonContent(ref: new Model(type: TimesheetConfig::class)))]
#[Route(methods: ['GET'], path: '/config/timesheet')]
#[Route(path: '/config/timesheet', methods: ['GET'])]
public function timesheetConfigAction(SystemConfiguration $configuration): Response
{
$model = new TimesheetConfig();
@@ -46,4 +46,17 @@ final class ConfigurationController extends BaseApiController
return $this->viewHandler->handle($view);
}
/**
* Returns the configured color codes and names
*/
#[OA\Response(response: 200, description: 'Returns the configured color codes and names', content: new OA\JsonContent(type: 'object', example: ['Red' => '#ff0000'], additionalProperties: new OA\AdditionalProperties(type: 'string')))]
#[Route(path: '/config/colors', methods: ['GET'])]
public function colorConfigAction(SystemConfiguration $configuration): Response
{
$view = new View($configuration->getThemeColors(), 200);
$view->getContext()->setGroups(['Default']);
return $this->viewHandler->handle($view);
}
}

View File

@@ -39,7 +39,7 @@ final class TagController extends BaseApiController
}
/**
* Fetch all existing tags
* Deprecated: Fetch tags by filter as string collection
*/
#[OA\Response(response: 200, description: 'Returns the collection of all existing tags as string array', content: new OA\JsonContent(type: 'array', items: new OA\Items(type: 'string')))]
#[Route(methods: ['GET'], name: 'get_tags')]
@@ -56,6 +56,27 @@ final class TagController extends BaseApiController
return $this->viewHandler->handle($view);
}
/**
* Fetch tags by filter as entities
*/
#[OA\Response(response: 200, description: 'Find the collection of all matching tags', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/TagEntity')))]
#[Route(path: '/find', name: 'get_tags_full', methods: ['GET'])]
#[Rest\QueryParam(name: 'name', strict: true, nullable: true, description: 'Search term to filter tag list')]
public function findTags(ParamFetcherInterface $paramFetcher): Response
{
$filter = $paramFetcher->get('name');
$data = [];
if (\is_string($filter)) {
$data = $this->repository->findAllTags($filter);
}
$view = new View($data, 200);
$view->getContext()->setGroups(self::GROUPS_COLLECTION);
return $this->viewHandler->handle($view);
}
/**
* Creates a new tag
*/

View File

@@ -9,7 +9,6 @@
namespace App\API;
use App\Configuration\SystemConfiguration;
use App\Entity\AccessToken;
use App\Entity\User;
use App\Event\PrepareUserEvent;
@@ -18,6 +17,7 @@ use App\Form\API\UserApiEditForm;
use App\Repository\AccessTokenRepository;
use App\Repository\Query\UserQuery;
use App\Repository\UserRepository;
use App\User\UserService;
use App\Utils\SearchTerm;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Request\ParamFetcherInterface;
@@ -27,8 +27,6 @@ use OpenApi\Attributes as OA;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -45,8 +43,6 @@ final class UserController extends BaseApiController
public function __construct(
private readonly ViewHandlerInterface $viewHandler,
private readonly UserRepository $repository,
private readonly UserPasswordHasherInterface $passwordHasher,
private readonly SystemConfiguration $configuration
) {
}
@@ -139,13 +135,9 @@ final class UserController extends BaseApiController
#[OA\Post(description: 'Creates a new user and returns it afterwards')]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/UserCreateForm'))]
#[Route(methods: ['POST'], path: '', name: 'post_user')]
public function postAction(Request $request): Response
public function postAction(Request $request, UserService $userService): Response
{
$user = new User();
$user->setEnabled(true);
$user->setRoles([User::DEFAULT_ROLE]);
$user->setTimezone($this->configuration->getUserDefaultTimezone());
$user->setLanguage($this->configuration->getUserDefaultLanguage());
$user = $userService->createNewUser();
$form = $this->createForm(UserApiCreateForm::class, $user, [
'include_roles' => $this->isGranted('roles', $user),
@@ -156,24 +148,11 @@ final class UserController extends BaseApiController
$form->submit($request->request->all());
if ($form->isValid()) {
$plainPassword = $user->getPlainPassword();
if ($plainPassword === null) {
throw new BadRequestHttpException('Password cannot be empty');
}
$password = $this->passwordHasher->hashPassword($user, $plainPassword);
$user->setPassword($password);
if ($user->getPlainApiToken() !== null) {
$user->setApiToken($this->passwordHasher->hashPassword($user, $user->getPlainApiToken()));
}
$this->repository->saveUser($user);
$user = $userService->saveNewUser($user);
$view = new View($user, 200);
$view->getContext()->setGroups(self::GROUPS_ENTITY);
$user->eraseCredentials();
return $this->viewHandler->handle($view);
}