mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Compare commits
No commits in common. "master" and "v1.17.0" have entirely different histories.
18 changed files with 1292 additions and 1627 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
1.17.1
|
1.17.0
|
||||||
|
|
|
@ -128,8 +128,6 @@ const PLACEHOLDERS = [
|
||||||
['[[BARCODE_QR]]', 'QR code linking to this element'],
|
['[[BARCODE_QR]]', 'QR code linking to this element'],
|
||||||
['[[BARCODE_C128]]', 'Code 128 barcode linking to this element'],
|
['[[BARCODE_C128]]', 'Code 128 barcode linking to this element'],
|
||||||
['[[BARCODE_C39]]', 'Code 39 barcode linking to this element'],
|
['[[BARCODE_C39]]', 'Code 39 barcode linking to this element'],
|
||||||
['[[BARCODE_C93]]', 'Code 93 barcode linking to this element'],
|
|
||||||
['[[BARCODE_DATAMATRIX]]', 'Datamatrix code linking to this element'],
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -69,8 +69,6 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
|
||||||
'QR code linking to this element': 'QR Code verknüpft mit diesem Element',
|
'QR code linking to this element': 'QR Code verknüpft mit diesem Element',
|
||||||
'Code 128 barcode linking to this element': 'Code 128 Barcode verknüpft mit diesem Element',
|
'Code 128 barcode linking to this element': 'Code 128 Barcode verknüpft mit diesem Element',
|
||||||
'Code 39 barcode linking to this element': 'Code 39 Barcode verknüpft mit diesem Element',
|
'Code 39 barcode linking to this element': 'Code 39 Barcode verknüpft mit diesem Element',
|
||||||
'Code 93 barcode linking to this element': 'Code 93 Barcode verknüpft mit diesem Element',
|
|
||||||
'Datamatrix code linking to this element': 'Datamatrix Code verknüpft mit diesem Element',
|
|
||||||
|
|
||||||
'Location ID': 'Lagerort ID',
|
'Location ID': 'Lagerort ID',
|
||||||
'Name': 'Name',
|
'Name': 'Name',
|
||||||
|
|
|
@ -43,7 +43,6 @@
|
||||||
"omines/datatables-bundle": "^0.9.1",
|
"omines/datatables-bundle": "^0.9.1",
|
||||||
"paragonie/sodium_compat": "^1.21",
|
"paragonie/sodium_compat": "^1.21",
|
||||||
"part-db/label-fonts": "^1.0",
|
"part-db/label-fonts": "^1.0",
|
||||||
"rhukster/dom-sanitizer": "^1.0",
|
|
||||||
"runtime/frankenphp-symfony": "^0.2.0",
|
"runtime/frankenphp-symfony": "^0.2.0",
|
||||||
"s9e/text-formatter": "^2.1",
|
"s9e/text-formatter": "^2.1",
|
||||||
"scheb/2fa-backup-code": "^6.8.0",
|
"scheb/2fa-backup-code": "^6.8.0",
|
||||||
|
|
887
composer.lock
generated
887
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -52,11 +52,6 @@ server {
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set Content-Security-Policy for svg files, to block embedded javascript in there
|
|
||||||
location ~* \.svg$ {
|
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';";
|
|
||||||
}
|
|
||||||
|
|
||||||
error_log /var/log/nginx/parts.error.log;
|
error_log /var/log/nginx/parts.error.log;
|
||||||
access_log /var/log/nginx/parts.access.log;
|
access_log /var/log/nginx/parts.access.log;
|
||||||
|
|
|
@ -127,6 +127,9 @@ You must create an organization there and create a "Production app". Most settin
|
||||||
grant access to the "Product Information" API.
|
grant access to the "Product Information" API.
|
||||||
You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below).
|
You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below).
|
||||||
|
|
||||||
|
**Attention**: Currently only the "Product Information V3 (Deprecated)" is supported by Part-DB.
|
||||||
|
Using "Product Information V4" will not work.
|
||||||
|
|
||||||
The following env configuration options are available:
|
The following env configuration options are available:
|
||||||
|
|
||||||
* `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory)
|
* `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory)
|
||||||
|
|
|
@ -118,10 +118,3 @@ DirectoryIndex index.php
|
||||||
# RedirectTemp cannot be used instead
|
# RedirectTemp cannot be used instead
|
||||||
</IfModule>
|
</IfModule>
|
||||||
</IfModule>
|
</IfModule>
|
||||||
|
|
||||||
# Set Content-Security-Policy for svg files (and compressed variants), to block embedded javascript in there
|
|
||||||
<IfModule mod_headers.c>
|
|
||||||
<FilesMatch "\.(svg|svg\.gz|svg\.br)$">
|
|
||||||
Header set Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';"
|
|
||||||
</FilesMatch>
|
|
||||||
</IfModule>
|
|
|
@ -1,90 +0,0 @@
|
||||||
<?php
|
|
||||||
/*
|
|
||||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 - 2025 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\Command\Attachments;
|
|
||||||
|
|
||||||
use App\Entity\Attachments\Attachment;
|
|
||||||
use App\Services\Attachments\AttachmentSubmitHandler;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
|
|
||||||
#[AsCommand('partdb:attachments:sanitize-svg', "Sanitize uploaded SVG files.")]
|
|
||||||
class SanitizeSVGAttachmentsCommand extends Command
|
|
||||||
{
|
|
||||||
public function __construct(private readonly EntityManagerInterface $entityManager, private readonly AttachmentSubmitHandler $attachmentSubmitHandler, ?string $name = null)
|
|
||||||
{
|
|
||||||
parent::__construct($name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function configure(): void
|
|
||||||
{
|
|
||||||
$this->setHelp('This command allows to sanitize SVG files uploaded via attachments. This happens automatically since version 1.17.1, this command is intended to be used for older files.');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
||||||
{
|
|
||||||
$io = new SymfonyStyle($input, $output);
|
|
||||||
|
|
||||||
$io->info('This command will sanitize all uploaded SVG files. This is only required if you have uploaded (untrusted) SVG files before version 1.17.1. If you are running a newer version, you don\'t need to run this command (again).');
|
|
||||||
if (!$io->confirm('Do you want to continue?', false)) {
|
|
||||||
$io->success('Command aborted.');
|
|
||||||
return Command::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$io->info('Sanitizing SVG files...');
|
|
||||||
|
|
||||||
//Finding all attachments with svg files
|
|
||||||
$qb = $this->entityManager->createQueryBuilder();
|
|
||||||
$qb->select('a')
|
|
||||||
->from(Attachment::class, 'a')
|
|
||||||
->where('a.internal_path LIKE :pattern ESCAPE \'#\'')
|
|
||||||
->orWhere('a.original_filename LIKE :pattern ESCAPE \'#\'')
|
|
||||||
->setParameter('pattern', '%.svg');
|
|
||||||
|
|
||||||
$attachments = $qb->getQuery()->getResult();
|
|
||||||
$io->note('Found '.count($attachments).' attachments with SVG files.');
|
|
||||||
|
|
||||||
if (count($attachments) === 0) {
|
|
||||||
$io->success('No SVG files found.');
|
|
||||||
return Command::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$io->info('Sanitizing SVG files...');
|
|
||||||
$io->progressStart(count($attachments));
|
|
||||||
foreach ($attachments as $attachment) {
|
|
||||||
/** @var Attachment $attachment */
|
|
||||||
$io->note('Sanitizing attachment '.$attachment->getId().' ('.($attachment->getFilename() ?? '???').')');
|
|
||||||
$this->attachmentSubmitHandler->sanitizeSVGAttachment($attachment);
|
|
||||||
$io->progressAdvance();
|
|
||||||
|
|
||||||
}
|
|
||||||
$io->progressFinish();
|
|
||||||
|
|
||||||
$io->success('Sanitization finished. All SVG files have been sanitized.');
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -318,7 +318,6 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
||||||
return new ArrayCollection();
|
return new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
//@phpstan-ignore-next-line
|
|
||||||
return $this->children ?? new ArrayCollection();
|
return $this->children ?? new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ class AttachmentSubmitHandler
|
||||||
'htpasswd', ''];
|
'htpasswd', ''];
|
||||||
|
|
||||||
public function __construct(protected AttachmentPathResolver $pathResolver, protected bool $allow_attachments_downloads,
|
public function __construct(protected AttachmentPathResolver $pathResolver, protected bool $allow_attachments_downloads,
|
||||||
protected HttpClientInterface $httpClient, protected MimeTypesInterface $mimeTypes, protected readonly SVGSanitizer $SVGSanitizer,
|
protected HttpClientInterface $httpClient, protected MimeTypesInterface $mimeTypes,
|
||||||
protected FileTypeFilterTools $filterTools, /**
|
protected FileTypeFilterTools $filterTools, /**
|
||||||
* @var string The user configured maximum upload size. This is a string like "10M" or "1G" and will be converted to
|
* @var string The user configured maximum upload size. This is a string like "10M" or "1G" and will be converted to
|
||||||
*/
|
*/
|
||||||
|
@ -214,9 +214,6 @@ class AttachmentSubmitHandler
|
||||||
//Move the attachment files to secure location (and back) if needed
|
//Move the attachment files to secure location (and back) if needed
|
||||||
$this->moveFile($attachment, $secure_attachment);
|
$this->moveFile($attachment, $secure_attachment);
|
||||||
|
|
||||||
//Sanitize the SVG if needed
|
|
||||||
$this->sanitizeSVGAttachment($attachment);
|
|
||||||
|
|
||||||
//Rename blacklisted (unsecure) files to a better extension
|
//Rename blacklisted (unsecure) files to a better extension
|
||||||
$this->renameBlacklistedExtensions($attachment);
|
$this->renameBlacklistedExtensions($attachment);
|
||||||
|
|
||||||
|
@ -501,32 +498,4 @@ class AttachmentSubmitHandler
|
||||||
|
|
||||||
return $this->max_upload_size_bytes;
|
return $this->max_upload_size_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitizes the given SVG file, if the attachment is an internal SVG file.
|
|
||||||
* @param Attachment $attachment
|
|
||||||
* @return Attachment
|
|
||||||
*/
|
|
||||||
public function sanitizeSVGAttachment(Attachment $attachment): Attachment
|
|
||||||
{
|
|
||||||
//We can not do anything on builtins or external ressources
|
|
||||||
if ($attachment->isBuiltIn() || !$attachment->hasInternal()) {
|
|
||||||
return $attachment;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Resolve the path to the file
|
|
||||||
$path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath());
|
|
||||||
|
|
||||||
//Check if the file exists
|
|
||||||
if (!file_exists($path)) {
|
|
||||||
return $attachment;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if the file is an SVG
|
|
||||||
if ($attachment->getExtension() === "svg") {
|
|
||||||
$this->SVGSanitizer->sanitizeFile($path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $attachment;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
<?php
|
|
||||||
/*
|
|
||||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 - 2025 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\Services\Attachments;
|
|
||||||
|
|
||||||
use Rhukster\DomSanitizer\DOMSanitizer;
|
|
||||||
|
|
||||||
class SVGSanitizer
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitizes the given SVG string by removing any potentially harmful content (like inline scripts).
|
|
||||||
* @param string $input
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function sanitizeString(string $input): string
|
|
||||||
{
|
|
||||||
return (new DOMSanitizer(DOMSanitizer::SVG))->sanitize($input);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitizes the given SVG file by removing any potentially harmful content (like inline scripts).
|
|
||||||
* The sanitized content is written back to the file.
|
|
||||||
* @param string $filepath
|
|
||||||
*/
|
|
||||||
public function sanitizeFile(string $filepath): void
|
|
||||||
{
|
|
||||||
//Open the file and read the content
|
|
||||||
$content = file_get_contents($filepath);
|
|
||||||
if ($content === false) {
|
|
||||||
throw new \RuntimeException('Could not read file: ' . $filepath);
|
|
||||||
}
|
|
||||||
//Sanitize the content
|
|
||||||
$sanitizedContent = $this->sanitizeString($content);
|
|
||||||
//Write the sanitized content back to the file
|
|
||||||
file_put_contents($filepath, $sanitizedContent);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -49,8 +49,8 @@ class PollinProvider implements InfoProviderInterface
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'Pollin',
|
'name' => 'Pollin',
|
||||||
'description' => 'Webscraping from pollin.de to get part information',
|
'description' => 'Webscrapping from pollin.de to get part information',
|
||||||
'url' => 'https://www.pollin.de/',
|
'url' => 'https://www.reichelt.de/',
|
||||||
'disabled_help' => 'Set PROVIDER_POLLIN_ENABLED env to 1'
|
'disabled_help' => 'Set PROVIDER_POLLIN_ENABLED env to 1'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ class ReicheltProvider implements InfoProviderInterface
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'Reichelt',
|
'name' => 'Reichelt',
|
||||||
'description' => 'Webscraping from reichelt.com to get part information',
|
'description' => 'Webscrapping from reichelt.com to get part information',
|
||||||
'url' => 'https://www.reichelt.com/',
|
'url' => 'https://www.reichelt.com/',
|
||||||
'disabled_help' => 'Set PROVIDER_REICHELT_ENABLED env to 1'
|
'disabled_help' => 'Set PROVIDER_REICHELT_ENABLED env to 1'
|
||||||
];
|
];
|
||||||
|
|
|
@ -63,24 +63,12 @@ final class BarcodeProvider implements PlaceholderProviderInterface
|
||||||
return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target);
|
return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('[[BARCODE_DATAMATRIX]]' === $placeholder) {
|
|
||||||
$label_options = new LabelOptions();
|
|
||||||
$label_options->setBarcodeType(BarcodeType::DATAMATRIX);
|
|
||||||
return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('[[BARCODE_C39]]' === $placeholder) {
|
if ('[[BARCODE_C39]]' === $placeholder) {
|
||||||
$label_options = new LabelOptions();
|
$label_options = new LabelOptions();
|
||||||
$label_options->setBarcodeType(BarcodeType::CODE39);
|
$label_options->setBarcodeType(BarcodeType::CODE39);
|
||||||
return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target);
|
return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('[[BARCODE_C93]]' === $placeholder) {
|
|
||||||
$label_options = new LabelOptions();
|
|
||||||
$label_options->setBarcodeType(BarcodeType::CODE93);
|
|
||||||
return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('[[BARCODE_C128]]' === $placeholder) {
|
if ('[[BARCODE_C128]]' === $placeholder) {
|
||||||
$label_options = new LabelOptions();
|
$label_options = new LabelOptions();
|
||||||
$label_options->setBarcodeType(BarcodeType::CODE128);
|
$label_options->setBarcodeType(BarcodeType::CODE128);
|
||||||
|
|
|
@ -12346,29 +12346,5 @@ Notare che non è possibile impersonare un utente disattivato. Quando si prova a
|
||||||
<target>Visualizza la versione esterna</target>
|
<target>Visualizza la versione esterna</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="X9HUFrv" name="part.table.actions.error">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>part.table.actions.error</source>
|
|
||||||
<target>Si sono verificati %count% errori durante l'esecuzione dell'azione:</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id=".ppbsNn" name="part.table.actions.error_detail">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>part.table.actions.error_detail</source>
|
|
||||||
<target>%part_name% (ID: %part_id%): %message%</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="4wpp6h." name="part_list.action.action.change_location">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>part_list.action.action.change_location</source>
|
|
||||||
<target>Cambia posizione (solo per componenti con stock singolo)</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="9_9I.m4" name="parts.table.action_handler.error.part_lots_multiple">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>parts.table.action_handler.error.part_lots_multiple</source>
|
|
||||||
<target>Questo componente contiene più di uno stock. Cambia manualmente la posizione per selezionare quale stock scegliere.</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -1,23 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en" trgLang="fr">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en" trgLang="fr">
|
||||||
<file id="security.en">
|
<file id="security.en">
|
||||||
<unit id="GrLNa9P" name="user.login_error.user_disabled">
|
<unit id="aazoCks" name="user.login_error.user_disabled">
|
||||||
<segment state="translated">
|
<segment state="translated">
|
||||||
<source>user.login_error.user_disabled</source>
|
<source>user.login_error.user_disabled</source>
|
||||||
<target>Votre compte est désactivé ! Contactez un administrateur si vous pensez que c'est une erreur.</target>
|
<target>Votre compte est désactivé ! Contactez un administrateur si vous pensez que c'est une erreur.</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="IFQ5XrG" name="saml.error.cannot_login_local_user_per_saml">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>saml.error.cannot_login_local_user_per_saml</source>
|
|
||||||
<target>Impossible de se connecter via le SSO (Single Sign On) !</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="wOYPZmb" name="saml.error.cannot_login_saml_user_locally">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>saml.error.cannot_login_saml_user_locally</source>
|
|
||||||
<target>Vous ne pouvez pas utiliser l'authentification par mot de passe ! Veuillez utiliser le SSO!</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue