ZUGFeRD: WIP Allow blocking factur-x and xrechnung invoice files with config flags

This commit is contained in:
Jonathan Jenne 2022-11-16 16:33:35 +01:00
parent f4adba98eb
commit 0410e11b59
15 changed files with 146 additions and 52 deletions

View File

@ -8,42 +8,87 @@ Imports DigitalData.Modules.Logging
Imports GdPicture14 Imports GdPicture14
Public Class ZUGFeRDInterface Public Class ZUGFeRDInterface
Private _logConfig As LogConfig Private ReadOnly _logConfig As LogConfig
Private _logger As Logger Private ReadOnly _logger As Logger
Private ReadOnly _Options As ZugferdOptions
Private ReadOnly ValidFilenames As New List(Of String) From {
PDFEmbeds.ZUGFERD_XML_FILENAME.ToUpper,
PDFEmbeds.FACTUR_X_XML_FILENAME_DE.ToUpper,
PDFEmbeds.FACTUR_X_XML_FILENAME_FR.ToUpper
}
Private AllowedFilenames As New List(Of String)
Public Enum ErrorType Public Enum ErrorType
NoValidFile NoValidFile
NoZugferd NoZugferd
NoValidZugferd NoValidZugferd
MissingProperties MissingProperties
UnsupportedFormat
UnknownError UnknownError
End Enum End Enum
Public ReadOnly Property FileGroup As FileGroups Public ReadOnly Property FileGroup As FileGroups
Public ReadOnly Property PropertyValues As PropertyValues Public ReadOnly Property PropertyValues As PropertyValues
Public Sub New(LogConfig As LogConfig, GDPictureKey As String) Public Class ZugferdOptions
_logConfig = LogConfig Public Property AllowFacturX_Filename As Boolean = True
Public Property AllowXRechnung_Filename As Boolean = True
End Class
''' <summary>
''' Create a new instance of ZUGFeRDInterface
''' </summary>
''' <param name="pLogConfig">A LogConfig object</param>
''' <param name="pGDPictureKey">A valid GDPicture License</param>
''' <param name="pOptions">Optional parameters to control various settings</param>
Public Sub New(pLogConfig As LogConfig, pGDPictureKey As String, Optional pOptions As ZugferdOptions = Nothing)
_logConfig = pLogConfig
_logger = _logConfig.GetLogger() _logger = _logConfig.GetLogger()
If pOptions Is Nothing Then
_Options = New ZugferdOptions()
Else
_Options = pOptions
End If
ApplyFilenameOptions(_Options)
FileGroup = New FileGroups(_logConfig) FileGroup = New FileGroups(_logConfig)
PropertyValues = New PropertyValues(_logConfig) PropertyValues = New PropertyValues(_logConfig)
Try Try
Dim oLicenseManager As New LicenseManager Dim oLicenseManager As New LicenseManager
oLicenseManager.RegisterKEY(GDPictureKey) oLicenseManager.RegisterKEY(pGDPictureKey)
Catch ex As Exception Catch ex As Exception
_logger.Warn("GDPicture License could not be registered!") _logger.Warn("GDPicture License could not be registered!")
_logger.Error(ex) _logger.Error(ex)
End Try End Try
End Sub End Sub
Private Sub ApplyFilenameOptions(pOptions As ZugferdOptions)
Dim oAllowedFilenames As List(Of String) = ValidFilenames
If pOptions.AllowFacturX_Filename = False Then
oAllowedFilenames = oAllowedFilenames.
Except(New List(Of String) From {PDFEmbeds.FACTUR_X_XML_FILENAME_FR}).ToList()
End If
If pOptions.AllowXRechnung_Filename = False Then
oAllowedFilenames = oAllowedFilenames.
Except(New List(Of String) From {PDFEmbeds.FACTUR_X_XML_FILENAME_DE}).ToList()
End If
AllowedFilenames = oAllowedFilenames
End Sub
''' <summary> ''' <summary>
''' Validates a ZUGFeRD File and extracts the XML Document from it ''' Validates a ZUGFeRD File and extracts the XML Document from it
''' </summary> ''' </summary>
''' <param name="Path"></param> ''' <param name="Path"></param>
''' <exception cref="ZUGFeRDExecption"></exception> ''' <exception cref="ZUGFeRDExecption"></exception>
''' <returns></returns>
Public Function ExtractZUGFeRDFileWithGDPicture(Path As String) As Object Public Function ExtractZUGFeRDFileWithGDPicture(Path As String) As Object
Dim oXmlDocument = ValidateZUGFeRDFileWithGDPicture(Path) Dim oXmlDocument = ValidateZUGFeRDFileWithGDPicture(Path)
@ -59,7 +104,6 @@ Public Class ZUGFeRDInterface
''' </summary> ''' </summary>
''' <param name="Stream"></param> ''' <param name="Stream"></param>
''' <exception cref="ZUGFeRDExecption"></exception> ''' <exception cref="ZUGFeRDExecption"></exception>
''' <returns></returns>
Public Function ExtractZUGFeRDFileWithGDPicture(Stream As Stream) As Object Public Function ExtractZUGFeRDFileWithGDPicture(Stream As Stream) As Object
Dim oXmlDocument = ValidateZUGFeRDFileWithGDPicture(Stream) Dim oXmlDocument = ValidateZUGFeRDFileWithGDPicture(Stream)
@ -73,15 +117,15 @@ Public Class ZUGFeRDInterface
''' <summary> ''' <summary>
''' Validates a ZUGFeRD File and extracts the XML Document from it ''' Validates a ZUGFeRD File and extracts the XML Document from it
''' </summary> ''' </summary>
''' <param name="Stream"></param> ''' <param name="pStream"></param>
''' <exception cref="ZUGFeRDExecption"></exception> ''' <exception cref="ZUGFeRDExecption"></exception>
''' <returns></returns> ''' <returns>The embedded xml data as an XPath document</returns>
Public Function ValidateZUGFeRDFileWithGDPicture(Stream As Stream) As XPathDocument Public Function ValidateZUGFeRDFileWithGDPicture(pStream As Stream) As XPathDocument
Dim oEmbedExtractor = New PDFEmbeds(_logConfig) Dim oEmbedExtractor = New PDFEmbeds(_logConfig)
Dim oAllowedExtensions = New List(Of String) From {"xml"}
Try Try
Dim oFiles = oEmbedExtractor.Extract(Stream, oAllowedExtensions) ' Extract XML attachments only!
Dim oFiles = oEmbedExtractor.Extract(pStream, New List(Of String) From {"xml"})
' Attachments are in this case the files that are embedded into a pdf file, ' Attachments are in this case the files that are embedded into a pdf file,
' like for example the zugferd-invoice.xml file ' like for example the zugferd-invoice.xml file
@ -98,12 +142,18 @@ Public Class ZUGFeRDInterface
End Try End Try
End Function End Function
Public Function ValidateZUGFeRDFileWithGDPicture(Path As String) As XPathDocument ''' <summary>
''' Validates a ZUGFeRD File and extracts the XML Document from it
''' </summary>
''' <param name="pPath"></param>
''' <exception cref="ZUGFeRDExecption"></exception>
''' <returns>The embedded xml data as an XPath document</returns>
Public Function ValidateZUGFeRDFileWithGDPicture(pPath As String) As XPathDocument
Dim oEmbedExtractor = New PDFEmbeds(_logConfig) Dim oEmbedExtractor = New PDFEmbeds(_logConfig)
Dim oAllowedExtensions = New List(Of String) From {"xml"}
Try Try
Dim oFiles = oEmbedExtractor.Extract(Path, oAllowedExtensions) ' Extract XML attachments only!
Dim oFiles = oEmbedExtractor.Extract(pPath, New List(Of String) From {"xml"})
' Attachments are in this case the files that are embedded into a pdf file, ' Attachments are in this case the files that are embedded into a pdf file,
' like for example the zugferd-invoice.xml file ' like for example the zugferd-invoice.xml file
@ -120,34 +170,38 @@ Public Class ZUGFeRDInterface
End Try End Try
End Function End Function
Private Function HandleEmbeddedFiles(Results As List(Of PDFEmbeds.EmbeddedFile)) As XPathDocument Private Function HandleEmbeddedFiles(pResults As List(Of PDFEmbeds.EmbeddedFile)) As XPathDocument
Dim oXmlDocument As XPathDocument Dim oXmlDocument As XPathDocument
If Results Is Nothing Then If pResults Is Nothing Then
Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei, weil die Attachments nicht gelesen werden konnten.") Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei, weil die Attachments nicht gelesen werden konnten.")
End If End If
If Results.Count = 0 Then If pResults.Count = 0 Then
Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei, weil sie keine Attachments enthält.") Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei, weil sie keine Attachments enthält.")
End If End If
Dim oValidFilenames As New List(Of String) From {
PDFEmbeds.ZUGFERD_XML_FILENAME.ToUpper,
PDFEmbeds.FACTUR_X_XML_FILENAME_DE.ToUpper,
PDFEmbeds.FACTUR_X_XML_FILENAME_FR.ToUpper
}
' Find the first file which filename matches the valid filenames for embedded invoice files ' Find the first file which filename matches the valid filenames for embedded invoice files
Dim oFoundResult As PDFEmbeds.EmbeddedFile = Results. Dim oValidResult As PDFEmbeds.EmbeddedFile = pResults.
Where(Function(result) oValidFilenames.Contains(result.FileName.ToUpper)). Where(Function(result) ValidFilenames.Contains(result.FileName.ToUpper)).
FirstOrDefault() FirstOrDefault()
If oFoundResult Is Nothing Then If oValidResult Is Nothing Then
Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei, weil die zugferd-invoice.xml nicht gefunden wurde.") Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei, weil keine entsprechende XML-Datei gefunden wurde.")
End If
' Search the embedded files for the ones which are allowed as per the configuration.
' The config might say, allow ZUGFeRD but not Factur-X.
Dim oAllowedResult As PDFEmbeds.EmbeddedFile = pResults.
Where(Function(result) AllowedFilenames.Contains(result.FileName.ToUpper)).
FirstOrDefault()
If oAllowedResult Is Nothing Then
Throw New ZUGFeRDExecption(ErrorType.UnsupportedFormat, "Datei ist eine ZUGFeRD Datei, aber das Format wird nicht unterstützt.")
End If End If
Try Try
Using oStream As New MemoryStream(oFoundResult.FileContents) Using oStream As New MemoryStream(oAllowedResult.FileContents)
oXmlDocument = New XPathDocument(oStream) oXmlDocument = New XPathDocument(oStream)
End Using End Using
@ -163,9 +217,9 @@ Public Class ZUGFeRDInterface
End Try End Try
End Function End Function
Public Function SerializeZUGFeRDDocument(Document As XPathDocument) As Object Public Function SerializeZUGFeRDDocument(pDocument As XPathDocument) As Object
Try Try
Dim oNavigator As XPathNavigator = Document.CreateNavigator() Dim oNavigator As XPathNavigator = pDocument.CreateNavigator()
Dim oReader As XmlReader Dim oReader As XmlReader
Dim oResult = Nothing Dim oResult = Nothing

View File

@ -28,7 +28,7 @@ Public Class PDFEmbeds
Public Function Extract(FilePath As String, AllowedExtensions As List(Of String)) As List(Of EmbeddedFile) Public Function Extract(FilePath As String, AllowedExtensions As List(Of String)) As List(Of EmbeddedFile)
Dim oFile As New List(Of EmbeddedFile) Dim oFile As New List(Of EmbeddedFile)
Dim oFileInfo As FileInfo Dim oFileInfo As FileInfo
Dim oExtensions = AllowedExtensions.ConvertAll(New Converter(Of String, String)(Function(ext) ext.ToUpper)) Dim oExtensions = AllowedExtensions.Select(Function(ext) ext.ToUpper).ToList()
Logger.Debug("Extracting embedded files from [{0}]", FilePath) Logger.Debug("Extracting embedded files from [{0}]", FilePath)
@ -69,7 +69,7 @@ Public Class PDFEmbeds
''' <param name="AllowedExtensions">List of allowed extensions to be extracted</param> ''' <param name="AllowedExtensions">List of allowed extensions to be extracted</param>
Public Function Extract(Stream As Stream, AllowedExtensions As List(Of String)) As List(Of EmbeddedFile) Public Function Extract(Stream As Stream, AllowedExtensions As List(Of String)) As List(Of EmbeddedFile)
Dim oResults As New List(Of EmbeddedFile) Dim oResults As New List(Of EmbeddedFile)
Dim oExtensions = AllowedExtensions.ConvertAll(New Converter(Of String, String)(Function(ext) ext.ToUpper)) Dim oExtensions = AllowedExtensions.Select(Function(ext) ext.ToUpper).ToList()
Logger.Debug("Extracting embedded files from stream") Logger.Debug("Extracting embedded files from stream")

View File

@ -40,6 +40,14 @@ Public Class Exceptions
End Sub End Sub
End Class End Class
Public Class UnsupportedFerdException
Inherits ApplicationException
Public Sub New()
MyBase.New("ZUGFeRD document found but is not supported!")
End Sub
End Class
Public Class NoFerdsException Public Class NoFerdsException
Inherits ApplicationException Inherits ApplicationException

View File

@ -91,17 +91,17 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="EDMI\ADSync\ADSyncArgs.vb" /> <Compile Include="ADSync\ADSyncArgs.vb" />
<Compile Include="EDMI\ADSync\ADSyncJob.vb" /> <Compile Include="ADSync\ADSyncJob.vb" />
<Compile Include="EDMI\GraphQL\GraphQLArgs.vb" /> <Compile Include="GraphQL\GraphQLArgs.vb" />
<Compile Include="EDMI\GraphQL\GraphQLConfig.vb" /> <Compile Include="GraphQL\GraphQLConfig.vb" />
<Compile Include="EDMI\GraphQL\GraphQLJob.vb" /> <Compile Include="GraphQL\GraphQLJob.vb" />
<Compile Include="EDMI\GraphQL\GraphQLQuery.vb" /> <Compile Include="GraphQL\GraphQLQuery.vb" />
<Compile Include="EDMI\ZUGFeRD\EmailData.vb" /> <Compile Include="ZUGFeRD\EmailData.vb" />
<Compile Include="EDMI\ZUGFeRD\EmailFunctions.vb" /> <Compile Include="ZUGFeRD\EmailFunctions.vb" />
<Compile Include="EDMI\ZUGFeRD\EmailStrings.vb" /> <Compile Include="ZUGFeRD\EmailStrings.vb" />
<Compile Include="EDMI\ZUGFeRD\ImportZUGFeRDFiles.vb" /> <Compile Include="ZUGFeRD\ImportZUGFeRDFiles.vb" />
<Compile Include="EDMI\ZUGFeRD\WorkerArgs.vb" /> <Compile Include="ZUGFeRD\WorkerArgs.vb" />
<Compile Include="Exceptions.vb" /> <Compile Include="Exceptions.vb" />
<Compile Include="JobInterface.vb" /> <Compile Include="JobInterface.vb" />
<Compile Include="JobBase.vb" /> <Compile Include="JobBase.vb" />

View File

@ -37,4 +37,8 @@
<li>Betrags-Werte weisen ungültiges Format auf (25,01 anstatt 25.01)</li> <li>Betrags-Werte weisen ungültiges Format auf (25,01 anstatt 25.01)</li>
</ul></p> </ul></p>
" "
Public Const EMAIL_UNSUPPORTED_DOCUMENT = "
<p>Ihre Email enthielt ein ZUGFeRD Dokument, welches aber zur Zeit noch nicht unsterstützt wird.</p>
"
End Class End Class

View File

@ -26,35 +26,33 @@ Public Class ImportZUGFeRDFiles
Private Const DIRECTORY_DONT_MOVE = "DIRECTORY_DONT_MOVE" Private Const DIRECTORY_DONT_MOVE = "DIRECTORY_DONT_MOVE"
' List of allowed extensions for PDF/A Attachments ' List of allowed extensions for PDF/A Attachments
' This list should not contain xml so the zugferd xml file will be filtered out ' This list should not contain xml so the zugferd xml file will be filtered out
Private ReadOnly AllowedExtensions As List(Of String) = New List(Of String) From {"docx", "doc", "pdf", "xls", "xlsx", "ppt", "pptx", "txt"} Private ReadOnly AllowedExtensions As New List(Of String) From {"docx", "doc", "pdf", "xls", "xlsx", "ppt", "pptx", "txt"}
Private ReadOnly _logger As Logger Private ReadOnly _logger As Logger
Private ReadOnly _logConfig As LogConfig Private ReadOnly _logConfig As LogConfig
Private ReadOnly _zugferd As ZUGFeRDInterface
Private ReadOnly _firebird As Firebird Private ReadOnly _firebird As Firebird
Private ReadOnly _filesystem As Filesystem.File Private ReadOnly _filesystem As Filesystem.File
Private ReadOnly _EmailOutAccountId As Integer
Private ReadOnly _mssql As MSSQLServer Private ReadOnly _mssql As MSSQLServer
Private ReadOnly _email As EmailFunctions Private ReadOnly _email As EmailFunctions
Private ReadOnly _gdpictureLicenseKey As String
Private _zugferd As ZUGFeRDInterface
Private _EmailOutAccountId As Integer
Public Sub New(LogConfig As LogConfig, Firebird As Firebird, pEmailOutAccount As Integer, pPortalName As String, Optional MSSQL As MSSQLServer = Nothing) Public Sub New(LogConfig As LogConfig, Firebird As Firebird, Optional MSSQL As MSSQLServer = Nothing)
_logConfig = LogConfig _logConfig = LogConfig
_logger = LogConfig.GetLogger() _logger = LogConfig.GetLogger()
_firebird = Firebird _firebird = Firebird
_filesystem = New Filesystem.File(_logConfig) _filesystem = New Filesystem.File(_logConfig)
_mssql = MSSQL _mssql = MSSQL
_EmailOutAccountId = pEmailOutAccount
_email = New EmailFunctions(LogConfig, _mssql, _firebird) _email = New EmailFunctions(LogConfig, _mssql, _firebird)
_logger.Debug("Registering GDPicture License") _logger.Debug("Registering GDPicture License")
If _mssql IsNot Nothing Then If _mssql IsNot Nothing Then
Dim oSQL = "SELECT LICENSE FROM TBDD_3RD_PARTY_MODULES WHERE NAME = 'GDPICTURE'" Dim oSQL = "SELECT LICENSE FROM TBDD_3RD_PARTY_MODULES WHERE NAME = 'GDPICTURE'"
Dim oLicenseKey As String = _mssql.GetScalarValue(oSQL) _gdpictureLicenseKey = _mssql.GetScalarValue(oSQL)
_zugferd = New ZUGFeRDInterface(_logConfig, oLicenseKey)
Else Else
_logger.Warn("GDPicture License could not be registered! MSSQL is not enabled!") _logger.Warn("GDPicture License could not be registered! MSSQL is not enabled!")
Throw New ArgumentNullException("MSSQL") Throw New ArgumentNullException("MSSQL")
@ -125,6 +123,14 @@ Public Class ImportZUGFeRDFiles
Dim oPropertyExtractor = New PropertyValues(_logConfig) Dim oPropertyExtractor = New PropertyValues(_logConfig)
Dim oAttachmentExtractor = New PDFEmbeds(_logConfig) Dim oAttachmentExtractor = New PDFEmbeds(_logConfig)
_EmailOutAccountId = oArgs.EmailOutProfileId
Dim oOptions As New ZUGFeRDInterface.ZugferdOptions() With {
.AllowFacturX_Filename = oArgs.AllowFacturX,
.AllowXRechnung_Filename = oArgs.AllowXRechnung
}
_zugferd = New ZUGFeRDInterface(_logConfig, _gdpictureLicenseKey, oOptions)
_logger.Debug("Starting Job {0}", [GetType].Name) _logger.Debug("Starting Job {0}", [GetType].Name)
Try Try
@ -238,6 +244,10 @@ Public Class ImportZUGFeRDFiles
oEmailAttachmentFiles.Add(oFile) oEmailAttachmentFiles.Add(oFile)
Continue For Continue For
Case ZUGFeRDInterface.ErrorType.UnsupportedFormat
_logger.Info("File [{0}] is an unsupported ZUFeRD document format!")
Throw New UnsupportedFerdException()
Case ZUGFeRDInterface.ErrorType.NoValidZugferd Case ZUGFeRDInterface.ErrorType.NoValidZugferd
_logger.Warn("File [{0}] is an Incorrectly formatted ZUGFeRD document!", oFile.Name) _logger.Warn("File [{0}] is an Incorrectly formatted ZUGFeRD document!", oFile.Name)
Throw New InvalidFerdException() Throw New InvalidFerdException()
@ -380,6 +390,18 @@ Public Class ImportZUGFeRDFiles
_email.AddToEmailQueueMSSQL(oMessageId, oBody, oEmailData, "MD5HashException", _EmailOutAccountId, oArgs.NamePortal) _email.AddToEmailQueueMSSQL(oMessageId, oBody, oEmailData, "MD5HashException", _EmailOutAccountId, oArgs.NamePortal)
AddRejectedState(oMessageId, "MD5HashException", "Die gesendete Rechnung wurde bereits verarbeitet!", "", oSQLTransaction) AddRejectedState(oMessageId, "MD5HashException", "Die gesendete Rechnung wurde bereits verarbeitet!", "", oSQLTransaction)
Catch ex As UnsupportedFerdException
_logger.Error(ex)
' When UnsupportedFerdException is thrown, we don't have a MD5Hash yet.
' That 's why we set it to String.Empty here.
Create_HistoryEntry(oMessageId, String.Empty, "REJECTED - ZUGFeRD yes but unsupported format", oFBTransaction)
Dim oBody = EmailStrings.EMAIL_UNSUPPORTED_DOCUMENT
Dim oEmailData = MoveAndRenameEmailToRejected(oArgs, oMessageId)
_email.AddToEmailQueueMSSQL(oMessageId, oBody, oEmailData, "UnsupportedFerdException", _EmailOutAccountId, oArgs.NamePortal)
AddRejectedState(oMessageId, "UnsupportedFerdException", "Nicht unterstütztes Datenformat", "", oSQLTransaction)
Catch ex As InvalidFerdException Catch ex As InvalidFerdException
_logger.Error(ex) _logger.Error(ex)

View File

@ -14,10 +14,16 @@ Public Class WorkerArgs
' Property Parameter ' Property Parameter
Public PropertyMap As New Dictionary(Of String, XmlItemProperty) Public PropertyMap As New Dictionary(Of String, XmlItemProperty)
' Email Parameter
Public EmailOutProfileId As Integer = 0
' Misc Flag Parameters ' Misc Flag Parameters
Public InsertIntoSQLServer As Boolean = False Public InsertIntoSQLServer As Boolean = False
Public ExceptionEmailAddress As String = Nothing Public ExceptionEmailAddress As String = Nothing
Public IgnoreRejectionStatus As Boolean = False Public IgnoreRejectionStatus As Boolean = False
Public MaxAttachmentSizeInMegaBytes As Integer = -1 Public MaxAttachmentSizeInMegaBytes As Integer = -1
Public NamePortal As String = "NO PORTAL_NAME IN CONFIG" Public NamePortal As String = "NO PORTAL_NAME IN CONFIG"
Public AllowFacturX As Boolean = True
Public AllowXRechnung As Boolean = True
End Class End Class