Private/Security.ps1
using namespace System.Security.Cryptography function Test-PodeIPLimit { param( [Parameter(Mandatory=$true)] [ValidateNotNull()] $IP ) $type = 'IP' # get the limit rules and active list $rules = $PodeContext.Server.Limits.Rules[$type] $active = $PodeContext.Server.Limits.Active[$type] # if there are no rules, it's valid if (($null -eq $rules) -or ($rules.Count -eq 0)) { return $true } # get the ip address in bytes $IP = @{ String = $IP.IPAddressToString Family = $IP.AddressFamily Bytes = $IP.GetAddressBytes() } # now $now = [DateTime]::UtcNow # is the ip active? (get a direct match, then try grouped subnets) $_active_ip = $active[$IP.String] if ($null -eq $_active_ip) { $_groups = @(foreach ($key in $active.Keys) { if ($active[$key].Rule.Grouped) { $active[$key] } }) $_active_ip = @(foreach ($_group in $_groups) { if (Test-PodeIPAddressInRange -IP $IP -LowerIP $_group.Rule.Lower -UpperIP $_group.Rule.Upper) { $_group break } })[0] } # the ip is active, or part of a grouped subnet if ($null -ne $_active_ip) { # if limit is -1, always allowed if ($_active_ip.Rule.Limit -eq -1) { return $true } # check expire time, a reset if needed if ($now -ge $_active_ip.Expire) { $_active_ip.Rate = 0 $_active_ip.Expire = $now.AddSeconds($_active_ip.Rule.Seconds) } # are we over the limit? if ($_active_ip.Rate -ge $_active_ip.Rule.Limit) { return $false } # increment the rate $_active_ip.Rate++ return $true } # the ip isn't active else { # get the ip's rule $_rule_ip = @(foreach ($rule in $rules.Values) { if (Test-PodeIPAddressInRange -IP $IP -LowerIP $rule.Lower -UpperIP $rule.Upper) { $rule break } })[0] # if ip not in rules, it's valid # (add to active list as always allowed - saves running where search everytime) if ($null -eq $_rule_ip) { $active[$IP.String] = @{ Rule = @{ Limit = -1 } } return $true } # add ip to active list (ip if not grouped, else the subnet if it's grouped) $_ip = (Resolve-PodeValue -Check $_rule_ip.Grouped -TrueValue $_rule_ip.IP -FalseValue $IP.String) $active[$_ip] = @{ Rule = $_rule_ip Rate = 1 Expire = $now.AddSeconds($_rule_ip.Seconds) } # if limit is 0, it's never allowed return ($_rule_ip -ne 0) } } function Test-PodeRouteLimit { param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] [string] $Path ) $type = 'Route' # get the limit rules and active list $rules = $PodeContext.Server.Limits.Rules[$type] $active = $PodeContext.Server.Limits.Active[$type] # if there are no rules, it's valid if (($null -eq $rules) -or ($rules.Count -eq 0)) { return $true } # now $now = [DateTime]::UtcNow # is the route active? $_active_route = $active[$Path] # the ip is active, or part of a grouped subnet if ($null -ne $_active_route) { # if limit is -1, always allowed if ($_active_route.Rule.Limit -eq -1) { return $true } # check expire time, a reset if needed if ($now -ge $_active_route.Expire) { $_active_route.Rate = 0 $_active_route.Expire = $now.AddSeconds($_active_route.Rule.Seconds) } # are we over the limit? if ($_active_route.Rate -ge $_active_route.Rule.Limit) { return $false } # increment the rate $_active_route.Rate++ return $true } # the route isn't active else { # get the route's rule $_rule_route = $rules[$Path] # if route not in rules, it's valid (add to active list as always allowed) if ($null -eq $_rule_route) { $active[$Path] = @{ Rule = @{ Limit = -1 } } return $true } # add route to active list $active[$Path] = @{ Rule = $_rule_route Rate = 1 Expire = $now.AddSeconds($_rule_route.Seconds) } # if limit is 0, it's never allowed return ($_rule_route -ne 0) } } function Test-PodeEndpointLimit { param ( [Parameter()] [string] $EndpointName ) $type = 'Endpoint' if ([string]::IsNullOrWhiteSpace($EndpointName)) { return $true } # get the limit rules and active list $rules = $PodeContext.Server.Limits.Rules[$type] $active = $PodeContext.Server.Limits.Active[$type] # if there are no rules, it's valid if (($null -eq $rules) -or ($rules.Count -eq 0)) { return $true } # now $now = [DateTime]::UtcNow # is the endpoint active? $_active_endpoint = $active[$EndpointName] # the endpoint is active if ($null -ne $_active_endpoint) { # if limit is -1, always allowed if ($_active_endpoint.Rule.Limit -eq -1) { return $true } # check expire time, a reset if needed if ($now -ge $_active_endpoint.Expire) { $_active_endpoint.Rate = 0 $_active_endpoint.Expire = $now.AddSeconds($_active_endpoint.Rule.Seconds) } # are we over the limit? if ($_active_endpoint.Rate -ge $_active_endpoint.Rule.Limit) { return $false } # increment the rate $_active_endpoint.Rate++ return $true } # the endpoint isn't active else { # get the endpoint's rule $_rule_endpoint = $rules[$EndpointName] # if endpoint not in rules, it's valid (add to active list as always allowed) if ($null -eq $_rule_endpoint) { $active[$EndpointName] = @{ Rule = @{ Limit = -1 } } return $true } # add endpoint to active list $active[$EndpointName] = @{ Rule = $_rule_endpoint Rate = 1 Expire = $now.AddSeconds($_rule_endpoint.Seconds) } # if limit is 0, it's never allowed return ($_rule_endpoint -ne 0) } } function Test-PodeIPAccess { param( [Parameter(Mandatory=$true)] [ValidateNotNull()] $IP ) $type = 'IP' # get permission lists for ip $allow = $PodeContext.Server.Access.Allow[$type] $deny = $PodeContext.Server.Access.Deny[$type] # are they empty? $alEmpty = (($null -eq $allow) -or ($allow.Count -eq 0)) $dnEmpty = (($null -eq $deny) -or ($deny.Count -eq 0)) # if both are empty, value is valid if ($alEmpty -and $dnEmpty) { return $true } # get the ip address in bytes $IP = @{ Family = $IP.AddressFamily Bytes = $IP.GetAddressBytes() } # if value in allow, it's allowed if (!$alEmpty) { $match = @(foreach ($value in $allow.Values) { if (Test-PodeIPAddressInRange -IP $IP -LowerIP $value.Lower -UpperIP $value.Upper) { $value break } })[0] if ($null -ne $match) { return $true } } # if value in deny, it's disallowed if (!$dnEmpty) { $match = @(foreach ($value in $deny.Values) { if (Test-PodeIPAddressInRange -IP $IP -LowerIP $value.Lower -UpperIP $value.Upper) { $value break } })[0] if ($null -ne $match) { return $false } } # if we have an allow, it's disallowed (because it's not in there) if (!$alEmpty) { return $false } # otherwise it's allowed (because it's not in the deny) return $true } function Add-PodeIPLimit { param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] [string] $IP, [Parameter(Mandatory=$true)] [int] $Limit, [Parameter(Mandatory=$true)] [int] $Seconds, [switch] $Group ) # current limit type $type = 'IP' # ensure limit and seconds are non-zero and negative if ($Limit -le 0) { throw "Limit value cannot be 0 or less for $($IP)" } if ($Seconds -le 0) { throw "Seconds value cannot be 0 or less for $($IP)" } # get current rules $rules = $PodeContext.Server.Limits.Rules[$type] # setup up perm type if ($null -eq $rules) { $PodeContext.Server.Limits.Rules[$type] = @{} $PodeContext.Server.Limits.Active[$type] = @{} $rules = $PodeContext.Server.Limits.Rules[$type] } # have we already added the ip? elseif ($rules.ContainsKey($IP)) { return } # calculate the lower/upper ip bounds if (Test-PodeIPAddressIsSubnetMask -IP $IP) { $_tmp = Get-PodeSubnetRange -SubnetMask $IP $_tmpLo = Get-PodeIPAddress -IP $_tmp.Lower $_tmpHi = Get-PodeIPAddress -IP $_tmp.Upper } elseif (Test-PodeIPAddressAny -IP $IP) { $_tmpLo = Get-PodeIPAddress -IP '0.0.0.0' $_tmpHi = Get-PodeIPAddress -IP '255.255.255.255' } else { $_tmpLo = Get-PodeIPAddress -IP $IP $_tmpHi = $_tmpLo } # add limit rule for ip $rules.Add($IP, @{ Limit = $Limit Seconds = $Seconds Grouped = [bool]$Group IP = $IP Lower = @{ Family = $_tmpLo.AddressFamily Bytes = $_tmpLo.GetAddressBytes() } Upper = @{ Family = $_tmpHi.AddressFamily Bytes = $_tmpHi.GetAddressBytes() } }) } function Add-PodeRouteLimit { param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] [string] $Path, [Parameter(Mandatory=$true)] [int] $Limit, [Parameter(Mandatory=$true)] [int] $Seconds, [switch] $Group ) # current limit type $type = 'Route' # ensure limit and seconds are non-zero and negative if ($Limit -le 0) { throw "Limit value cannot be 0 or less for $($IP)" } if ($Seconds -le 0) { throw "Seconds value cannot be 0 or less for $($IP)" } # get current rules $rules = $PodeContext.Server.Limits.Rules[$type] # setup up perm type if ($null -eq $rules) { $PodeContext.Server.Limits.Rules[$type] = @{} $PodeContext.Server.Limits.Active[$type] = @{} $rules = $PodeContext.Server.Limits.Rules[$type] } # have we already added the route? elseif ($rules.ContainsKey($Path)) { return } # add limit rule for the route $rules.Add($Path, @{ Limit = $Limit Seconds = $Seconds Grouped = [bool]$Group Path = $Path }) } function Add-PodeEndpointLimit { param( [Parameter(Mandatory=$true)] [ValidateNotNull()] [string] $EndpointName, [Parameter(Mandatory=$true)] [int] $Limit, [Parameter(Mandatory=$true)] [int] $Seconds, [switch] $Group ) # current limit type $type = 'Endpoint' # does the endpoint exist? $endpoint = Get-PodeEndpointByName -Name $EndpointName if ($null -eq $endpoint) { throw "Endpoint not found: $($EndpointName)" } # ensure limit and seconds are non-zero and negative if ($Limit -le 0) { throw "Limit value cannot be 0 or less for $($IP)" } if ($Seconds -le 0) { throw "Seconds value cannot be 0 or less for $($IP)" } # we need to check endpoints on requests switch ($endpoint.Type.ToLowerInvariant()) { 'http' { $PodeContext.Server.FindEndpoints.Route = $true } 'ws' { $PodeContext.Server.FindEndpoints.Route = $true } 'smtp' { $PodeContext.Server.FindEndpoints.Smtp = $true } 'tcp' { $PodeContext.Server.FindEndpoints.Tcp = $true } } # get current rules $rules = $PodeContext.Server.Limits.Rules[$type] # setup up perm type if ($null -eq $rules) { $PodeContext.Server.Limits.Rules[$type] = @{} $PodeContext.Server.Limits.Active[$type] = @{} $rules = $PodeContext.Server.Limits.Rules[$type] } # have we already added the endpoint? elseif ($rules.ContainsKey($EndpointName)) { return } # add limit rule for the endpoint $rules.Add($EndpointName, @{ Limit = $Limit Seconds = $Seconds Grouped = [bool]$Group EndpointName = $EndpointName }) } function Add-PodeIPAccess { param ( [Parameter(Mandatory=$true)] [ValidateSet('Allow', 'Deny')] [string] $Access, [Parameter(Mandatory=$true)] [string] $IP ) # current access type $type = 'IP' # get opposite permission $opp = "$(if ($Access -ieq 'allow') { 'Deny' } else { 'Allow' })" # get permission lists for type $permType = $PodeContext.Server.Access[$Access][$type] $oppType = $PodeContext.Server.Access[$opp][$type] # setup up perm type if ($null -eq $permType) { $PodeContext.Server.Access[$Access][$type] = @{} $permType = $PodeContext.Server.Access[$Access][$type] } # have we already added the ip? elseif ($permType.ContainsKey($IP)) { return } # remove from opp type if ($null -ne $oppType -and $oppType.ContainsKey($IP)) { $oppType.Remove($IP) } # calculate the lower/upper ip bounds if (Test-PodeIPAddressIsSubnetMask -IP $IP) { $_tmp = Get-PodeSubnetRange -SubnetMask $IP $_tmpLo = Get-PodeIPAddress -IP $_tmp.Lower $_tmpHi = Get-PodeIPAddress -IP $_tmp.Upper } elseif (Test-PodeIPAddressAny -IP $IP) { $_tmpLo = Get-PodeIPAddress -IP '0.0.0.0' $_tmpHi = Get-PodeIPAddress -IP '255.255.255.255' } else { $_tmpLo = Get-PodeIPAddress -IP $IP $_tmpHi = $_tmpLo } # add access rule for ip $permType.Add($IP, @{ Lower = @{ Family = $_tmpLo.AddressFamily Bytes = $_tmpLo.GetAddressBytes() } Upper = @{ Family = $_tmpHi.AddressFamily Bytes = $_tmpHi.GetAddressBytes() } }) } function Get-PodeCsrfToken { # key name to search $key = $PodeContext.Server.Cookies.Csrf.Name # check the payload if (!(Test-PodeIsEmpty $WebEvent.Data[$key])) { return $WebEvent.Data[$key] } # check the query string if (!(Test-PodeIsEmpty $WebEvent.Query[$key])) { return $WebEvent.Query[$key] } # check the headers $value = (Get-PodeHeader -Name $key) if (!(Test-PodeIsEmpty $value)) { return $value } return $null } function Test-PodeCsrfToken { param ( [Parameter()] [string] $Secret, [Parameter()] [string] $Token ) # if there's no token/secret, fail if ((Test-PodeIsEmpty $Secret) -or (Test-PodeIsEmpty $Token)) { return $false } # the token must start with "t:" if (!$Token.StartsWith('t:')) { return $false } # get the salt from the token $_token = $Token.Substring(2) $periodIndex = $_token.LastIndexOf('.') if ($periodIndex -eq -1) { return $false } $salt = $_token.Substring(0, $periodIndex) # ensure the token is valid if ((Restore-PodeCsrfToken -Secret $Secret -Salt $salt) -ne $Token) { return $false } return $true } function New-PodeCsrfSecret { # see if there's already a secret in session/cookie $secret = (Get-PodeCsrfSecret) if (!(Test-PodeIsEmpty $secret)) { return $secret } # otherwise, make a new secret and cache it $secret = (New-PodeGuid -Secure -Length 16) Set-PodeCsrfSecret -Secret $secret return $secret } function Get-PodeCsrfSecret { # key name to get secret $key = $PodeContext.Server.Cookies.Csrf.Name # are we getting it from a cookie, or session? if ($PodeContext.Server.Cookies.Csrf.UseCookies) { return (Get-PodeCookie ` -Name $PodeContext.Server.Cookies.Csrf.Name ` -Secret $PodeContext.Server.Cookies.Csrf.Secret).Value } # on session else { return $WebEvent.Session.Data[$key] } } function Set-PodeCsrfSecret { param ( [Parameter(Mandatory=$true)] [string] $Secret ) # key name to set secret under $key = $PodeContext.Server.Cookies.Csrf.Name # are we setting this on a cookie, or session? if ($PodeContext.Server.Cookies.Csrf.UseCookies) { $null = Set-PodeCookie ` -Name $PodeContext.Server.Cookies.Csrf.Name ` -Value $Secret ` -Secret $PodeContext.Server.Cookies.Csrf.Secret } # on session else { $WebEvent.Session.Data[$key] = $Secret } } function Restore-PodeCsrfToken { param ( [Parameter(Mandatory=$true)] [string] $Secret, [Parameter(Mandatory=$true)] [string] $Salt ) return "t:$($Salt).$(Invoke-PodeSHA256Hash -Value "$($Salt)-$($Secret)")" } function Test-PodeCsrfConfigured { return (!(Test-PodeIsEmpty $PodeContext.Server.Cookies.Csrf)) } function Get-PodeCertificateByFile { param( [Parameter(Mandatory=$true)] [string] $Certificate, [Parameter()] [string] $Password = $null, [Parameter()] [string] $Key = $null ) # cert + key if (![string]::IsNullOrWhiteSpace($Key)) { return (Get-PodeCertificateByPemFile -Certificate $Certificate -Password $Password -Key $Key) } $path = Get-PodeRelativePath -Path $Certificate -JoinRoot -Resolve # cert + password if (![string]::IsNullOrWhiteSpace($Password)) { return [X509Certificates.X509Certificate2]::new($path, $Password) } # plain cert return [X509Certificates.X509Certificate2]::new($path) } function Get-PodeCertificateByPemFile { param( [Parameter(Mandatory=$true)] [string] $Certificate, [Parameter()] [string] $Password = $null, [Parameter()] [string] $Key = $null ) $cert = $null $certPath = Get-PodeRelativePath -Path $Certificate -JoinRoot -Resolve $keyPath = Get-PodeRelativePath -Path $Key -JoinRoot -Resolve # pem's kinda work in .NET3/.NET5 if ([version]$PSVersionTable.PSVersion -ge [version]'7.0.0') { $cert = [X509Certificates.X509Certificate2]::new($certPath) $keyText = [System.IO.File]::ReadAllText($keyPath) $rsa = [RSA]::Create() # .NET5 if ([version]$PSVersionTable.PSVersion -ge [version]'7.1.0') { if ([string]::IsNullOrWhiteSpace($Password)) { $rsa.ImportFromPem($keyText) } else { $rsa.ImportFromEncryptedPem($keyText, $Password) } } # .NET3 else { $keyBlocks = $keyText.Split('-', [System.StringSplitOptions]::RemoveEmptyEntries) $keyBytes = [System.Convert]::FromBase64String($keyBlocks[1]) if ($keyBlocks[0] -ieq 'BEGIN PRIVATE KEY') { $rsa.ImportPkcs8PrivateKey($keyBytes, [ref]$null) } elseif ($keyBlocks[0] -ieq 'BEGIN RSA PRIVATE KEY') { $rsa.ImportRSAPrivateKey($keyBytes, [ref]$null) } elseif ($keyBlocks[0] -ieq 'BEGIN ENCRYPTED PRIVATE KEY') { $rsa.ImportEncryptedPkcs8PrivateKey($Password, $keyBytes, [ref]$null) } } $cert = [X509Certificates.RSACertificateExtensions]::CopyWithPrivateKey($cert, $rsa) $cert = [X509Certificates.X509Certificate2]::new($cert.Export([X509Certificates.X509ContentType]::Pkcs12)) } # for everything else, there's the openssl way else { $tempFile = Join-Path (Split-Path -Parent -Path $certPath) 'temp.pfx' try { if ([string]::IsNullOrWhiteSpace($Password)) { $Password = [string]::Empty } $result = openssl pkcs12 -inkey $keyPath -in $certPath -export -passin pass:$Password -password pass:$Password -out $tempFile if (!$?) { throw "Failed to create openssl cert: $($result)" } $cert = [X509Certificates.X509Certificate2]::new($tempFile, $Password) } finally { $null = Remove-Item $tempFile -Force } } return $cert } function Find-PodeCertificateInCertStore { param( [Parameter(Mandatory=$true)] [X509Certificates.X509FindType] $FindType, [Parameter(Mandatory=$true)] [string] $Query, [Parameter(Mandatory=$true)] [X509Certificates.StoreName] $StoreName, [Parameter(Mandatory=$true)] [X509Certificates.StoreLocation] $StoreLocation ) # fail if not windows if (!(Test-PodeIsWindows)) { throw "Certificate Thumbprints/Name are only supported on Windows" } # open the currentuser\my store $x509store = [X509Certificates.X509Store]::new($StoreName, $StoreLocation) try { # attempt to find the cert $x509store.Open([X509Certificates.OpenFlags]::ReadOnly) $x509certs = $x509store.Certificates.Find($FindType, $Query, $false) } finally { # close the store! if ($null -ne $x509store) { Close-PodeDisposable -Disposable $x509store -Close } } # fail if no cert found for query if (($null -eq $x509certs) -or ($x509certs.Count -eq 0)) { throw "No certificate could be found in $($StoreLocation)\$($StoreName) for '$($Query)'" } return ([X509Certificates.X509Certificate2]($x509certs[0])) } function Get-PodeCertificateByThumbprint { param( [Parameter(Mandatory=$true)] [string] $Thumbprint, [Parameter(Mandatory=$true)] [X509Certificates.StoreName] $StoreName, [Parameter(Mandatory=$true)] [X509Certificates.StoreLocation] $StoreLocation ) return (Find-PodeCertificateInCertStore ` -FindType ([X509Certificates.X509FindType]::FindByThumbprint) ` -Query $Thumbprint ` -StoreName $StoreName ` -StoreLocation $StoreLocation) } function Get-PodeCertificateByName { param( [Parameter(Mandatory=$true)] [string] $Name, [Parameter(Mandatory=$true)] [X509Certificates.StoreName] $StoreName, [Parameter(Mandatory=$true)] [X509Certificates.StoreLocation] $StoreLocation ) return (Find-PodeCertificateInCertStore ` -FindType ([X509Certificates.X509FindType]::FindBySubjectName) ` -Query $Name ` -StoreName $StoreName ` -StoreLocation $StoreLocation) } function New-PodeSelfSignedCertificate { $sanBuilder = [X509Certificates.SubjectAlternativeNameBuilder]::new() $null = $sanBuilder.AddIpAddress([ipaddress]::Loopback) $null = $sanBuilder.AddIpAddress([ipaddress]::IPv6Loopback) $null = $sanBuilder.AddDnsName('localhost') if (![string]::IsNullOrWhiteSpace($PodeContext.Server.ComputerName)) { $null = $sanBuilder.AddDnsName($PodeContext.Server.ComputerName) } $rsa = [RSA]::Create(2048) $distinguishedName = [X500DistinguishedName]::new("CN=localhost") $req = [X509Certificates.CertificateRequest]::new( $distinguishedName, $rsa, [HashAlgorithmName]::SHA256, [RSASignaturePadding]::Pkcs1 ) $flags = ( [X509Certificates.X509KeyUsageFlags]::DataEncipherment -bor [X509Certificates.X509KeyUsageFlags]::KeyEncipherment -bor [X509Certificates.X509KeyUsageFlags]::DigitalSignature ) $null = $req.CertificateExtensions.Add( [X509Certificates.X509KeyUsageExtension]::new( $flags, $false ) ) $oid = [OidCollection]::new() $null = $oid.Add([Oid]::new('1.3.6.1.5.5.7.3.1')) $req.CertificateExtensions.Add( [X509Certificates.X509EnhancedKeyUsageExtension]::new( $oid, $false ) ) $null = $req.CertificateExtensions.Add($sanBuilder.Build()) $cert = $req.CreateSelfSigned( [System.DateTimeOffset]::UtcNow.AddDays(-1), [System.DateTimeOffset]::UtcNow.AddYears(10) ) if (Test-PodeIsWindows) { $cert.FriendlyName = 'localhost' } $cert = [X509Certificates.X509Certificate2]::new( $cert.Export([X509Certificates.X509ContentType]::Pfx, 'self-signed'), 'self-signed' ) return $cert } function Protect-PodeContentSecurityKeyword { param( [Parameter(Mandatory=$true)] [string] $Name, [Parameter()] [string[]] $Value, [switch] $Append ) # cache it if ($Append -and !(Test-PodeIsEmpty $PodeContext.Server.Security.Cache.ContentSecurity[$Name])) { $Value += @($PodeContext.Server.Security.Cache.ContentSecurity[$Name]) } $PodeContext.Server.Security.Cache.ContentSecurity[$Name] = $Value # do nothing if no value if (($null -eq $Value) -or ($Value.Length -eq 0)) { return $null } # keywords $Name = $Name.ToLowerInvariant() $keywords = @( 'none', 'self', 'unsafe-inline', 'unsafe-eval' ) $schemes = @( 'http', 'https', 'ws', 'wss', 'data', 'file' ) # build the value $values = @(foreach ($v in $Value) { if ($keywords -icontains $v) { "'$($v.ToLowerInvariant())'" continue } if ($schemes -icontains $v) { "$($v.ToLowerInvariant()):" continue } $v }) return "$($Name) $($values -join ' ')" } function Protect-PodePermissionsPolicyKeyword { param( [Parameter(Mandatory=$true)] [string] $Name, [Parameter()] [string[]] $Value, [switch] $Append ) # cache it if ($Append -and !(Test-PodeIsEmpty $PodeContext.Server.Security.Cache.PermissionsPolicy[$Name])) { if (($Value.Length -eq 0) -or (@($PodeContext.Server.Security.Cache.PermissionsPolicy[$Name])[0] -ine 'none')) { $Value += @($PodeContext.Server.Security.Cache.PermissionsPolicy[$Name]) } } $PodeContext.Server.Security.Cache.PermissionsPolicy[$Name] = $Value # do nothing if no value if (($null -eq $Value) -or ($Value.Length -eq 0)) { return $null } # build value $Name = $Name.ToLowerInvariant() if ($Value -icontains 'none') { return "$($Name)=()" } $keywords = @( 'self' ) $values = @(foreach ($v in $Value) { if ($keywords -icontains $v) { $v continue } "`"$($v)`"" }) return "$($Name)=($($values -join ' '))" } |