cambios en apidenuncias
This commit is contained in:
490
.codex-links/WebIntranet/Models/CertificateLoginBridgeHelper.vb
Normal file
490
.codex-links/WebIntranet/Models/CertificateLoginBridgeHelper.vb
Normal file
@@ -0,0 +1,490 @@
|
||||
Imports System.Security.Cryptography.X509Certificates
|
||||
Imports System.Text.RegularExpressions
|
||||
Imports Newtonsoft.Json
|
||||
|
||||
Namespace Models
|
||||
Public Class CertificateLoginBridgeOptions
|
||||
Public Property PublicUrl As String = String.Empty
|
||||
Public Property ForwardedCertificateHeader As String = "X-ARR-ClientCert"
|
||||
Public Property AdditionalForwardedCertificateHeaders As List(Of String) = New List(Of String)()
|
||||
Public Property AllowedParentOrigins As List(Of String) = New List(Of String)()
|
||||
|
||||
Public Shared Function LoadFromConfiguration() As CertificateLoginBridgeOptions
|
||||
Dim publicUrl = FirstNonEmpty(
|
||||
ConfigurationManager.AppSettings("CertificateLoginPublicUrl"),
|
||||
ConfigurationManager.AppSettings("UrlCertLogin"))
|
||||
|
||||
Dim forwardedCertificateHeader = FirstNonEmpty(
|
||||
ConfigurationManager.AppSettings("CertificateForwardedHeader"),
|
||||
ConfigurationManager.AppSettings("CertHeaderName"),
|
||||
"X-ARR-ClientCert")
|
||||
|
||||
Dim additionalHeadersSetting = FirstNonEmpty(
|
||||
ConfigurationManager.AppSettings("CertificateAdditionalForwardedHeaders"),
|
||||
ConfigurationManager.AppSettings("AdditionalCertHeaders"))
|
||||
|
||||
Dim allowedOriginsSetting = FirstNonEmpty(
|
||||
ConfigurationManager.AppSettings("CertificateAllowedParentOrigins"),
|
||||
ConfigurationManager.AppSettings("AllowedParentOrigins"))
|
||||
|
||||
Return New CertificateLoginBridgeOptions With {
|
||||
.PublicUrl = publicUrl,
|
||||
.ForwardedCertificateHeader = forwardedCertificateHeader,
|
||||
.AdditionalForwardedCertificateHeaders = SplitSetting(additionalHeadersSetting),
|
||||
.AllowedParentOrigins = SplitSetting(allowedOriginsSetting)
|
||||
}
|
||||
End Function
|
||||
|
||||
Private Shared Function SplitSetting(value As String) As List(Of String)
|
||||
If String.IsNullOrWhiteSpace(value) Then
|
||||
Return New List(Of String)()
|
||||
End If
|
||||
|
||||
Return value.Split(New Char() {";"c, "|"c}, StringSplitOptions.RemoveEmptyEntries).
|
||||
Select(Function(item) item.Trim()).
|
||||
Where(Function(item) item <> String.Empty).
|
||||
ToList()
|
||||
End Function
|
||||
|
||||
Private Shared Function FirstNonEmpty(ParamArray values() As String) As String
|
||||
For Each value In values
|
||||
If Not String.IsNullOrWhiteSpace(value) Then
|
||||
Return value.Trim()
|
||||
End If
|
||||
Next
|
||||
|
||||
Return String.Empty
|
||||
End Function
|
||||
End Class
|
||||
|
||||
Public Class CertificateProxyLoginResponse
|
||||
Public Property Token As String = String.Empty
|
||||
Public Property User As CertificateProxyUser
|
||||
End Class
|
||||
|
||||
Public Class CertificateProxyUser
|
||||
Public Property NIF As String = String.Empty
|
||||
Public Property NOMBRE As String = String.Empty
|
||||
Public Property APELLIDOS As String = String.Empty
|
||||
Public Property ADMINISTRARPTYREGISTRO As Boolean?
|
||||
End Class
|
||||
|
||||
Public Module CertificateLoginBridgeHelper
|
||||
Private ReadOnly DefaultCertificateHeaders As String() = {
|
||||
"X-ARR-ClientCert",
|
||||
"X-Client-Cert",
|
||||
"X-Client-Cert-Der-Base64",
|
||||
"X-Client-Certificate",
|
||||
"X-Client-Certificate-Der-Base64",
|
||||
"X-SSL-CERT",
|
||||
"X-SSL-Client-Cert",
|
||||
"X-SSL-Client-Cert-Base64",
|
||||
"X-Tls-Client-Cert",
|
||||
"X-Tls-Client-Cert-Der-Base64",
|
||||
"X-Forwarded-Tls-Client-Cert",
|
||||
"Ssl-Client-Cert"
|
||||
}
|
||||
|
||||
Private ReadOnly DefaultCertificateServerVariables As String() = {
|
||||
"SSL_CLIENT_CERT",
|
||||
"HTTP_SSL_CLIENT_CERT",
|
||||
"HTTP_X_SSL_CLIENT_CERT",
|
||||
"HTTP_X_CLIENT_CERT",
|
||||
"HTTP_X_CLIENT_CERTIFICATE",
|
||||
"HTTP_X_ARR_CLIENTCERT",
|
||||
"CERT_CERTIFICATE"
|
||||
}
|
||||
|
||||
Public Function IsCertificateHostRequest(request As HttpRequest, publicUrl As String) As Boolean
|
||||
If request Is Nothing OrElse String.IsNullOrWhiteSpace(publicUrl) Then
|
||||
Return False
|
||||
End If
|
||||
|
||||
Dim configuredUri As Uri = Nothing
|
||||
If Not Uri.TryCreate(publicUrl, UriKind.Absolute, configuredUri) Then
|
||||
Return False
|
||||
End If
|
||||
|
||||
Dim forwardedHostRaw = GetFirstHeaderValue(request, "X-Forwarded-Host")
|
||||
Dim forwardedProtoRaw = GetFirstHeaderValue(request, "X-Forwarded-Proto")
|
||||
Dim forwardedPortRaw = GetFirstHeaderValue(request, "X-Forwarded-Port")
|
||||
Dim hasForwardedInfo =
|
||||
Not String.IsNullOrWhiteSpace(forwardedHostRaw) OrElse
|
||||
Not String.IsNullOrWhiteSpace(forwardedProtoRaw) OrElse
|
||||
Not String.IsNullOrWhiteSpace(forwardedPortRaw)
|
||||
|
||||
Dim requestHost = ExtractHost(If(hasForwardedInfo, forwardedHostRaw, Nothing))
|
||||
If String.IsNullOrWhiteSpace(requestHost) AndAlso request.Url IsNot Nothing Then
|
||||
requestHost = request.Url.Host
|
||||
End If
|
||||
|
||||
If Not String.Equals(requestHost, configuredUri.Host, StringComparison.OrdinalIgnoreCase) Then
|
||||
Return False
|
||||
End If
|
||||
|
||||
If Not hasForwardedInfo Then
|
||||
Return True
|
||||
End If
|
||||
|
||||
Dim requestScheme = FirstToken(forwardedProtoRaw)
|
||||
If String.IsNullOrWhiteSpace(requestScheme) Then
|
||||
requestScheme = configuredUri.Scheme
|
||||
End If
|
||||
|
||||
Dim requestPort = ParseNullableInt(forwardedPortRaw)
|
||||
If Not requestPort.HasValue Then
|
||||
requestPort = ExtractPort(forwardedHostRaw)
|
||||
End If
|
||||
If Not requestPort.HasValue Then
|
||||
requestPort = GetDefaultPort(requestScheme)
|
||||
End If
|
||||
|
||||
Dim configuredPort = If(configuredUri.IsDefaultPort, GetDefaultPort(configuredUri.Scheme), configuredUri.Port)
|
||||
Return String.Equals(requestScheme, configuredUri.Scheme, StringComparison.OrdinalIgnoreCase) AndAlso
|
||||
requestPort.Value = configuredPort
|
||||
End Function
|
||||
|
||||
Public Function IsAllowedParentOrigin(parentOrigin As String, allowedParentOrigins As IEnumerable(Of String)) As Boolean
|
||||
Dim normalizedParentOrigin = NormalizeOrigin(parentOrigin)
|
||||
If String.IsNullOrWhiteSpace(normalizedParentOrigin) Then
|
||||
Return False
|
||||
End If
|
||||
|
||||
Return allowedParentOrigins.
|
||||
Where(Function(origin) Not String.IsNullOrWhiteSpace(origin)).
|
||||
Select(Function(origin) NormalizeOrigin(origin)).
|
||||
Any(Function(origin) String.Equals(origin, normalizedParentOrigin, StringComparison.OrdinalIgnoreCase))
|
||||
End Function
|
||||
|
||||
Public Function ReadCertificate(
|
||||
request As HttpRequest,
|
||||
forwardedCertificateHeader As String,
|
||||
additionalForwardedCertificateHeaders As IEnumerable(Of String)) As X509Certificate2
|
||||
|
||||
Dim directCertificate = ReadDirectCertificate(request)
|
||||
If directCertificate IsNot Nothing Then
|
||||
Return directCertificate
|
||||
End If
|
||||
|
||||
Dim headerCandidates As New List(Of String)()
|
||||
AddHeaderCandidate(headerCandidates, forwardedCertificateHeader)
|
||||
|
||||
If additionalForwardedCertificateHeaders IsNot Nothing Then
|
||||
For Each headerName In additionalForwardedCertificateHeaders
|
||||
AddHeaderCandidate(headerCandidates, headerName)
|
||||
Next
|
||||
End If
|
||||
|
||||
For Each headerName In DefaultCertificateHeaders
|
||||
AddHeaderCandidate(headerCandidates, headerName)
|
||||
Next
|
||||
|
||||
For Each headerName In headerCandidates.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
Dim rawHeader = request.Headers(headerName)
|
||||
If String.IsNullOrWhiteSpace(rawHeader) Then
|
||||
Continue For
|
||||
End If
|
||||
|
||||
Dim certificate = TryCreateCertificate(rawHeader)
|
||||
If certificate IsNot Nothing Then
|
||||
Return certificate
|
||||
End If
|
||||
Next
|
||||
|
||||
For Each serverVariableName In DefaultCertificateServerVariables
|
||||
Dim rawValue = request.ServerVariables(serverVariableName)
|
||||
If String.IsNullOrWhiteSpace(rawValue) Then
|
||||
Continue For
|
||||
End If
|
||||
|
||||
Dim certificate = TryCreateCertificate(rawValue)
|
||||
If certificate IsNot Nothing Then
|
||||
Return certificate
|
||||
End If
|
||||
Next
|
||||
|
||||
Return Nothing
|
||||
End Function
|
||||
|
||||
Public Function ObtenerDni(certificado As X509Certificate2) As String
|
||||
Try
|
||||
If certificado Is Nothing Then
|
||||
Return String.Empty
|
||||
End If
|
||||
|
||||
Dim candidateValues As New List(Of String) From {
|
||||
ExtractDistinguishedNameValue(certificado.Subject, "OID.2.5.4.97"),
|
||||
ExtractDistinguishedNameValue(certificado.Subject, "2.5.4.97"),
|
||||
ExtractDistinguishedNameValue(certificado.Subject, "SERIALNUMBER"),
|
||||
ExtractDistinguishedNameValue(certificado.Subject, "OID.2.5.4.5"),
|
||||
ExtractDistinguishedNameValue(certificado.Subject, "2.5.4.5"),
|
||||
certificado.GetNameInfo(X509NameType.SimpleName, False)
|
||||
}
|
||||
|
||||
For Each candidateValue In candidateValues
|
||||
Dim nif = NormalizarIdentificadorFiscal(candidateValue)
|
||||
If nif <> String.Empty Then
|
||||
Return nif
|
||||
End If
|
||||
Next
|
||||
|
||||
Return NormalizarIdentificadorFiscal(certificado.Subject)
|
||||
Catch
|
||||
Return String.Empty
|
||||
End Try
|
||||
End Function
|
||||
|
||||
Public Function BuildIframeDniHtml(dni As String, parentOrigin As String) As String
|
||||
Dim payloadJson = JsonConvert.SerializeObject(New With {
|
||||
.dni = dni
|
||||
})
|
||||
Dim targetOriginJson = JsonConvert.SerializeObject(NormalizeOrigin(parentOrigin))
|
||||
|
||||
Return BuildIframePostMessageHtml(
|
||||
payloadJson,
|
||||
targetOriginJson,
|
||||
"Validando acceso con certificado...")
|
||||
End Function
|
||||
|
||||
Public Function BuildIframeLoginHtml(response As CertificateProxyLoginResponse, parentOrigin As String) As String
|
||||
Dim payloadJson = JsonConvert.SerializeObject(New With {
|
||||
.token = response.Token,
|
||||
.user = response.User
|
||||
})
|
||||
Dim targetOriginJson = JsonConvert.SerializeObject(NormalizeOrigin(parentOrigin))
|
||||
|
||||
Return BuildIframePostMessageHtml(
|
||||
payloadJson,
|
||||
targetOriginJson,
|
||||
"Validando acceso con certificado...")
|
||||
End Function
|
||||
|
||||
Public Function BuildIframeErrorHtml(message As String, parentOrigin As String) As String
|
||||
Dim payloadJson = JsonConvert.SerializeObject(New With {
|
||||
.error = message
|
||||
})
|
||||
Dim targetOriginJson = JsonConvert.SerializeObject(NormalizeOrigin(parentOrigin))
|
||||
Dim safeMessage = If(String.IsNullOrWhiteSpace(message), "No se pudo completar el acceso con certificado.", message)
|
||||
|
||||
Return BuildIframePostMessageHtml(
|
||||
payloadJson,
|
||||
targetOriginJson,
|
||||
safeMessage)
|
||||
End Function
|
||||
|
||||
Private Function BuildIframePostMessageHtml(
|
||||
payloadJson As String,
|
||||
targetOriginJson As String,
|
||||
visibleMessage As String) As String
|
||||
|
||||
Dim visibleMessageJson = JsonConvert.SerializeObject(visibleMessage)
|
||||
|
||||
Return "<!DOCTYPE html>" &
|
||||
"<html>" &
|
||||
"<head><meta charset=""utf-8"" /><title>Autenticacion Certificado</title></head>" &
|
||||
"<body>" &
|
||||
"<div id=""bridgeStatus"" style=""font-family:Arial,sans-serif;font-size:14px;padding:16px;""></div>" &
|
||||
"<script>" &
|
||||
"(function(){" &
|
||||
"var payload = " & payloadJson & ";" &
|
||||
"var targetOrigin = " & targetOriginJson & ";" &
|
||||
"var visibleMessage = " & visibleMessageJson & ";" &
|
||||
"var targetWindow = null;" &
|
||||
"function renderMessage(){" &
|
||||
"var container = document.getElementById('bridgeStatus');" &
|
||||
"if (container && visibleMessage) { container.innerText = visibleMessage; }" &
|
||||
"}" &
|
||||
"if (window.opener && !window.opener.closed) {" &
|
||||
"targetWindow = window.opener;" &
|
||||
"} else if (window.parent && window.parent !== window) {" &
|
||||
"targetWindow = window.parent;" &
|
||||
"}" &
|
||||
"try {" &
|
||||
"if (targetWindow) {" &
|
||||
"targetWindow.postMessage(payload, targetOrigin);" &
|
||||
"}" &
|
||||
"} catch (postMessageError) {}" &
|
||||
"renderMessage();" &
|
||||
"if (window.opener && !window.opener.closed) {" &
|
||||
"setTimeout(function(){ try { window.close(); } catch (closeError) {} }, 150);" &
|
||||
"}" &
|
||||
"})();" &
|
||||
"</script>" &
|
||||
"</body>" &
|
||||
"</html>"
|
||||
End Function
|
||||
|
||||
Private Function ReadDirectCertificate(request As HttpRequest) As X509Certificate2
|
||||
Try
|
||||
If request Is Nothing OrElse request.ClientCertificate Is Nothing OrElse Not request.ClientCertificate.IsPresent Then
|
||||
Return Nothing
|
||||
End If
|
||||
|
||||
Dim rawCertificate = request.ClientCertificate.Certificate
|
||||
If rawCertificate Is Nothing OrElse rawCertificate.Length = 0 Then
|
||||
Return Nothing
|
||||
End If
|
||||
|
||||
Return New X509Certificate2(rawCertificate)
|
||||
Catch
|
||||
Return Nothing
|
||||
End Try
|
||||
End Function
|
||||
|
||||
Private Function TryCreateCertificate(rawHeader As String) As X509Certificate2
|
||||
Dim normalizedValue = Uri.UnescapeDataString(rawHeader).Trim()
|
||||
If normalizedValue = String.Empty Then
|
||||
Return Nothing
|
||||
End If
|
||||
|
||||
If normalizedValue.IndexOf("BEGIN CERTIFICATE", StringComparison.OrdinalIgnoreCase) >= 0 Then
|
||||
Try
|
||||
Dim pemPayload = Regex.Replace(
|
||||
normalizedValue,
|
||||
"-+BEGIN CERTIFICATE-+|-+END CERTIFICATE-+|\s+",
|
||||
String.Empty,
|
||||
RegexOptions.IgnoreCase)
|
||||
|
||||
If pemPayload <> String.Empty Then
|
||||
Return New X509Certificate2(Convert.FromBase64String(pemPayload))
|
||||
End If
|
||||
Catch
|
||||
End Try
|
||||
End If
|
||||
|
||||
Dim compactValue = Regex.Replace(normalizedValue, "\s+", String.Empty)
|
||||
If compactValue.Length > 0 AndAlso compactValue.Length Mod 2 = 0 AndAlso Regex.IsMatch(compactValue, "^[0-9a-fA-F]+$") Then
|
||||
Try
|
||||
Return New X509Certificate2(HexStringToBytes(compactValue))
|
||||
Catch
|
||||
End Try
|
||||
End If
|
||||
|
||||
Try
|
||||
Return New X509Certificate2(Convert.FromBase64String(compactValue))
|
||||
Catch
|
||||
Return Nothing
|
||||
End Try
|
||||
End Function
|
||||
|
||||
Private Function NormalizarIdentificadorFiscal(value As String) As String
|
||||
If String.IsNullOrWhiteSpace(value) Then
|
||||
Return String.Empty
|
||||
End If
|
||||
|
||||
Dim normalizedValue = value.Trim().Trim(""""c).ToUpperInvariant()
|
||||
If normalizedValue.StartsWith("IDCES-", StringComparison.OrdinalIgnoreCase) OrElse
|
||||
normalizedValue.StartsWith("VATES-", StringComparison.OrdinalIgnoreCase) Then
|
||||
normalizedValue = normalizedValue.Split("-"c).Last().Trim()
|
||||
End If
|
||||
|
||||
Dim match = Regex.Match(normalizedValue, "\b[0-9XYZ]\d{7}[A-Z]\b", RegexOptions.IgnoreCase)
|
||||
If match.Success Then
|
||||
Return match.Value.ToUpperInvariant()
|
||||
End If
|
||||
|
||||
Return String.Empty
|
||||
End Function
|
||||
|
||||
Private Function ExtractDistinguishedNameValue(subject As String, attributeName As String) As String
|
||||
If String.IsNullOrWhiteSpace(subject) OrElse String.IsNullOrWhiteSpace(attributeName) Then
|
||||
Return String.Empty
|
||||
End If
|
||||
|
||||
Dim pattern = "(?:^|,\s*)" & Regex.Escape(attributeName) & "\s*=\s*(""[^""]+""|[^,]+)"
|
||||
Dim match = Regex.Match(subject, pattern, RegexOptions.IgnoreCase)
|
||||
If Not match.Success Then
|
||||
Return String.Empty
|
||||
End If
|
||||
|
||||
Return match.Groups(1).Value.Trim().Trim(""""c)
|
||||
End Function
|
||||
|
||||
Private Function HexStringToBytes(value As String) As Byte()
|
||||
Dim bytes((value.Length \ 2) - 1) As Byte
|
||||
For i = 0 To bytes.Length - 1
|
||||
bytes(i) = Convert.ToByte(value.Substring(i * 2, 2), 16)
|
||||
Next
|
||||
|
||||
Return bytes
|
||||
End Function
|
||||
|
||||
Private Function NormalizeOrigin(value As String) As String
|
||||
Dim uri As Uri = Nothing
|
||||
If Not Uri.TryCreate(value, UriKind.Absolute, uri) Then
|
||||
Return String.Empty
|
||||
End If
|
||||
|
||||
Return uri.GetLeftPart(UriPartial.Authority).TrimEnd("/"c)
|
||||
End Function
|
||||
|
||||
Private Function ExtractHost(value As String) As String
|
||||
Dim token = FirstToken(value)
|
||||
If String.IsNullOrWhiteSpace(token) Then
|
||||
Return String.Empty
|
||||
End If
|
||||
|
||||
Dim uri As Uri = Nothing
|
||||
If Uri.TryCreate("http://" & token, UriKind.Absolute, uri) Then
|
||||
Return uri.Host
|
||||
End If
|
||||
|
||||
Return token
|
||||
End Function
|
||||
|
||||
Private Function ExtractPort(value As String) As Integer?
|
||||
Dim token = FirstToken(value)
|
||||
If String.IsNullOrWhiteSpace(token) Then
|
||||
Return Nothing
|
||||
End If
|
||||
|
||||
Dim uri As Uri = Nothing
|
||||
If Uri.TryCreate("http://" & token, UriKind.Absolute, uri) AndAlso Not uri.IsDefaultPort Then
|
||||
Return uri.Port
|
||||
End If
|
||||
|
||||
Return Nothing
|
||||
End Function
|
||||
|
||||
Private Function ParseNullableInt(value As String) As Integer?
|
||||
Dim parsedValue As Integer
|
||||
If Integer.TryParse(FirstToken(value), parsedValue) Then
|
||||
Return parsedValue
|
||||
End If
|
||||
|
||||
Return Nothing
|
||||
End Function
|
||||
|
||||
Private Function FirstToken(value As String) As String
|
||||
If String.IsNullOrWhiteSpace(value) Then
|
||||
Return String.Empty
|
||||
End If
|
||||
|
||||
Return value.Split(","c)(0).Trim()
|
||||
End Function
|
||||
|
||||
Private Function GetFirstHeaderValue(request As HttpRequest, headerName As String) As String
|
||||
If request Is Nothing OrElse String.IsNullOrWhiteSpace(headerName) Then
|
||||
Return String.Empty
|
||||
End If
|
||||
|
||||
Return request.Headers(headerName)
|
||||
End Function
|
||||
|
||||
Private Function GetDefaultPort(scheme As String) As Integer
|
||||
If String.Equals(scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) Then
|
||||
Return 80
|
||||
End If
|
||||
|
||||
Return 443
|
||||
End Function
|
||||
|
||||
Private Sub AddHeaderCandidate(headerCandidates As ICollection(Of String), headerName As String)
|
||||
If Not String.IsNullOrWhiteSpace(headerName) Then
|
||||
headerCandidates.Add(headerName.Trim())
|
||||
End If
|
||||
End Sub
|
||||
End Module
|
||||
End Namespace
|
||||
|
||||
|
||||
Reference in New Issue
Block a user