From 9b1456e2d03baa5a4876f551949bf1ead6be7715 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Fri, 3 Feb 2023 19:51:55 +0000 Subject: [PATCH 01/57] fix for choco packer --- packers/choco/tools/ChocolateyInstall.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packers/choco/tools/ChocolateyInstall.ps1 b/packers/choco/tools/ChocolateyInstall.ps1 index 70babe97c..574ed5853 100644 --- a/packers/choco/tools/ChocolateyInstall.ps1 +++ b/packers/choco/tools/ChocolateyInstall.ps1 @@ -24,7 +24,7 @@ function Install-PodeModule($path, $version) try { - Push-Location (Join-Path $env:ChocolateyPackageFolder 'src') + Push-Location (Join-Path $toolsDir 'src') # which folders do we need? $folders = @('Private', 'Public', 'Misc', 'Libs') @@ -52,6 +52,9 @@ function Install-PodeModule($path, $version) # Determine which Program Files path to use $progFiles = [string]$env:ProgramFiles +# determine the path to choco tools +$toolsDir = Split-Path -Path (Split-Path -Parent $MyInvocation.MyCommand.Definition) + # Install PS Module # Set the module path $modulePath = Join-Path $progFiles (Join-Path 'WindowsPowerShell' 'Modules') From b0f0dcc3d618695b4d7f9464b60faf79d83d202a Mon Sep 17 00:00:00 2001 From: Avinesh Singh Date: Mon, 6 Mar 2023 12:14:47 +0530 Subject: [PATCH 02/57] Fix: Test-PodeJwt comparison against Local/Unspecified Kind --- src/Private/Cryptography.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Private/Cryptography.ps1 b/src/Private/Cryptography.ps1 index d1d5b5c26..17ef90712 100644 --- a/src/Private/Cryptography.ps1 +++ b/src/Private/Cryptography.ps1 @@ -261,8 +261,8 @@ function Test-PodeJwt $Payload ) - $now = [datetime]::Now - $unixStart = [datetime]::new(1970, 1, 1) + $now = [datetime]::UtcNow + $unixStart = [datetime]::new(1970, 1, 1, 0, 0, [DateTimeKind]::Utc) # validate expiry if (![string]::IsNullOrWhiteSpace($Payload.exp)) { From 613e5de42e21ef5c90675efaccbdc34519a7ecec Mon Sep 17 00:00:00 2001 From: plk Date: Sat, 22 Apr 2023 20:38:23 +0200 Subject: [PATCH 03/57] Allowing JSON depth >10 in Save/Restore Pode state cmdlets --- src/Public/State.ps1 | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Public/State.ps1 b/src/Public/State.ps1 index 79fd16896..8361cbb87 100644 --- a/src/Public/State.ps1 +++ b/src/Public/State.ps1 @@ -207,6 +207,9 @@ An optional array of state object names to exclude from being saved. (This has a .PARAMETER Include An optional array of state object names to only include when being saved. +.PARAMETER Depth +Saved JSON maximum depth. Will be passed to ConvertTo-JSON's -Depth parameter. Default is 10. + .PARAMETER Compress If supplied, the saved JSON will be compressed. @@ -239,6 +242,10 @@ function Save-PodeState [string[]] $Include, + [Parameter()] + [int16] + $depth = 10, + [switch] $Compress ) @@ -301,7 +308,7 @@ function Save-PodeState } # save the state - $null = ConvertTo-Json -InputObject $state -Depth 10 -Compress:$Compress | Out-File -FilePath $Path -Force + $null = ConvertTo-Json -InputObject $state -Depth $depth -Compress:$Compress | Out-File -FilePath $Path -Force } <# @@ -317,6 +324,9 @@ The path to a JSON file that contains the state information. .PARAMETER Merge If supplied, the state loaded from the JSON file will be merged with the current state, instead of overwriting it. +.PARAMETER Depth +Saved JSON maximum depth. Will be passed to ConvertFrom-JSON's -Depth parameter (Powershell >=6). Default is 10. + .EXAMPLE Restore-PodeState -Path './state.json' #> @@ -329,7 +339,10 @@ function Restore-PodeState $Path, [switch] - $Merge + $Merge, + + [int16] + $Depth = 10 ) # error if attempting to use outside of the pode server @@ -347,7 +360,7 @@ function Restore-PodeState $state = @{} if (Test-PodeIsPSCore) { - $state = (Get-Content $Path -Force | ConvertFrom-Json -AsHashtable -Depth 10) + $state = (Get-Content $Path -Force | ConvertFrom-Json -AsHashtable -Depth $depth) } else { $props = (Get-Content $Path -Force | ConvertFrom-Json).psobject.properties @@ -413,4 +426,4 @@ function Test-PodeState } return $PodeContext.Server.State.ContainsKey($Name) -} \ No newline at end of file +} From 440aae0cc408c06302e17e6610260aebb40f4e42 Mon Sep 17 00:00:00 2001 From: plk Date: Sun, 23 Apr 2023 10:44:18 +0200 Subject: [PATCH 04/57] Fix casing --- src/Public/State.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Public/State.ps1 b/src/Public/State.ps1 index 8361cbb87..ec2ca2a11 100644 --- a/src/Public/State.ps1 +++ b/src/Public/State.ps1 @@ -244,7 +244,7 @@ function Save-PodeState [Parameter()] [int16] - $depth = 10, + $Depth = 10, [switch] $Compress @@ -308,7 +308,7 @@ function Save-PodeState } # save the state - $null = ConvertTo-Json -InputObject $state -Depth $depth -Compress:$Compress | Out-File -FilePath $Path -Force + $null = ConvertTo-Json -InputObject $state -Depth $Depth -Compress:$Compress | Out-File -FilePath $Path -Force } <# @@ -360,7 +360,7 @@ function Restore-PodeState $state = @{} if (Test-PodeIsPSCore) { - $state = (Get-Content $Path -Force | ConvertFrom-Json -AsHashtable -Depth $depth) + $state = (Get-Content $Path -Force | ConvertFrom-Json -AsHashtable -Depth $Depth) } else { $props = (Get-Content $Path -Force | ConvertFrom-Json).psobject.properties From 34045a6061a72642cbe2cd585751dbe5d4a2373c Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Fri, 7 Jul 2023 21:46:21 +0100 Subject: [PATCH 05/57] #1106: Add helper security functions to hide/show the Server header --- docs/Tutorials/Middleware/Types/Security.md | 13 +++++++ src/Listener/PodeListener.cs | 10 ++++++ src/Listener/PodeResponse.cs | 16 +++++++-- src/Pode.psd1 | 2 ++ src/Private/Context.ps1 | 1 + src/Private/PodeServer.ps1 | 1 + src/Public/Security.ps1 | 40 +++++++++++++++++++++ 7 files changed, 80 insertions(+), 3 deletions(-) diff --git a/docs/Tutorials/Middleware/Types/Security.md b/docs/Tutorials/Middleware/Types/Security.md index 57efc8419..69f3d60f7 100644 --- a/docs/Tutorials/Middleware/Types/Security.md +++ b/docs/Tutorials/Middleware/Types/Security.md @@ -19,6 +19,8 @@ The following headers are currently supported, but you can add custom header val * X-Content-Type-Options * Referrer-Policy +You can also set the "Server" header to be hidden on responses if required. + ## Types Pode has an inbuilt wrapper to easily toggle all headers with default values: [`Set-PodeSecurity`](../../../../Functions/Security/Set-PodeSecurity). This function lets you specify a `-Type` of either `Simple` or `Strict`. The specified value will setup the headers with the default values defined below. You can also force `X-XSS-Protection` to use blocking mode if you want to support older browsers, or enable `Strict-Transport-Security` via `-UseHsts`. @@ -51,6 +53,8 @@ The following values are used for each header when the `Simple` type is supplied | X-Content-Type-Options | nosniff | | Referred-Policy | strict-origin | +The Server header is also hidden. + ### Strict The following values are used for each header when the `Strict` type is supplied: @@ -72,6 +76,8 @@ The following values are used for each header when the `Strict` type is supplied | X-Content-Type-Options | nosniff | | Referred-Policy | no-referrer | +The Server header is also hidden. + ## Headers You can setup the values of headers individually by using their relevant functions. @@ -205,6 +211,13 @@ The `Referrer-Policy` header tells the browser how much information to include i Set-PodeSecurityReferrerPolicy -Type Strict-Origin ``` +### Server + +You can hide or show the Server header on responses using the following functions, by default the Server header is visible: + +* [`Hide-PodeSecurityServer`](../../../../Functions/Security/Hide-PodeSecurityServer) +* [`Show-PodeSecurityServer`](../../../../Functions/Security/Show-PodeSecurityServer) + ## Custom There could be some headers for security that Pode doesn't support, but that you need. In this case you can use [`Add-PodeSecurityHeader`](../../../../Functions/Security/Add-PodeSecurityHeader) to specify a custom header and value that will be added: diff --git a/src/Listener/PodeListener.cs b/src/Listener/PodeListener.cs index c87636401..9ad432b0f 100644 --- a/src/Listener/PodeListener.cs +++ b/src/Listener/PodeListener.cs @@ -35,6 +35,16 @@ public int RequestBodySize } } + private bool _showServerDetails = true; + public bool ShowServerDetails + { + get => _showServerDetails; + set + { + _showServerDetails = value; + } + } + public PodeListener(CancellationToken cancellationToken = default(CancellationToken)) : base(cancellationToken) { diff --git a/src/Listener/PodeResponse.cs b/src/Listener/PodeResponse.cs index 3fe8a7b96..a56963c6b 100644 --- a/src/Listener/PodeResponse.cs +++ b/src/Listener/PodeResponse.cs @@ -298,10 +298,20 @@ private void SetDefaultHeaders() Headers.Add("Date", DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture)); - // set the server - if (!Headers.ContainsKey("Server")) + // set the server if allowed + if (Context.Listener.ShowServerDetails) { - Headers.Add("Server", "Pode"); + if (!Headers.ContainsKey("Server")) + { + Headers.Add("Server", "Pode"); + } + } + else + { + if (Headers.ContainsKey("Server")) + { + Headers.Remove("Server"); + } } // set context/socket ID diff --git a/src/Pode.psd1 b/src/Pode.psd1 index 10cc580da..1ddcdccaf 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -312,6 +312,8 @@ 'Set-PodeSecurityPermissionsPolicy', 'Set-PodeSecurityReferrerPolicy', 'Set-PodeSecurityStrictTransportSecurity', + 'Hide-PodeSecurityServer', + 'Show-PodeSecurityServer', # Verbs 'Add-PodeVerb', diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 515577feb..939c728ec 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -415,6 +415,7 @@ function New-PodeContext # setup security $ctx.Server.Security = @{ + ServerDetails = $true Headers = @{} Cache = @{ ContentSecurity = @{} diff --git a/src/Private/PodeServer.ps1 b/src/Private/PodeServer.ps1 index 0ef9f6a6a..72b995891 100644 --- a/src/Private/PodeServer.ps1 +++ b/src/Private/PodeServer.ps1 @@ -66,6 +66,7 @@ function Start-PodeWebServer $listener.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevels) $listener.RequestTimeout = $PodeContext.Server.Request.Timeout $listener.RequestBodySize = $PodeContext.Server.Request.BodySize + $listener.ShowServerDetails = [bool]$PodeContext.Server.Security.ServerDetails try { diff --git a/src/Public/Security.ps1 b/src/Public/Security.ps1 index 04c233375..e99e2010a 100644 --- a/src/Public/Security.ps1 +++ b/src/Public/Security.ps1 @@ -75,6 +75,9 @@ function Set-PodeSecurity Set-PodeSecurityReferrerPolicy -Type No-Referrer } } + + # hide server info + Hide-PodeSecurityServer } <# @@ -93,6 +96,7 @@ function Remove-PodeSecurity param() $PodeContext.Server.Security.Headers.Clear() + Show-PodeSecurityServer } <# @@ -154,6 +158,42 @@ function Remove-PodeSecurityHeader $PodeContext.Server.Security.Headers.Remove($Name) } +<# +.SYNOPSIS +Hide the Server HTTP Header from Responses + +.DESCRIPTION +Hide the Server HTTP Header from Responses + +.EXAMPLE +Hide-PodeSecurityServer +#> +function Hide-PodeSecurityServer +{ + [CmdletBinding()] + param() + + $PodeContext.Server.Security.ServerDetails = $false +} + +<# +.SYNOPSIS +Show the Server HTTP Header on Responses + +.DESCRIPTION +Show the Server HTTP Header on Responses + +.EXAMPLE +Show-PodeSecurityServer +#> +function Show-PodeSecurityServer +{ + [CmdletBinding()] + param() + + $PodeContext.Server.Security.ServerDetails = $true +} + <# .SYNOPSIS Set a value for the X-Frame-Options header. From 9af23b494c44749a44f679a81995c9a7fb5b484d Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 8 Jul 2023 11:32:06 +0100 Subject: [PATCH 06/57] #1081: Don't parse Query String if there is no Query String --- src/Listener/PodeHttpRequest.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Listener/PodeHttpRequest.cs b/src/Listener/PodeHttpRequest.cs index 2f9411949..a78fcdb79 100644 --- a/src/Listener/PodeHttpRequest.cs +++ b/src/Listener/PodeHttpRequest.cs @@ -205,15 +205,11 @@ private int ParseHeaders(string[] reqLines, string newline) // query string var reqQuery = reqMeta[1].Trim(); - if (!string.IsNullOrWhiteSpace(reqQuery)) - { - var qmIndex = reqQuery.IndexOf("?"); - QueryString = HttpUtility.ParseQueryString(qmIndex > 0 ? reqQuery.Substring(qmIndex) : reqQuery); - } - else - { - QueryString = default(NameValueCollection); - } + var qmIndex = string.IsNullOrEmpty(reqQuery) ? 0 : reqQuery.IndexOf("?"); + + QueryString = qmIndex > 0 + ? HttpUtility.ParseQueryString(reqQuery.Substring(qmIndex)) + : default(NameValueCollection); // http protocol version Protocol = (reqMeta[2] ?? "HTTP/1.1").Trim(); From 2e448b8a8300a2b1d0d0f5699449c76ee459967a Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 8 Jul 2023 13:10:09 +0100 Subject: [PATCH 07/57] #1107, #1082: Add new Running event, which triggers after Start when Runspaces are available --- docs/Tutorials/Events.md | 25 +++++++++++++++++-------- docs/images/event-flow.png | Bin 0 -> 44458 bytes src/Private/Context.ps1 | 2 ++ src/Private/Events.ps1 | 2 +- src/Private/Server.ps1 | 4 ++++ src/Public/Events.ps1 | 10 +++++----- 6 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 docs/images/event-flow.png diff --git a/docs/Tutorials/Events.md b/docs/Tutorials/Events.md index 85e4eabb6..bd284a7f9 100644 --- a/docs/Tutorials/Events.md +++ b/docs/Tutorials/Events.md @@ -8,6 +8,11 @@ Pode lets you register scripts to be run when certain server events are triggere * Browser * Crash * Stop +* Running + +And these events are triggered in the following order: + +![event_flow](../../images/event-flow.png) ## Overview @@ -33,28 +38,32 @@ $evt = Get-PodeEvent -Type Start -Name '' ### Start -Scripts registered to the `Start` event will all be invoked just after the server's main scriptblock has been invoked - ie: the `-ScriptBlock` supplied to [`Start-PodeServer`](../../Functions/Core/Start-PodeServer). +Scripts registered to the `Start` event will all be invoked just after the `-ScriptBlock` supplied to [`Start-PodeServer`](../../Functions/Core/Start-PodeServer) has been invoked, and just before the runspaces for Pode have been opened. + +If you need the runspaces to be opened, you'll want to look at the `Running` event below. These scripts will also be re-invoked after a server restart has occurred. ### Terminate -Scripts registered to the `Terminate` event will all be invoked just before the server terminates. Ie, when the `Terminating...` message usually appears in the terminal, the script will run just after this and just before the `Done` message. - -These script *will not* run when a Restart is triggered. +Scripts registered to the `Terminate` event will all be invoked just before the server terminates. Ie, when the `Terminating...` message usually appears in the terminal, the script will run just after this and just before the `Done` message. Runspaces at this point will still be open. ### Restart -Scripts registered to the `Restart` event will all be invoked whenever an internal server restart occurs. This could be due to file monitoring, auto-restarting, `Ctrl+R`, or [`Restart-PodeServer`](../../Functions/Core/Restart-PodeServer). They will be invoked just after the `Restarting...` message appears in the terminal, and just before the `Done` message. +Scripts registered to the `Restart` event will all be invoked whenever an internal server restart occurs. This could be due to file monitoring, auto-restarting, `Ctrl+R`, or [`Restart-PodeServer`](../../Functions/Core/Restart-PodeServer). They will be invoked just after the `Restarting...` message appears in the terminal, and just before the `Done` message. Runspaces at this point will still be open. ### Browser -Scripts registered to the `Browser` event will all be invoked whenever the server is told to open a browser, ie: when `Ctrl+B` is pressed. +Scripts registered to the `Browser` event will all be invoked whenever the server is told to open a browser, ie: when `Ctrl+B` is pressed. Runspaces at this point will still be open. ### Crash -Scripts registered to the `Crash` event will all be invoked if the server ever terminates due to an exception being thrown. If a Crash event it triggered, then Terminate will not be triggered. +Scripts registered to the `Crash` event will all be invoked if the server ever terminates due to an exception being thrown. If a Crash event it triggered, then Terminate will not be triggered. Runspaces at this point will still be open, but there could be a chance not all of them will be available as the crash could have occurred from a runspace error. ### Stop -Scripts registered to the `Stop` event will all be invoked when the server stops and closes. This event will be fired after either the Terminate or Crash events - which ever one causes the server to ultimately stop. +Scripts registered to the `Stop` event will all be invoked when the server stops and closes. This event will be fired after either the Terminate or Crash events - which ever one causes the server to ultimately stop. Runspaces at this point will still be open. + +### Running + +Scripts registered to the `Running` event will all be run soon after the `Start` event, even after a `Restart`. At this point all of the runspaces will have been opened and available for use. diff --git a/docs/images/event-flow.png b/docs/images/event-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..47e8051ed494c1ac95a8080241e3c0cef4605f4f GIT binary patch literal 44458 zcmeFZbySq?_cuD2kD%}Wz|4JLv9I0xvoCK&dFdmhbfhR0>WHk&b!8N4 z?UMiX# z-7n{}56ehka%s{Gx4rNaW#L~5|J$BX zhS^c%H%Q^b@((hG@P{xJ=VAEq{4@q$7*vcshg#9Mw455Mdfxd-Pt<9u>&1%~g^rWY zFc@ksb?%=AA3l5#FmJ!tn>+pW!!;Ui&AFMPS^a$53VhlD)E8z9Nx9HWf06UT&u2>d8S18i2JUKA@#ScCH&iRu;ne}202}=4DhGbCborLG5OP31W7V-KcHji%x zpQWayb^ZDELy_~Ww&y77lmH8i=&qcyxwc4DBPp`z${6BGFD7&vptU@2keqUaRhQEn zc&(a@x4aWqwahWcUUI}=eDL$@{kHft?HqIBSThXe#XnH$xt?p)n`_-)7#0>5z#_f$ z^IMG9=2}HXg|}XNkb*=3yddg4qv;t(aw-S5F*p<{>TF%-&wNPG!uuH8r3XJxO&6Io zh6pizZ+RyQ`~I3|gM;<5Es@SY+IujybP4633`F+VU7qQ89}U*HdhJ@jXpVk~8zqPQ zn@G>DJ@Q{GF=Q|W!?9yOww_D*oS0vqEz|LoLtQ?5g>>)bEZqY8IGsG}8LOUDt?V1G zj<3c()U)i%&on6Wnz_Qj$mlgvcQnO@0wr?<&P}E#)98zNd*V=FEbLjk*v0PV^^EQi z9-TtxS?j1E%@RtZ>7E>Q+*#D%GV*xkK#6Q)g0#G<*Qzr#i3^hO4dZX{-&fHCLD7FGORjXLd}r_dmZZid_Sl__{ozeF&{s2 z``oEoTbxW$P5MFTvDMX$#0s52tuO&+9?L*)dl8-WpFVxs^p^h%{=imrnoeG!hmQ@g zlRC~6HHe12*g9^k*bzr6*lxZF6S9fIE>4=SFHH+rbX@G5dr8feS~`j+g5(-fiMf6<_XiP{+MQ z4GO`^R-`R0EnQslzrBIg^p&{hVLwE0Uwd}=Jk7?|=6YHw4@tQsvM@hde$z0VbCLb% z%@j(Zf~Bc$l?3^dM#H0Kr5JKl;1j9?57)icCKCMp|BhilNX~H25huZL$eB20LrB5q zok6XTz+z_TMeJk01sEC{0#ALuWX;smrXXphi)jevB4V0Q{;bG?B^<_lR)fq0SJexi z=K%YEkCUVCyo*g%Q^$Qm-EoHV+YrD1oi*lEibi^=4Nf8@yF>2DK?=c7GQd?ceFa8~ zx5-esQlxur3xQ|e@EHu8sXIi;u6gqFfctb#=gl`~40UO=?k=YNe(Ig5Gu^c2hpYS1 z9-$dpzyrQ1ZFx2ZDK>hj3<2d658D=J`g`wv0dnZP%knet&N8XRa_Sea03W87J|ZbE z1v0)qPET*)C0&8XrliShF(vAk6x#WMDzRR)I*eKZ-DUgPbA5>xs~LBbdV8}xvYWqE z%t$v0RE(A<{oaS30hK5Qs~^?gQUXgb8UDrM5u!WmS+(Q`%QJc@Hm0ZyPFP5cfOTJ2 zVr^L1i4>L&A$U+H*HUZP?WCkvA3lN>W%-ioz@V;>b>A8N;(UC~-I|wHxYB+Y>#b>X zI4x(f!?=8k&Hde>96UD{JEi#4Kk0VkJr?6Y8_;Q3)%wt?KAslSte&cAsG9=^rt(8wU7G)Xxj5b1`Jx7qr(WzA5I9p^(SH|pLrRJ+bLGb?K6tbAvqdNGaB?uO zUJqV}?iWeh;&1c2*^Sf%?Chy4qaxoN*fD!8x8iiS_{ zlQxKV+CLjxY!RJ3QPNfH>X1UjhJO6`(I-Xl!B541m#{fkMIpr(0=x>R!A5*j4>sVB z@uz$9n)zQN)Wknte0$&s-HFc0={#KSKP%9V5VZ0!judmv!tS4>2jSvY`uNnY1hl@{ zV)J2?;0^=A_E~6)9!%VItX`{)6mjt3Qp+~Ionm8z%HTt|>!g^=*TmYdU%!gGFKPB? zN_@>WQy9MKODE8P?>&KL1<)E=7zP|sDBl|B{=ushA)TpVe=GB+e8 z>@?ZYjDO>)dj90L4y@DwgO97v)`@@~3QI{_u?8D2; zJCl?Ubpg$c!3s>4Xz07fX=dK7bzEI&Q%EayBq{GeRF-?qOKK?z^+ZKOu%PNfX`w)W zQ{8~$JldL`MSC9h!$@<>*c6RsrYK??%`-5yql1HYhP7qB>iquR76%5)=LNS~ay#&E zDsS&|)2%{(Q1Q4D_f9-LHEQZLqR~F-mzYOANd?hW51p=hEAH;(Uk_F}we$u_ImM1z z3^nlT)(zq}Yx!Jg6EvCAsn751@#Xc&KNM$go{f-2N7G*oz;|nny{usDjV+yIWa@-) z9!85+hfrIOV5;qa7s#0f1RtTh*tM&MpDM*jy)|fh?d_eproInV1{Sw`ZwRlRId3DZ zLA$>&s_C6*r|#RY)wQ45F^5%ir1iY zW1=k~t#lJr9u2D>#QRBZC8{L6E_f#Wn&H0Va$z7jug0gyOboafOdfNOuz3uq6b=~&v0F(5J= z_5Fc!h`!yA^74k&T|as0LE?jn?_)nLJ5syB!AX62=g@pzT^%T_w?g;7$I5~V&R&b( zgDL~Tf5?G&O84$1iimTmrwW_3Fl?SIwi~XEza4QvTqd^~uk#mb;{@1i*_E>G^)hgu z3NGG*0dhP^zC?+;>$?o?5A962cJ->h=b~DQ4Lxek2JrxL1`&Jk z8##L_K1rFn{RqxOLsL@Nl$jxmD3J(0tIV+Oc z{vsiE_IOy)6>IQgyoM_G|Ksqu{I$I~i>AMLZ!jt$PA+r?o6?zO(#RIK!%nWXZiYV-xL4q^<{4UAAS7!a?yfzifL8Xj#`7>a^7H9JXCOCY+vQ*=^q(E>O0$mV)g(j8YbXeRzz4 zG|iT6Z^g<6g$vm*9y>-*T-C`oZ8ofWX4RdA>1IYsTm84eX*)?vD`HgZ6RPhvF<9{=lvgjy&7Gv&8`k)B1pLz3+F^cM)E!DV&wE*L z01sXCikx!XbWo^e&;aib0aG|#0}f(`2fB{HU*W*>cZ86~!GC(#4}blihyD)<{y%X+ z&~?&n!%F**^%{cv^32=N#`L8=`4Catf`H1CqD~LAaHC%98KE|PtxWUwnl1Jr*_~yTW^1mS4dg-+_|qNo}ug0Rr;lNlIZG2Ly<C4YBdSUqgrMN z{IZn1{iw)FhsnTSq+A2>yF&(Y_H*2XRO4Lc+F8_P^UEM|Ui$I2cWrJRK)E~zWo5;< zXygD-tsDp0>_KFc50TB`2v-dHmD!>r$;0-C$YWs|;C&$4ZBs0d_m>#IXM$gHe~o#L z2xHqIG9Mkv*>w0AYETCmsLI&<*N})7C_%*i2=e5A{T4mm!G0; z%RT45G*zvf4b$^!IUXoMPcaga<5kfYQ>?m-yEqpKm=Irib2g&8L;{}eghNv~& zGOtY`>7Ft)y65wKJ>`{x@u+OKqEx#@yjsevH;UmY)qy)Slbz`l_Q_ynB1DBf*A{NP zq{?)QN&QyjY_s!B^)5Li#z&|^=5B}bz6+>aC5&dfz2XMaeywk$5|p&m@}x$^9+Q_h z3=p!>$5uRg^hn+2RWJ~V?%Z2CRIi;2DVg`+=lgBPd3C$+M>3Cb9=^O6+|KDRm3)O+i{l z6By(UO>BnILC(YFXMwix=$y#zdv~b$RKjRkQ7a6bQ(fRief6ntM|F|^{sUeV-F@*g z{}}cE{73oOD4kGP0mbf18Kf`^uZ-ara@c`d@hwAmZ_we7+2Le=&;Fhk4)@7}5 zUNnV6{h$9(+u`)fOuyF;v*`I(zCH<@l;X0(MCIdu%x>zB`aSz)es-0;|LzAN@UIt} zL5?0yuQ-yXuZ;D)N=IKX-_)UWW_Z0i_dHYqm+cX>aSue~q7g7u?r;L9*cjHiQA|zp zcKAgS_HKvKwC)d`P0nSR9>k5i@LcMTUG7>jA{Am1ZqO}%|-`-V4dy7-Agbxh%(*I$eZDcK)DP6>8XG*?75BjV_NbKtbe*~11! zX4vgbHnPr57w3+HU)ZiWjBzE(TZwWv2){~Ym%Fgv^!9`mmwM^T4d(8{jW%&3hyNu1 zR&eE{e2sVrN7;z!hrkaheg@74R~lB5ocf70Tm>6l)JDvlk%I`Y)BrmlIE0cIH{fL0 z9B{_pz%d5Vu;sQiCd~3|{RnhDwENt>TF@YtU&@q)@xBUbq|1?otRj<=MaFVpl-i=q zJM~%ud9NK<)q1ub@obzytlMs3!9ItchF7mtvHt7JMV$xkc3Xt>ZCfp?fiGt5Y#Z6< zMYmPbd&_&?t#fZo6>EPQh*Bz*S6Q5F_mt)-s}VcAOtsaD-cm_Yjx&q)?7+LTJ!}Vu z`Hm=TtkF+TDcZl0MzwM@4!2Lb<^a#$J~93G{+X3MLC)bz^ijN?Zp+xL@dmC@tJ2T& zI^>B#9UHbqUdv|ft${5;%N{dcPT!n6u(J~wv&QhkXx_tK~^`C8p3gr#LDk5 z;|3STteH|^r>Cj=xbI>m$e|WC1DuH8qEp-s-f$6lF;R&Vafll+U0eRm=A=2Zki}?;ZK_3aykY zE!K^-yg_`Gj*$zsIe)LIt9dqE+?Asu+r=f_ww*ruz;g`WN^9#(BR00?SlbL|q<|T3 z!L4OEV$m`w4X-X@Ge>KtE|@gH-t?g6ScKb2cTe z%_B{x&Tj`r$4_mZ9;;&VnH@`PPxOsUk;NI-UXmi^aAJnT>}PLc$B$QGJw{>vz1giS*o#(R$b#_nJ!Gs6Q$HbxeHHHEocKT6+pk zZj!VPKE>h(udWysO7q;4uvM+_zfcf;M*HGLZfwFirii`O6;BS59p4y#br#Z(R*Pe? zrnlk`lshS_kftc*XdQHv(OFo%k;9rv73J_#wP$6b%G}YTXCt5pR7nuKI?a!#RHhdM z24Rcp%jo&dWt>ilND|n27{P#aIlNHyWUFcMy03PiCTYq*a~RvXidoxFis=Oo%3I`@_US zIMA<)_kRzIuXB{ukyT#2(V*opmaJKZNtN`Pe2$M*Z@sN(7VvVKvg9V|%EUzD=(uG8 zf$A_PF@v*@op2-Ps`pueo9by=@owUcHoVFwWY}REu!*i1l9fnY+}zsAtiJVD(>)@0 zWvyJzha5WJ{*BUgtpiOu>H4G8v=>agtS8imW34I2-}Y+r#+ql7J4mj5d$mj|=a4d+ zsB-nJn!;4k`{{EshUGp~9pDb^YF9r_OCOQv^0&L3Z0iWq0bI_v`s`e4ggV7ii|4Y5 zEFIG)iTEP9(v_OgSOE@eU)9X3R;^-T5EFwtic8p$COeOj4J5#~V!5v3Jh4=n&Mle7Z8F-n@z%q!J z48Hoqt1$M?3ciBd3r}789jah<`4*l-eEyy1U=2_9?L;R#Ljc$P8o~mJJCG;>nGtA> za);vEncaP(mcT?P0@M5(tH4tr(z8 z{^AGl5`Xc-4$JREs0jTa68s+y{ZIN`a<$fjQ@^;&8itOV{@gvH3S(+trABR_hry)h@8>1iZ4gHAEe<_zRqZxHgFMj+-0_8S*oTN$!hlo zns+l2QzFnP%OeWqnA;TUb@zRFNzWvYk3#JB26Aj#VF>If$cT%MQSOY3Qrfw znItS1#gBHZ4?dx|*w-zV_h~=p2EY&wRLs)}Sw|jQ=I!c(glSsoq!v4rOUB5_49?gL@=0<^!fV*kPZb8D})(yRGO3}DosUr zbc~uEbM!i?XmFKEzT$_gkXH|&Y6<2F#rEE1Q$ArAd+tZ~B}_9tzW2z51Y6BT!ci6- zeufL$%Qlu9=^U7M^^uiBi(EQh(e0ASLg}ip3zT|NpR+vVF8amy<7u}iWeuyv!zy!h z)bloY8@>wSb5cHVV5UXihKpb#7E{ZHdz7DDs|{@B2&g1ClOM8<>DBg%HoiySw&ZgP zV+iFrv+?Duoj0XYXB0?-D#;hAvFoa(zm`ebLAp~vuJE`q89EDPsU;(vckeqnz4 zz^;M&a~-Q)44h4KtEVu3tir!CHp2n(4F4{m%0?NSDOw4p|JsBh8(Xf=|7{b^U&dq% zpSAwS2v4ZkG4H^E`D687L69x_@40~j2j8wZJ=tC3E+G)x{T%0E`I^df#lydH*OkcM z{h{1d0GX2=@1_cIa)yKu9d|gILiq&pmSlVe!#=23NdJQ;k!jJgLr^owx%aISpC$vw06w`jB)Pqj4fQI%k_BaZb*OY` z1hL;%b_?nYs61Ro??ucMQ4Sj-?$Uhj3zgTI*tx3HJ1);Z3mi(|>`PFHhBRtoG}QG( z9r5kyWm|=Ep}bJ;BO@cz`Ft;cy9_E%Q0s&cFcPv%tAOc;C(Us zkr#HEHf5VmFwQneIh3vP%&`rc_NxRF5jiLx%LShm0Kkn!=O z7p-xq45>BB8a~4vDU*e~4v-X_ar9ER8NongPBl&25i(!3Ni$#0*}+>-Q`O48_wA$O zWQXS>Rs&05LT@OM`{F!^zIILpXGDBx%Lel8Vn9RLzacaw1~hpk!PAkc2}a z>Y{p)D{-vYl~`F-HA8KxDCe*O$itp_xYoBf%jDID5Tto1*>5RM$_X6j*0gPj6oCS^PR5-p0H}x% zu!xsGjiL1t+JI#8Esvrr$l{pdGAs@6-TMhs3R!j`wPKO)#ehY@t1cUbJwOmP4x743B$ZoEMx>I;PSeT300xlHzON#~^Y6f>qbjO(n5{Gv0&3AbQWAP<4b> z_HJtyZ{DmN&PkEuF5?omT~Q(}VwmIAD`Y)Lzj(!dnMEC{{XKa$v-ZsP`%Vg4YU*y# zpBO1Vs{^#^%{FUglM882H^6D$zKva&NQiDQe|O0-@Z<>Ct+7|huzKLSem>lavs&nM zCchPDWYpm{S)jk^59uM(TsIL_sEt{ADX1?>89&1i^4%XeHDZ1(jW(whDN* z>F;rkko0CbeH!W<(xmIgtj7<9%Z5*&f6V2nfa26^YlTU&_y`Ul(m(y<_LTJ;50yAxeWUXfo=%}eFs{D zQN+I5C0M3-Yi$zhtifz=`OU1`;(g~8;_O#}W4IPh6(B`;wtR7?cpi-S)K)hmMQVHE z)K86rnx)dL4i~4?Sj@J*MCo)?5qMnR3?)*jD?}mq!t4mBg>ua>s|X7UsxwmhEFbw& zgMRv&%4;EWg-Ao`{zfMHyxxbv)!xHa`D-hO8<*d;Bf=H@rt5-e?A&&^uvIvTLy@Le zo^9P6j;hpa(+&1DT7EqA>(P{}7*cR59u)WkQUh=R$_CW;_nn+o-M@2~|>%5~&#!fxAa|Vs5A^b}oj3g46OZ5AMcS zHiy6E$_AuFdAYf*cOR^c{b``Jgf(8}@-%b)j*dtf5@L#w6X1;GJL9~t9@Atr<{ndP z$?!GNxfu>}Uv=442BKlZ!1M`oV93r?t+wLpB?pM}Uzk9`s_7&=TtFT9<}JPCD_yzT zfdg3HnjFG{zTiC5C*@0r!C>Ap?MHwR8y;&5_Y0l^D6FCN+H-Oq#rK!* z8W`UoO$2u}nEzSMvw>CTQs?nA3a!4>5 z>f)f2YHHA@)ya85VJdBBxovH%0{7ItPFMFzq=Ex*vbD7Z1jg0|u{D3iY>bd6GbGI< zMe6KlzImB-iitBoF`+RC-+t>6EzL<+rB-(8bk*xyA0L>%+zLL_JA*@CFlkQQ7SsR% zeOGy-f$Eke#w_%~a%hzy{)E??N>FzbxUw<^Bi74#mffcjw56V7^7_%q@o02Q5Ql<` z%L*imp~9H6xxS24?Y#uP3j)*eo&yzUuVn81=F`U6WpShAZ(x%_Q7d{b&42(nj5XCq ziV$mF(TsM2V2?G1DhOUaxjOQFL^UQ^c4*)8@qTnwfk*{4SxP^;0Nf&C3*=Ms->QtC3>oABjSs#tMNbh9c8W1yyybVq-PRYkLQc)xw!C40rsc=Dmd zfIFm22+U%rsV3XNsqFAI~L%uMS zQ)R4K2)B?So@)|B6>@&K|7b1Q)sR^_`4e8@l@x$(@miflZ%-03HDuLXYq>bYe_#Lt zp~Jh;Yf~1S(BS8ybUZL&xt1kjQ$;}(Y{tI|R1#2N<43{m|s+4b5igDc}wXq*i= z?6GbA`j!IIX+s2Jie&3FP1{8MaccHi?~Cc#6F)W{5m}52BJ&iJHVlt6EJS{H>i5gm z<&`ua|GOZOxzW9Fd%~)WFEGg1a#P?@r+d3#@mx=KZQ!VZ!o<0cvfbg#n44pvPU!=e zEa!e$NANW(OeHsOKe(DQPWOhCf>-mjfz0SfU$@T4t);nrowr&R7wc_h5f9D*66v%T zjOXTFU$T%b@S3loF(*795q4inO`SbfK{1+#^9qs^w)^qa^^)?F5dtt?{MZAc%989* z=O^BJf6kKBwhWCUMnerY3CSfFDzat7i)LHK`pq^K&S$MZ72wv%fADB9<8)SAz4cFf zW?uB&HBlBialM%9!KW6_UASXSv&`~oRl&uR*}RVBS)_J>U1HybEU%!_oG}KvJeIAJ zC-(Qn*>hwIwprw53|a4kBe%TGmV0Hd>$5=`PHs_*w%OwR#Mr0~FQ13geZRR7YPG$p z=Bl~NP2}_~&9ZY>bGI{HsvA8mKBqINmijYPGin_DF}5A?CjL2imU|l#iNxKyQY(S+i)e)Q(5y_PXgX~8@C=4KjW_7 z&q1kM_GKj3Ub#N`%Apwv`xgySqA87xp%O-yLR+sG-o3=o2edf)r=84gk0pV_Bj z@ioTnE(IO$^To5b8w;YvH7XdwZCsVRq&WM;lXD80^2L&rmp?SzzRJJf^i84F!h<8- zw8t6WCdvnotKn-{U6(p6v_(Z`Qz@h+u zbWKu`pIv@B+Ln>HRvUdr{exo2)Z5t#(JB1%{Sn3qdnrezV*))I zPOHnWHCk%x_BY5f2fz9x$=~boJ*1H=+EVF?d_x%9lj@XAU7j~D1=5SBmv4Yi^+q&t zL&)MaH!n|btN{-;72o+fCcGit-p{%0br#cW+@-qW>z>CImuQKSrHPq@qWkz7%g~gm zp|v$98Pf6kAP#`W@+!!JPafSC3lO+N0}iiGiiYqrmulngE3Yj*FernL1VP!M+ce`T zE0oGlYwgojG%s~|YD;N-8T@L7a0gX0fQ ze7kqhHy$MIqf$TI{*X-3 zq>$w*LZ1)5`1w*K_qN;1by|Z>SL>FI<>>DMc)d1F;RiLFe(xnhI3is0yn95hAMcpa z&AH--Cp=lbO-_(j%OKb9yUn9yqMN2x!o0(GAa3N*yNPF=Odp06ac_C_` zg1CJ&&UUC0p)I3$_{T}n+c$AhGn3NSuiuj&+EYX1-JCThhHckB1q0P!c+E<=#2VW) zPPl}LIFdMAy0sR4w>|DH&edkaJ5-_7NmA~W8U8?Z+1&6J$FW;O#`<;m;kMEHW!zIC zb!Pjb^?Fz?+mO!oa!c%&PhW#R(mUDfDNs^ehI0ddzGe%cZ$tccqF@=*d1yTj#8V-_kCs<a8N=+TZqtRUzv5b`r3^(9kvi0;79rL_+C2EsnHB5$d za_A}5QYwTuyrS(f#TvpD*>x>v5*jkkzD0l3*(k~l+@PdzX9kCx`~~{3?A%JpFfN9Z zGjeU^2B}%Yo21Ens*O6+yDTCGnE?W3gCqUh?q_w*Zfs4tSUnfGIdx&Z=0PBJvLKfk zw-TSftQc#;v!KW;R~fU$JjL{RZ+^eZ^LG84a8AXf2oI~(& z)y(YuT89qSWm+ckMg5`2`^lip{cS^21+Y()?2=n6Mvs>x0k;$_^CG>&Is8qjcz>rP zdaL-P^2+=kKkSUIK!uGz!Idlq8jkkPh#*#9(nk*MZf*&)xQbu6T<;2@&lQ7lIBZz$QoI|9*m-#>k*bzVC& zaUs?{*@|u1S(`ni5(yupNn|2;(=J|1PntFs3VrGCDOhZB!|`ow#zDaT`Fq<4)k$!2 zv)sKZ=g4#cy}+8e{uK;xQt%fm+BaSMYZ(|)I?fMhe`eXL`e6QHZQ)i>=`wCz^sb(P zBRw8Op!%y)z6IIPgOX?bx>J@?{~_p(7W0iYT|B*R3Ur3AU!Z8 zl0Ijl5t=B@E4S42DOz^ZjLjLPYYv3dQ?p^8^UN$dWrDpfo@O|7gc3OFvie2AIQg_*0o_d=qYJ`| z-izzAs`4Y7i-*Wllr{UA0O1Wi6qj%VUl>NANI)o?1-M0?+qJ+N0xfI%Q8vizjV;g6 zv%qPClr18zwY7wDA(QB%`H%aD$ z(%z&vpij>jdyNNKUJJJ;PCy3QE3tnYoKz7)zvLcj?ts?KYl%{K ztqUT{oiDH1kfu-!lZv1?T#h^5W5wifYJX0A`rdh5+!6m~1G0Y0Soext&f=;CHixlR z?9|Hgqngp%!2?Hz>GPZT^w`2GYg4}QIwT}lxMRd23XN59nWs_0^UP^E90)Hnz;DUv+XQL`-_?{u%ZR(s7GpBQVw7vW{)SiorxIa#( zO_?~b_Xme)kdfbNtnc`E5)q8|$dr98`=)ul)@k<750ZN8-A`lt`o|RYU5q~#>YGHJ zwch$DL(3N()$ehqpS$~lY$(U}+M4Z(9w}6~3ZRqK{%f*wTm$5;AbFxN91Cfcsjf^T zXt&C8rsmbvRCkBo+oti7e>3D$i+F5ru2qEHcL=)sp(mp;ln>%oB6L^qLMsonLo_qp zS+>=mhqUtYEUpZaiuSeVjcPq0LlA3ww+MZ=^=Xxkx}y6$1Pc+NjlA(ooZXxqQ9gnp zjl8B)Wf19`$JabuaXw7nsm+)#^=URVVQv^aR8~yTMMyIxsU&!9Z!SY>#rx$;Wimef z;xnT5_wL<;6cA*jAn|%P6%DPCBzq6M;n7k5WDlhZiH*5yS}WhAnd)1?L*!J6_t}_R z>@;Y++Pw+xsy^i9UC+4^>N&<+qtW9nCmosbLqDJ`=LDp^Agcr|cHa`eZMHsbhtxe} zSbJ>t8e}<;&c4`I7ZIR$4vrsfYQ*M|#I}aDj2caf{=nUP!*?dnHDn%qd2VUrL%N~Q z!_&%mQ~O4?%9umZb4QLrLZPmbLq}DyjBfUGYUlKpM#pH=c39&pt*pmuc|V`u90I>m z*-Qy%n#UY4!L4k)0_7^@+oT*x>e8LFKJy+-Js$T<*9O=F#5FoHo|%{=^5rT%j~0+@ zx;5uhE4?sMI^$)I9UH|ay^|{P5`AXreTmZ{_m)79TF46<(bRyoTrs6|3xUu@j%m7Z zHutfIE3JtQ^50GGRdrmWtD`Vy7w%RaE@B9B@bnZ0G~yRu`cTkt%-5dD{4FglJ>DFV z2R%NJm17%I+kKdCl0A5()<_HZaCZkYZNLGOk?*3FQel6*SS>On0L9-u$rlj0U zj?lFS>5W9q%#R!1JUY2`=(bo{en1b7L@pz#jlUqF)(S04##IN%8IXohW#P6&W*mjJ-9xVAkRR^&1gc z?OcylLSPz!brl+3AiD?osys2j;H6-n7U=(ea^T3wa|m)88$hQAkcS)$Dsf+KAnLeY z_CKk+spOhp_l7e_Y~}j(>j3Rk-Lf_w*;GI;P4{Z>wpHp3f9n>*IB%@Gwmm#(us1~7 z2cNcm|KUcBy%1h6yXpn|5qGR21r-T}*HFv%m>{3gE2CrrOqv=_#;*QK(V`83JZ;5( zOeV~n=x(GeS_;#(p=*sV< zcrx};kqj!=*B;#orK=u~E#kkP!dL}V5>)xzXV$4j@xl9?9?5Fwx>A$S?~JRzVh!D`cnw)BR69;23>3R&L;wD< zBzz|{I~_c5Ae^Hwm?dsx7Sb6%e)w5IW(o3+VRlHDHWUfpMG{M)v<1C$+e^JRzK92U zlawQO-Q5NKW^Ih_o7Z(pqP^=Uoji;DwGhr5ua6Vp6VC*E$HY8qGhq#1WI<=F$X@yurI^O2^JFo|uI&e?!eNbIh!7xDwEB5orCoQ1jV+SvDI@iR3Y#9 z6{5tTiBynpswXFfVF}R5l}L}Mijd6!w;v~me58MLoP~)=Xycn~VB(9sTU{Xn7V1P< zNYcV#eq8G&x`J8+fT#{p76K!-S~*;6ScYfDlqmnQ-z|G-ViEJf6%&Kof@OQJ-}WTB zJo=(M;}`#$%KF1MQdNUZz0l4#e&y%uQjD%(oO6ES);J z{)7Hnli~D*5gGBxC*O<&$^Bz;*CTi!iFCob@jc{HZhEmU&2Ji$FP@>vu}GZ9=Jq_p z#qM~;XRxv7h7F;pYTFtmo}!jAmZeAz4S1uIl`94`TNfiz=S4rLT3>Q_hEaM^+~Vs7b;|Jp|l=!Lo)@XBqz} zgo;7uvaE6_rd47+b^iEY%Tysxq8XwgCvaBs`LkzEGkvROLK>fRz2(kg#G~_6lazUB zuaNQw^6QJxtCTo~H6C=rhAnm2E{<_6r*{&DWIQ=NCcd*3e1p34U}+v5)7ih#TdU)U zegAOnpcbMC%|D1>aPG6>BC@Z2YPF!+C{4<*Jbwx!YdB6Wd2)Tu3Fac$Q+s;$FiX~2E4-nu%H*O_$K!3C*1&W!y*0raUj_MTy+8RKMSSC+CZZ( zuLCN5SfwLA9Kq_GT5Fm43<)r(j9H6G+CYZxq|`+v8`{I4G=*>AMuXRsHB}Q8E2=_G z1_~V;H!N&z=b&nd1#YfANTKgKN?ovtbt~D~<4`i7zjn|Uk)(hr=(2)J z7p}k|LH^yPRpL1E8^cFdA!rpn6_+A0bCM>y5_|xc2M-ni1Pw-jhMJn~Ajhz<^U{>o zp$@Pph!;YzfICkB@qNj8CRKhhvNEa_sRTkg@FJ9X1ZD9aP@aIR7Glq_)FW}9Q4g*2 z=w!&viDk0*1}d+~v^;3Fy{22-!FU zU;3-22|$@DKV{82rDSOCpZLDXPbLioainNItSX`i#w-a=&OG@MFf|2}MBm2wqS?}Q z=)qlYvzNWxm}IXA`8FY}Y?MkD(RKKTK_;)_`*O+XE4&)=B@b^SyoF>~f4nV(j4Pk& z*)kvd967-W9Sg#Y%n|e@wfGFk{F0NGN}iPdq^tZHnEH~}Mm~2Lw2USy#bnhsg3|!W zY@dXN)9mb0)0Hamw*m~H3`u$7geK@5aJ)KFG;9XTA2GrfOcS%wa}@_UT`(#qb`F#& zf&~Ho0Ia_O5GP0%bOXO!=xD7wh7VH2aA`pG*8B*7B%(c+-p*?iuFcyHKn+heRPMaH zU1kS@flOKcCTjOv!T;lN~YXdm^%a5Ac+QP^O_;yYruLo535FEiR^RA2`;Ag6sRD{pZFhyLmN6d$oT&oN%haF{|mR}*9<_7 zUudRZJ2=RBi&WJ4KX6dMhi^6h0U({i{KkU)hBWPfq5hiy|1rW2q5pfMe;}#;d3jf# z*F{!h>D5l=n4YR)HY#uW6q`}?Rwe(+bL|iu2W^kka^|#*VHkX3f|Clt-GEPuDw-E! zGtdD4{c?H-eP_!JC_Re!a}2Mjo;d_mwp=G=c|j#5rhMXxpn{<|k+dpP#?b6E0`X&E zZI$4fyUkd9Y~tWO~`Bp_Z`%M;-v=+QtL$kk4-!AdAqHff}Sj z0Iih(&))On#`Wu(J0C%Yr=c186k)A9{f!x`x10Y#YJaUQMS$A;NSh|ojEz8!9||tL z10-xD0$a735(OZ!FoODB`xiQUS@IWh8nwCGWw(O?zAX6%n)U0m-(cpi&wjyjFVp`< zW&iP;w9|T11ekS6tF5N!^)lUk!aY+A%HlGHqLvgpZ-^SY`a5%q@eky? z*pw`Wz=Mp-d^?kvxe`L*rXQ&|TktZbGj_(z;Xy-I3>d|EW5q7Q($68pk4aMhu@gLj z<~Z%`*9?8YCIB%xiz#xNHebv|eKiNImo*D@nQ=%esoRh-B6xY|!XMOx%Pka((6?^+ zV0#}Q1avb3!WQmWnIFc^NIFevLL)H+pqG(eT-#J|#RDss5LViGH8ul+hTRvAb6V~+ z_yPw(!$B|7J`An>4~+sUbCJ&4|8@FOIise@B$^~IVJ$&A%_z7eOGGNSry72+hC%;D7{Pt~PS{gw|@cHzk;AcStGVywCw9Kf>i8kpvC8hc^B@RJ{BhjJ^f{ zBE9oCJkn>{+;MDKt`}h-@bi&d%+~|T0iI%A*&u%Y5)<+iM)zj0EeNjA|37< z0`z8pgKXJYL$C!nl4E3{!4~a_%#z3rQ{W?DNU3|E{am*l+?Ool`eT%oG3pKliEw96 zNgfGmc9$NPzspBU@O;StHP)m_0BC9oQ|aasT+7u4H-YsFPXJ&Kco5P-y}0eANix3T zJCT>n+s}CS=jRJAP6GUd)eHyN%Z+0D(T4K!a=4@A1lMeLHV0@vyqfXyt388!y9agZ zKG3ob!filF!EI}9NSnMYU@H-vNaLEg+X8?biO3v)xv@rUauU?Tr}_$1HrL?RAOM#w zcckeM8~_Xlp4pha68LQTK)J^>`^ydL^cfgzASLum3@&FOt_&PzQ`q^KrVE>FCS?>ZS8ER8iDdz*hhvI)I9XCjC?QA%JK5} zi<;cOfBy|>>6B=jzWi_bcEdno?IKxu(1R$HW@}{Sj-^78I|39B!vCkwpUXY#NH^Gq z>w{DpL7nu#)n?GrPiG+y-JEc^`C>t4#qL(WfRp@D z^U4|DJ^={Si}K-61p^^LAklD-3^kATXV4J@jdoeUCfk9yWEMdtv#gd|5J*qKnILB zy=6HMr$}`6_$C1n*!NQNW>{ssT&VnGHLs8Z!Pzo_Hy<_-Aj50i6gCgxDEbR}Q0+%P zXbQN~Km^dsaODA#2Adna;>ZlA7^=edo~;zoElDT;{`(vpM%i$JMdZc%%5c8{ouDOD z&i$Gi3<7Q|j|Y=A`7>Vwod;43{D_Cr)dDK1o zK#k+a0aJc*I2J;3P|50>+!fj%uX}^u8g55!Ybvw(4(S*vQgEw9(OVmEO%A_&A)q%Q zMGSa&A2~=;zp<;gvjgDo=&P46-9ATr;Y1i_@H*(r^UHC{04jliG$q=sDJ%fc#t?f5 zT6Ekh5%^K-OAk^j#7-^*fN@jEWdVy?>AQe#efaPpL`yTXD|g^pow(I@z^UDk*UL8b zT@%Qp8Yn}Tr7nRMh-l?P%UoDn#-SDF0;4?$_0aUfwjN@85wSqRN>J&{HfdDOFBXoUdqH*7 zpMcu)k%IK`i#?SrXJn3E|Lbo;DbAr(vS7G)*!q~De^Zm`CtQIa$J+vDCnuRKjgI=% zeTkowh#e3(J8Q8C*$uo;)*QdH2|jN7?e>JsxH*u1lknKwtz1xG0T{e4x$=q{qFxD2 zZu}!)Y81lGRSb&m4-A9kzHwv7ZFEQa0N}CTiMbMd>4mo70yi%&FS6s@iin%00r}|7 zThk+hfK3;)>^hfQ6m%y|Iqs!b{Yz@Dk%M&rS-~a*Rx&d)_h|EAZ2Ak?+<(mR;E+B5 z44H)vJH~(xGz=eHfYH|px&ut4GF;WRD)rjO?f+`;t;4cjx31C0UV^m{6%oN8q(l(u zumvgUE(xWilnx6KMI@v}RJxIt7F0k=r9%)YY3YV<{BW(k_WPapoO6Be`RAPTz1Ov` zy_O4ko?qPeoMVnT$GD%^Ae#UOJn?j(n+98aYot}NS_7~9ee8lXwtTIykPs}!=snnY zksTjbDJXjrv~%KMXVEJ5Rc{?ofmGJZFcr3UgF{1$;4>??8ua=>dY+A$nd_sy z?5*&r0!5@*eEa`iPQ!P|Isoyjd4)zLjq)WhBHN4NpZp2MCk4}AwxYX%9 z8)iz_uO*MM{D3=U@#B$>g0`}Bdl(bH0X*zM9&+L3{Q7I?O7{efRu zTwL6;X;WNLkPlN01;gd~Bz0Rv7_eHwe3n;{VO?-d<$NiVDQE<@R#uw80yeD$mUkWl zc&5H3w;esk$tX@7B$m`KfNj&3G&pw2X{vB@4U)|ahRIGdFX58Q%Me5BZrA~FK4X@T zFS_^EX+AA+88?!dIe7P_4@YjzDoikYAHaz1bE8(i9VdboP(7rmCddolHzDI#dwc9s)YepMSmzC+5^q3S7FV8LZtHz^Z(ln|lhZ)&jq!cE4+LsxGjkRWlot z80p3(mn(?1Rl^0hVd_ZII<46$l67)@CMBYd^_45$rT4#o|Bic#e_FISnunq9`MEhe zJ3EDVg#q~C%2sw4*0{RwILM+9`#n_^A3(8@xaVEn(<6JF$zTS1`gNo3Sv)!h^=S!O z5hnnV4y4G6?daT5%)cI5B5)X-C3MrVrs@A6uHewep|*s_gG2ImXz)!Cwz(PnJ86Z= zeU7?kBreo!sA~Fc{MMD1@X-(go)t31EMbZ*gfdYW8}mJ zl{T7yge_?o83jT1B5$C91w7*dFIs~4!Z0`b5Aw>_1srEg5twn2Ni_??PIDGeHGp5; zzrX+1N<$24Lm=sIMXsw@a`phFND+&3b41J}SwcvId9Y$z6$Zr-ANXK_ zwDvrck&M5zpy&t_?6(l!vIW-%)Ml=i@8W`KbX!?KOpg9cyxLWu67)C&*z?VF>QEVFJ7>w417Vd4%@FyhCGENdzph6J(w}8|KG=Pw-|Ik>7w3;JE0} zRpR!MW}_egGKoY3HqHcW;MYQ=_fTxWo=#?sjO$;ppaG7>9k_v&i03xh(2De}l%l2C z*IDGMy%!95Yh7JB%`6HE&6~;LO51J3fwk zkH!SI7Nf;{u9iO7ZY+q+!H*I0@bK{5uUD~)2u;q7Bh2lwvPYLMVyhNE^|?=wL1zGg zQ#u$U@E8oo$Zzj+)Fm5h%jWR<*XHMe88ciAV?}`TEYVu;xkoGPu#HwoMdTO~3?2=1N{AybMq!aQs!gWGNxHIX($gFQR(h*?Od7+{IyZib#HFW}i=H`H^H;(mS58Ggbd zN0dGqp(Tduav2yHc@)0i$#8r)YVyof5QgBC_pe9|E)mQr&JQw<0*I=k?vM1pa{lOgGx zLd2vZSg-_K1#x#e>X?_9Xa0o5UDdH~X~SO^*haQ>t8C}W(TkoZ9JPP#{Oh+&!G(bD zT>ErzIO+tYL);E%6n26(As#;Hp8$g1!KD;~B&q!CDe6Ef2?Ay9dQ#ctnnAvhPW-{V ztr^^l>i_3Io<13bz_zDfb1e0yHB=(S{>-H3Irlvf{+gw3W7>*r$K@{Czs)IO7=RHz^AAt8yIX02#li^^^ z@45&p(S9s=!!c^&FH-dbWE?d!f)nt{%E|)M*_}I|f@%mV(C&i{)xSmC=WzoP3ktTTA|K~&4l03)S?|YMgZyX3G$Fzpt3~g{9NN%1* z);}48z)`elJ49EY{$FVmg~Zfw%*W{BQ(Chb<=2EMwB_*gk|O{qgC3tND_OA~4adBU z#B?1oK|?;WbF(j#4DGI6Iy0j|SjIXg;MFU%J#_4kPtDHqI%-#)Cu{Somghq+b8xI5 zfY@-RFo>{9Fb~x6EYJgpL}C$oo(sd7l+eFN5WrFW-&RF6*a_;f7LA= z;c+-+*4Q_#7CrW0JmT8qUaUu<7@4k_F&^@~?1#F3oc3kAqS zalx`1oP?bhW!4DMZE1rE3~ASs|Nhhe!zaOhM$WGC+a~qV9HG(UCkoc@M`N@dwU^7( z_?!91BEj6f#ly2M8U@F#I@i_Nii@tyPTpHhA1hvMb1k0|wlpB#0zM;W{(EkPhEjpq zraIOWA7%5b{ijsxW;ENsObK%udY4!{rrfc5vo=x7`EqY%|1ZDySe0kk)tA$Z(gz%4 zFILM%8Kf}U*th-tiBBk%x=YCq8_!x%R$c8`KEA>;m&DATTXWrP=^Ue*?lR>Hx4uK; zw*495v>jSnEnJ3@1Kknq;@v}eHa*wtW+#gxE$$)^QZeD~2HsZG&Frju>M~|4C!=64 zW73;y{Dt@3$7`MBWtUvp*fjHEhb_u<&dCJP7QB7hZbT+Z*G-4A=5frg{K91+A)%fF z? z@~yRLuH13p%|M^fy)hrk2Of#fOWGKvl|;{38P+&Cw77HD%-4SWY_*A8WOc{o7fTbl zPx?krFh9SIK>hKsTQ!UyB=uV;SFFpDw?Sr_a=O-v@Wuxp{;vC(tAK2Q$n_;;4viR%U4Uc-gclJ5+^LROW zSauDvp6pGQXgVH}V8PSkHJmDRpUT#*GHlaW^3dT~xzw{AG@H^Xj|KKWNluY!IeA|6 zTKT8swBA88R-x6^qKbkZz5SdoxQ7?+pgqSCAk{SXVu3vk{Yf z%gbCx+6z)wiwzayX+usbi}v?VA2^pgZ5QD*?|WzdQGa#YvDmM8F_A@j?cW==ztR)9 znJ62t;M2a&fSl4)F=@g5T&HusV@fHvGg)V6p~?8|Guv4<@2T{Qkz85-?Ev?ZO^a3C zQx;@W&Jzr7+3lK#-cfhcH8hy+qg=@ARwetO#d;{A)yw#pQ7H2zhf~(gt)n|k9BWMi zg$%5g8?hYpLrkwseChI%n_J+!pO_U!weNCkbVW~p>YwIxDVB7z*3E53i|)N_&rW^3 zq@19~5T}1CNu%IL&2BD3nOT-Ck0>wZtMIs4FJANEn84dFd~cRXFI=bt`!m<+<+M|F zzG?^Fjun4yG|Ml@e_j-`R2L)q_PR%DW0vR6##R3pj3y7ZIY!rdj=P8kb6?=;J<(ZN zH_4Fpg2-6cE|V2C#%rX-rmq)}zP_+B;Ng3-vM~Iso=w}$mD<(7>%jsSL>Kg4Sw9VM zjtH7EiLYlK%vX%kvq-mRDjRHyUr20Etoq6B+|yxTQfMS!;k&KntD?F3nH$X+t|JwB zTO=fT2b3k2*`@va3^XOXgg#i8OOYvmYH}67)-v}#Y(wbw#G?n7miBX=NOxIzZGKnL zYhRMD>*tB%?`e(t=OmX6^LMC%m2c)bn8R)n_&ir7R{7Zrc=lP6Zt)^VL?v%Pv zBge#_uR->h*Q9XNsiw5zg2dfNl=Eg1L6cU~2PXO~cW1bD3y6rybZ7oxcs^#Zn#i9Q zEp1(>-BSK~-G+mV=2XH;BVU7eDh0XNt-=iU_KSDS`Msm-8!VFCo1Tt8zksS#S5(ne89^c}08=8$Bbt+0osZwZbs!?mC z)$`mbwZko>XntmCU-YlagMVFjz%zwT=6y|xt{$NY^hw&Uu9})JMQi$0>ngBJELlD3 z7QEFMkY5)W$7VFIcB*MC+ckY}O#3Am~xVMw<>ZsyWzvz|m(pdd~{`tjihb7EM-u9U7H{xr#Eu+sgy^LR>IY*@zU z#7~n&$Ee!VGI{#%9WJdWFR<<$nC(e-PO&N)q$qFX*9^key1qHaqQ9crK({eJc(2o` z<5_>I2xM0cWJlJqM*j*BB)=-rC7!pLL5z$^rfX!0N?9wU15fl8;~PZc;_@d2hI9ve z9mtB>|61?5BO5)OPZ}1zuhtGK34b8Tv476hy!-iKsgRXwwf1>C$I18<{>We5-yAD@ zo{9)csAYXHY3Q3Bah)mk=ddgqSXvU(Z34RY)UzPt9h5&AY=Zqh=*7I$<>p=WmMiv& zvWa}*WMtpLd!dTPYHEf`rV5{?DP7)fxIlJPD>1mFUqmCSFe_&E=R5`PQ$g{VTVruN zT70&nKIU1083#LNs*k+gus%&+u^~aGYxSb0m)Vs{B^PG-!J>f)gW8pM-nu)Obi7Np z5>S-pp1g`CHCvb~wMS)+`^rWQ-pgHTVJrz5VYloeUHTSdnEIG~>i+%9SC<#B8p|bc zShn%`PJLRC(t1%VW9*QpwJi4S0}Z`#lt)8$%sH}zal8Y^YgN>3ujH@z98ue4o21WE+e>(j<>g&Ar{r|%kAZ*11 z2o{~ZKq1!kgtsr=k!DY8o4r+j&7Qv=C0l68Z1&jYaDiKCvAgY4%vveRs`A>hW^c5g zcTMu(^S#*iO6STshKbDr&r8mbF&!d+xMxwaMFq8+zWwOy7T%== zEzj)b?@J22QeCM<+IvO?3KX|7(=6_x*B7jr)c$eWr{v+j%IH+jq<4Pxvl2B`!9^~v zMxNF#- zkrwL-a8x!&`NG!9l&q-VTwg8Hsxayr(|~ePu2968TIx8t7?mZn)X->)>Y3VAI+KM& zqk{ssMmW+d8`=AwmmCh?DNoMutRX|0e{FMpVrJ8m9nSz>00}%xaNs=EI}nu3AN3P7Z2xeXr4^Q5p#mV81ZYvx9j_+AkU~&M)`o_SKtD zT_#@fTQ}r=9ys?ebTjd>a<|ApI7{_pQTfc}QHDE2J|YwrTL#%5pVXI#$^Cp#o&b7Z z-&MSpo-`BAU&$MC*rxd{Y2bG2z)~G$l5Z!!Ta>eWw986#V%*6&q(7BhHX}x7fuB)3 z*H(!gS!FXn1HHt;wu+SimJ5&CS5&8(ti^_gvVka?akwHBN<>_H`5d&5GmM?)Eht5iWA}WD+a?SrnPx zDxDUatr~FCW#ISL4KLv;5&d>DK{;G*eu80ms{tqd-jvj1q&7_1#=B%>PrDZVyDlG=9*l=<^_SE^Zfi}-F;;XL)MIAv_hs* zv$C%Wq9}Kd8E^f#Q@q(H)ZuCKXX}|SvYdt587^$Ax`V(5A6>t?_I?<6Km`69J|(Sl zW)jU`?8!?QC6}D~hUt`&JeIC`d^9;fJg-4L8!`TF@mlq~UYpmxnxZ1-rmoq(N=;WT zqff}ZNYI^A5#x_AJ$CzgU-XI6_h-mT3*>@)Zm<0p%!6Qf41*v4{ipxplR!N_=g`9I z&ZFC`UvHxAN14U%OvalqzYv?9d%~1Uttk%JpxAnW@&P!cnMfI3a6{XOql9`6yNv** z(jr1(jIjJ1S=%0FPay=|&^#!eF?WF4e_q7Y2#vIfO%`ZPrx#J^-bI6>qG$z;ys8zh z`}{bx>9{rtP1h*!8eRnXRPM2x$>F?D%*OQDO}Af(EKkW#kI-Sbf2Qy|-P+Ib!G0GA zi;|Yb^~G1v1RQL01!D-(lQ;_Ke_yhT;1BU?5p!XNt_KV6zGMgIeaZ(o^@Qj^oY=u^+J+Pk z6HlTELFK;2&FbR#526p@i7W!jHWg=&5UmI>TxPKKgp24}H=-NUl_0!K!S15paoZ9D zh&V!o=h9lsqZs&#zyeSd5M2))nl(`H0IBJJ!>{#HkNK6WZRqYKVmjPdC_lSHbxP_N zXiRSBz;+x|@~`J>Sw~5`D!%%of!x8)j=oMjl>b(>&6&wU8k2?5w;y0O`}ce4_F`_o zdD_f*-C~qPZ`Zu56q|GVTv$_fyXwno?Ip8c>hfwg!@jgm31`>;`)}DWw>v8xm>%|z zvGe*8x@bB#Ev%Z;J9>AM_S3+QYH#)=wKl`^8%7wpqyi#;O>#Q-v!lTO*XzzvCNqDV zen>Nbd!pVyPi*E%Q9-}ZnNK+mZDcKziz#mf*T=ZnxUMs|nBP6IdvG!DJ9g-;IdTVkkUTDQ{h4Y4Waay+i zQ9s+A0Z#*8gw-3X6uefP=x2MDdA(r?gvu%^wz{Xjk4q1`;ncgP6m3!Tb;u7ncx(c{NJ zfVb*g>6SanGxgkAzo$9XcJOB0VDAVsx%J*pxubzC7!y)q30%syh;mvEy!pcZx?OpH zseq^XkH8LP&5|Qo%FozODUkJl@~?#JmiA=i3EwOP#VH)bx_CF7%ivLq5pf>$ zsb+N6=e@?D1hVw7+$--pd`k#+dH91if+xSbzyI1zXObbTcoku!iy;!T85!;OXtnFm z%u4Al2ccqjAD2q&tBw?I6Yn!N0mCJ|JG5c~w9orDy{{D~D`~dgFsmS!&oNv)6nrIL z?M&THiS~10tj`%F_HuA=I3MG_(LTp?$YTN8Doh3#&(vz>lZaI!e$=g@+3MPvMaw6O zwY|rEHPva=33f?7U|*s)ua}vUnW}W(vt&&L^@$S78$79AlI7}LF#}uZM_*~4If504 zVeAHJ>b}}OQqWp)8TqWlSix9Mjp3WS=z~~>H&GS|y3@WFG|7*6CHaH>v@-P%_5Tu( zf5Pp+l#|Pj8?PSSiKcs$<6e5p4K}W_ug`BqE*K!Pg}4KAyPl1@e~Gn>r>q3XwvNfR zN~>^%igV^495UV6>|cc6{E}nNzINaGPtJ0kdDK32?$22Zm+ZHgpQ#Nw#mdH3HJoyp zM4W(3tO|=-e)Hr)u9IF{r4}0GnDb}GhSrq@lt|P{%bj#j9^Km=9ZhGaEEV)JPuilL zIe+5EP4n>nm=2p4_3cx7duAVwKM7n{d~HrcMytkw1MbfyhDZ>?7TCH!QDmW3XkO@a!( znY1rxEsJK|_Yg-xg%I~{nL;^A)h<~e$(p2^uK5`>d#3e)M(yyErc$;QN~g}eR?Sny zeylHPx%6Vol-jqvGWG>Vl>&P!#ISu%r=-tYXeaci1w!eY4I7IR z+OZ<#uWfz2ARHg+l87(6@q3+J*4;nGHWRVGGZ{v-p=R3c%#Bt;Zys3v#?1l?=_{K z?&uB1=1bMv1wh*B5OWUQIeKI!-OF{mnFxl%Y_@C2QSzIhe%NT$mi{+O?r{;>Q~Qx8 z{rAO`@j-C7l;v_x$;RKPW~?J|ySr$p`eNmUGXvWA3#x+-*SoR)HD|mmF3S z?YU1&vRdmz^ToZqkgH$Wma5>f0ovf7Z!_zyO^ed-@jMM@<4@4sQuW*w>DUryjD5;1`I z7ZCgFs{hMhQPDtIh>@IH4-WLeWa-#RP~ZLj4y#jcF;bxcOykZ_-+C-uUx(dKH0l4R zZ~PHfGEcsjMI&Fo#4G>K9gM~XlHYSP4WxEHKirJnX#ND_)y&s%WWhox8Ib8e~v0y z(R8Ca#v{lvX*SU#?ztP&jtsl?)s}W_Af59j7>^C96xouxoW}LBR7@DM@yB7}PMBtb z7-e9CI%bB8oYujV1H8w&Lm_$5sqXG&5W$QJfv3<>B_{2t89vf!lStif4ajhI@A(zt z^j{3q3P=)-DidrT3fVRI=JJcLpv6D#aqq zb52;mz<0Xj(lRDnFpN0_6?6_D25dYCQG9B?Osd9SIo#3@ z6d>4sB}?@B=a)D(eIv&9IJC8Rtj2#_FiUFJ$T}+X8ikE2CeDW+Ncxpg(9k%IbqM!= z6M;kpvrsssIG96Q#6A_=&aIAy^4NW`J<_a-@$~9`tJ2T;zSt9E>zjAd(oj;;mnC6P z7z?Dpb=4)`hW0iw0+nFdMCpPm2BMpuZV0-EnBE7Kz_yi!28Vtf;s~ z^Dx8}T(ymyyb=;1CvP4zwdD!;5;W)*3f;$`9)G(NoUnmz2*(c$-@u2zes>oJXu)Yp zhh2tPe;AX|+w*M4v`}W@_7~>nOxts0rHFC-9!8j@r>JGdOZv4x+bI`ynSng&3d6HZ zDK~d_6*x=_*bE<=WwnO&KAZFKLd}NHYhz!dAtgStcYf}^U@RH>{doyRSlau} z&Q(IRj~RCvjHh*@4wE7d7AUi{jW=Lmz4V0oR`*Br;EqRI;KxzS4V%9A6hU<0<3G=CTSHxpO$?TZ64RsWDT)bhzH^w^Tp`BxDdxjo0itjvVdSut zo1b#9iw+_;bt-CVVg?YRF-d0!C>AmCh=ZkJb)_15)CAQeoPw682ITkI{{7~!vU3rYDdG}%__Xdq^jGEGmf~y*%SIJyqEwaGh)z#%2 zgz?jcfS%fLZE9|>#|#OS$v8~Abi#cn9p*+q#p6ni$-`>H19<%TzoxWO%=3NJdo6nA z$gyMPb#;>vyYH}$mI+T5W#nkLClSMSi60B(Rk*KzJ{@oqQ~l0^-X&>g<})B)s|ocz zJ@xC?XQsIHW?x<{=3yarn9460!B*WD{fe8g=r#4sdnK2HA;g-5p3VzOQb8xq6#c@K z73PiGpn_A#$t9(yq@-{ss5#=rzQy3Z;t#v;u+fXCiqp)?nvBP=VMRPdEx9?9ByNOM zl_za6sg!1zO*ZJl`0tT0xjD>O`^aqBMzyQEk(fR-DJ?D4%gY0`?rsEq5}QJ-x5M3S zfBfMcwv(K^uE<@ChB^OQEv>uqJ17GnC{t7Y+1=eeGQ!?cpjWm-R8*AR4R7|i2?i)> z+9DeJu$T?cOUgw{s$zckpBJ=9>9r)dC2AETWIf&`IOSnt0OQws?P{2fHZb#4R=(mX z-c=r(&Zw{4+ro*d_`%>bXeD9mk*;>&5*eMqO|jLCT&QN|xnUd~aOpfhO!(oHj{(mN zN~JRTtl49-5NRADl!$W2_byn6b$9&rJ{XxJQ>nQ0#nNJV^;d9m%M`QI)KA&@46M7F zP$HrZ9K?sn(t?BT%dPqgw*T>m-87|_XYS|oh&vc&OJ1Roin4$?>zGAY4qzNclJ` zOo5#Sod&d*w^CS1av4@pb#s0Za+hZ(>>FXZmqxxCLGk#EVMiS?@Twahl^S$H_bi_2u`l6F&X_rb@&S@E zWoJUZIekA#lUM33bX;Y0uK&ataV9ssGS=tv7%eQr_dRoin|~0J4Gunp3O#xy)fD1B z%T7&GcGe{3O8H1J&g-8KwS31lV#F8@@aqPO=3tVuIJ>;OEG#UH<3;E_<{Ol?%`olz zgtx={0Ce+8)w0Y8@jd^Et3$K=*u>}BHy~zC#D}=0H|Lj0?JFjc!h=>#W2Af282Bes=pztMld~A5+9NLQ6t4{| zyn7yl)D4SW%)a#)ivBEp;&aU&5&MbAT>4aB%y?snlHZgV?S_sTTZ==R2%3%IhhB+o zuRM2U>~)M7nPC2f=QP>4i8$>8lyr1-;=fSM<`n+D{X?UECCuTMBAI7L+H9c;dHC?5 za_4tsn5WS3V7L?ClE2V6is^Q+60o9rf!BdsTu|KM%7gpl4`w9StCSLNhHSL*=rRtB z_)#eTHi9s)YJ{Wz?zTqy5Qga4;g+L$vq%_oqe3LY7(hucg3?CYQsd0}+XvP*hL|od z>Q8ZEf>`MsS>8IFUq}t`iE-rS?d|#Y))>x*sPu&6B~m^G5k&u)NgkK(YBdwE-DxkX zd+7~VFUBgxX^BtSJw_=F=z5#p^WO%R5%3b2p+9_w-0LODjf}K)>(;lH-4|brsF6lZNT6~@e;{SXfuxIsRJzaK`Yl+ua|I5hUzEEwE5*a&~mueitN=Ml*H_OIJ z+sjd%_9grg{x65TEz>Ez9zL`i?D5~Zo*;gS3rJrxmGR{#QVagagX;U!r41yC-;XYU zS>xsDJMpf7qEKJ?IoJ%eWskAa?IMY>ttA`v=wCo4Gy$%2UG6Vlyht-fT;Q;#A{`nB z=+!xe`1AqO_6CZGpFfy=OhV2J+}zv>HYzjmkVZfyz5q#8SoZGOnleTqA;Jh^dZ;N-@5mx^ZRf(dS&E$+ z8o4)(un^CjIWy7R`$rX2_aM?aZ(I=xaBqZ12TY90m;O9@@qNyjVjK%iJRK&ts1beQ zsn^F4vJWSg3%w_#Zm?DYqQ|lu0dTSrITbnt>z;C1fH}w@rf+7NcZ!&)f%7-;s8`41 z?(K%jZhT%Hm-IJ1zy?IK!I2RlC;RzBit!4AO1`$O(JRv_uJenFl~!Bjp}01`WeYu? zRkM1<6~vEQ4pRsh$KAz5YJyMp0|qzTM~Hwg(j_TxrY0as!thW84hj3C; zc?o4^dFdEFoV=Ke3Tq@Mzm-U4nXI}IR{xiSTYn3wY0Qa zw}1Gp2?0oW?RuC8Zxq>?F!fo;Km<)2$N0NRo+Tt)M0PI0!9`8B9l?pdon$=^AiHeR zaxb*#I*X&7^}C=h$_!2xwP@LG?}?lSZDCs0723xDXE(C~np!u*;lO%%;a29A8@}x7 zJgUbIc$kH+drTZnZ`{u12C4OAco7^ZjUZ9NbbLTB#A1tpt_@YlZ~L^JUWM<4x5JkJ zoR2jf=TKn}4S?lvXl@xqp?qwvbH8+TbY3P3I84Q=9jUg3nAzQkIGc$d!tlBPv4fv{ zp^p-*sxP?-J6LXDuGuh)2&*zhOInep*2w0DPg3jKp-PvZR^ zuggeRt>4^f*S+1{2ph|<7j>Ff(}s147;iw3sHF6gJ$<_Pjb2$s@Hl9`$~Tvh3r-q* ziWCvC?(?jw2sxFY-;0jY)%NV>QQ3pSs;6{IHZGrSF}!{~He(Do+*5~)_KBvYa1F>V z&(m@aetvbY%43|q8*iwR+YVLSPOs90$%Dyw_>2zt)qmKs0{eqcWfKyQC%KJm zRT~g7a(Hu`=B?aa1&fKpJatap2rj`68r&5Hhsg;e$f;n!=Lad~z_mgLvdWd~%xsL( z&wrwxIqB@=6tA3uY}*5Dlxy|$>|Hsfikbu&4(&onaiyj`fA;ss)I3HpoiF0npuNtx z-|>AcL=0RpkbfP09=rRY=)%vVxXxgKdoA+~CU0y;)KpJF3}z>6G5t_Ky~dICHw+%n z;r+Q}z9k1|m!djgmsb%~o`c>V;#O~l13ab{LBZddY4%TF9)ZaKi*oWXz%}pq#6$>4 z0rD|Uu0aKzkqVrN0*yKai!}Ta60dvLjc|BMd%7#KS@Ufds z?k{pYKOjndH9NbU$@uC}-pY8$JU$HFsVwqYC}7AcXsE#f);@&(Oy|^S7z#jFb)3#_ zSas}7TzD`QbK*SgR}BOa9~;8|IB@AtqP$evPQ!O%VodaEf@*qnDNUS~Gg+mfU%^kV zic`k*-u#4}H;uJMyZyDnTfLAL3Z%%z2v@xjR(Br!u|auN4mFNl7p-rJSo~V%&z8~s zQs8L2931@#@>m01yzPTosVJ0`WpoXo8n!BhmN8aFu^8*O+TJkY;uHM_Ahq6ZsF6OE zVdGi`oiu4=b0!eCMCT#|GP$={vknHqUVtlRDEe)=Zhu&ubs%bqfmC?soYg%41D0g+ z5*kk5p&NeYesm|xC~M9ITu&}>Jt2|aB>+6Kfi10Qeq<44R~kJUlh6tWX8i(TqO{s6 zTF6ZJc-cY+3znScrJ2J46ESSFz(PdNkl|sM_?)x z+oox*S{x-Gw$RScvUXCeTd1VCbg>}jBY~<@&&W}U?-alWopqqQI+!0QYsr zog+|lzc>%Ofn$~&5-2(&`Ur;=V@0l;P&3QD_DurJ_OA)xhZ@w{xnC~>2QRe{_6Jb5 zGS42{BBg1BvCj6^3qwDqiy6uqj>cbkb$978jj)5lj4RZdR;5UJ?2Y2O^onc1fkIsO z3$UZ=G*}a33Fvjz5!ivaUp~un9I$qaVrvL3FdBqXF^qm!9C;3)K)l3yRtm3$giUyi zX$dn*fH@85z145cSwImM--Wjd*9O^8Hs-_{-p@|+y@61FHjJqNBOKiEh1^zz2borW zO!Kv!qoYK(01aysot?hI_NNDUk3+)R${lCc(uacMIqy#kbvdfhwY)~atQ5d*h&Tx; zTQVk) z$fseT@$c~SV*}+2Br^{R1`3Y5P+KeGO_A?APCpp2iL`qy1|>(IAbsDn_OJM`{~$yB zcLe-jMcf2jhobTS=A)G5=kO*(nF`_GbKW_Fq;q5~X5-?)e7tx+i!Kz?KbQQk>%>j( zW4ZED|I6#Gg6k*y7~Iy;+Y{db_Z5~#P-iT?`WzK}Bytv<-_FdSaovWifd z$+S>+0Wq71@aE=Ypjd2xZGokbSf|FvTR)@Zz0 zz<##MURs>5rbwJumkZZ||3Ec|bu2ForBuGCEW^Q@a39U*xdx`fii0Z&RM5oE!~f1R z-&m_I|3diR7!w}sq)Q&L06qaR|EiiFc4+j0kx6?4Syt%dFF!jr4k$dtVc+L;_r7&~ zuFF1HFY5Qg=M$RouGIDql*nJ{+yunX_gJ!pkyhH3A9A6Lbd|mc@x~H8g>V>nRbZEB zqbMwHV(Wk`#wZn1es(0&IP(Nj&EMoRA^L{dhFbJA?6pH}K>cGDf=xAz=0BsL`8EE6 z`8<~z7Zw&^D4>=n*FkPeo0Oz_^u+D-TMghKo7Y3S%DnZBOh=h}~bChaBE0SQP6l|I)$~FXc>g!7bO@%3=wvthT zo5@%3sEKSqx&&J;n(5@^H0QgSWQ~rKGJfSA(x5XsLZiRjl>#}m8&_f?!^5>9AJak8 zJp4BE(j(hf;*pd#K{n{U5Do|gx$1p%|K7dP6fhV#;vVqGW_gA24tI<}}X zI9FPpYr&=+DNW(gHyk1uu!K^04VG^VIK;d(X#0wj5qxY-$RFFco8eSdcwW_?w0v{* zgA1Az`$h*}opbjcSoutBnh7J+b8Jr^T7fVCPynaW8Ub)m*@jNI>{X!wDDhV1 zXYDO7$NqH*Z?TCH_|Bd^>wpsP<(Uoh{v3!_3p)b1j>|+QqrpQ z1j+k7eKn;BEORCO`l;M|+X(-`UO(ZK>9x+z7tQo=;Bay!lfVXfIem7r%&Hral^4{=>oOPpd4`L zy_ebv2LTsuk`B4}NjK1;EOk@+Iu!}nO@(}-^6mvouV`~1E!ZE$%KZi_FqV^`S5801u_mtoOXF9YD}QB~o6_@0aMUrf1A z+3Qi+yqb%Ew+0@i8EBzi4l+i;DH(LAjy_`AeH;P7ihH!9AVEZt<8`)4Q%%<@xi*45 zs#+W8{&n(LPGU(oDS>-5xuJZ_d?GJAJKOfvSvNFl`aembqF@r;Sg)!=y1g-wTb^%X zY}KO^L?==X;UM!HlbZwYYYr<+3tY!zMwH6MUZP@J+qx#IMpCLTV8(?)5Yrik*z9{) z6pmead3#FRNnXXKwzIRdu1*-MdRmOFePUScntF zMA9Jd&AtnY#HA7#P{6&g@wi_iX6Ddw4bJ3;`P(WoBf{@7xzdSU z^?5&;eM@$j|H)J!2)oab>r9VxS={L7u;w&6?Tf|WO@9tSNYAtp>AP~wbmDQsQtOP7 zP{r6CJkudC^9@C=^a8QK98M=E40e*XmRHO16`~-|1sMRxbGQnfD9kQErojRRdN??{ zyEdWS?Q!1W1EMgpooutuzmMisIBaf^{xRd{48s~1|8tq)sO6$7N}6d4>4$N{y>C|W zSS>r_+I?s)wzPtwx!M+TKK=a^dII{hv_f{fI>WYj9@dUU#PZXge=e8_+oOr07BpRc zs26iCptlC^%)Q-1@3F;j-f?crrS&A=hx9K>EW%hhbT-)70R15o^W8_d(?KXf)#!g{ z)9`mJf7%l|rx6-yMl+BOcQ6fsqTj*nc|G#PE_WCii{BQYHv)JQfuGU)hdKqRsj0Bf z5G!nmE&o`K@`5a8du?8k^Y`k;l&O(?`LwtjTM(_dBOVCTZ z67Wz4^G@RE8^WJH@WRPTu&n?ruR0Tet~x+051-pay1@)OUMb?Eg!^`xX4#dolfM9` z8{b_2a>UtkFN=bh3q=UkyO8Y%c=$9dh>e)fagdaM-!5j&RYv7K7UHyM7};L79G!RT z(+|w8qHooZs48FN4rFR$*L0CoOpa>c`%kd4Ukh8GM!&xB7#qEQc;VI*khS2_;cp1( z)go4;?}i#uUEtw>0dMKQ#vHYzm{>WS?kJ|etS=@<(h@v&Ol+tUTr|#~Zyp|fqN<{z z0{U#~RMqr@b%(Sxq(F?^!-}~z)}cuk2yA&O&#pTS$pIRe z?h>85{C&bf*k-7(8CNwGC>-F-vv_R%?KX^(9BL()n`g`srws5)04I%Xe&*_<2eojw z6XyZ7lu?k{i3ZUZq`jyHebn5_Nooc*KXQ$cL-1xBYzpzUYvYU-=quq9zrTf>r}*l{ z8(14$dFI{M5C(k5T328!)j52WjCA8Ro~F z6N@j`-)_Le++`2n-Q~Ag*wQ?LCfXNORDvi|XO1E4>&#egA|(*kDTLvu+L^+cRueqtM`?j5Qdh5S9gB|RhYR&5v(G>Y6$*L@oU^emJ$x3O`--tt!ML| z(GxaKdpNW-95t{8PA#6;aQgzjWOxX1rZtqkatlmF4KJDAHA3DGC_zh=b5ZQ?ztXw7 zlHK?ip^#Tld$%0oPNqAL(O3HPr`#kJVu-GgI2a{qAKa@9Z3-D9k;aU7CLz=s(ixFb zq1LW6b>suyf36TYrP0B%Z`FKkMJr^mo8%?BDz2xN zz!1{xW*~Ze;Z93+74@s~NBZQYcXvJO-6nqdq}9f*)2&9{3VjNU_xJ5N|L09b9qym* z`#K&SWc!(uYrnT)mS3t;JEq^d&%S6h-?Fo|f3SLP^m+2}0jErrtlJGYh194>dod$t zk&ISTtcvHwh_55YoKMRlqC}+6a0NEBB{+l+8GZnQ!dECloat(J{qNnQ>RXen^TiaF?;!4194Ec;ODeM)MUfu>FH_XjE)Yi0jUkQ zrLan#SDx@RU6-I=&n=a!!=COqd*f8o++5Dz*Xz|cIj_K#m6<8NFY(la2M?-JLPA4A ze6>#gzN#;|d3lOlv>5vkxSC{%39wW308df2@SMjsu*g7i4hk<46cS?l_GT|18X?s2 zQBfBh%1Aoexa>Ue#dBhIy4o*_@KHT>}NNA`c--46K zz%$LHni}a-(perb-DNA#A*F)iN1XR2=e1Iwts% zn9$JBwl{k2pUQMj9Meo^ZR+eSs_DTuer?-CQb)k}{y5sGkFFNSR$S&CAqx)QZi~jv zK*9RkI9-qQ>q67?j~`d}_-x<4{i{?S1*V#g2TDl23q3{Bk;SJw?S67m@%-MsL~SRZ zYZ_SXB$1Pr#^qNb#<7Pq3}W%Nx#6{Im7hLUF*9psRn~QM+$dyN`qg!A;K};av_x!l zM*$yX!bRaBHajA>ecLuuc;$r&?fOG(jDj>l#92|Q2eO))W-H4YM|`}K+do9f#l^?7 zOuj+XU@wp$l@rEZOH5(X=FO>R3=A$@xWEYzsM)= zHKZ9E8_UH#yE(d|RI^97sMDP4!4%nFKYuLAxO>~?YG->8l>e zuYWeRLOK+VkMWjhx;1&PBJJ!e@0+uIxi%vqW!;>jq)~JSzJ_kNErcF>@p?ukrfk!e ztI1ub59x%5Qh=6%pC>OJN3i7Ov2COYf)FPU4GJoc>lz#!eD&(^z&2Vuhv)NJ=aty@ z`gQCj^)P^Ya~VPnj63T4*Cyu07Q&!F%RsM2a!9j-Poq|0z=XX;eZ1USL zux#f$3cr9|mzDLYdk28)-LZ?*!-)^tJB~g9R!R@|PXxv8@wwIA?<({skeS`w-1;`y z3BB;~`E%eK>9h+LgpOryGBG|F^B~=fdK)^?TUNC;#>mBad3mL!r9FSz0ix*r`}v#(Eb4r#kqn{+n@7pvDoN!Y zaj9={_<(VU4H$jVjNwQA0Rb<-Zdh5#Ib0^Gq!WK3>Frx$5Yu6$f(X6BnWoKy1F)|1e(U47Gxfg+*`8>nL%m|TCGprhd~jU0r3 z>_D_3k&0j8s$j8 Date: Sat, 8 Jul 2023 15:16:26 +0100 Subject: [PATCH 08/57] Clean-up from review --- src/Private/Context.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 7cf275518..3ad6abb15 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -409,7 +409,6 @@ function New-PodeContext Crash = [ordered]@{} Stop = [ordered]@{} Running = [ordered]@{} - Closed = [ordered]@{} } # modules From cfad91b198b688b9b20d3c1d663a72de33ba04e3 Mon Sep 17 00:00:00 2001 From: Arie Heinrich Date: Sun, 9 Jul 2023 15:29:59 +0200 Subject: [PATCH 09/57] fix port Either the comment is wrong or the actual command is :) --- examples/schedules-routes.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/schedules-routes.ps1 b/examples/schedules-routes.ps1 index 1fecdeb53..cfe3ef0fa 100644 --- a/examples/schedules-routes.ps1 +++ b/examples/schedules-routes.ps1 @@ -7,7 +7,7 @@ Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop # create a server, and start listening on port 8085 Start-PodeServer -EnablePool Schedules { - Add-PodeEndpoint -Address * -Port 8081 -Protocol Http + Add-PodeEndpoint -Address * -Port 8085 -Protocol Http # create a new schdule via a route Add-PodeRoute -Method Get -Path '/api/schedule' -ScriptBlock { From 638891e0383aa5ce48153eee36b8dec1753fc2a9 Mon Sep 17 00:00:00 2001 From: Arie Heinrich Date: Sun, 9 Jul 2023 15:34:43 +0200 Subject: [PATCH 10/57] fix comment IN all web related examplse you used 8090, so i guess the comment is wrong :) --- examples/web-funcs-to-routes.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/web-funcs-to-routes.ps1 b/examples/web-funcs-to-routes.ps1 index 8816fb690..dc21f4784 100644 --- a/examples/web-funcs-to-routes.ps1 +++ b/examples/web-funcs-to-routes.ps1 @@ -22,7 +22,7 @@ Start-PodeServer -Threads 2 { return @{ Message = 'Invalid details supplied' } } - # listen on localhost:8085 + # listen on localhost:8090 Add-PodeEndpoint -Address localhost -Port 8090 -Protocol Http New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging @@ -32,4 +32,4 @@ Start-PodeServer -Threads 2 { # make routes for every exported command in Pester # ConvertTo-PodeRoute -Module Pester -Verbose -} \ No newline at end of file +} From e975841202ec2d14734c1bb4bd7ddbae735f0355 Mon Sep 17 00:00:00 2001 From: Arie Heinrich Date: Sun, 9 Jul 2023 15:54:14 +0200 Subject: [PATCH 11/57] Mardown style changes added new lines before code examples as mandated by the markdown styleguide :) --- docs/Tutorials/Authentication/Inbuilt/UserFile.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Tutorials/Authentication/Inbuilt/UserFile.md b/docs/Tutorials/Authentication/Inbuilt/UserFile.md index 2a28456b7..564c54dbc 100644 --- a/docs/Tutorials/Authentication/Inbuilt/UserFile.md +++ b/docs/Tutorials/Authentication/Inbuilt/UserFile.md @@ -62,6 +62,7 @@ Start-PodeServer { Regardless of whether the password is a standard SHA256 hash or HMAC hash, the hashed output should be a base64 string. The following functions will return the hashed value in the expected format: **SHA256 HASH**: + ```powershell function ConvertTo-SHA256([string]$String) { @@ -73,6 +74,7 @@ function ConvertTo-SHA256([string]$String) ``` **HMAC HASH:** + ```powershell function ConvertTo-HMACSHA256([string]$String, [string]$Secret) { $HMACSHA256 = New-Object System.Security.Cryptography.HMACSHA256 From 05136a7969e227dfb0448e77bf77c9dc72016348 Mon Sep 17 00:00:00 2001 From: Arie Heinrich Date: Sun, 9 Jul 2023 15:59:51 +0200 Subject: [PATCH 12/57] Markdown fixes Removed double lines Added line before list --- docs/Tutorials/Misc/DesktopApp.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/Tutorials/Misc/DesktopApp.md b/docs/Tutorials/Misc/DesktopApp.md index debc03e5e..4f64f8c4b 100644 --- a/docs/Tutorials/Misc/DesktopApp.md +++ b/docs/Tutorials/Misc/DesktopApp.md @@ -5,7 +5,6 @@ Normally in Pode you define a server and run it, however if you use the [`Show-P !!! warning Currently only supported in Windows PowerShell, and PowerShell 7 on Windows due to using WPF. - ## Setting Server to run as Application To serve up you server as a desktop application you can just write you Pode server script as normal. The only difference is you can use the [`Show-PodeGui`](../../../Functions/Core/Show-PodeGui) function to display the application. @@ -61,11 +60,11 @@ In order to switch to [`CefSharp`](http://cefsharp.github.io/), either compile o Pode will automatically switch to CefSharp, if the binaries are loaded into the Powershell Session before the Pode Module itself gets initialized. The required packages are the following and can be compiled from scratch or downloaded from the Nuget Repository: + - [`cef.redist.x64`](https://www.nuget.org/packages/cef.redist.x64/) - [`cefsharp.common`](https://www.nuget.org/packages/cefsharp.common) - [`cefsharp.wpf`](https://www.nuget.org/packages/cefsharp.wpf) - This example shows how to load them: ```Powershell From 114a1493340d660ea78afb556589ebc49384ae92 Mon Sep 17 00:00:00 2001 From: Arie Heinrich Date: Sun, 9 Jul 2023 16:02:35 +0200 Subject: [PATCH 13/57] Markdown fixes Added empty lines before code removed extra line --- docs/Tutorials/Misc/UploadFiles.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Tutorials/Misc/UploadFiles.md b/docs/Tutorials/Misc/UploadFiles.md index af830dcc0..530a1fc59 100644 --- a/docs/Tutorials/Misc/UploadFiles.md +++ b/docs/Tutorials/Misc/UploadFiles.md @@ -39,6 +39,7 @@ The following HTML is an example of a `
` for a simple sign-up flow. Here t The inputs will be POSTed to the server, and accessible via the [web event](../../WebEvent)'s `.Data` and `.Files`. For the `.Data`: + ```powershell $WebEvent.Data['username'] # the username entered $WebEvent.Data['password'] # the password entered @@ -46,6 +47,7 @@ $WebEvent.Data['avatar'] # the name of the file (assume image.png) ``` For the `.Files`: + ```powershell $WebEvent.Files['image.png'] # the bytes of the uploaded file ``` @@ -60,7 +62,6 @@ If you use the `multiple` property then all the file names will be available und You can upload files from the CLI by using `Invoke-WebRequest` (or `Invoke-RestMethod`), and to do so you'll need to pass the `-Form` parameter. Assuming you have the following Route to save some "avatar" file: - ```powershell Start-PodeServer { Add-PodeEndpoint -Address * -Port 8085 -Protocol Http From b8165e1e4b42191dc90a4400c300255f9185bbd6 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 5 Aug 2023 18:42:40 +0100 Subject: [PATCH 14/57] #992: initial work for authorization middleware --- src/Pode.psd1 | 1 + src/Private/Authentication.ps1 | 104 +++++++++++++++++++++++++++++-- src/Public/Authentication.ps1 | 62 ++++++++++++++++++- src/Public/Routes.ps1 | 108 +++++++++++++++++++++++++++++++-- 4 files changed, 262 insertions(+), 13 deletions(-) diff --git a/src/Pode.psd1 b/src/Pode.psd1 index 1ddcdccaf..ef171e8cf 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -221,6 +221,7 @@ 'Use-PodeAuth', 'ConvertFrom-PodeOIDCDiscovery', 'Test-PodeAuthUser', + 'New-PodeAuthAccess', # logging 'New-PodeLoggingMethod', diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index fbd3f7514..f5e16bf70 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1078,11 +1078,18 @@ function Get-PodeAuthMiddlewareScript Remove-PodeAuthSession if ($useHeaders) { - return (Set-PodeAuthStatus -StatusCode 401 -Sessionless:$sessionless -NoSuccessRedirect) + return (Set-PodeAuthStatus ` + -StatusCode 401 ` + -Sessionless:$sessionless ` + -NoSuccessRedirect) } else { $auth.Failure.Url = (Protect-PodeValue -Value $auth.Failure.Url -Default $WebEvent.Request.Url.AbsolutePath) - return (Set-PodeAuthStatus -StatusCode 302 -Failure $auth.Failure -Sessionless:$sessionless -NoSuccessRedirect) + return (Set-PodeAuthStatus ` + -StatusCode 302 ` + -Failure $auth.Failure ` + -Sessionless:$sessionless ` + -NoSuccessRedirect) } } @@ -1091,7 +1098,13 @@ function Get-PodeAuthMiddlewareScript # existing session auth'd if (Test-PodeAuthUser) { $WebEvent.Auth = $WebEvent.Session.Data.Auth - return (Set-PodeAuthStatus -Success $auth.Success -LoginRoute:$loginRoute -Sessionless:$sessionless -NoSuccessRedirect) + return (Set-PodeAuthStatus ` + -Success $auth.Success ` + -Failure $auth.Failure ` + -Access $auth.Access ` + -LoginRoute:$loginRoute ` + -Sessionless:$sessionless ` + -NoSuccessRedirect) } # if we're allowing anon access, and using sessions, then stop here - as a session will be created from a login route for auth'ing users @@ -1174,7 +1187,11 @@ function Get-PodeAuthMiddlewareScript } catch { $_ | Write-PodeErrorLog - return (Set-PodeAuthStatus -StatusCode 500 -Description $_.Exception.Message -Failure $auth.Failure -Sessionless:$sessionless) + return (Set-PodeAuthStatus ` + -StatusCode 500 ` + -Description $_.Exception.Message ` + -Failure $auth.Failure ` + -Sessionless:$sessionless) } # did the auth force a redirect? @@ -1205,6 +1222,7 @@ function Get-PodeAuthMiddlewareScript -Headers $result.Headers ` -Failure $auth.Failure ` -Success $auth.Success ` + -Access $auth.Access ` -LoginRoute:$loginRoute ` -Sessionless:$sessionless ` -NoFailureRedirect:$isErrored) @@ -1220,8 +1238,14 @@ function Get-PodeAuthMiddlewareScript $WebEvent.Auth.IsAuthenticated = $true $WebEvent.Auth.Store = !$sessionless - # continue - return (Set-PodeAuthStatus -Headers $result.Headers -Success $auth.Success -LoginRoute:$loginRoute -Sessionless:$sessionless) + # continue, but check access + return (Set-PodeAuthStatus ` + -Headers $result.Headers ` + -Success $auth.Success ` + -Failure $auth.Failure ` + -Access $auth.Access ` + -LoginRoute:$loginRoute ` + -Sessionless:$sessionless) } } @@ -1294,6 +1318,10 @@ function Set-PodeAuthStatus [hashtable] $Success, + [Parameter()] + [hashtable[]] + $Access, + [switch] $LoginRoute, @@ -1355,6 +1383,23 @@ function Set-PodeAuthStatus return $false } + # check any access for authorization + if (($null -eq $Access) -or ($Access.Length -eq 0)) { + return $true + } + + foreach ($acc in $Access) { + if (!(Test-PodeAuthAccess -Access $acc)) { + return (Set-PodeAuthStatus ` + -StatusCode 403 ` + -Description $Description ` + -Failure $Failure ` + -LoginRoute:$LoginRoute ` + -Sessionless:$Sessionless ` + -NoFailureRedirect:$NoFailureRedirect) + } + } + return $true } @@ -1907,4 +1952,51 @@ function Get-PodeAuthADProvider # ds return 'DirectoryServices' +} + +function Test-PodeAuthAccess +{ + param( + [Parameter(Mandatory=$true)] + [hashtable] + $Access + ) + + # get route access values - if none then skip + $routeAccess = $WebEvent.Route.Access.($Access.Property) + if (($null -eq $routeAccess) -or ($routeAccess.Length -eq 0)) { + return $true + } + + # if there's no scriptblock, try the Property fallback + if ($null -eq $Access.Scriptblock) { + $userAccess = $WebEvent.Auth.User.($Access.Property) + } + + # otherwise, invoke scriptblock + else { + $_args = @($WebEvent.Auth.User) + @($Access.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $Access.Scriptblock.UsingVariables) + $userAccess = Invoke-PodeScriptBlock -ScriptBlock $Access.Scriptblock.Script -Arguments $_args -Return -Splat + } + + # one or all match? + if ($Access.Match -ieq 'one') { + foreach ($item in $userAccess) { + if ($item -iin $routeAccess) { + return $true + } + } + + return $false + } + else { + foreach ($item in $routeAccess) { + if ($item -inotin $userAccess) { + return $false + } + } + + return $true + } } \ No newline at end of file diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index 81f0e1259..f993faea5 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -672,6 +672,9 @@ A unique Name for the Authentication method. .PARAMETER Scheme The Scheme to use for retrieving credentials (From New-PodeAuthScheme). +.PARAMETER Access +One or more optional Authorization objects to validate access to Routes (From New-PodeAuthAccess) + .PARAMETER ScriptBlock The ScriptBlock defining logic that retrieves and verifys a user. @@ -708,6 +711,10 @@ function Add-PodeAuth [hashtable] $Scheme, + [Parameter()] + [hashtable[]] + $Access, + [Parameter(Mandatory=$true)] [ValidateScript({ if (Test-PodeIsEmpty $_) { @@ -749,7 +756,16 @@ function Add-PodeAuth # ensure the Scheme contains a scriptblock if (Test-PodeIsEmpty $Scheme.ScriptBlock) { - throw "The supplied Scheme for the '$($Name)' authentication validator requires a valid ScriptBlock" + throw "The supplied '$($Scheme.Name)' Scheme for the '$($Name)' authentication validator requires a valid ScriptBlock" + } + + # ensure the Access contains a Property as a minimum + if (!(Test-PodeIsEmpty $Access)) { + foreach ($acc in $Access) { + if ([string]::IsNullOrEmpty($acc.Property)) { + throw "The supplied '$($acc.Type)' Access for the '$($Name)' authentication validator requires a valid Property for lookup (or a Scriptblock if custom)" + } + } } # if we're using sessions, ensure sessions have been setup @@ -763,6 +779,7 @@ function Add-PodeAuth # add auth method to server $PodeContext.Server.Authentications[$Name] = @{ Scheme = $Scheme + Access = $Access ScriptBlock = $ScriptBlock UsingVariables = $usingVars Arguments = $ArgumentList @@ -1938,4 +1955,47 @@ function Test-PodeAuthUser (($null -ne $WebEvent.Auth.User) -and $WebEvent.Auth.IsAuthenticated) -or (($null -ne $WebEvent.Session.Data.Auth.User) -and $WebEvent.Session.Data.Auth.IsAuthenticated) ) +} + +function New-PodeAuthAccess +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [ValidateSet('Role', 'Group', 'Scope', 'Attribute')] + [string] + $Type, + + [Parameter()] + [scriptblock] + $ScriptBlock, + + [Parameter()] + [object[]] + $ArgumentList, + + [Parameter()] + [ValidateSet('All', 'One')] + [string] + $Match = 'One' + ) + + # parse using variables + $scriptObj = $null + if (!(Test-PodeIsEmpty $ScriptBlock)) { + $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + $scriptObj = @{ + Script = $ScriptBlock + UsingVariables = $usingScriptVars + } + } + + # return access object + return @{ + Type = $Type + ScriptBlock = $scriptObj + Arguments = $ArgumentList + Property = "$($Type)s" + Match = $Match + } } \ No newline at end of file diff --git a/src/Public/Routes.ps1 b/src/Public/Routes.ps1 index 76ed89814..44e6d383c 100644 --- a/src/Public/Routes.ps1 +++ b/src/Public/Routes.ps1 @@ -124,8 +124,25 @@ function Add-PodeRoute [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] + [string] $IfExists = 'Default', + [Parameter()] + [string[]] + $Role, + + [Parameter()] + [string[]] + $Group, + + [Parameter()] + [string[]] + $Scope, + + [Parameter()] + [string[]] + $Attribute, + [switch] $AllowAnon, @@ -172,6 +189,26 @@ function Add-PodeRoute if ($RouteGroup.AllowAnon) { $AllowAnon = $RouteGroup.AllowAnon } + + if ($RouteGroup.IfExists -ine 'default') { + $IfExists = $RouteGroup.IfExists + } + + if ($null -ne $RouteGroup.Role) { + $Role = $RouteGroup.Role + $Role + } + + if ($null -ne $RouteGroup.Group) { + $Group = $RouteGroup.Group + $Group + } + + if ($null -ne $RouteGroup.Scope) { + $Scope = $RouteGroup.Scope + $Scope + } + + if ($null -ne $RouteGroup.Attribute) { + $Attribute = $RouteGroup.Attribute + $Attribute + } } # var for new routes created @@ -272,6 +309,12 @@ function Add-PodeRoute UsingVariables = $usingVars Middleware = $Middleware Authentication = $Authentication + Access = @{ + Roles = $Role + Groups = $Group + Scopes = $Scope + Attributes = $Attribute + } Endpoint = @{ Protocol = $_endpoint.Protocol Address = $_endpoint.Address.Trim() @@ -418,6 +461,7 @@ function Add-PodeStaticRoute [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] + [string] $IfExists = 'Default', [switch] @@ -475,6 +519,10 @@ function Add-PodeStaticRoute if ($RouteGroup.DownloadOnly) { $DownloadOnly = $RouteGroup.DownloadOnly } + + if ($RouteGroup.IfExists -ine 'default') { + $IfExists = $RouteGroup.IfExists + } } # store the route method @@ -671,6 +719,7 @@ function Add-PodeSignalRoute [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] + [string] $IfExists = 'Default' ) @@ -683,6 +732,10 @@ function Add-PodeSignalRoute if ([string]::IsNullOrWhiteSpace($EndpointName)) { $EndpointName = $RouteGroup.EndpointName } + + if ($RouteGroup.IfExists -ine 'default') { + $IfExists = $RouteGroup.IfExists + } } $Method = 'Signal' @@ -845,8 +898,25 @@ function Add-PodeRouteGroup [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] + [string] $IfExists = 'Default', + [Parameter()] + [string[]] + $Role, + + [Parameter()] + [string[]] + $Group, + + [Parameter()] + [string[]] + $Scope, + + [Parameter()] + [string[]] + $Attribute, + [switch] $AllowAnon ) @@ -896,8 +966,24 @@ function Add-PodeRouteGroup $AllowAnon = $RouteGroup.AllowAnon } - if ($IfExists -ieq 'default') { - $IfExists = Get-PodeRouteIfExistsPreference + if ($RouteGroup.IfExists -ine 'default') { + $IfExists = $RouteGroup.IfExists + } + + if ($null -ne $RouteGroup.Role) { + $Role = $RouteGroup.Role + $Role + } + + if ($null -ne $RouteGroup.Group) { + $Group = $RouteGroup.Group + $Group + } + + if ($null -ne $RouteGroup.Scope) { + $Scope = $RouteGroup.Scope + $Scope + } + + if ($null -ne $RouteGroup.Attribute) { + $Attribute = $RouteGroup.Attribute + $Attribute } } @@ -911,6 +997,12 @@ function Add-PodeRouteGroup Authentication = $Authentication AllowAnon = $AllowAnon IfExists = $IfExists + Access = @{ + Roles = $Role + Groups = $Group + Scopes = $Scope + Attributes = $Attribute + } } # add routes @@ -1015,6 +1107,7 @@ function Add-PodeStaticRouteGroup [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] + [string] $IfExists = 'Default', [switch] @@ -1081,8 +1174,8 @@ function Add-PodeStaticRouteGroup $DownloadOnly = $RouteGroup.DownloadOnly } - if ($IfExists -ieq 'default') { - $IfExists = Get-PodeRouteIfExistsPreference + if ($RouteGroup.IfExists -ine 'default') { + $IfExists = $RouteGroup.IfExists } } @@ -1147,6 +1240,7 @@ function Add-PodeSignalRouteGroup [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] + [string] $IfExists = 'Default' ) @@ -1171,8 +1265,8 @@ function Add-PodeSignalRouteGroup $EndpointName = $RouteGroup.EndpointName } - if ($IfExists -ieq 'default') { - $IfExists = Get-PodeRouteIfExistsPreference + if ($RouteGroup.IfExists -ine 'default') { + $IfExists = $RouteGroup.IfExists } } @@ -2035,6 +2129,7 @@ function Use-PodeRoutes [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] + [string] $IfExists = 'Default' ) @@ -2065,6 +2160,7 @@ function Set-PodeRouteIfExistsPreference param( [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] + [string] $Value = 'Default' ) From b576a64dafd325e1ae248a2f751e3045022e0953 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Mon, 7 Aug 2023 22:10:41 +0100 Subject: [PATCH 15/57] #992: added support for custom access validation --- examples/web-auth-basic-access.ps1 | 86 +++++++++++++++++++++++++++++ src/Private/Authentication.ps1 | 48 ++++++++++------ src/Public/Authentication.ps1 | 89 ++++++++++++++++++++++++++++-- src/Public/Routes.ps1 | 61 ++++++++++---------- 4 files changed, 230 insertions(+), 54 deletions(-) create mode 100644 examples/web-auth-basic-access.ps1 diff --git a/examples/web-auth-basic-access.ps1 b/examples/web-auth-basic-access.ps1 new file mode 100644 index 000000000..96275d46c --- /dev/null +++ b/examples/web-auth-basic-access.ps1 @@ -0,0 +1,86 @@ +$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) +Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop + +# or just: +# Import-Module Pode + +<# +This example shows how to use sessionless authentication, which will mostly be for +REST APIs. The example used here is Basic authentication. + +Calling the '[POST] http://localhost:8085/users' endpoint, with an Authorization +header of 'Basic bW9ydHk6cGlja2xl' will display the uesrs. Anything else and +you'll get a 401 status code back. + +Success: +Invoke-RestMethod -Uri http://localhost:8085/users -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' } + +Failure: +Invoke-RestMethod -Uri http://localhost:8085/users -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cmljaw==' } +#> + +# create a server, and start listening on port 8085 +Start-PodeServer -Threads 2 { + + # listen on localhost:8085 + Add-PodeEndpoint -Address * -Port 8085 -Protocol Http + + # setup RBAC + $rbac = New-PodeAuthAccess -Type Role + + # setup basic auth (base64> username:password in header) + New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Access $rbac -Sessionless -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if ($username -eq 'morty' -and $password -eq 'pickle') { + return @{ + User = @{ + ID ='M0R7Y302' + Name = 'Morty' + Type = 'Human' + Roles = @('Developer') + } + } + } + + return @{ Message = 'Invalid details supplied' } + } + + # POST request to get list of users - there's no Roles, so any auth'd user can access + Add-PodeRoute -Method Post -Path '/users-all' -Authentication 'Validate' -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = @( + @{ + Name = 'Deep Thought' + Age = 42 + } + ) + } + } + + # POST request to get list of users - only Developer roles can access + Add-PodeRoute -Method Post -Path '/users-dev' -Authentication 'Validate' -Role Developer -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = @( + @{ + Name = 'Leeroy Jenkins' + Age = 1337 + } + ) + } + } + + # POST request to get list of users - only Admin roles can access + Add-PodeRoute -Method Post -Path '/users-admin' -Authentication 'Validate' -Role Admin -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = @( + @{ + Name = 'Arthur Dent' + Age = 30 + } + ) + } + } + +} \ No newline at end of file diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index f5e16bf70..a507b332f 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1963,14 +1963,21 @@ function Test-PodeAuthAccess ) # get route access values - if none then skip - $routeAccess = $WebEvent.Route.Access.($Access.Property) + $routeAccess = $WebEvent.Route.Access[$Access.Type] + if ($Access.IsCustom) { + $routeAccess = $routeAccess[$Access.Name] + } + if (($null -eq $routeAccess) -or ($routeAccess.Length -eq 0)) { return $true } - # if there's no scriptblock, try the Property fallback + # if there's no scriptblock, try the Path fallback if ($null -eq $Access.Scriptblock) { - $userAccess = $WebEvent.Auth.User.($Access.Property) + $userAccess = $WebEvent.Auth.User + foreach ($atom in $Access.Path.Split('.')) { + $userAccess = $userAccess.($atom) + } } # otherwise, invoke scriptblock @@ -1980,23 +1987,32 @@ function Test-PodeAuthAccess $userAccess = Invoke-PodeScriptBlock -ScriptBlock $Access.Scriptblock.Script -Arguments $_args -Return -Splat } - # one or all match? - if ($Access.Match -ieq 'one') { - foreach ($item in $userAccess) { - if ($item -iin $routeAccess) { - return $true - } - } - - return $false + # check for custom validator, or use default match logic + if ($null -ne $Access.Validator) { + $_args = @(,$userAccess) + @(,$routeAccess) + @($Access.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $Access.Validator.UsingVariables) + return (Invoke-PodeScriptBlock -ScriptBlock $Access.Validator.Script -Arguments $_args -Return -Splat) } + + # one or all match? else { - foreach ($item in $routeAccess) { - if ($item -inotin $userAccess) { - return $false + if ($Access.Match -ieq 'one') { + foreach ($item in $userAccess) { + if ($item -iin $routeAccess) { + return $true + } } + + return $false } + else { + foreach ($item in $routeAccess) { + if ($item -inotin $userAccess) { + return $false + } + } - return $true + return $true + } } } \ No newline at end of file diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index f993faea5..637cc82ab 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -762,8 +762,8 @@ function Add-PodeAuth # ensure the Access contains a Property as a minimum if (!(Test-PodeIsEmpty $Access)) { foreach ($acc in $Access) { - if ([string]::IsNullOrEmpty($acc.Property)) { - throw "The supplied '$($acc.Type)' Access for the '$($Name)' authentication validator requires a valid Property for lookup (or a Scriptblock if custom)" + if ([string]::IsNullOrEmpty($acc.Path)) { + throw "The supplied '$($acc.Type)' Access for the '$($Name)' authentication validator requires a valid Path for lookup (or a Scriptblock if custom)" } } } @@ -1961,26 +1961,49 @@ function New-PodeAuthAccess { [CmdletBinding()] param( + [Parameter()] + [string] + $Name, + [Parameter(Mandatory=$true)] - [ValidateSet('Role', 'Group', 'Scope', 'Attribute')] + [ValidateSet('Role', 'Group', 'Scope', 'Custom')] [string] $Type, [Parameter()] [scriptblock] - $ScriptBlock, + $ScriptBlock = $null, [Parameter()] [object[]] $ArgumentList, + [Parameter()] + [scriptblock] + $Validator = $null, + + [Parameter()] + [string] + $Path, + [Parameter()] [ValidateSet('All', 'One')] [string] $Match = 'One' ) - # parse using variables + # for custom a validator and name are mandatory + if ($Type -ieq 'custom') { + if ([string]::IsNullOrEmpty($Name)) { + throw "A Name is required for creating Custom Access objects" + } + + if ($null -eq $Validator) { + throw "A Validator scriptblock is required for creating Custom Access objects" + } + } + + # parse using variables in scriptblock $scriptObj = $null if (!(Test-PodeIsEmpty $ScriptBlock)) { $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState @@ -1990,12 +2013,66 @@ function New-PodeAuthAccess } } + # parse using variables in validator + $validObj = $null + if (!(Test-PodeIsEmpty $Validator)) { + $Validator, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $Validator -PSSession $PSCmdlet.SessionState + $validObj = @{ + Script = $Validator + UsingVariables = $usingScriptVars + } + } + + # default path + if ([string]::IsNullOrEmpty($Path)) { + $Path = "$($Type)s" + } + # return access object return @{ + Name = $Name Type = $Type + IsCustom = ($Type -ieq 'custom') ScriptBlock = $scriptObj + Validator = $validObj Arguments = $ArgumentList - Property = "$($Type)s" + Path = $Path Match = $Match } +} + +function Add-PodeAuthCustomAccess +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [hashtable[]] + $Route, + + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter(Mandatory=$true)] + [object] + $Value + ) + + begin { + $routes = @() + } + + process { + $routes += $Route + } + + end { + foreach ($r in $routes) { + if ($r.Access.Custom.ContainsKey($Name)) { + throw "Route '[$($r.Method)] $($r.Path)' already contains Custom Access with name '$($Name)'" + } + + $r.Access.Custom[$Name] = $Value + } + } } \ No newline at end of file diff --git a/src/Public/Routes.ps1 b/src/Public/Routes.ps1 index 44e6d383c..2564b6c06 100644 --- a/src/Public/Routes.ps1 +++ b/src/Public/Routes.ps1 @@ -139,10 +139,6 @@ function Add-PodeRoute [string[]] $Scope, - [Parameter()] - [string[]] - $Attribute, - [switch] $AllowAnon, @@ -194,20 +190,20 @@ function Add-PodeRoute $IfExists = $RouteGroup.IfExists } - if ($null -ne $RouteGroup.Role) { - $Role = $RouteGroup.Role + $Role + if ($null -ne $RouteGroup.Access.Role) { + $Role = $RouteGroup.Access.Role + $Role } - if ($null -ne $RouteGroup.Group) { - $Group = $RouteGroup.Group + $Group + if ($null -ne $RouteGroup.Access.Group) { + $Group = $RouteGroup.Access.Group + $Group } - if ($null -ne $RouteGroup.Scope) { - $Scope = $RouteGroup.Scope + $Scope + if ($null -ne $RouteGroup.Access.Scope) { + $Scope = $RouteGroup.Access.Scope + $Scope } - if ($null -ne $RouteGroup.Attribute) { - $Attribute = $RouteGroup.Attribute + $Attribute + if ($null -ne $RouteGroup.Access.Custom) { + $CustomAccess = $RouteGroup.Access.Custom } } @@ -272,6 +268,11 @@ function Add-PodeRoute $Middleware = (@(Get-PodeAuthMiddlewareScript | New-PodeMiddleware -ArgumentList $options) + $Middleware) } + # custom access + if ($null -eq $CustomAccess) { + $CustomAccess = @{} + } + # workout a default content type for the route $ContentType = Find-PodeRouteContentType -Path $Path -ContentType $ContentType @@ -310,10 +311,10 @@ function Add-PodeRoute Middleware = $Middleware Authentication = $Authentication Access = @{ - Roles = $Role - Groups = $Group - Scopes = $Scope - Attributes = $Attribute + Role = $Role + Group = $Group + Scope = $Scope + Custom = $CustomAccess } Endpoint = @{ Protocol = $_endpoint.Protocol @@ -913,10 +914,6 @@ function Add-PodeRouteGroup [string[]] $Scope, - [Parameter()] - [string[]] - $Attribute, - [switch] $AllowAnon ) @@ -970,20 +967,20 @@ function Add-PodeRouteGroup $IfExists = $RouteGroup.IfExists } - if ($null -ne $RouteGroup.Role) { - $Role = $RouteGroup.Role + $Role + if ($null -ne $RouteGroup.Access.Role) { + $Role = $RouteGroup.Access.Role + $Role } - if ($null -ne $RouteGroup.Group) { - $Group = $RouteGroup.Group + $Group + if ($null -ne $RouteGroup.Access.Group) { + $Group = $RouteGroup.Access.Group + $Group } - if ($null -ne $RouteGroup.Scope) { - $Scope = $RouteGroup.Scope + $Scope + if ($null -ne $RouteGroup.Access.Scope) { + $Scope = $RouteGroup.Access.Scope + $Scope } - if ($null -ne $RouteGroup.Attribute) { - $Attribute = $RouteGroup.Attribute + $Attribute + if ($null -ne $RouteGroup.Access.Custom) { + $CustomAccess = $RouteGroup.Access.Custom } } @@ -998,10 +995,10 @@ function Add-PodeRouteGroup AllowAnon = $AllowAnon IfExists = $IfExists Access = @{ - Roles = $Role - Groups = $Group - Scopes = $Scope - Attributes = $Attribute + Role = $Role + Group = $Group + Scope = $Scope + Custom = $CustomAccess } } From 02080bd691ea1b4543d16a9e476842a89f69f43c Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Mon, 14 Aug 2023 21:09:42 +0100 Subject: [PATCH 16/57] #992: tweak to the functions, it's now Add-PodeAuthAccess with a re-usable Test-PodeAuthUserAccess. Also added IsAuthorised property to WebEvent.Auth --- examples/web-auth-basic-access.ps1 | 9 +- src/Pode.psd1 | 6 +- src/Private/Authentication.ps1 | 70 ++++---------- src/Private/Context.ps1 | 7 +- src/Private/OpenApi.ps1 | 4 +- src/Private/Server.ps1 | 3 +- src/Public/Authentication.ps1 | 144 ++++++++++++++++++++++++----- tests/unit/Server.Tests.ps1 | 2 +- 8 files changed, 162 insertions(+), 83 deletions(-) diff --git a/examples/web-auth-basic-access.ps1 b/examples/web-auth-basic-access.ps1 index 96275d46c..5404f484c 100644 --- a/examples/web-auth-basic-access.ps1 +++ b/examples/web-auth-basic-access.ps1 @@ -26,10 +26,10 @@ Start-PodeServer -Threads 2 { Add-PodeEndpoint -Address * -Port 8085 -Protocol Http # setup RBAC - $rbac = New-PodeAuthAccess -Type Role + Add-PodeAuthAccess -Type Role -Name 'TestRbac' # setup basic auth (base64> username:password in header) - New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Access $rbac -Sessionless -ScriptBlock { + New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Access 'TestRbac' -Sessionless -ScriptBlock { param($username, $password) # here you'd check a real user storage, this is just for example @@ -47,6 +47,11 @@ Start-PodeServer -Threads 2 { return @{ Message = 'Invalid details supplied' } } + # Endware to output user auth state + Add-PodeEndware -ScriptBlock { + $WebEvent.Auth | Out-Default + } + # POST request to get list of users - there's no Roles, so any auth'd user can access Add-PodeRoute -Method Post -Path '/users-all' -Authentication 'Validate' -ScriptBlock { Write-PodeJsonResponse -Value @{ diff --git a/src/Pode.psd1 b/src/Pode.psd1 index ef171e8cf..dc7c331a5 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -221,7 +221,11 @@ 'Use-PodeAuth', 'ConvertFrom-PodeOIDCDiscovery', 'Test-PodeAuthUser', - 'New-PodeAuthAccess', + 'Add-PodeAuthAccess', + 'Add-PodeAuthCustomAccess', + 'Get-PodeAuthAccess', + 'Test-PodeAuthAccess', + 'Test-PodeAuthUserAccess', # logging 'New-PodeLoggingMethod', diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index a507b332f..7727decd4 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1236,6 +1236,8 @@ function Get-PodeAuthMiddlewareScript $WebEvent.Auth = @{} $WebEvent.Auth.User = $result.User $WebEvent.Auth.IsAuthenticated = $true + $WebEvent.Auth.Access = $null + $WebEvent.Auth.IsAuthorised = $true $WebEvent.Auth.Store = !$sessionless # continue, but check access @@ -1319,7 +1321,7 @@ function Set-PodeAuthStatus $Success, [Parameter()] - [hashtable[]] + [string[]] $Access, [switch] @@ -1389,7 +1391,7 @@ function Set-PodeAuthStatus } foreach ($acc in $Access) { - if (!(Test-PodeAuthAccess -Access $acc)) { + if (!(Test-PodeAuthRouteAccess -Name $acc)) { return (Set-PodeAuthStatus ` -StatusCode 403 ` -Description $Description ` @@ -1901,7 +1903,7 @@ function Find-PodeAuth $Name ) - return $PodeContext.Server.Authentications[$Name] + return $PodeContext.Server.Authentications.Methods[$Name] } function Test-PodeAuth @@ -1913,7 +1915,7 @@ function Test-PodeAuth $Name ) - return $PodeContext.Server.Authentications.ContainsKey($Name) + return $PodeContext.Server.Authentications.Methods.ContainsKey($Name) } function Import-PodeAuthADModule @@ -1954,65 +1956,27 @@ function Get-PodeAuthADProvider return 'DirectoryServices' } -function Test-PodeAuthAccess +function Test-PodeAuthRouteAccess { param( [Parameter(Mandatory=$true)] - [hashtable] - $Access + [string] + $Name ) + # get the access method + $access = $PodeContext.Server.Authentications.Access[$Name] + # get route access values - if none then skip - $routeAccess = $WebEvent.Route.Access[$Access.Type] - if ($Access.IsCustom) { - $routeAccess = $routeAccess[$Access.Name] + $routeAccess = $WebEvent.Route.Access[$access.Type] + if ($access.IsCustom) { + $routeAccess = $routeAccess[$access.Name] } if (($null -eq $routeAccess) -or ($routeAccess.Length -eq 0)) { return $true } - # if there's no scriptblock, try the Path fallback - if ($null -eq $Access.Scriptblock) { - $userAccess = $WebEvent.Auth.User - foreach ($atom in $Access.Path.Split('.')) { - $userAccess = $userAccess.($atom) - } - } - - # otherwise, invoke scriptblock - else { - $_args = @($WebEvent.Auth.User) + @($Access.Arguments) - $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $Access.Scriptblock.UsingVariables) - $userAccess = Invoke-PodeScriptBlock -ScriptBlock $Access.Scriptblock.Script -Arguments $_args -Return -Splat - } - - # check for custom validator, or use default match logic - if ($null -ne $Access.Validator) { - $_args = @(,$userAccess) + @(,$routeAccess) + @($Access.Arguments) - $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $Access.Validator.UsingVariables) - return (Invoke-PodeScriptBlock -ScriptBlock $Access.Validator.Script -Arguments $_args -Return -Splat) - } - - # one or all match? - else { - if ($Access.Match -ieq 'one') { - foreach ($item in $userAccess) { - if ($item -iin $routeAccess) { - return $true - } - } - - return $false - } - else { - foreach ($item in $routeAccess) { - if ($item -inotin $userAccess) { - return $false - } - } - - return $true - } - } + # now test the user's access against the route's access + return (Test-PodeAuthUserAccess -Name $Name -Value $routeAccess) } \ No newline at end of file diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 3ad6abb15..fa8320fbd 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -351,8 +351,11 @@ function New-PodeContext } } - # authnetication methods - $ctx.Server.Authentications = @{} + # authentication methods and access + $ctx.Server.Authentications = @{ + Methods = @{} + Access = @{} + } # create new cancellation tokens $ctx.Tokens = @{ diff --git a/src/Private/OpenApi.ps1 b/src/Private/OpenApi.ps1 index a2b8cdcf6..77324f494 100644 --- a/src/Private/OpenApi.ps1 +++ b/src/Private/OpenApi.ps1 @@ -267,12 +267,12 @@ function Get-PodeOpenApiDefinitionInternal $def['components'] = $PodeContext.Server.OpenAPI.components # auth/security components - if ($PodeContext.Server.Authentications.Count -gt 0) { + if ($PodeContext.Server.Authentications.Methods.Count -gt 0) { if ($null -eq $def.components.securitySchemes) { $def.components.securitySchemes = @{} } - foreach ($authName in $PodeContext.Server.Authentications.Keys) { + foreach ($authName in $PodeContext.Server.Authentications.Methods.Keys) { $authType = (Find-PodeAuth -Name $authName).Scheme $_authName = ($authName -replace '\s+', '') diff --git a/src/Private/Server.ps1 b/src/Private/Server.ps1 index f4a5c0e69..0e0763332 100644 --- a/src/Private/Server.ps1 +++ b/src/Private/Server.ps1 @@ -240,7 +240,8 @@ function Restart-PodeInternalServer $PodeContext.Server.Sessions.Clear() # clear up authentication methods - $PodeContext.Server.Authentications.Clear() + $PodeContext.Server.Authentications.Methods.Clear() + $PodeContext.Server.Authentications.Access.Clear() # clear up shared state $PodeContext.Server.State.Clear() diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index 637cc82ab..62b75d1f9 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -673,7 +673,7 @@ A unique Name for the Authentication method. The Scheme to use for retrieving credentials (From New-PodeAuthScheme). .PARAMETER Access -One or more optional Authorization objects to validate access to Routes (From New-PodeAuthAccess) +One or more optional Access Names to validate access to Routes (From Add-PodeAuthAccess) .PARAMETER ScriptBlock The ScriptBlock defining logic that retrieves and verifys a user. @@ -712,7 +712,7 @@ function Add-PodeAuth $Scheme, [Parameter()] - [hashtable[]] + [string[]] $Access, [Parameter(Mandatory=$true)] @@ -759,11 +759,11 @@ function Add-PodeAuth throw "The supplied '$($Scheme.Name)' Scheme for the '$($Name)' authentication validator requires a valid ScriptBlock" } - # ensure the Access contains a Property as a minimum + # ensure the Access methods exists if (!(Test-PodeIsEmpty $Access)) { foreach ($acc in $Access) { - if ([string]::IsNullOrEmpty($acc.Path)) { - throw "The supplied '$($acc.Type)' Access for the '$($Name)' authentication validator requires a valid Path for lookup (or a Scriptblock if custom)" + if (!(Test-PodeAuthAccess -Name $acc)) { + throw "Access method not found: $($acc)" } } } @@ -777,7 +777,8 @@ function Add-PodeAuth $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState # add auth method to server - $PodeContext.Server.Authentications[$Name] = @{ + $PodeContext.Server.Authentications.Methods[$Name] = @{ + Name = $Name Scheme = $Scheme Access = $Access ScriptBlock = $ScriptBlock @@ -830,7 +831,7 @@ function Get-PodeAuth } # get auth method - return $PodeContext.Server.Authentications[$Name] + return $PodeContext.Server.Authentications.Methods[$Name] } <# @@ -1019,7 +1020,7 @@ function Add-PodeAuthWindowsAd } # add Windows AD auth method to server - $PodeContext.Server.Authentications[$Name] = @{ + $PodeContext.Server.Authentications.Methods[$Name] = @{ Scheme = $Scheme ScriptBlock = (Get-PodeAuthWindowsADMethod) Arguments = @{ @@ -1071,7 +1072,7 @@ function Remove-PodeAuth $Name ) - $null = $PodeContext.Server.Authentications.Remove($Name) + $null = $PodeContext.Server.Authentications.Methods.Remove($Name) } <# @@ -1089,7 +1090,7 @@ function Clear-PodeAuth [CmdletBinding()] param() - $PodeContext.Server.Authentications.Clear() + $PodeContext.Server.Authentications.Methods.Clear() } <# @@ -1456,7 +1457,7 @@ function Add-PodeAuthUserFile } # add Windows AD auth method to server - $PodeContext.Server.Authentications[$Name] = @{ + $PodeContext.Server.Authentications.Methods[$Name] = @{ Scheme = $Scheme ScriptBlock = (Get-PodeAuthUserFileMethod) Arguments = @{ @@ -1603,7 +1604,7 @@ function Add-PodeAuthWindowsLocal } # add Windows Local auth method to server - $PodeContext.Server.Authentications[$Name] = @{ + $PodeContext.Server.Authentications.Methods[$Name] = @{ Scheme = $Scheme ScriptBlock = (Get-PodeAuthWindowsLocalMethod) Arguments = @{ @@ -1957,11 +1958,11 @@ function Test-PodeAuthUser ) } -function New-PodeAuthAccess +function Add-PodeAuthAccess { [CmdletBinding()] param( - [Parameter()] + [Parameter(Mandatory=$true)] [string] $Name, @@ -1992,14 +1993,15 @@ function New-PodeAuthAccess $Match = 'One' ) - # for custom a validator and name are mandatory - if ($Type -ieq 'custom') { - if ([string]::IsNullOrEmpty($Name)) { - throw "A Name is required for creating Custom Access objects" - } + # check name unique + if (Test-PodeAuthAccess -Name $Name) { + throw "Access method already defined: $($Name)" + } + # for custom access a validator is mandatory + if ($Type -ieq 'custom') { if ($null -eq $Validator) { - throw "A Validator scriptblock is required for creating Custom Access objects" + throw "A Validator scriptblock is required for creating Custom Access methods" } } @@ -2029,7 +2031,7 @@ function New-PodeAuthAccess } # return access object - return @{ + $PodeContext.Server.Authentications.Access[$Name] = @{ Name = $Name Type = $Type IsCustom = ($Type -ieq 'custom') @@ -2075,4 +2077,104 @@ function Add-PodeAuthCustomAccess $r.Access.Custom[$Name] = $Value } } +} + +function Get-PodeAuthAccess +{ + [CmdletBinding()] + param( + [Parameter()] + [string[]] + $Name + ) + + # return all if no Name + if ([string]::IsNullOrEmpty($Name) -or ($Name.Length -eq 0)) { + return $PodeContext.Server.Authentications.Access.Values + } + + # return filtered + return @(foreach ($n in $Name) { + $PodeContext.Server.Authentications.Access[$n] + }) +} + +function Test-PodeAuthAccess +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name + ) + + return $PodeContext.Server.Authentications.Access.ContainsKey($Name) +} + +function Test-PodeAuthUserAccess +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter(Mandatory=$true)] + [object[]] + $Value + ) + + # get the access method + $access = $PodeContext.Server.Authentications.Access[$Name] + + # if there's no scriptblock, try the Path fallback + if ($null -eq $access.Scriptblock) { + $userAccess = $WebEvent.Auth.User + foreach ($atom in $access.Path.Split('.')) { + $userAccess = $userAccess.($atom) + } + } + + # otherwise, invoke scriptblock + else { + $_args = @($WebEvent.Auth.User) + @($access.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Scriptblock.UsingVariables) + $userAccess = Invoke-PodeScriptBlock -ScriptBlock $access.Scriptblock.Script -Arguments $_args -Return -Splat + } + + # set access values on auth object + $WebEvent.Auth.Access = $userAccess + $WebEvent.Auth.IsAuthorised = $false + + # check for custom validator, or use default match logic + if ($null -ne $access.Validator) { + $_args = @(,$userAccess) + @(,$Value) + @($access.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Validator.UsingVariables) + $WebEvent.Auth.IsAuthorised = [bool](Invoke-PodeScriptBlock -ScriptBlock $access.Validator.Script -Arguments $_args -Return -Splat) + } + + # one or all match? + else { + if ($access.Match -ieq 'one') { + foreach ($item in $userAccess) { + if ($item -iin $Value) { + $WebEvent.Auth.IsAuthorised = $true + break + } + } + } + else { + $WebEvent.Auth.IsAuthorised = $true + + foreach ($item in $Value) { + if ($item -inotin $userAccess) { + $WebEvent.Auth.IsAuthorised = $false + break + } + } + } + } + + # authorised? + return $WebEvent.Auth.IsAuthorised } \ No newline at end of file diff --git a/tests/unit/Server.Tests.ps1 b/tests/unit/Server.Tests.ps1 index ef5020646..b1c90d25d 100644 --- a/tests/unit/Server.Tests.ps1 +++ b/tests/unit/Server.Tests.ps1 @@ -219,7 +219,7 @@ Describe 'Restart-PodeInternalServer' { $PodeContext.Server.Middleware.Count | Should Be 0 $PodeContext.Server.Endware.Count | Should Be 0 $PodeContext.Server.Sessions.Count | Should Be 0 - $PodeContext.Server.Authentications.Count | Should Be 0 + $PodeContext.Server.Authentications.Methods.Count | Should Be 0 $PodeContext.Server.State.Count | Should Be 0 $PodeContext.Server.Configuration | Should Be $null From 5780b18eb6d46d1f311b29b34aba0ce8d3f14304 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Tue, 15 Aug 2023 21:33:09 +0100 Subject: [PATCH 17/57] #992: added User based authorisation as an inbuilt type from Pode.Web --- examples/web-auth-basic-access.ps1 | 12 +++++++++--- src/Public/Authentication.ps1 | 9 +++++++-- src/Public/Routes.ps1 | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/examples/web-auth-basic-access.ps1 b/examples/web-auth-basic-access.ps1 index 5404f484c..807f78603 100644 --- a/examples/web-auth-basic-access.ps1 +++ b/examples/web-auth-basic-access.ps1 @@ -27,6 +27,10 @@ Start-PodeServer -Threads 2 { # setup RBAC Add-PodeAuthAccess -Type Role -Name 'TestRbac' + # Add-PodeAuthAccess -Type Custom -Name 'TestRbac' -Path 'CustomAccess' -Validator { + # param($userRoles, $customValues) + # return $userRoles.Example -iin $customValues.Example + # } # setup basic auth (base64> username:password in header) New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Access 'TestRbac' -Sessionless -ScriptBlock { @@ -39,7 +43,9 @@ Start-PodeServer -Threads 2 { ID ='M0R7Y302' Name = 'Morty' Type = 'Human' - Roles = @('Developer') + Username = 'm.orty' + Roles = @('Developer', 'Admin') + CustomAccess = @{ Example = 'test-val-1' } } } } @@ -74,7 +80,7 @@ Start-PodeServer -Threads 2 { } ) } - } + } -PassThru | Add-PodeAuthCustomAccess -Name 'TestRbac' -Value @{ Example = 'test-val-1' } # POST request to get list of users - only Admin roles can access Add-PodeRoute -Method Post -Path '/users-admin' -Authentication 'Validate' -Role Admin -ScriptBlock { @@ -86,6 +92,6 @@ Start-PodeServer -Threads 2 { } ) } - } + } -PassThru | Add-PodeAuthCustomAccess -Name 'TestRbac' -Value @{ Example = 'test-val-2' } } \ No newline at end of file diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index 62b75d1f9..b9a296caa 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -1967,7 +1967,7 @@ function Add-PodeAuthAccess $Name, [Parameter(Mandatory=$true)] - [ValidateSet('Role', 'Group', 'Scope', 'Custom')] + [ValidateSet('Role', 'Group', 'Scope', 'User', 'Custom')] [string] $Type, @@ -2027,7 +2027,12 @@ function Add-PodeAuthAccess # default path if ([string]::IsNullOrEmpty($Path)) { - $Path = "$($Type)s" + if ($Type -ieq 'user') { + $Path = 'Username' + } + else { + $Path = "$($Type)s" + } } # return access object diff --git a/src/Public/Routes.ps1 b/src/Public/Routes.ps1 index 2564b6c06..a02e8b962 100644 --- a/src/Public/Routes.ps1 +++ b/src/Public/Routes.ps1 @@ -139,6 +139,10 @@ function Add-PodeRoute [string[]] $Scope, + [Parameter()] + [string[]] + $User, + [switch] $AllowAnon, @@ -202,6 +206,10 @@ function Add-PodeRoute $Scope = $RouteGroup.Access.Scope + $Scope } + if ($null -ne $RouteGroup.Access.User) { + $User = $RouteGroup.Access.User + $User + } + if ($null -ne $RouteGroup.Access.Custom) { $CustomAccess = $RouteGroup.Access.Custom } @@ -314,6 +322,7 @@ function Add-PodeRoute Role = $Role Group = $Group Scope = $Scope + User = $User Custom = $CustomAccess } Endpoint = @{ @@ -914,6 +923,10 @@ function Add-PodeRouteGroup [string[]] $Scope, + [Parameter()] + [string[]] + $User, + [switch] $AllowAnon ) @@ -979,6 +992,10 @@ function Add-PodeRouteGroup $Scope = $RouteGroup.Access.Scope + $Scope } + if ($null -ne $RouteGroup.Access.User) { + $User = $RouteGroup.Access.User + $User + } + if ($null -ne $RouteGroup.Access.Custom) { $CustomAccess = $RouteGroup.Access.Custom } @@ -998,6 +1015,7 @@ function Add-PodeRouteGroup Role = $Role Group = $Group Scope = $Scope + User = $User Custom = $CustomAccess } } From bc4f5a3aabb73ec6dfba3285817741c12fe40395 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Wed, 16 Aug 2023 22:29:55 +0100 Subject: [PATCH 18/57] #992: add function summaries --- src/Public/Authentication.ps1 | 113 +++++++++++++++++++++++++++++++++- src/Public/Routes.ps1 | 27 ++++++++ 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index b9a296caa..ac04891aa 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -673,7 +673,7 @@ A unique Name for the Authentication method. The Scheme to use for retrieving credentials (From New-PodeAuthScheme). .PARAMETER Access -One or more optional Access Names to validate access to Routes (From Add-PodeAuthAccess) +One or more optional Access method Names to validate authorisation to Routes (From Add-PodeAuthAccess) .PARAMETER ScriptBlock The ScriptBlock defining logic that retrieves and verifys a user. @@ -1958,6 +1958,48 @@ function Test-PodeAuthUser ) } +<# +.SYNOPSIS +Add an authorisation Access method. + +.DESCRIPTION +Add an authorisation Access method for use with Authentication methods, which will authorise access to Routes. + +.PARAMETER Name +A unique Name for the Access method. + +.PARAMETER Type +The Type of Access this method is for: Role, Group, Scope, User, Custom. + +.PARAMETER ScriptBlock +An optional ScriptBlock for retrieving authorisation values for the authenticated user, useful if the values reside in an external data store. + +.PARAMETER ArgumentList +An array of arguments to supply to the Access's ScriptBlock and Validator. + +.PARAMETER Validator +An optional Validator scriptblock, which can be used to invoke custom validation logic to verify authorisation. + +.PARAMETER Path +An optional property Path within the $WebEvent.Auth.User object to extract authorisation values. +The default Path is based on the Access Type, either Roles; Groups; Scopes; Username; or Custom. + +.PARAMETER Match +An optional inbuilt Match method to use when verifying access to a Route, this only applies when no custom Validator scriptblock is supplied. +"One" will allow access if the User as at least one of the Route's access values, and with "All" the User must have all values. (Default: One) + +.EXAMPLE +Add-PodeAuthAccess -Name 'Example' -Type Role + +.EXAMPLE +Add-PodeAuthAccess -Name 'Example' -Type Group -Path 'Metadata.Groups' -Match All + +.EXAMPLE +Add-PodeAuthAccess -Name 'Example' -Type Scope -Scriptblock { param($user) return @(Get-ExampleAccess -Username $user.Username) } + +.EXAMPLE +Add-PodeAuthAccess -Name 'Example' -Type Custom -Validator { param($userAccess, $customAccess) return $userAccess.Country -ieq $customAccess.Country } +#> function Add-PodeAuthAccess { [CmdletBinding()] @@ -2048,6 +2090,25 @@ function Add-PodeAuthAccess } } +<# +.SYNOPSIS +Assigns Custom Access value(s) to a Route. + +.DESCRIPTION +Assigns Custom Access value(s) to a Route. + +.PARAMETER Route +The Route to assign the Custom Access value(s). + +.PARAMETER Name +The Name of the Access method the Custom Access value(s) are for. + +.PARAMETER Value +The Custom Access Value(s) + +.EXAMPLE +Add-PodeRoute -Method Get -Path '/users' -ScriptBlock {} -PassThru | Add-PodeAuthCustomAccess -Name 'Example' -Value @{ Country = 'UK' } +#> function Add-PodeAuthCustomAccess { [CmdletBinding()] @@ -2061,7 +2122,7 @@ function Add-PodeAuthCustomAccess $Name, [Parameter(Mandatory=$true)] - [object] + [object[]] $Value ) @@ -2084,6 +2145,25 @@ function Add-PodeAuthCustomAccess } } +<# +.SYNOPSIS +Get one or more Access methods. + +.DESCRIPTION +Get one or more Access methods. + +.PARAMETER Name +The Name of the Access method. If no name supplied, all methods will be returned. + +.EXAMPLE +$methods = Get-PodeAuthAccess + +.EXAMPLE +$methods = Get-PodeAuthAccess -Name 'Example' + +.EXAMPLE +$methods = Get-PodeAuthAccess -Name 'Example1', 'Example2' +#> function Get-PodeAuthAccess { [CmdletBinding()] @@ -2104,6 +2184,19 @@ function Get-PodeAuthAccess }) } +<# +.SYNOPSIS +Test if an Access method exists. + +.DESCRIPTION +Test if an Access method exists. + +.PARAMETER Name +The Name of the Access method. + +.EXAMPLE +if (Test-PodeAuthAccess -Name 'Example') { } +#> function Test-PodeAuthAccess { [CmdletBinding()] @@ -2116,6 +2209,22 @@ function Test-PodeAuthAccess return $PodeContext.Server.Authentications.Access.ContainsKey($Name) } +<# +.SYNOPSIS +Test the currently authenticated User's access against the supplied values. + +.DESCRIPTION +Test the currently authenticated User's access against the supplied values. + +.PARAMETER Name +The Name of the Access method to use to verify the access. + +.PARAMETER Value +An array of access values to pass to the Access method for verification against the User. + +.EXAMPLE +if (Test-PodeAuthUserAccess -Name 'Example' -Value 'Developer', 'QA') +#> function Test-PodeAuthUserAccess { [CmdletBinding()] diff --git a/src/Public/Routes.ps1 b/src/Public/Routes.ps1 index a02e8b962..17f7e2d56 100644 --- a/src/Public/Routes.ps1 +++ b/src/Public/Routes.ps1 @@ -53,6 +53,18 @@ If supplied, the route created will be returned so it can be passed through a pi .PARAMETER IfExists Specifies what action to take when a Route already exists. (Default: Default) +.PARAMETER Role +One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Group +One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Scope +One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER User +One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method. + .EXAMPLE Add-PodeRoute -Method Get -Path '/' -ScriptBlock { /* logic */ } @@ -70,6 +82,9 @@ Add-PodeRoute -Method Get -Path '/api/cpu' -ErrorContentType 'application/json' .EXAMPLE Add-PodeRoute -Method Get -Path '/' -ScriptBlock { /* logic */ } -ArgumentList 'arg1', 'arg2' + +.EXAMPLE +Add-PodeRoute -Method Get -Path '/' -Role 'Developer', 'QA' -ScriptBlock { /* logic */ } #> function Add-PodeRoute { @@ -862,6 +877,18 @@ The name of an Authentication method which should be used as middleware on the R .PARAMETER IfExists Specifies what action to take when a Route already exists. (Default: Default) +.PARAMETER Role +One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Group +One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Scope +One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER User +One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method. + .PARAMETER AllowAnon If supplied, the Routes will allow anonymous access for non-authenticated users. From 78a4e1aca431f668da86a42b2986fd5617f8ca36 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Wed, 16 Aug 2023 22:59:49 +0100 Subject: [PATCH 19/57] #992: fix unit test --- tests/unit/Server.Tests.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/unit/Server.Tests.ps1 b/tests/unit/Server.Tests.ps1 index b1c90d25d..f30715609 100644 --- a/tests/unit/Server.Tests.ps1 +++ b/tests/unit/Server.Tests.ps1 @@ -128,7 +128,10 @@ Describe 'Restart-PodeInternalServer' { } Cookies = @{} Sessions = @{ 'key' = 'value' } - Authentications = @{ 'key' = 'value' } + Authentications = @{ + Methods = @{ 'key' = 'value' } + Access = @{ 'key' = 'value' } + } State = @{ 'key' = 'value' } Output = @{ Variables = @{ 'key' = 'value' } From 3691990cac08c23542ad8ca5330090ed483ab6c5 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sun, 20 Aug 2023 15:19:31 +0100 Subject: [PATCH 20/57] #992: add authorisation docs --- README.md | 1 + docs/Tutorials/Authentication/Overview.md | 4 + docs/Tutorials/Authorisation/Overview.md | 284 ++++++++++++++++++++++ docs/index.md | 1 + docs/roadmap.md | 2 +- examples/tcp-server-auth.ps1 | 37 +++ examples/tcp-server.ps1 | 1 + examples/web-auth-basic-access.ps1 | 2 +- src/Pode.psd1 | 6 +- src/Private/Authentication.ps1 | 29 +-- src/Public/Authentication.ps1 | 202 +++++++++++---- 11 files changed, 496 insertions(+), 73 deletions(-) create mode 100644 docs/Tutorials/Authorisation/Overview.md create mode 100644 examples/tcp-server-auth.ps1 diff --git a/README.md b/README.md index c2d0fdb58..9ba22b6c6 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Then navigate to `http://127.0.0.1:8000` in your browser. * Basic rate limiting for IP addresses and subnets * Middleware and Sessions on web servers, with Flash message and CSRF support * Authentication on requests, such as Basic, Windows and Azure AD +* Authorisation support on requests, using Roles, Groups, Scopes, etc. * Support for dynamically building Routes from Functions and Modules * Generate/bind self-signed certificates * Secret management support to load secrets from vaults diff --git a/docs/Tutorials/Authentication/Overview.md b/docs/Tutorials/Authentication/Overview.md index 31562f89c..3f50298bd 100644 --- a/docs/Tutorials/Authentication/Overview.md +++ b/docs/Tutorials/Authentication/Overview.md @@ -7,6 +7,8 @@ Authentication can either be sessionless (requiring validation on every request) To setup and use authentication in Pode you need to use the [`New-PodeAuthScheme`](../../../Functions/Authentication/New-PodeAuthScheme) and [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) functions. +You can also setup [Authorisation](../../Authorisation/Overview) for use with Authentication as well. + ## Usage ### Schemes @@ -161,6 +163,8 @@ The `Auth` object will also contain: | User | Details about the authenticated user | | IsAuthenticated | States if the request is for an authenticated user, can be `$true`, `$false` or `$null` | | Store | States whether the authentication is for a session, and will be stored as a cookie | +| IsAuthorised | If using [Authorisation](../../Authorisation/Overview), this value will be `$true` or `$false` depending on whether or not the authenticated user is authorised to access the Route. If not using Authorisation this value will just be `$true` | +| Access | If using [Authorisation](../../Authorisation/Overview), this property will contain the access values for the User per Access method. If not using Authorisation this value will just be an empty hashtable | The following example get the user's name from the `Auth` object: diff --git a/docs/Tutorials/Authorisation/Overview.md b/docs/Tutorials/Authorisation/Overview.md new file mode 100644 index 000000000..c1cbf2b6e --- /dev/null +++ b/docs/Tutorials/Authorisation/Overview.md @@ -0,0 +1,284 @@ +# Overview + +Authorisation can either be used in conjunction with [Authentication](../../Authentication/Overview) and [Routes](../../Routes/Overview), or on it's own for custom scenarios. + +When used with Authentication, Pode can automatically authorise access to Routes based on Roles; Groups; Scopes; Users; or custom validation logic for you. When authorisation fails Pode will respond with an HTTP 403 status code. + +With authentication, Pode will set the following properties on the `$WebEvent.Auth` object: + +| Name | Description | +| ---- | ----------- | +| IsAuthorised | This value will be `$true` or `$false` depending on whether or not the authenticated user is authorised to access the Route | +| Access | This property will contain the access values for the User per Access method | + +## Create an Access Method + +To validate authorisation in Pode you'll first need to create an Access method using [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess). At its most simple you'll just need a Name, Type and possibly a Match type. + +For example, you can create a simple Access method for any of the inbuilt types as follows: + +```powershell +Add-PodeAuthAccess -Name 'RoleExample' -Type Role +Add-PodeAuthAccess -Name 'GroupExample' -Type Group +Add-PodeAuthAccess -Name 'ScopeExample' -Type Scope +Add-PodeAuthAccess -Name 'UserExample' -Type User +``` + +!!! note + These Types mainly apply when using the Access method with Authentication/Routes. If you're going to be using the Access method in a more adhoc manner via [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess) then the Type doesn't apply. + +### Match Type + +Pode supports 3 inbuilt "Match" types for validating access to resources: One, All and None. The default Match type is One; each of them are applied as follows: + +| Type | Description | +| ---- | ----------- | +| One | If the Source's (ie: User's) access values contain at least one of the Destination's (ie: Route's) access values, then authorisation is granted. | +| All | The Source's access values must contain all of the Destination's access values for authorisation to be granted. | +| None | The Source's access values must contain none of the Destination's access values for authorisation to be granted. | + +For example, to setup an Access method where a User must be in every Group that a Route specifies: + +```powershell +Add-PodeAuthAccess -Name 'GroupExample' -Type Group -Match All +``` + +### User Access Lookup + +When using Access methods with Authentication, Pode will lookup the User's "access values" from the `$WebEvent.Auth.User` object. The property within this object that Pode uses depends on the `-Type` supplied to [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess): + +| Type | Property | +| ---- | -------- | +| Role | Roles | +| Group | Groups | +| Scope | Scopes | +| User | Username | +| Custom | Custom.[Name] | + +You can override this default lookup in one of two ways, by either supplying a custom property `-Path` or a `-ScriptBlock` for more a more advanced lookup (ie: external sources). + +!!! note + If you're using Access methods in a more adhoc manner via [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess), the `-Path` property does nothing. However, if you don't supply a `-Source` to this function then the `-ScriptBlock` will be invoked. + +#### Lookup Path + +The `-Path` property on [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess) allows you to specify a custom property path within the `$WebEvent.Auth.User` object, which will be used to retrieve the access values for the User. + +For example, if you have Roles for the User set in a `Roles` property within a `Metadata` property, then you'd use: + +```powershell +Add-PodeAuthAccess -Name 'RoleExample' -Type Role -Path 'Metadata.Roles' + +<# +$User = @{ + Username = 'joe.bloggs' + Metadata = @{ + Roles = @('Developer') + } +} +#> +``` + +And Pode will retrieve the appropriate data for you. + +#### Lookup ScriptBlock + +If the access values you require are not stored in the `$WebEvent.Auth.User` object but else where (ie: external source), then you can supply a `-ScriptBlock` on [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess). When Pode attempts to retrieve access values for the User, or another Source, this scriptblock will be invoked. + +!!! note + When using this scriptblock with Authentication the currently authenticated User will be supplied as the first parameter, followed by the `-ArgumentList` values. When using the Access methods in a more adhoc manner via [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess), just the `-ArgumentList` values are supplied. + +For example, if the Role values you need to retrieve are stored in some SQL database: + +```powershell +Add-PodeAuthAccess -Name 'RoleExample' -Type Role -ScriptBlock { + param($user) + return Invoke-Sqlcmd -Query "SELECT Roles FROM UserRoles WHERE Username = '$($user.Username)'" -ServerInstance '(local)' +} +``` + +Or if you need to get the Groups from AD: + +```powershell +Add-PodeAuthAccess -Name 'GroupExample' -Type Group -ScriptBlock { + param($user) + return Get-ADPrincipalGroupMembership $user.Username | select name +} +``` + +### Custom Validator + +By default Pode will perform basic array contains checks, to see if the Source/Destination access values meet the `-Match` type required. + +For example, if the User has just the Role value `Developer`, and Route has `-Role` values of `Developer` and `QA` supplied, and the `-Match` type is left as `One`, then "if the User Role is contained within the Routes Roles" access is authorised. + +However, if you require a more custom/advanced validation logic to be applied, you can supply a custom `-Validator` scriptblock to [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess). The scriptblock will be supplied with the "Source" access values as the first parameter; the "Destination" access values as the second parameter; then followed by the `-ArgumentList` values. This scriptblock should return a boolean value: true if authorisation granted, or false otherwise. + +!!! note + Supplying a `-Validator` scriptblock will override the `-Match` type supplied, as this scriptblock will be used for validation instead of Pode's inbuilt Match logic. + +For example, if you want to validate that the User's Scopes definitely contains a Route's first Scope value and then at least any 1 of the other Scope values: + +```powershell +Add-PodeAuthAccess -Name 'ScopeExample' -Type Scope -ScriptBlock { + param($userScopes, $routeScopes) + + if ($routeScopes[0] -inotin $userScopes) { + return $false + } + + foreach ($scope in $routeScopes[1..($routeScopes.Length - 1)]) { + if ($scope -iin $userScopes) { + return $true + } + } + + return $false +} +``` + +## Using with Authentication + +The Access methods will mostly commonly be used in conjunction with [Authentication](../../Authentication/Overview) and [Routes](../../Routes/Overview). When used together, Pode will automatically validate Route authorised for you as a part of the Authentication flow. If authorisation fails, an HTTP 403 status code will be returned. + +After creating an Access method as outlined above, you can supply one or more Access method Names to [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) using the `-Access` property. This allows you to check authorisation based on multiple types of Access methods - ie, Roles and Groups, etc. + +On [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute) and [`Add-PodeRouteGroup`](../../../Functions/Routes/Add-PodeRouteGroup) there are the following parameters: `-Role`, `-Group`, `-Scope`, and `-User`. You can supply one ore more string values to these parameters, depending on which Access method type you're using. + +For example, to verify access to a Route to authorise only Developer accounts: + +```powershell +Start-PodeServer { + Add-PodeEndpoint -Address * -Port 8080 -Protocol Http + + # create a simple role access method + Add-PodeAuthAccess -Name 'RoleExample' -Type Role + + # setup Basic authentication + New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -Access 'RoleExample' -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if (($username -eq 'morty') -and ($password -eq 'pickle')) { + return @{ + User = @{ + Username = 'Morty' + Roles = @('Developer') + } + } + } + + # authentication failed + return $null + } + + # create a route which only developers can access + Add-PodeRoute -Method Get -Path '/route1' -Role 'Developer' -Authentication 'AuthExample' -ScriptBlock { + Write-PodeJsonResponse -Value @{ 'Value' = 'Hello!' } + } + + # create a route which only admins can access + Add-PodeRoute -Method Get -Path '/route2' -Role 'Admin' -Authentication 'AuthExample' -ScriptBlock { + Write-PodeJsonResponse -Value @{ 'Value' = 'Hi!' } + } +} +``` + +Calling the following will succeed: + +```powershell +Invoke-RestMethod -Uri http://localhost:8080/route1 -Method Get -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' } +``` + +But calling the following will fail with a 403: + +```powershell +Invoke-RestMethod -Uri http://localhost:8080/route2 -Method Get -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' } +``` + +## Custom Access + +Pode has inbuilt support for Roles, Groups, Scopes, and Users authorisation on Routes. However, if you need to setup a more Custom authorisation policy on Routes you can create an Access method with `-Type` "Custom", and add custom access values to a Route using [`Add-PodeAuthCustomAccess`](../../../Functions/Authentication/Add-PodeAuthCustomAccess). + +Custom access values on a User won't be automatically loaded from the User object, and a `-Path` or `-ScriptBlock` on [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess) will be required. + +For example, if you wanted to authorise access from a set of user attributes, and based on favourite colour, you could do the following: + +```powershell +Start-PodeServer { + Add-PodeEndpoint -Address * -Port 8080 -Protocol Http + + # create a simple role access method + Add-PodeAuthAccess -Name 'CustomExample' -Type Custom -Path 'Metadata.Attributes' -Validator { + param($userAttrs, $routeAttrs) + return ($userAttrs.Colour -ieq $routeAttrs.Colour) + } + + # setup Basic authentication + New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -Access 'CustomExample' -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if (($username -eq 'morty') -and ($password -eq 'pickle')) { + return @{ + User = @{ + Username = 'Morty' + Metadata = @{ + Attributes = @{ + Country = 'UK' + Colour = 'Blue' + } + } + } + } + } + + # authentication failed + return $null + } + + # create a route which only users who like the colour blue can access + Add-PodeRoute -Method Get -Path '/blue' -Authentication 'AuthExample' -ScriptBlock { + Write-PodeJsonResponse -Value @{ 'Value' = 'Hello!' } + } -PassThru | + Add-PodeAuthCustomAccess -Name 'CustomExample' -Value @{ Colour = 'Blue' } + + # create a route which only users who like the colour red can access + Add-PodeRoute -Method Get -Path '/red' -Authentication 'AuthExample' -ScriptBlock { + Write-PodeJsonResponse -Value @{ 'Value' = 'Hi!' } + } -PassThru | + Add-PodeAuthCustomAccess -Name 'CustomExample' -Value @{ Colour = 'Red' } +} +``` + +## Using Adhoc + +It is possible to invoke the Access method validation in an adhoc manner, without (or while) using Authentication, using [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess). + +When using the Access methods outside of Authentication/Routes, the `-Type` doesn't really having any bearing. + +For example, you could create a Roles Access method and verify some Users Roles within a TCP Verb: + +```powershell +Start-PodeServer { + Add-PodeEndpoint -Address * -Port 9000 -Protocol Tcp -CRLFMessageEnd + + # create a role access method get retrieves roles from a database + Add-PodeAuthAccess -Name 'RoleExample' -Type Role -ScriptBlock { + param($username) + return Invoke-Sqlcmd -Query "SELECT Roles FROM UserRoles WHERE Username = '$($username)'" -ServerInstance '(local)' + } + + # setup a Verb that only allows Developers + Add-PodeVerb -Verb 'EXAMPLE :username' -ScriptBlock { + if (!(Test-PodeAuthAccess -Name 'RoleExample' -Destination 'Developer' -ArgumentList $TcpEvent.Parameters.username)) { + Write-PodeTcpClient -Message "Forbidden Access" + return + } + + Write-PodeTcpClient -Message "Hello, there!" + } +} +``` + +The `-ArgumentList`, on [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess), will supply values as the first set of parameters to the `-ScriptBlock` defined on [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess). diff --git a/docs/index.md b/docs/index.md index 1060fb5c7..c5d175bd9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -39,6 +39,7 @@ Pode is a Cross-Platform framework to create web servers that host REST APIs, We * Basic rate limiting for IP addresses and subnets * Middleware and Sessions on web servers, with Flash message and CSRF support * Authentication on requests, such as Basic, Windows and Azure AD +* Authorisation support on requests, using Roles, Groups, Scopes, etc. * Support for dynamically building Routes from Functions and Modules * Generate/bind self-signed certificates * Secret management support to load secrets from vaults diff --git a/docs/roadmap.md b/docs/roadmap.md index 0a37bcfef..2e2b1049d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -29,7 +29,7 @@ Sometimes there could be more, if patch releases are needed. But sometimes there - [ ] gRPC support - [ ] HTTP/2.0 support - [ ] HTTP/3.0 support -- [ ] Inbuilt authorization support, on top the current authentications support [#992](https://github.com/Badgerati/Pode/issues/992) +- [x] Inbuilt authorization support, on top the current authentications support [#992](https://github.com/Badgerati/Pode/issues/992) - [x] Secret management support [#980](https://github.com/Badgerati/Pode/issues/980) - [ ] Some way of being able to merge authentication types [588](https://github.com/Badgerati/Pode/issues/588) - [ ] Improved garbage collection in runspaces, to help free up memory diff --git a/examples/tcp-server-auth.ps1 b/examples/tcp-server-auth.ps1 new file mode 100644 index 000000000..1678e8c3c --- /dev/null +++ b/examples/tcp-server-auth.ps1 @@ -0,0 +1,37 @@ +$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) +Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop + +# or just: +# Import-Module Pode + +# create a server, and start listening on port 9000 +Start-PodeServer -Threads 2 { + + # add endpoint + Add-PodeEndpoint -Address * -Port 9000 -Protocol Tcp -CRLFMessageEnd + + # enable logging + New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging + + # create a role access method get retrieves roles from a database + Add-PodeAuthAccess -Name 'RoleExample' -Type Role -ScriptBlock { + param($username) + if ($username -ieq 'morty') { + return @('Developer') + } + + return 'QA' + } + + # setup a Verb that only allows Developers + Add-PodeVerb -Verb 'EXAMPLE :username' -ScriptBlock { + if (!(Test-PodeAuthAccess -Name 'RoleExample' -Destination 'Developer' -ArgumentList $TcpEvent.Parameters.username)) { + Write-PodeTcpClient -Message "Forbidden Access" + 'Forbidden!' | Out-Default + return + } + + Write-PodeTcpClient -Message "Hello, there!" + 'Hello!' | Out-Default + } +} \ No newline at end of file diff --git a/examples/tcp-server.ps1 b/examples/tcp-server.ps1 index 388f301d5..c28677b46 100644 --- a/examples/tcp-server.ps1 +++ b/examples/tcp-server.ps1 @@ -17,6 +17,7 @@ Start-PodeServer -Threads 2 { Add-PodeVerb -Verb 'HELLO' -ScriptBlock { Write-PodeTcpClient -Message "HI" + 'here' | Out-Default } Add-PodeVerb -Verb 'HELLO2 :username' -ScriptBlock { diff --git a/examples/web-auth-basic-access.ps1 b/examples/web-auth-basic-access.ps1 index 807f78603..dd19ff3a8 100644 --- a/examples/web-auth-basic-access.ps1 +++ b/examples/web-auth-basic-access.ps1 @@ -44,7 +44,7 @@ Start-PodeServer -Threads 2 { Name = 'Morty' Type = 'Human' Username = 'm.orty' - Roles = @('Developer', 'Admin') + Roles = @('Developer') CustomAccess = @{ Example = 'test-val-1' } } } diff --git a/src/Pode.psd1 b/src/Pode.psd1 index dc7c331a5..6fdabf613 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -224,8 +224,10 @@ 'Add-PodeAuthAccess', 'Add-PodeAuthCustomAccess', 'Get-PodeAuthAccess', + 'Test-PodeAuthAccessExists', 'Test-PodeAuthAccess', - 'Test-PodeAuthUserAccess', + 'Test-PodeAuthAccessUser', + 'Test-PodeAuthAccessRoute', # logging 'New-PodeLoggingMethod', @@ -394,7 +396,7 @@ Tags = @('powershell', 'web', 'server', 'http', 'listener', 'rest', 'api', 'tcp', 'smtp', 'websites', 'powershell-core', 'windows', 'unix', 'linux', 'pode', 'PSEdition_Core', 'cross-platform', 'file-monitoring', 'multithreaded', 'schedule', 'middleware', 'session', - 'authentication', 'arm', 'raspberry-pi', 'aws-lambda', + 'authentication', 'authorisation', 'arm', 'raspberry-pi', 'aws-lambda', 'azure-functions', 'websockets', 'swagger', 'openapi', 'webserver', 'secrets', 'fim') # A URL to the license for this module. diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index 7727decd4..8aa389016 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1236,7 +1236,7 @@ function Get-PodeAuthMiddlewareScript $WebEvent.Auth = @{} $WebEvent.Auth.User = $result.User $WebEvent.Auth.IsAuthenticated = $true - $WebEvent.Auth.Access = $null + $WebEvent.Auth.Access = @{} $WebEvent.Auth.IsAuthorised = $true $WebEvent.Auth.Store = !$sessionless @@ -1391,7 +1391,7 @@ function Set-PodeAuthStatus } foreach ($acc in $Access) { - if (!(Test-PodeAuthRouteAccess -Name $acc)) { + if (!(Test-PodeAuthAccessRoute -Name $acc)) { return (Set-PodeAuthStatus ` -StatusCode 403 ` -Description $Description ` @@ -1954,29 +1954,4 @@ function Get-PodeAuthADProvider # ds return 'DirectoryServices' -} - -function Test-PodeAuthRouteAccess -{ - param( - [Parameter(Mandatory=$true)] - [string] - $Name - ) - - # get the access method - $access = $PodeContext.Server.Authentications.Access[$Name] - - # get route access values - if none then skip - $routeAccess = $WebEvent.Route.Access[$access.Type] - if ($access.IsCustom) { - $routeAccess = $routeAccess[$access.Name] - } - - if (($null -eq $routeAccess) -or ($routeAccess.Length -eq 0)) { - return $true - } - - # now test the user's access against the route's access - return (Test-PodeAuthUserAccess -Name $Name -Value $routeAccess) } \ No newline at end of file diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index ac04891aa..b66abd087 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -762,7 +762,7 @@ function Add-PodeAuth # ensure the Access methods exists if (!(Test-PodeIsEmpty $Access)) { foreach ($acc in $Access) { - if (!(Test-PodeAuthAccess -Name $acc)) { + if (!(Test-PodeAuthAccessExists -Name $acc)) { throw "Access method not found: $($acc)" } } @@ -1964,6 +1964,7 @@ Add an authorisation Access method. .DESCRIPTION Add an authorisation Access method for use with Authentication methods, which will authorise access to Routes. +Or they can be used independant of Authentication/Routes for custom scenarios. .PARAMETER Name A unique Name for the Access method. @@ -1985,8 +1986,10 @@ An optional property Path within the $WebEvent.Auth.User object to extract autho The default Path is based on the Access Type, either Roles; Groups; Scopes; Username; or Custom. .PARAMETER Match -An optional inbuilt Match method to use when verifying access to a Route, this only applies when no custom Validator scriptblock is supplied. -"One" will allow access if the User as at least one of the Route's access values, and with "All" the User must have all values. (Default: One) +An optional inbuilt Match method to use when verifying access to a Route, this only applies when no custom Validator scriptblock is supplied. (Default: One) +"One" will allow access if the User has at least one of the Route's access values. +"All" will allow access only if the User has all the values. +"None" will allow access only if the User has none of the values. .EXAMPLE Add-PodeAuthAccess -Name 'Example' -Type Role @@ -2030,20 +2033,20 @@ function Add-PodeAuthAccess $Path, [Parameter()] - [ValidateSet('All', 'One')] + [ValidateSet('All', 'One', 'None')] [string] $Match = 'One' ) # check name unique - if (Test-PodeAuthAccess -Name $Name) { + if (Test-PodeAuthAccessExists -Name $Name) { throw "Access method already defined: $($Name)" } # for custom access a validator is mandatory if ($Type -ieq 'custom') { - if ($null -eq $Validator) { - throw "A Validator scriptblock is required for creating Custom Access methods" + if ([string]::IsNullOrEmpty($Path) -and ($null -eq $ScriptBlock)) { + throw "A Path or ScriptBlock is required for sourcing the Custom access values, for the Custom Access method '$($Name)'" } } @@ -2086,7 +2089,7 @@ function Add-PodeAuthAccess Validator = $validObj Arguments = $ArgumentList Path = $Path - Match = $Match + Match = $Match.ToLowerInvariant() } } @@ -2195,9 +2198,9 @@ Test if an Access method exists. The Name of the Access method. .EXAMPLE -if (Test-PodeAuthAccess -Name 'Example') { } +if (Test-PodeAuthAccessExists -Name 'Example') { } #> -function Test-PodeAuthAccess +function Test-PodeAuthAccessExists { [CmdletBinding()] param( @@ -2211,11 +2214,117 @@ function Test-PodeAuthAccess <# .SYNOPSIS -Test the currently authenticated User's access against the supplied values. +Test access values for a Source/Destination against an Access method. .DESCRIPTION +Test access values for a Source/Destination against an Access method. + +.PARAMETER Name +The Name of the Access method to use to verify the access. + +.PARAMETER Source +An array of Source access values to pass to the Access method for verification against the Destination access values. (ie: User) + +.PARAMETER Destination +An array of Destination access values to pass to the Access method for verification. (ie: Route) + +.EXAMPLE +if (Test-PodeAuthAccess -Name 'Example' -Source 'Developer' -Destination 'Admin') { } +#> +function Test-PodeAuthAccess +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter()] + [object[]] + $Source = $null, + + [Parameter()] + [object[]] + $Destination = $null, + + [Parameter()] + [object[]] + $ArgumentList = $null + ) + + # get the access method + $access = $PodeContext.Server.Authentications.Access[$Name] + + # authorised if no destination values + if (($null -eq $Destination) -or ($Destination.Length -eq 0)) { + return $true + } + + # if we have no source values, invoke the scriptblock + if (($null -eq $Source) -or ($Source.Length -eq 0)) { + if ($null -ne $access.ScriptBlock) { + $_args = $ArgumentList + @($access.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Scriptblock.UsingVariables) + $Source = Invoke-PodeScriptBlock -ScriptBlock $access.Scriptblock.Script -Arguments $_args -Return -Splat + } + } + + # check for custom validator, or use default match logic + if ($null -ne $access.Validator) { + $_args = @(,$Source) + @(,$Destination) + @($access.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Validator.UsingVariables) + return [bool](Invoke-PodeScriptBlock -ScriptBlock $access.Validator.Script -Arguments $_args -Return -Splat) + } + + # not authorised if no source values + if (($access.Match -ne 'none') -and (($null -eq $Source) -or ($Source.Length -eq 0))) { + return $false + } + + # one or all match? + else { + switch ($access.Match) { + 'one' { + foreach ($item in $Source) { + if ($item -iin $Destination) { + return $true + } + } + } + + 'all' { + foreach ($item in $Destination) { + if ($item -inotin $Source) { + return $false + } + } + + return $true + } + + 'none' { + foreach ($item in $Source) { + if ($item -iin $Destination) { + return $false + } + } + + return $true + } + } + } + + # default is not authorised + return $false +} + +<# +.SYNOPSIS Test the currently authenticated User's access against the supplied values. +.DESCRIPTION +Test the currently authenticated User's access against the supplied values. This will be the user in a WebEvent object. + .PARAMETER Name The Name of the Access method to use to verify the access. @@ -2223,9 +2332,9 @@ The Name of the Access method to use to verify the access. An array of access values to pass to the Access method for verification against the User. .EXAMPLE -if (Test-PodeAuthUserAccess -Name 'Example' -Value 'Developer', 'QA') +if (Test-PodeAuthAccessUser -Name 'Example' -Value 'Developer', 'QA') { } #> -function Test-PodeAuthUserAccess +function Test-PodeAuthAccessUser { [CmdletBinding()] param( @@ -2257,38 +2366,47 @@ function Test-PodeAuthUserAccess } # set access values on auth object - $WebEvent.Auth.Access = $userAccess - $WebEvent.Auth.IsAuthorised = $false + $WebEvent.Auth.Access[$Name] = $userAccess - # check for custom validator, or use default match logic - if ($null -ne $access.Validator) { - $_args = @(,$userAccess) + @(,$Value) + @($access.Arguments) - $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Validator.UsingVariables) - $WebEvent.Auth.IsAuthorised = [bool](Invoke-PodeScriptBlock -ScriptBlock $access.Validator.Script -Arguments $_args -Return -Splat) - } + # is the user authorised? + $WebEvent.Auth.IsAuthorised = Test-PodeAuthAccess -Name $Name -Source $userAccess -Destination $Value + return $WebEvent.Auth.IsAuthorised +} - # one or all match? - else { - if ($access.Match -ieq 'one') { - foreach ($item in $userAccess) { - if ($item -iin $Value) { - $WebEvent.Auth.IsAuthorised = $true - break - } - } - } - else { - $WebEvent.Auth.IsAuthorised = $true +<# +.SYNOPSIS +Test the currently authenticated User's access against the access values supplied for the current Route. - foreach ($item in $Value) { - if ($item -inotin $userAccess) { - $WebEvent.Auth.IsAuthorised = $false - break - } - } - } +.DESCRIPTION +Test the currently authenticated User's access against the access values supplied for the current Route. + +.PARAMETER Name +The Name of the Access method to use to verify the access. + +.EXAMPLE +if (Test-PodeAuthAccessRoute -Name 'Example') { } +#> +function Test-PodeAuthAccessRoute +{ + param( + [Parameter(Mandatory=$true)] + [string] + $Name + ) + + # get the access method + $access = $PodeContext.Server.Authentications.Access[$Name] + + # get route access values - if none then skip + $routeAccess = $WebEvent.Route.Access[$access.Type] + if ($access.IsCustom) { + $routeAccess = $routeAccess[$access.Name] } - # authorised? - return $WebEvent.Auth.IsAuthorised + if (($null -eq $routeAccess) -or ($routeAccess.Length -eq 0)) { + return $true + } + + # now test the user's access against the route's access + return (Test-PodeAuthAccessUser -Name $Name -Value $routeAccess) } \ No newline at end of file From a5dab780bfe6fb0198748366a188684030127549 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sun, 20 Aug 2023 15:26:09 +0100 Subject: [PATCH 21/57] #992: update authorisation docs for multiple methods --- docs/Tutorials/Authorisation/Overview.md | 45 ++++++++++++++++++++++++ examples/web-auth-basic-access.ps1 | 8 +++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/docs/Tutorials/Authorisation/Overview.md b/docs/Tutorials/Authorisation/Overview.md index c1cbf2b6e..dd97d2573 100644 --- a/docs/Tutorials/Authorisation/Overview.md +++ b/docs/Tutorials/Authorisation/Overview.md @@ -196,6 +196,51 @@ But calling the following will fail with a 403: Invoke-RestMethod -Uri http://localhost:8080/route2 -Method Get -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' } ``` +### Multiple Access Methods + +You can configure multiple Access methods on a single Authentication, and each of them will have to succeed for a user to be authorised for access to a Route. + +Using the same example above, we could add Group authorisation to this as well so the Developers have to be in a Software Group, and the Admins in a Operations Group: + +```powershell +Start-PodeServer { + Add-PodeEndpoint -Address * -Port 8080 -Protocol Http + + # create simple role and group access methods + Add-PodeAuthAccess -Name 'RoleExample' -Type Role + Add-PodeAuthAccess -Name 'GroupExample' -Type Group + + # setup Basic authentication + New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -Access 'RoleExample', 'GroupExample' -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if (($username -eq 'morty') -and ($password -eq 'pickle')) { + return @{ + User = @{ + Username = 'Morty' + Roles = @('Developer') + Groups = @('Software') + } + } + } + + # authentication failed + return $null + } + + # create a route which only developers can access + Add-PodeRoute -Method Get -Path '/route1' -Role 'Developer' -Group 'Software' -Authentication 'AuthExample' -ScriptBlock { + Write-PodeJsonResponse -Value @{ 'Value' = 'Hello!' } + } + + # create a route which only admins can access + Add-PodeRoute -Method Get -Path '/route2' -Role 'Admin' -Group 'Operations' -Authentication 'AuthExample' -ScriptBlock { + Write-PodeJsonResponse -Value @{ 'Value' = 'Hi!' } + } +} +``` + ## Custom Access Pode has inbuilt support for Roles, Groups, Scopes, and Users authorisation on Routes. However, if you need to setup a more Custom authorisation policy on Routes you can create an Access method with `-Type` "Custom", and add custom access values to a Route using [`Add-PodeAuthCustomAccess`](../../../Functions/Authentication/Add-PodeAuthCustomAccess). diff --git a/examples/web-auth-basic-access.ps1 b/examples/web-auth-basic-access.ps1 index dd19ff3a8..9fab294a2 100644 --- a/examples/web-auth-basic-access.ps1 +++ b/examples/web-auth-basic-access.ps1 @@ -27,13 +27,14 @@ Start-PodeServer -Threads 2 { # setup RBAC Add-PodeAuthAccess -Type Role -Name 'TestRbac' + Add-PodeAuthAccess -Type Group -Name 'TestGbac' # Add-PodeAuthAccess -Type Custom -Name 'TestRbac' -Path 'CustomAccess' -Validator { # param($userRoles, $customValues) # return $userRoles.Example -iin $customValues.Example # } # setup basic auth (base64> username:password in header) - New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Access 'TestRbac' -Sessionless -ScriptBlock { + New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Access 'TestRbac', 'TestGbac' -Sessionless -ScriptBlock { param($username, $password) # here you'd check a real user storage, this is just for example @@ -45,6 +46,7 @@ Start-PodeServer -Threads 2 { Type = 'Human' Username = 'm.orty' Roles = @('Developer') + Groups = @('Software') CustomAccess = @{ Example = 'test-val-1' } } } @@ -59,7 +61,7 @@ Start-PodeServer -Threads 2 { } # POST request to get list of users - there's no Roles, so any auth'd user can access - Add-PodeRoute -Method Post -Path '/users-all' -Authentication 'Validate' -ScriptBlock { + Add-PodeRoute -Method Post -Path '/users-all' -Authentication 'Validate' -Group 'Ops' -ScriptBlock { Write-PodeJsonResponse -Value @{ Users = @( @{ @@ -71,7 +73,7 @@ Start-PodeServer -Threads 2 { } # POST request to get list of users - only Developer roles can access - Add-PodeRoute -Method Post -Path '/users-dev' -Authentication 'Validate' -Role Developer -ScriptBlock { + Add-PodeRoute -Method Post -Path '/users-dev' -Authentication 'Validate' -Role Developer -Group Software -ScriptBlock { Write-PodeJsonResponse -Value @{ Users = @( @{ From 9b9849c866b847668449b13a696296ca1354c7ff Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sun, 20 Aug 2023 15:35:40 +0100 Subject: [PATCH 22/57] #992: fix missing function parameter summary --- src/Public/Authentication.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index b66abd087..a3cc2f172 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -1976,7 +1976,7 @@ The Type of Access this method is for: Role, Group, Scope, User, Custom. An optional ScriptBlock for retrieving authorisation values for the authenticated user, useful if the values reside in an external data store. .PARAMETER ArgumentList -An array of arguments to supply to the Access's ScriptBlock and Validator. +An optional array of arguments to supply to the Access's ScriptBlock and Validator. .PARAMETER Validator An optional Validator scriptblock, which can be used to invoke custom validation logic to verify authorisation. @@ -2228,6 +2228,9 @@ An array of Source access values to pass to the Access method for verification a .PARAMETER Destination An array of Destination access values to pass to the Access method for verification. (ie: Route) +.PARAMETER ArgumentList +An optional array of arguments to supply to the Access's methods ScriptBlock for retrieving access values. + .EXAMPLE if (Test-PodeAuthAccess -Name 'Example' -Source 'Developer' -Destination 'Admin') { } #> From a026ed6fe30a5c5526140a998379c21900606cc6 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sun, 20 Aug 2023 16:09:37 +0100 Subject: [PATCH 23/57] #1125: fix verb cleardown on server restart --- src/Private/Server.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Private/Server.ps1 b/src/Private/Server.ps1 index 0e0763332..59a4c551d 100644 --- a/src/Private/Server.ps1 +++ b/src/Private/Server.ps1 @@ -176,9 +176,12 @@ function Restart-PodeInternalServer # clear up timers, schedules and loggers $PodeContext.Server.Routes | Clear-PodeHashtableInnerKeys $PodeContext.Server.Handlers | Clear-PodeHashtableInnerKeys - $PodeContext.Server.Verbs | Clear-PodeHashtableInnerKeys $PodeContext.Server.Events | Clear-PodeHashtableInnerKeys + if ($null -ne $PodeContext.Server.Verbs) { + $PodeContext.Server.Verbs.Clear() + } + $PodeContext.Server.Views.Clear() $PodeContext.Timers.Items.Clear() $PodeContext.Server.Logging.Types.Clear() From a1da354aa918b320a9f5614886befac30297e022 Mon Sep 17 00:00:00 2001 From: Christopher Andrews <6584350+Chris--A@users.noreply.github.com> Date: Fri, 25 Aug 2023 19:28:48 +1000 Subject: [PATCH 24/57] Fixed links to Lock-PodeState docs Original links referenced: ../../Functions/Utilities/Lock-PodeObject Corrected to: ../../Functions/Threading/Lock-PodeObject --- docs/Tutorials/SharedState.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Tutorials/SharedState.md b/docs/Tutorials/SharedState.md index f391e5bcb..b34ba137e 100644 --- a/docs/Tutorials/SharedState.md +++ b/docs/Tutorials/SharedState.md @@ -4,13 +4,13 @@ Most things in Pode run in isolated runspaces: routes, middleware, schedules - t You also have the option of saving the current state to a file, and then restoring the state back on server start. This way you won't lose state between server restarts. -You can also use the State in combination with [`Lock-PodeObject`](../../Functions/Utilities/Lock-PodeObject) to ensure thread safety - if needed. +You can also use the State in combination with [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject) to ensure thread safety - if needed. !!! tip - It's wise to use the State in conjunction with [`Lock-PodeObject`](../../Functions/Utilities/Lock-PodeObject), to ensure thread safety between runspaces. + It's wise to use the State in conjunction with [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject), to ensure thread safety between runspaces. !!! warning - If you omit the use of [`Lock-PodeObject`](../../Functions/Utilities/Lock-PodeObject), you might run into errors due to multi-threading. Only omit if you are *absolutely confident* you do not need locking. (ie: you set in state once and then only ever retrieve, never updating the variable). + If you omit the use of [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject), you might run into errors due to multi-threading. Only omit if you are *absolutely confident* you do not need locking. (ie: you set in state once and then only ever retrieve, never updating the variable). ## Usage @@ -98,7 +98,7 @@ Start-PodeServer { ### Save -The [`Save-PodeState`](../../Functions/State/Save-PodeState) function will save the current state, as JSON, to the specified file. The file path can either be relative, or literal. When saving the state, it's recommended to wrap the function within [`Lock-PodeObject`](../../Functions/Utilities/Lock-PodeObject). +The [`Save-PodeState`](../../Functions/State/Save-PodeState) function will save the current state, as JSON, to the specified file. The file path can either be relative, or literal. When saving the state, it's recommended to wrap the function within [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject). An example of saving the current state every hour is as follows: @@ -120,7 +120,7 @@ By default the JSON will be saved expanded, but you can saved the JSON as compre ### Restore -The [`Restore-PodeState`](../../Functions/State/Restore-PodeState) function will restore the current state from the specified file. The file path can either be relative, or a literal path. if you're restoring the state immediately on server start, you don't need to use [`Lock-PodeObject`](../../Functions/Utilities/Lock-PodeObject). +The [`Restore-PodeState`](../../Functions/State/Restore-PodeState) function will restore the current state from the specified file. The file path can either be relative, or a literal path. if you're restoring the state immediately on server start, you don't need to use [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject). An example of restore the current state on server start is as follows: From 0dbebb074559b6f18313972cba5e892764880ecb Mon Sep 17 00:00:00 2001 From: Christopher Andrews <6584350+Chris--A@users.noreply.github.com> Date: Mon, 28 Aug 2023 19:12:21 +1000 Subject: [PATCH 25/57] Update RestApiSessions.md Fixed broken link to Enable-PodeSessionMiddleware --- docs/Tutorials/Routes/Examples/RestApiSessions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Tutorials/Routes/Examples/RestApiSessions.md b/docs/Tutorials/Routes/Examples/RestApiSessions.md index d63fab54e..f8b4dad2f 100644 --- a/docs/Tutorials/Routes/Examples/RestApiSessions.md +++ b/docs/Tutorials/Routes/Examples/RestApiSessions.md @@ -23,7 +23,7 @@ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http ## Enabling Sessions -To use sessions with headers for our authentication, we need to setup Session Middleware using the [`Enable-PodeSessionMiddleware`](../../../../Functions/Middleware/Enable-PodeSessionMiddleware) function. Here our sessions will last for 2 minutes, and will be extended on each request: +To use sessions with headers for our authentication, we need to setup Session Middleware using the [`Enable-PodeSessionMiddleware`](../../../../Functions/Sessions/Enable-PodeSessionMiddleware/) function. Here our sessions will last for 2 minutes, and will be extended on each request: ```powershell Enable-PodeSessionMiddleware -Duration 120 -Extend -UseHeaders From e44d05270f46b44fd40a1ce4982670a85031e688 Mon Sep 17 00:00:00 2001 From: Christopher Andrews <6584350+Chris--A@users.noreply.github.com> Date: Mon, 28 Aug 2023 19:18:57 +1000 Subject: [PATCH 26/57] Update Sessions.md Fixed broken links to Get-PodeSessionId & Enable-PodeSessionMiddleware --- docs/Tutorials/Middleware/Types/Sessions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Tutorials/Middleware/Types/Sessions.md b/docs/Tutorials/Middleware/Types/Sessions.md index 958a7c87c..fc1c04e5d 100644 --- a/docs/Tutorials/Middleware/Types/Sessions.md +++ b/docs/Tutorials/Middleware/Types/Sessions.md @@ -12,7 +12,7 @@ The duration of the session cookie/header can be specified, as well as whether t ## Usage -To initialise sessions in Pode you'll need to call [`Enable-PodeSessionMiddleware`](../../../../Functions/Middleware/Enable-PodeSessionMiddleware). This function will configure and automatically create the Middleware needed to enable sessions. By default sessions are set to use cookies, but support is also available for headers. +To initialise sessions in Pode you'll need to call [`Enable-PodeSessionMiddleware`](../../../../Functions/Sessions/Enable-PodeSessionMiddleware). This function will configure and automatically create the Middleware needed to enable sessions. By default sessions are set to use cookies, but support is also available for headers. Sessions are automatically signed using a random GUID. For Pode running on a single server using the default in-memory storage this is OK, however if you're running Pode on multiple servers, or if you're defining a custom storage then a `-Secret` is required - this is so that sessions from different servers, or after a server restart, don't become corrupt and unusable. @@ -46,7 +46,7 @@ The inbuilt SessionId generator used for sessions is a GUID, but you can supply If supplied, the `-Generator` is a scriptblock that must return a valid string. The string itself should be a random unique value, that can be used as a unique session identifier. -Within a route, or middleware, you can get the currently authenticated session'd ID using [`Get-PodeSessionId`](../../../../Functions/Middleware/Get-PodeSessionId). If there is no session, or the session is not authenticated, then `$null` is returned. This function can also returned the fully signed sessionId as well. If you want the sessionId even if it's not authenticated, then you can supply `-Force` to get the current SessionId back. +Within a route, or middleware, you can get the currently authenticated session'd ID using [`Get-PodeSessionId`](../../../../Functions/Sessions/Get-PodeSessionId). If there is no session, or the session is not authenticated, then `$null` is returned. This function can also returned the fully signed sessionId as well. If you want the sessionId even if it's not authenticated, then you can supply `-Force` to get the current SessionId back. ### Strict From 8d6dae7ba1196363f699652d2abeccb33df481f7 Mon Sep 17 00:00:00 2001 From: Christopher Andrews <6584350+Chris--A@users.noreply.github.com> Date: Mon, 28 Aug 2023 19:26:49 +1000 Subject: [PATCH 27/57] Update 0X-to-1X.md Fixed broken link to Enable-PodeSessionMiddleware. --- docs/Getting-Started/Migrating/0X-to-1X.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Getting-Started/Migrating/0X-to-1X.md b/docs/Getting-Started/Migrating/0X-to-1X.md index 36e9e3133..8760d6d78 100644 --- a/docs/Getting-Started/Migrating/0X-to-1X.md +++ b/docs/Getting-Started/Migrating/0X-to-1X.md @@ -186,7 +186,7 @@ There is a new [`New-PodeMiddleware`](../../../Functions/Middleware/New-PodeMidd ([Tutorial](../../../Tutorials/Middleware/Types/Sessions)) -The `session` function has now been replaced by the new [`Enable-PodeSessionMiddleware`](../../../Functions/Middleware/Enable-PodeSessionMiddleware) function. With the new function, not only will it automatically enabled session middleware for you, but the old `-Options` hashtable has now been converted into proper function parameters. +The `session` function has now been replaced by the new [`Enable-PodeSessionMiddleware`](../../../Functions/Sessions/Enable-PodeSessionMiddleware) function. With the new function, not only will it automatically enabled session middleware for you, but the old `-Options` hashtable has now been converted into proper function parameters. ### CSRF From 2d18e84c3751b8034fc18843de39c4f33758958c Mon Sep 17 00:00:00 2001 From: Christopher Andrews <6584350+Chris--A@users.noreply.github.com> Date: Mon, 28 Aug 2023 19:28:35 +1000 Subject: [PATCH 28/57] Update LoginPage.md Fixed broken link to Enable-PodeSessionMiddleware. --- docs/Tutorials/Routes/Examples/LoginPage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Tutorials/Routes/Examples/LoginPage.md b/docs/Tutorials/Routes/Examples/LoginPage.md index 438566b8a..eaaee628a 100644 --- a/docs/Tutorials/Routes/Examples/LoginPage.md +++ b/docs/Tutorials/Routes/Examples/LoginPage.md @@ -35,7 +35,7 @@ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http Set-PodeViewEngine -Type Pode ``` -To use sessions for our authentication (so we can stay logged in), we need to setup Session Middleware using the [`Enable-PodeSessionMiddleware`](../../../../Functions/Middleware/Enable-PodeSessionMiddleware) function. Here our sessions will last for 2 minutes, and will be extended on each request: +To use sessions for our authentication (so we can stay logged in), we need to setup Session Middleware using the [`Enable-PodeSessionMiddleware`](../../../../Functions/Sessions/Enable-PodeSessionMiddleware) function. Here our sessions will last for 2 minutes, and will be extended on each request: ```powershell Enable-PodeSessionMiddleware -Duration 120 -Extend From e8f1437b2875b7948d67bb6a3ff4d15eaeb6f358 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Mon, 18 Sep 2023 23:03:02 +0100 Subject: [PATCH 29/57] #588: initial work for supporting 'merging' auth methods --- examples/web-auth-form-merged.ps1 | 102 +++++ src/Pode.psd1 | 1 + src/Private/Authentication.ps1 | 610 ++++++++++++++++++++++-------- src/Public/Authentication.ps1 | 233 +++++++++++- 4 files changed, 774 insertions(+), 172 deletions(-) create mode 100644 examples/web-auth-form-merged.ps1 diff --git a/examples/web-auth-form-merged.ps1 b/examples/web-auth-form-merged.ps1 new file mode 100644 index 000000000..9f180fbcf --- /dev/null +++ b/examples/web-auth-form-merged.ps1 @@ -0,0 +1,102 @@ +$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) +Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop + +# or just: +# Import-Module Pode + +<# +This examples shows how to use session persistant authentication, for things like logins on websites. +The example used here is Form authentication, sent from the in HTML. + +Navigating to the 'http://localhost:8085' endpoint in your browser will auto-rediect you to the '/login' +page. Here, you can type the username (morty) and the password (pickle); clicking 'Login' will take you +back to the home page with a greeting and a view counter. Clicking 'Logout' will purge the session and +take you back to the login page. +#> + +# create a server, and start listening on port 8085 +Start-PodeServer -Threads 2 { + + # listen on localhost:8085 + Add-PodeEndpoint -Address * -Port 8085 -Protocol Http + + # set the view engine + Set-PodeViewEngine -Type Pode + + # enable error logging + New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging + + # setup session details + Enable-PodeSessionMiddleware -Duration 120 -Extend + + # setup form auth ( in HTML) + New-PodeAuthScheme -Form | Add-PodeAuth -Name 'Login1' -FailureUrl '/login' -SuccessUrl '/' -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if ($username -eq 'morty' -and $password -eq 'pickle') { + return @{ + User = @{ + ID ='M0R7Y302' + Name = 'Morty' + Type = 'Human' + } + } + } + + return @{ Message = 'Invalid details supplied' } + } + + New-PodeAuthScheme -Form | Add-PodeAuth -Name 'Login2' -FailureUrl '/login' -SuccessUrl '/' -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if ($username -eq 'morty' -and $password -eq 'bob') { + return @{ + User = @{ + ID ='M0R7Y302' + Name = 'Morty' + Type = 'Human' + } + } + } + + return @{ Message = 'Invalid details supplied' } + } + + Merge-PodeAuth -Name 'Login' -Authentication Login1, Login2 + + + # home page: + # redirects to login page if not authenticated + Add-PodeRoute -Method Get -Path '/' -Authentication Login -ScriptBlock { + $session:Views++ + + Write-PodeViewResponse -Path 'auth-home' -Data @{ + Username = $WebEvent.Auth.User.Name + Views = $session:Views + Expiry = Get-PodeSessionExpiry + } + } + + + # login page: + # the login flag set below checks if there is already an authenticated session cookie. If there is, then + # the user is redirected to the home page. If there is no session then the login page will load without + # checking user authetication (to prevent a 401 status) + Add-PodeRoute -Method Get -Path '/login' -Authentication Login -Login -ScriptBlock { + Write-PodeViewResponse -Path 'auth-login' -FlashMessages + } + + + # login check: + # this is the endpoint the 's action will invoke. If the user validates then they are set against + # the session as authenticated, and redirect to the home page. If they fail, then the login page reloads + Add-PodeRoute -Method Post -Path '/login' -Authentication Login -Login + + + # logout check: + # when the logout button is click, this endpoint is invoked. The logout flag set below informs this call + # to purge the currently authenticated session, and then redirect back to the login page + Add-PodeRoute -Method Post -Path '/logout' -Authentication Login -Logout +} \ No newline at end of file diff --git a/src/Pode.psd1 b/src/Pode.psd1 index 6fdabf613..99cbb6d48 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -228,6 +228,7 @@ 'Test-PodeAuthAccess', 'Test-PodeAuthAccessUser', 'Test-PodeAuthAccessRoute', + 'Merge-PodeAuth', # logging 'New-PodeLoggingMethod', diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index 8aa389016..e32f47b0b 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1059,51 +1059,299 @@ function Test-PodeAuthUserGroups return $false } +function Invoke-PodeAuthValidation +{ + param( + [Parameter(Mandatory=$true)] + [string] + $Name + ) + + # get auth method + $auth = $PodeContext.Server.Authentications.Methods[$Name] + + # if it's a merged auth, re-call this function and check against "succeed" value + if ($auth.Merged) { + $results = @() + + foreach ($authName in $auth.Authentications) { + $result = Invoke-PodeAuthValidation -Name $authName + + # if the auth is trying to redirect, we need to bubble the this back now + if ($result.Redirected) { + return $result + } + + # if the auth passed, and we only need one auth to pass, return current result + if ($result.Success -and $auth.PassOne) { + return $result + } + + # if the auth failed, but we need all to pass, return current result + if (!$result.Success -and !$auth.PassOne) { + return $result + } + + # remember result if we need all to pass + if (!$auth.PassOne) { + if ($result.Aggregated) { + $results += $result.Results + } + else { + $results += $result + } + } + } + + # if the last auth failed, and we only need one auth to pass, set failure and return + if (!$result.Success -and $auth.PassOne) { + return $result + } + + # if the last auth succeeded, and we need all to pass, return aggregated results + if ($result.Success -and !$auth.PassOne) { + return @{ + Success = $true + Aggregated = $true + Results = $results + Auth = $results.Auth + } + } + + # default failure + return @{ + Success = $false + StatusCode = 500 + } + } + + # main auth validation logic + $result = (Test-PodeAuthValidation -Name $Name) + $result.Auth = $Name + return $result +} + +function Test-PodeAuthValidation +{ + param( + [Parameter(Mandatory=$true)] + [string] + $Name + ) + + try { + # get auth method + $auth = $PodeContext.Server.Authentications.Methods[$Name] + + # auth result + $result = $null + + # run pre-auth middleware + if ($null -ne $auth.Scheme.Middleware) { + if (!(Invoke-PodeMiddleware -Middleware $auth.Scheme.Middleware)) { + return @{ + Success = $false + } + } + } + + # run auth scheme script to parse request for data + $_args = @(Get-PodeScriptblockArguments -ArgumentList $auth.Scheme.Arguments -UsingVariables $auth.Scheme.ScriptBlock.UsingVariables) + + # call inner schemes first + if ($null -ne $auth.Scheme.InnerScheme) { + $schemes = @() + + $_scheme = $auth.Scheme + $_inner = @(while ($null -ne $_scheme.InnerScheme) { + $_scheme = $_scheme.InnerScheme + $_scheme + }) + + for ($i = $_inner.Length - 1; $i -ge 0; $i--) { + $_tmp_args = @(Get-PodeScriptblockArguments -ArgumentList $_inner[$i].Arguments -UsingVariables $_inner[$i].ScriptBlock.UsingVariables) + + $_tmp_args += ,$schemes + $result = (Invoke-PodeScriptBlock -ScriptBlock $_inner[$i].ScriptBlock.Script -Arguments $_tmp_args -Return -Splat) + if ($result -is [hashtable]) { + break + } + + $schemes += ,$result + $result = $null + } + + $_args += ,$schemes + } + + if ($null -eq $result) { + $result = (Invoke-PodeScriptBlock -ScriptBlock $auth.Scheme.ScriptBlock.Script -Arguments $_args -Return -Splat) + } + + # if data is a hashtable, then don't call validator (parser either failed, or forced a success) + if ($result -isnot [hashtable]) { + $original = $result + + $_args = @($result) + @($auth.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $auth.UsingVariables) + $result = (Invoke-PodeScriptBlock -ScriptBlock $auth.ScriptBlock -Arguments $_args -Return -Splat) + + # if we have user, then run post validator if present + if ([string]::IsNullOrEmpty($result.Code) -and ($null -ne $auth.Scheme.PostValidator.Script)) { + $_args = @($original) + @($result) + @($auth.Scheme.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $auth.Scheme.PostValidator.UsingVariables) + $result = (Invoke-PodeScriptBlock -ScriptBlock $auth.Scheme.PostValidator.Script -Arguments $_args -Return -Splat) + } + } + + # is the auth trying to redirect ie: oauth? + if ($result.IsRedirected) { + return @{ + Success = $false + Redirected = $true + } + } + + # headers + if ($null -eq $result.Headers) { + $result.Headers = @{} + } + + # if there's no result, or no user, then the auth failed - but allow auth if anon enabled + if (($null -eq $result) -or ($result.Count -eq 0) -or (Test-PodeIsEmpty $result.User)) { + $code = (Protect-PodeValue -Value $result.Code -Default 401) + + # set the www-auth header + $validCode = (($code -eq 401) -or ![string]::IsNullOrEmpty($result.Challenge)) + + if ($validCode -and !$result.Headers.ContainsKey('WWW-Authenticate')) { + $authHeader = Get-PodeAuthWwwHeaderValue -Name $auth.Scheme.Name -Realm $auth.Scheme.Realm -Challenge $result.Challenge + $result.Headers['WWW-Authenticate'] = $authHeader + } + + return @{ + Success = $false + StatusCode = $code + Description = $result.Message + Headers = $result.Headers + FailureRedirect = [bool]$result.IsErrored + } + } + + # authentication was successful + return @{ + Success = $true + User = $result.User + Headers = $result.Headers + } + } + catch { + return @{ + Success = $false + StatusCode = 500 + Exception = $_ + } + } +} + +function Test-PodeAuthValidationAccess +{ + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter()] + [string] + $BaseName + ) + + # base name + if ([string]::IsNullOrEmpty($BaseName)) { + $BaseName = $Name + } + + # get auth method + $auth = $PodeContext.Server.Authentications.Methods[$Name] + $access = @() + + # cached access? + if ($null -ne $auth.Cache.Access) { + $access = $auth.Cache.Access + } + + # recursively find access + else { + # have access and/or parent? + $hasAccess = (($null -ne $auth.Access) -and ($auth.Access.Length -gt 0)) + $hasParent = ![string]::IsNullOrEmpty($auth.Parent) + + # no access + if (!$hasAccess) { + $PodeContext.Server.Authentications.Methods[$Name].Cache.Access = @() + + # skip if no parent + if (!$hasParent) { + return $true + } + + # re-call on parent + else { + return (Test-PodeAuthValidationAccess -Name $auth.Parent -BaseName $BaseName) + } + } + + # set access + $access = $auth.Access + $PodeContext.Server.Authentications.Methods[$BaseName].Cache.Access = $access + } + + # if we have access methods, check them + foreach ($acc in $access) { + if (!(Test-PodeAuthAccessRoute -Name $acc)) { + return $false + } + } + + # access allowed! + return $true +} + function Get-PodeAuthMiddlewareScript { return { param($opts) # get the auth method - $auth = Find-PodeAuth -Name $opts.Name - - # route options for using sessions - $sessionless = $auth.Sessionless - $usingSessions = (Test-PodeSessionsInUse) - $useHeaders = $PodeContext.Server.Sessions.Info.UseHeaders - $loginRoute = $opts.Login + $auth = $PodeContext.Server.Authentications.Methods[$opts.Name] # check for logout command if ($opts.Logout) { Remove-PodeAuthSession - if ($useHeaders) { + if ($PodeContext.Server.Sessions.Info.UseHeaders) { return (Set-PodeAuthStatus ` -StatusCode 401 ` - -Sessionless:$sessionless ` + -Name $opts.Name ` -NoSuccessRedirect) } else { $auth.Failure.Url = (Protect-PodeValue -Value $auth.Failure.Url -Default $WebEvent.Request.Url.AbsolutePath) return (Set-PodeAuthStatus ` -StatusCode 302 ` - -Failure $auth.Failure ` - -Sessionless:$sessionless ` + -Name $opts.Name ` -NoSuccessRedirect) } } # if the session already has a user/isAuth'd, then skip auth - or allow anon - if ($usingSessions) { + if (Test-PodeSessionsInUse) { # existing session auth'd if (Test-PodeAuthUser) { $WebEvent.Auth = $WebEvent.Session.Data.Auth return (Set-PodeAuthStatus ` - -Success $auth.Success ` - -Failure $auth.Failure ` - -Access $auth.Access ` - -LoginRoute:$loginRoute ` - -Sessionless:$sessionless ` + -Name $opts.Name ` + -LoginRoute:($opts.Login) ` -NoSuccessRedirect) } @@ -1118,7 +1366,7 @@ function Get-PodeAuthMiddlewareScript } # check if the login flag is set, in which case just return and load a login get-page (allowing anon access) - if ($loginRoute -and !$useHeaders -and ($WebEvent.Method -ieq 'get')) { + if ($opts.Login -and !$PodeContext.Server.Sessions.Info.UseHeaders -and ($WebEvent.Method -ieq 'get')) { if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) { Revoke-PodeSession } @@ -1127,127 +1375,74 @@ function Get-PodeAuthMiddlewareScript } try { - $result = $null - - # run pre-auth middleware - if ($null -ne $auth.Scheme.Middleware) { - if (!(Invoke-PodeMiddleware -Middleware $auth.Scheme.Middleware)) { - return $false - } - } - - # run auth scheme script to parse request for data - $_args = @(Get-PodeScriptblockArguments -ArgumentList $auth.Scheme.Arguments -UsingVariables $auth.Scheme.ScriptBlock.UsingVariables) - - # call inner schemes first - if ($null -ne $auth.Scheme.InnerScheme) { - $schemes = @() - - $_scheme = $auth.Scheme - $_inner = @(while ($null -ne $_scheme.InnerScheme) { - $_scheme = $_scheme.InnerScheme - $_scheme - }) - - for ($i = $_inner.Length - 1; $i -ge 0; $i--) { - $_tmp_args = @(Get-PodeScriptblockArguments -ArgumentList $_inner[$i].Arguments -UsingVariables $_inner[$i].ScriptBlock.UsingVariables) - - $_tmp_args += ,$schemes - $result = (Invoke-PodeScriptBlock -ScriptBlock $_inner[$i].ScriptBlock.Script -Arguments $_tmp_args -Return -Splat) - if ($result -is [hashtable]) { - break - } - - $schemes += ,$result - $result = $null - } - - $_args += ,$schemes - } - - if ($null -eq $result) { - $result = (Invoke-PodeScriptBlock -ScriptBlock $auth.Scheme.ScriptBlock.Script -Arguments $_args -Return -Splat) - } - - # if data is a hashtable, then don't call validator (parser either failed, or forced a success) - if ($result -isnot [hashtable]) { - $original = $result - - $_args = @($result) + @($auth.Arguments) - $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $auth.UsingVariables) - $result = (Invoke-PodeScriptBlock -ScriptBlock $auth.ScriptBlock -Arguments $_args -Return -Splat) - - # if we have user, then run post validator if present - if ([string]::IsNullOrWhiteSpace($result.Code) -and !(Test-PodeIsEmpty $auth.Scheme.PostValidator.Script)) { - $_args = @($original) + @($result) + @($auth.Scheme.Arguments) - $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $auth.Scheme.PostValidator.UsingVariables) - $result = (Invoke-PodeScriptBlock -ScriptBlock $auth.Scheme.PostValidator.Script -Arguments $_args -Return -Splat) - } - } + $result = Invoke-PodeAuthValidation -Name $opts.Name } catch { $_ | Write-PodeErrorLog return (Set-PodeAuthStatus ` -StatusCode 500 ` -Description $_.Exception.Message ` - -Failure $auth.Failure ` - -Sessionless:$sessionless) + -Name $opts.Name) } # did the auth force a redirect? - if ($result.IsRedirected) { + if ($result.Redirected) { return $false } - # if there is no result, return false (failed auth) - but skip if allow anon access - if ((Test-PodeIsEmpty $result) -or (Test-PodeIsEmpty $result.User)) { - if (!$opts.Anon) { - $_code = (Protect-PodeValue -Value $result.Code -Default 401) + # if auth failed, are we allowing anon access? + if (!$result.Success -and $opts.Anon) { + return $true + } - # set the www-auth header - $validCode = (($_code -eq 401) -or ![string]::IsNullOrWhiteSpace($result.Challenge)) - $validHeaders = (($null -eq $result.Headers) -or !$result.Headers.ContainsKey('WWW-Authenticate')) + # if auth failed, set appropriate response headers/redirects + if (!$result.Success) { + return (Set-PodeAuthStatus ` + -StatusCode $result.StatusCode ` + -Description $result.Description ` + -Headers $result.Headers ` + -Name $opts.Name ` + -LoginRoute:($opts.Login) ` + -NoFailureRedirect:($result.FailureRedirect)) + } - if ($validCode -and $validHeaders) { - $_wwwHeader = Get-PodeAuthWwwHeaderValue -Name $auth.Scheme.Name -Realm $auth.Scheme.Realm -Challenge $result.Challenge - if (![string]::IsNullOrWhiteSpace($_wwwHeader)) { - Set-PodeHeader -Name 'WWW-Authenticate' -Value $_wwwHeader - } - } + # if auth passed, assign the user(s) to the session + $user = $result.User + if ($result.Aggregated) { + $user = @{} + foreach ($r in $result.Results) { + $user[$r.Auth] = $r.User + } + } - $isErrored = [bool]$result.IsErrored + $WebEvent.Auth = @{ + User = $user + IsAuthenticated = $true + Access = @{} + IsAuthorised = $true + Store = !$auth.Sessionless + Name = $result.Auth + Multiple = $result.Aggregated + } + + # check access + foreach ($authName in $result.Auth) { + if (!(Test-PodeAuthValidationAccess -Name $authName)) { return (Set-PodeAuthStatus ` - -StatusCode $_code ` - -Description $result.Message ` + -StatusCode 403 ` + -Description $result.Description ` -Headers $result.Headers ` - -Failure $auth.Failure ` - -Success $auth.Success ` - -Access $auth.Access ` - -LoginRoute:$loginRoute ` - -Sessionless:$sessionless ` - -NoFailureRedirect:$isErrored) - } - else { - return $true + -Name $authName ` + -LoginRoute:($opts.Login) ` + -NoFailureRedirect:($result.FailureRedirect)) } } - # assign the user to the session, and wire up a quick method - $WebEvent.Auth = @{} - $WebEvent.Auth.User = $result.User - $WebEvent.Auth.IsAuthenticated = $true - $WebEvent.Auth.Access = @{} - $WebEvent.Auth.IsAuthorised = $true - $WebEvent.Auth.Store = !$sessionless - - # continue, but check access + # successful auth return (Set-PodeAuthStatus ` -Headers $result.Headers ` - -Success $auth.Success ` - -Failure $auth.Failure ` - -Access $auth.Access ` - -LoginRoute:$loginRoute ` - -Sessionless:$sessionless) + -Name @($result.Auth)[0] ` + -LoginRoute:($opts.Login)) } } @@ -1297,38 +1492,133 @@ function Remove-PodeAuthSession Revoke-PodeSession } -function Set-PodeAuthStatus +function Get-PodeAuthFailureInfo { - param ( + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + [Parameter()] - [int] - $StatusCode = 0, + [hashtable] + $Info, [Parameter()] [string] - $Description, + $BaseName + ) + + # base name + if ([string]::IsNullOrEmpty($BaseName)) { + $BaseName = $Name + } + + # get auth method + $auth = $PodeContext.Server.Authentications.Methods[$Name] + + # cached failure? + if ($null -ne $auth.Cache.Failure) { + return $auth.Cache.Failure + } + + # find failure info + if ($null -eq $Info) { + $Info = @{ + Url = $auth.Failure.Url + Message = $auth.Failure.Message + } + } + + if ([string]::IsNullOrEmpty($Info.Url)) { + $Info.Url = $auth.Failure.Url + } + + if ([string]::IsNullOrEmpty($Info.Message)) { + $Info.Message = $auth.Failure.Message + } + + if ((![string]::IsNullOrEmpty($Info.Url) -and ![string]::IsNullOrEmpty($Info.Message)) -or [string]::IsNullOrEmpty($auth.Parent)) { + $PodeContext.Server.Authentications.Methods[$BaseName].Cache.Failure = $Info + return $Info + } + + return (Get-PodeAuthFailureInfo -Name $auth.Parent -Info $Info -BaseName $BaseName) +} + +function Get-PodeAuthSuccessInfo +{ + param( + [Parameter(Mandatory=$true)] + [string] + $Name, [Parameter()] [hashtable] - $Headers, + $Info, [Parameter()] - [hashtable] - $Failure, + [string] + $BaseName + ) + + # base name + if ([string]::IsNullOrEmpty($BaseName)) { + $BaseName = $Name + } + + # get auth method + $auth = $PodeContext.Server.Authentications.Methods[$Name] + + # cached success? + if ($null -ne $auth.Cache.Success) { + return $auth.Cache.Success + } + + # find success info + if ($null -eq $Info) { + $Info = @{ + Url = $auth.Success.Url + UseOrigin = $auth.Success.UseOrigin + } + } + + if ([string]::IsNullOrEmpty($Info.Url)) { + $Info.Url = $auth.Success.Url + } + + if (!$Info.UseOrigin) { + $Info.UseOrigin = $auth.Success.UseOrigin + } + + if ((![string]::IsNullOrEmpty($Info.Url) -and $Info.UseOrigin) -or [string]::IsNullOrEmpty($auth.Parent)) { + $PodeContext.Server.Authentications.Methods[$BaseName].Cache.Success = $Info + return $Info + } + + return (Get-PodeAuthSuccessInfo -Name $auth.Parent -Info $Info -BaseName $BaseName) +} + +function Set-PodeAuthStatus +{ + param( + [Parameter(Mandatory=$true)] + [string] + $Name, [Parameter()] - [hashtable] - $Success, + [int] + $StatusCode = 0, [Parameter()] - [string[]] - $Access, + [string] + $Description, - [switch] - $LoginRoute, + [Parameter()] + [hashtable] + $Headers, [switch] - $Sessionless, + $LoginRoute, [switch] $NoSuccessRedirect, @@ -1339,28 +1629,40 @@ function Set-PodeAuthStatus # if we have any headers, set them if (($null -ne $Headers) -and ($Headers.Count -gt 0)) { - foreach ($name in $Headers.Keys) { - Set-PodeHeader -Name $name -Value $Headers[$name] + foreach ($key in $Headers.Keys) { + Set-PodeHeader -Name $key -Value $Headers[$key] } } + # get auth method + $auth = $PodeContext.Server.Authentications.Methods[$Name] + + # cookie redirect name + $redirectCookie = 'pode.redirecturl' + + # get Success object from auth + $success = Get-PodeAuthSuccessInfo -Name $Name + # if a statuscode supplied, assume failure if ($StatusCode -gt 0) { + # get Failure object from auth + $failure = Get-PodeAuthFailureInfo -Name $Name + # override description with the failureMessage if supplied - $Description = (Protect-PodeValue -Value $Failure.Message -Default $Description) + $Description = (Protect-PodeValue -Value $failure.Message -Default $Description) # add error to flash - if ($LoginRoute -and !$Sessionless -and ![string]::IsNullOrWhiteSpace($Description)) { + if ($LoginRoute -and !$auth.Sessionless -and ![string]::IsNullOrWhiteSpace($Description)) { Add-PodeFlashMessage -Name 'auth-error' -Message $Description } # check if we have a failure url redirect - if (!$NoFailureRedirect -and ![string]::IsNullOrWhiteSpace($Failure.Url)) { - if ($Success.UseOrigin -and ($WebEvent.Method -ieq 'get')) { - $null = Set-PodeCookie -Name 'pode.redirecturl' -Value $WebEvent.Request.Url.PathAndQuery + if (!$NoFailureRedirect -and ![string]::IsNullOrWhiteSpace($failure.Url)) { + if ($success.UseOrigin -and ($WebEvent.Method -ieq 'get')) { + $null = Set-PodeCookie -Name $redirectCookie -Value $WebEvent.Request.Url.PathAndQuery } - Move-PodeResponseUrl -Url $Failure.Url + Move-PodeResponseUrl -Url $failure.Url } else { Set-PodeResponseStatus -Code $StatusCode -Description $Description @@ -1370,11 +1672,12 @@ function Set-PodeAuthStatus } # if no statuscode, success, so check if we have a success url redirect (but only for auto-login routes) - if ((!$NoSuccessRedirect -or $LoginRoute) -and ![string]::IsNullOrWhiteSpace($Success.Url)) { - $url = $Success.Url - if ($Success.UseOrigin) { - $tmpUrl = Get-PodeCookieValue -Name 'pode.redirecturl' - Remove-PodeCookie -Name 'pode.redirecturl' + if ((!$NoSuccessRedirect -or $LoginRoute) -and ![string]::IsNullOrWhiteSpace($success.Url)) { + $url = $success.Url + + if ($success.UseOrigin) { + $tmpUrl = Get-PodeCookieValue -Name $redirectCookie + Remove-PodeCookie -Name $redirectCookie if (![string]::IsNullOrWhiteSpace($tmpUrl)) { $url = $tmpUrl @@ -1385,23 +1688,6 @@ function Set-PodeAuthStatus return $false } - # check any access for authorization - if (($null -eq $Access) -or ($Access.Length -eq 0)) { - return $true - } - - foreach ($acc in $Access) { - if (!(Test-PodeAuthAccessRoute -Name $acc)) { - return (Set-PodeAuthStatus ` - -StatusCode 403 ` - -Description $Description ` - -Failure $Failure ` - -LoginRoute:$LoginRoute ` - -Sessionless:$Sessionless ` - -NoFailureRedirect:$NoFailureRedirect) - } - } - return $true } diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index a3cc2f172..aa36ddc65 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -188,7 +188,7 @@ function New-PodeAuthScheme [Parameter(ParameterSetName='Custom')] [scriptblock] - $PostValidator, + $PostValidator = $null, [Parameter(ParameterSetName='Digest')] [switch] @@ -489,7 +489,7 @@ function New-PodeAuthScheme 'custom' { $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState - if (!(Test-PodeIsEmpty $PostValidator)) { + if ($null -ne $PostValidator) { $PostValidator, $usingPostVars = Convert-PodeScopedVariables -ScriptBlock $PostValidator -PSSession $PSCmdlet.SessionState } @@ -784,15 +784,18 @@ function Add-PodeAuth ScriptBlock = $ScriptBlock UsingVariables = $usingVars Arguments = $ArgumentList - Sessionless = $Sessionless + Sessionless = $Sessionless.IsPresent Failure = @{ Url = $FailureUrl Message = $FailureMessage } Success = @{ Url = $SuccessUrl - UseOrigin = $SuccessUseOrigin + UseOrigin = $SuccessUseOrigin.IsPresent } + Cache = @{} + Merged = $false + Parent = $null } # if the scheme is oauth2, and there's no redirect, set up a default one @@ -803,6 +806,151 @@ function Add-PodeAuth } } +function Merge-PodeAuth +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter(Mandatory=$true)] + [Alias('Auth')] + [string[]] + $Authentication, + + [Parameter()] + [ValidateSet('One', 'All')] + [string] + $PassCount = 'One', + + [Parameter()] + [string] + $Default, + + [Parameter()] + [string[]] + $Access, + + [Parameter()] + [string] + $FailureUrl, + + [Parameter()] + [string] + $FailureMessage, + + [Parameter()] + [string] + $SuccessUrl, + + [switch] + $Sessionless, + + [switch] + $SuccessUseOrigin + ) + + # ensure the name doesn't already exist + if (Test-PodeAuth -Name $Name) { + throw "Authentication method already defined: $($Name)" + } + + # ensure all the auth methods exist + foreach ($authName in $Authentication) { + if (!(Test-PodeAuth -Name $authName)) { + throw "Authentication method does not exist for merging: $($authName)" + } + } + + # ensure the default is in the auth list + if (![string]::IsNullOrEmpty($Default) -and ($Default -inotin @($Authentication))) { + throw "the Default Authentication '$($Default)' is not in the Authentication list supplied" + } + + # ensure the Access methods exists + if (!(Test-PodeIsEmpty $Access)) { + foreach ($acc in $Access) { + if (!(Test-PodeAuthAccessExists -Name $acc)) { + throw "Access method not found: $($acc)" + } + } + } + + # if we're using sessions, ensure sessions have been setup + if (!$Sessionless -and !(Test-PodeSessionsConfigured)) { + throw 'Sessions are required to use session persistent authentication' + } + + # set default + if ([string]::IsNullOrEmpty($Default)) { + $Default = $Authentication[0] + } + + # get auth for default + $tmpAuth = $PodeContext.Server.Authentications.Methods[$Default] + + # check sessionless from default + if (!$Sessionless) { + $Sessionless = $tmpAuth.Sessionless + } + + # check access from default + if (Test-PodeIsEmpty $Access) { + $Access = $tmpAuth.Access + } + + # check failure url from default + if ([string]::IsNullOrEmpty($FailureUrl)) { + $FailureUrl = $tmpAuth.Failure.Url + } + + # check failure message from default + if ([string]::IsNullOrEmpty($FailureMessage)) { + $FailureMessage = $tmpAuth.Failure.Message + } + + # check success url from default + if ([string]::IsNullOrEmpty($SuccessUrl)) { + $SuccessUrl = $tmpAuth.Success.Url + } + + # check success use origin from default + if (!$SuccessUseOrigin) { + $SuccessUseOrigin = $tmpAuth.Success.UseOrigin + } + + # set parent auth + foreach ($authName in $Authentication) { + $PodeContext.Server.Authentications.Methods[$authName].Parent = $Name + } + + # add auth method to server + $PodeContext.Server.Authentications.Methods[$Name] = @{ + Name = $Name + Authentications = @($Authentication) + PassOne = ($PassCount -ieq 'one') + Default = $Default + Access = $Access + Sessionless = $Sessionless.IsPresent + Failure = @{ + Url = $FailureUrl + Message = $FailureMessage + } + Success = @{ + Url = $SuccessUrl + UseOrigin = $SuccessUseOrigin.IsPresent + } + Cache = @{} + Merged = $true + Parent = $null + } +} + +#TODO: +# Update-PodeAuth - clear cache +# Update-PodeAuthAccess + <# .SYNOPSIS Gets an Authentication method. @@ -2080,7 +2228,7 @@ function Add-PodeAuthAccess } } - # return access object + # add access object $PodeContext.Server.Authentications.Access[$Name] = @{ Name = $Name Type = $Type @@ -2090,6 +2238,47 @@ function Add-PodeAuthAccess Arguments = $ArgumentList Path = $Path Match = $Match.ToLowerInvariant() + Merged = $false + } +} + +#TODO: +function Merge-PodeAuthAccess +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter(Mandatory=$true)] + [string[]] + $Access, + + [Parameter()] + [ValidateSet('One', 'All')] + [string] + $Succeed = 'One' + ) + + # ensure the name doesn't already exist + if (Test-PodeAuthAccessExists -Name $Name) { + throw "Access method already defined: $($Name)" + } + + # ensure all the auth methods exist + foreach ($accName in $Access) { + if (Test-PodeAuthAccessExists -Name $accName) { + throw "Access method does not exist for merging: $($accName)" + } + } + + # add auth method to server + $PodeContext.Server.Authentications.Access[$Name] = @{ + Name = $Name + Access = @($Access) + Succeed = $Succeed + Merged = $true } } @@ -2347,15 +2536,30 @@ function Test-PodeAuthAccessUser [Parameter(Mandatory=$true)] [object[]] - $Value + $Value, + + [Parameter()] + [string] + $Authentication ) + # check if an auth name was passed for mutliple auths + if ($WebEvent.Auth.Multiple -and [string]::IsNullOrEmpty($Authentication)) { + throw "No Authentication name supplied to select User for Access testing, when mutliple authentications were used" + } + # get the access method $access = $PodeContext.Server.Authentications.Access[$Name] + # get the user + $user = $WebEvent.Auth.User + if ($WebEvent.Auth.Multiple) { + $user = $user[$Authentication] + } + # if there's no scriptblock, try the Path fallback if ($null -eq $access.Scriptblock) { - $userAccess = $WebEvent.Auth.User + $userAccess = $user foreach ($atom in $access.Path.Split('.')) { $userAccess = $userAccess.($atom) } @@ -2363,13 +2567,22 @@ function Test-PodeAuthAccessUser # otherwise, invoke scriptblock else { - $_args = @($WebEvent.Auth.User) + @($access.Arguments) + $_args = @($user) + @($access.Arguments) $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Scriptblock.UsingVariables) $userAccess = Invoke-PodeScriptBlock -ScriptBlock $access.Scriptblock.Script -Arguments $_args -Return -Splat } # set access values on auth object - $WebEvent.Auth.Access[$Name] = $userAccess + if ($WebEvent.Auth.Multiple) { + if (!$WebEvent.Auth.Access.ContainsKey($Authentication)) { + $WebEvent.Auth.Access[$Authentication] = @{} + } + + $WebEvent.Auth.Access[$Name] = $userAccess + } + else { + $WebEvent.Auth.Access[$Name] = $userAccess + } # is the user authorised? $WebEvent.Auth.IsAuthorised = Test-PodeAuthAccess -Name $Name -Source $userAccess -Destination $Value @@ -2411,5 +2624,5 @@ function Test-PodeAuthAccessRoute } # now test the user's access against the route's access - return (Test-PodeAuthAccessUser -Name $Name -Value $routeAccess) + return (Test-PodeAuthAccessUser -Name $Name -Value $routeAccess -Authentication $WebEvent.Route.Authentication) } \ No newline at end of file From 1d8428de9b6311851ab7452bca11827b0d49a4a5 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Thu, 28 Sep 2023 21:48:11 +0100 Subject: [PATCH 30/57] #588: allows for merging of access methods as well --- .../Routes/Examples/AnonymousAccess.md | 4 + examples/web-auth-basic-access.ps1 | 10 +- src/Pode.psd1 | 5 + src/Private/Authentication.ps1 | 172 +++-------- src/Private/OpenApi.ps1 | 4 +- src/Public/Authentication.ps1 | 276 ++++++++++++++---- src/Public/Routes.ps1 | 4 +- src/Public/Utilities.ps1 | 8 + 8 files changed, 290 insertions(+), 193 deletions(-) diff --git a/docs/Tutorials/Routes/Examples/AnonymousAccess.md b/docs/Tutorials/Routes/Examples/AnonymousAccess.md index 0fc9744f7..48bfdc237 100644 --- a/docs/Tutorials/Routes/Examples/AnonymousAccess.md +++ b/docs/Tutorials/Routes/Examples/AnonymousAccess.md @@ -47,6 +47,10 @@ Add-PodeRoute -Method Get -Path '/' -Authentication 'Login' -AllowAnon -ScriptBl Now, when an authenticated user hits the page, they're shown the original personal greeting page with view counter. However, when an unauthenticated user hits the page they are shown a generic greeting with a login button. +The [`Test-PodeAuthUser`] will check both the `$WebEvent.Auth` and `$WebEvent.Session` objects for an authenticated user. You can force the function to only check the former by supplying `-IgnoreSession`. + +You can also retrieve the user object using [`Get-PodeAuthUser`]. Under most circumstances you'll be able to access the authenticated user at `$WebEvent.Auth.User`, however if you're relying on Sessions and a Route without Authentication configured then you'll ave to use this function. Similar to above, you can supply `-IgnoreSession` here as well. + ## Example Code This is the full code for the server above: diff --git a/examples/web-auth-basic-access.ps1 b/examples/web-auth-basic-access.ps1 index 9fab294a2..d6d92aecc 100644 --- a/examples/web-auth-basic-access.ps1 +++ b/examples/web-auth-basic-access.ps1 @@ -8,15 +8,15 @@ Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop This example shows how to use sessionless authentication, which will mostly be for REST APIs. The example used here is Basic authentication. -Calling the '[POST] http://localhost:8085/users' endpoint, with an Authorization +Calling the '[POST] http://localhost:8085/users-all' endpoint, with an Authorization header of 'Basic bW9ydHk6cGlja2xl' will display the uesrs. Anything else and you'll get a 401 status code back. Success: -Invoke-RestMethod -Uri http://localhost:8085/users -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' } +Invoke-RestMethod -Uri http://localhost:8085/users-all -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' } Failure: -Invoke-RestMethod -Uri http://localhost:8085/users -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cmljaw==' } +Invoke-RestMethod -Uri http://localhost:8085/users-all -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cmljaw==' } #> # create a server, and start listening on port 8085 @@ -33,8 +33,10 @@ Start-PodeServer -Threads 2 { # return $userRoles.Example -iin $customValues.Example # } + Merge-PodeAuthAccess -Name 'TestMerged' -Access 'TestRbac', 'TestGbac' -Valid All + # setup basic auth (base64> username:password in header) - New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Access 'TestRbac', 'TestGbac' -Sessionless -ScriptBlock { + New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Access 'TestMerged' -Sessionless -ScriptBlock { param($username, $password) # here you'd check a real user storage, this is just for example diff --git a/src/Pode.psd1 b/src/Pode.psd1 index 99cbb6d48..0f5d1bc04 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -116,6 +116,7 @@ 'Out-PodeVariable', 'Test-PodeIsHosted', 'New-PodeCron', + 'Test-PodeInRunspace', # routes 'Add-PodeRoute', @@ -229,6 +230,10 @@ 'Test-PodeAuthAccessUser', 'Test-PodeAuthAccessRoute', 'Merge-PodeAuth', + 'Test-PodeAuth', + 'Test-PodeAuthExists', + 'Merge-PodeAuthAccess', + 'Get-PodeAuthUser', # logging 'New-PodeLoggingMethod', diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index e32f47b0b..50690a43b 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1273,7 +1273,7 @@ function Test-PodeAuthValidationAccess # get auth method $auth = $PodeContext.Server.Authentications.Methods[$Name] - $access = @() + $access = $null # cached access? if ($null -ne $auth.Cache.Access) { @@ -1283,12 +1283,12 @@ function Test-PodeAuthValidationAccess # recursively find access else { # have access and/or parent? - $hasAccess = (($null -ne $auth.Access) -and ($auth.Access.Length -gt 0)) + $hasAccess = ![string]::IsNullOrEmpty($auth.Access) $hasParent = ![string]::IsNullOrEmpty($auth.Parent) # no access if (!$hasAccess) { - $PodeContext.Server.Authentications.Methods[$Name].Cache.Access = @() + $PodeContext.Server.Authentications.Methods[$Name].Cache.Access = [string]::Empty # skip if no parent if (!$hasParent) { @@ -1306,143 +1306,65 @@ function Test-PodeAuthValidationAccess $PodeContext.Server.Authentications.Methods[$BaseName].Cache.Access = $access } - # if we have access methods, check them - foreach ($acc in $access) { - if (!(Test-PodeAuthAccessRoute -Name $acc)) { - return $false - } - } - - # access allowed! - return $true + # check access + return ([string]::IsNullOrEmpty($access) -or (Invoke-PodeAuthValidationAccess -Name $access)) } -function Get-PodeAuthMiddlewareScript +function Invoke-PodeAuthValidationAccess { - return { - param($opts) - - # get the auth method - $auth = $PodeContext.Server.Authentications.Methods[$opts.Name] - - # check for logout command - if ($opts.Logout) { - Remove-PodeAuthSession - - if ($PodeContext.Server.Sessions.Info.UseHeaders) { - return (Set-PodeAuthStatus ` - -StatusCode 401 ` - -Name $opts.Name ` - -NoSuccessRedirect) - } - else { - $auth.Failure.Url = (Protect-PodeValue -Value $auth.Failure.Url -Default $WebEvent.Request.Url.AbsolutePath) - return (Set-PodeAuthStatus ` - -StatusCode 302 ` - -Name $opts.Name ` - -NoSuccessRedirect) - } - } + param( + [Parameter(Mandatory=$true)] + [string] + $Name + ) - # if the session already has a user/isAuth'd, then skip auth - or allow anon - if (Test-PodeSessionsInUse) { - # existing session auth'd - if (Test-PodeAuthUser) { - $WebEvent.Auth = $WebEvent.Session.Data.Auth - return (Set-PodeAuthStatus ` - -Name $opts.Name ` - -LoginRoute:($opts.Login) ` - -NoSuccessRedirect) - } + # get the access method + $access = $PodeContext.Server.Authentications.Access[$Name] - # if we're allowing anon access, and using sessions, then stop here - as a session will be created from a login route for auth'ing users - if ($opts.Anon) { - if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) { - Revoke-PodeSession - } + # if it's a merged access, re-call this function and check against "succeed" value + if ($access.Merged) { + foreach ($accName in $access.Access) { + $result = Invoke-PodeAuthValidationAccess -Name $accName + # if the access passed, and we only need one access to pass, return true + if ($result -and $access.PassOne) { return $true } - } - # check if the login flag is set, in which case just return and load a login get-page (allowing anon access) - if ($opts.Login -and !$PodeContext.Server.Sessions.Info.UseHeaders -and ($WebEvent.Method -ieq 'get')) { - if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) { - Revoke-PodeSession + # if the access failed, but we need all to pass, return false + if (!$result -and !$access.PassOne) { + return $false } - - return $true } - try { - $result = Invoke-PodeAuthValidation -Name $opts.Name - } - catch { - $_ | Write-PodeErrorLog - return (Set-PodeAuthStatus ` - -StatusCode 500 ` - -Description $_.Exception.Message ` - -Name $opts.Name) - } - - # did the auth force a redirect? - if ($result.Redirected) { + # if the last access failed, and we only need one access to pass, return false + if (!$result -and $access.PassOne) { return $false } - # if auth failed, are we allowing anon access? - if (!$result.Success -and $opts.Anon) { + # if the last access succeeded, and we need all to pass, return true + if ($result -and !$access.PassOne) { return $true } - # if auth failed, set appropriate response headers/redirects - if (!$result.Success) { - return (Set-PodeAuthStatus ` - -StatusCode $result.StatusCode ` - -Description $result.Description ` - -Headers $result.Headers ` - -Name $opts.Name ` - -LoginRoute:($opts.Login) ` - -NoFailureRedirect:($result.FailureRedirect)) - } - - # if auth passed, assign the user(s) to the session - $user = $result.User - if ($result.Aggregated) { - $user = @{} - foreach ($r in $result.Results) { - $user[$r.Auth] = $r.User - } - } + # default failure + return $false + } - $WebEvent.Auth = @{ - User = $user - IsAuthenticated = $true - Access = @{} - IsAuthorised = $true - Store = !$auth.Sessionless - Name = $result.Auth - Multiple = $result.Aggregated - } + # main access validation logic + return (Test-PodeAuthAccessRoute -Name $Name) +} - # check access - foreach ($authName in $result.Auth) { - if (!(Test-PodeAuthValidationAccess -Name $authName)) { - return (Set-PodeAuthStatus ` - -StatusCode 403 ` - -Description $result.Description ` - -Headers $result.Headers ` - -Name $authName ` - -LoginRoute:($opts.Login) ` - -NoFailureRedirect:($result.FailureRedirect)) - } - } +function Get-PodeAuthMiddlewareScript +{ + return { + param($opts) - # successful auth - return (Set-PodeAuthStatus ` - -Headers $result.Headers ` - -Name @($result.Auth)[0] ` - -LoginRoute:($opts.Login)) + return (Test-PodeAuth ` + -Name $opts.Name ` + -Login:($opts.Login) ` + -Logout:($opts.Logout) ` + -AllowAnon:($opts.Anon)) } } @@ -2192,18 +2114,6 @@ function Find-PodeAuth return $PodeContext.Server.Authentications.Methods[$Name] } -function Test-PodeAuth -{ - param( - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [string] - $Name - ) - - return $PodeContext.Server.Authentications.Methods.ContainsKey($Name) -} - function Import-PodeAuthADModule { if (!(Test-PodeIsWindows)) { diff --git a/src/Private/OpenApi.ps1 b/src/Private/OpenApi.ps1 index 77324f494..68b4d7980 100644 --- a/src/Private/OpenApi.ps1 +++ b/src/Private/OpenApi.ps1 @@ -453,7 +453,7 @@ function Set-PodeOAAuth ) foreach ($n in @($Name)) { - if (!(Test-PodeAuth -Name $n)) { + if (!(Test-PodeAuthExists -Name $n)) { throw "Authentication method does not exist: $($n)" } } @@ -479,7 +479,7 @@ function Set-PodeOAGlobalAuth $Route ) - if (!(Test-PodeAuth -Name $Name)) { + if (!(Test-PodeAuthExists -Name $Name)) { throw "Authentication method does not exist: $($Name)" } diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index aa36ddc65..99df18543 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -702,7 +702,7 @@ New-PodeAuthScheme -Form | Add-PodeAuth -Name 'Main' -ScriptBlock { /* logic */ function Add-PodeAuth { [CmdletBinding()] - param ( + param( [Parameter(Mandatory=$true)] [string] $Name, @@ -712,7 +712,7 @@ function Add-PodeAuth $Scheme, [Parameter()] - [string[]] + [string] $Access, [Parameter(Mandatory=$true)] @@ -750,7 +750,7 @@ function Add-PodeAuth ) # ensure the name doesn't already exist - if (Test-PodeAuth -Name $Name) { + if (Test-PodeAuthExists -Name $Name) { throw "Authentication method already defined: $($Name)" } @@ -759,13 +759,9 @@ function Add-PodeAuth throw "The supplied '$($Scheme.Name)' Scheme for the '$($Name)' authentication validator requires a valid ScriptBlock" } - # ensure the Access methods exists - if (!(Test-PodeIsEmpty $Access)) { - foreach ($acc in $Access) { - if (!(Test-PodeAuthAccessExists -Name $acc)) { - throw "Access method not found: $($acc)" - } - } + # ensure the Access method exists + if (!(Test-PodeIsEmpty $Access) -and !(Test-PodeAuthAccessExists -Name $Access)) { + throw "Access method not found: $($Access)" } # if we're using sessions, ensure sessions have been setup @@ -822,14 +818,14 @@ function Merge-PodeAuth [Parameter()] [ValidateSet('One', 'All')] [string] - $PassCount = 'One', + $Valid = 'One', [Parameter()] [string] $Default, [Parameter()] - [string[]] + [string] $Access, [Parameter()] @@ -852,13 +848,13 @@ function Merge-PodeAuth ) # ensure the name doesn't already exist - if (Test-PodeAuth -Name $Name) { + if (Test-PodeAuthExists -Name $Name) { throw "Authentication method already defined: $($Name)" } # ensure all the auth methods exist foreach ($authName in $Authentication) { - if (!(Test-PodeAuth -Name $authName)) { + if (!(Test-PodeAuthExists -Name $authName)) { throw "Authentication method does not exist for merging: $($authName)" } } @@ -869,12 +865,8 @@ function Merge-PodeAuth } # ensure the Access methods exists - if (!(Test-PodeIsEmpty $Access)) { - foreach ($acc in $Access) { - if (!(Test-PodeAuthAccessExists -Name $acc)) { - throw "Access method not found: $($acc)" - } - } + if (!(Test-PodeIsEmpty $Access) -and !(Test-PodeAuthAccessExists -Name $Access)) { + throw "Access method not found: $($Access)" } # if we're using sessions, ensure sessions have been setup @@ -929,7 +921,7 @@ function Merge-PodeAuth $PodeContext.Server.Authentications.Methods[$Name] = @{ Name = $Name Authentications = @($Authentication) - PassOne = ($PassCount -ieq 'one') + PassOne = ($Valid -ieq 'one') Default = $Default Access = $Access Sessionless = $Sessionless.IsPresent @@ -947,10 +939,6 @@ function Merge-PodeAuth } } -#TODO: -# Update-PodeAuth - clear cache -# Update-PodeAuthAccess - <# .SYNOPSIS Gets an Authentication method. @@ -974,7 +962,7 @@ function Get-PodeAuth ) # ensure the name exists - if (!(Test-PodeAuth -Name $Name)) { + if (!(Test-PodeAuthExists -Name $Name)) { throw "Authentication method not defined: $($Name)" } @@ -982,6 +970,160 @@ function Get-PodeAuth return $PodeContext.Server.Authentications.Methods[$Name] } +function Test-PodeAuthExists +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name + ) + + return $PodeContext.Server.Authentications.Methods.ContainsKey($Name) +} + +function Test-PodeAuth +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [switch] + $Login, + + [switch] + $Logout, + + [switch] + $AllowAnon + ) + + # get the auth method + $auth = $PodeContext.Server.Authentications.Methods[$Name] + + # check for logout command + if ($Logout) { + Remove-PodeAuthSession + + if ($PodeContext.Server.Sessions.Info.UseHeaders) { + return (Set-PodeAuthStatus ` + -StatusCode 401 ` + -Name $Name ` + -NoSuccessRedirect) + } + else { + $auth.Failure.Url = (Protect-PodeValue -Value $auth.Failure.Url -Default $WebEvent.Request.Url.AbsolutePath) + return (Set-PodeAuthStatus ` + -StatusCode 302 ` + -Name $Name ` + -NoSuccessRedirect) + } + } + + # if the session already has a user/isAuth'd, then skip auth - or allow anon + if (Test-PodeSessionsInUse) { + # existing session auth'd + if (Test-PodeAuthUser) { + $WebEvent.Auth = $WebEvent.Session.Data.Auth + return (Set-PodeAuthStatus ` + -Name $Name ` + -LoginRoute:($Login) ` + -NoSuccessRedirect) + } + + # if we're allowing anon access, and using sessions, then stop here - as a session will be created from a login route for auth'ing users + if ($AllowAnon) { + if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) { + Revoke-PodeSession + } + + return $true + } + } + + # check if the login flag is set, in which case just return and load a login get-page (allowing anon access) + if ($Login -and !$PodeContext.Server.Sessions.Info.UseHeaders -and ($WebEvent.Method -ieq 'get')) { + if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) { + Revoke-PodeSession + } + + return $true + } + + try { + $result = Invoke-PodeAuthValidation -Name $Name + } + catch { + $_ | Write-PodeErrorLog + return (Set-PodeAuthStatus ` + -StatusCode 500 ` + -Description $_.Exception.Message ` + -Name $Name) + } + + # did the auth force a redirect? + if ($result.Redirected) { + return $false + } + + # if auth failed, are we allowing anon access? + if (!$result.Success -and $AllowAnon) { + return $true + } + + # if auth failed, set appropriate response headers/redirects + if (!$result.Success) { + return (Set-PodeAuthStatus ` + -StatusCode $result.StatusCode ` + -Description $result.Description ` + -Headers $result.Headers ` + -Name $Name ` + -LoginRoute:$Login ` + -NoFailureRedirect:($result.FailureRedirect)) + } + + # if auth passed, assign the user(s) to the session + $user = $result.User + if ($result.Aggregated) { + $user = [ordered]@{} + foreach ($r in $result.Results) { + $user[$r.Auth] = $r.User + } + } + + $WebEvent.Auth = [ordered]@{ + User = $user + IsAuthenticated = $true + IsAuthorised = $true + Store = !$auth.Sessionless + Name = $result.Auth + Multiple = [bool]$result.Aggregated + } + + # check access + foreach ($authName in $result.Auth) { + $WebEvent.Auth.IsAuthorised = Test-PodeAuthValidationAccess -Name $authName + + if (!$WebEvent.Auth.IsAuthorised) { + return (Set-PodeAuthStatus ` + -StatusCode 403 ` + -Description $result.Description ` + -Headers $result.Headers ` + -Name $authName ` + -LoginRoute:$Login ` + -NoFailureRedirect:($result.FailureRedirect)) + } + } + + # successful auth + return (Set-PodeAuthStatus ` + -Headers $result.Headers ` + -Name @($result.Auth)[0] ` + -LoginRoute:$Login) +} + <# .SYNOPSIS Adds the inbuilt Windows AD Authentication method for verifying users. @@ -1129,7 +1271,7 @@ function Add-PodeAuthWindowsAd ) # ensure the name doesn't already exist - if (Test-PodeAuth -Name $Name) { + if (Test-PodeAuthExists -Name $Name) { throw "Windows AD Authentication method already defined: $($Name)" } @@ -1281,7 +1423,7 @@ function Add-PodeAuthMiddleware $Route ) - if (!(Test-PodeAuth -Name $Authentication)) { + if (!(Test-PodeAuthExists -Name $Authentication)) { throw "Authentication method does not exist: $($Authentication)" } @@ -1413,7 +1555,7 @@ function Add-PodeAuthIIS } # ensure the name doesn't already exist - if (Test-PodeAuth -Name $Name) { + if (Test-PodeAuthExists -Name $Name) { throw "IIS Authentication method already defined: $($Name)" } @@ -1572,7 +1714,7 @@ function Add-PodeAuthUserFile ) # ensure the name doesn't already exist - if (Test-PodeAuth -Name $Name) { + if (Test-PodeAuthExists -Name $Name) { throw "User File Authentication method already defined: $($Name)" } @@ -1732,7 +1874,7 @@ function Add-PodeAuthWindowsLocal } # ensure the name doesn't already exist - if (Test-PodeAuth -Name $Name) { + if (Test-PodeAuthExists -Name $Name) { throw "Windows Local Authentication method already defined: $($Name)" } @@ -2098,12 +2240,43 @@ if (Test-PodeAuthUser) { ... } function Test-PodeAuthUser { [CmdletBinding()] - param() + param( + [switch] + $IgnoreSession + ) - return ( - (($null -ne $WebEvent.Auth.User) -and $WebEvent.Auth.IsAuthenticated) -or - (($null -ne $WebEvent.Session.Data.Auth.User) -and $WebEvent.Session.Data.Auth.IsAuthenticated) + # auth middleware + if (($null -ne $WebEvent.Auth.User) -and $WebEvent.Auth.IsAuthenticated) { + return $true + } + + # session? + if (!$IgnoreSession -and ($null -ne $WebEvent.Session.Data.Auth.User) -and $WebEvent.Session.Data.Auth.IsAuthenticated) { + return $true + } + + return $false +} + +function Get-PodeAuthUser +{ + [CmdletBinding()] + param( + [switch] + $IgnoreSession ) + + # auth middleware + if (($null -ne $WebEvent.Auth.User) -and $WebEvent.Auth.IsAuthenticated) { + return $WebEvent.Auth.User + } + + # session? + if (!$IgnoreSession -and ($null -ne $WebEvent.Session.Data.Auth.User) -and $WebEvent.Session.Data.Auth.IsAuthenticated) { + return $WebEvent.Session.Data.Auth.User + } + + return $null } <# @@ -2238,11 +2411,12 @@ function Add-PodeAuthAccess Arguments = $ArgumentList Path = $Path Match = $Match.ToLowerInvariant() + Cache = @{} Merged = $false + Parent = $null } } -#TODO: function Merge-PodeAuthAccess { [CmdletBinding()] @@ -2258,7 +2432,7 @@ function Merge-PodeAuthAccess [Parameter()] [ValidateSet('One', 'All')] [string] - $Succeed = 'One' + $Valid = 'One' ) # ensure the name doesn't already exist @@ -2266,19 +2440,26 @@ function Merge-PodeAuthAccess throw "Access method already defined: $($Name)" } - # ensure all the auth methods exist + # ensure all the access methods exist foreach ($accName in $Access) { - if (Test-PodeAuthAccessExists -Name $accName) { + if (!(Test-PodeAuthAccessExists -Name $accName)) { throw "Access method does not exist for merging: $($accName)" } } + # set parent access + foreach ($accName in $Access) { + $PodeContext.Server.Authentications.Access[$accName].Parent = $Name + } + # add auth method to server $PodeContext.Server.Authentications.Access[$Name] = @{ Name = $Name Access = @($Access) - Succeed = $Succeed + PassOne = ($Valid -ieq 'one') + Cache = @{} Merged = $true + Parent = $null } } @@ -2572,21 +2753,8 @@ function Test-PodeAuthAccessUser $userAccess = Invoke-PodeScriptBlock -ScriptBlock $access.Scriptblock.Script -Arguments $_args -Return -Splat } - # set access values on auth object - if ($WebEvent.Auth.Multiple) { - if (!$WebEvent.Auth.Access.ContainsKey($Authentication)) { - $WebEvent.Auth.Access[$Authentication] = @{} - } - - $WebEvent.Auth.Access[$Name] = $userAccess - } - else { - $WebEvent.Auth.Access[$Name] = $userAccess - } - # is the user authorised? - $WebEvent.Auth.IsAuthorised = Test-PodeAuthAccess -Name $Name -Source $userAccess -Destination $Value - return $WebEvent.Auth.IsAuthorised + return (Test-PodeAuthAccess -Name $Name -Source $userAccess -Destination $Value) } <# diff --git a/src/Public/Routes.ps1 b/src/Public/Routes.ps1 index 17f7e2d56..cfb4f38bd 100644 --- a/src/Public/Routes.ps1 +++ b/src/Public/Routes.ps1 @@ -277,7 +277,7 @@ function Add-PodeRoute # if an auth name was supplied, setup the auth as the first middleware if (![string]::IsNullOrWhiteSpace($Authentication)) { - if (!(Test-PodeAuth -Name $Authentication)) { + if (!(Test-PodeAuthExists -Name $Authentication)) { throw "Authentication method does not exist: $($Authentication)" } @@ -619,7 +619,7 @@ function Add-PodeStaticRoute # if an auth name was supplied, setup the auth as the first middleware if (![string]::IsNullOrWhiteSpace($Authentication)) { - if (!(Test-PodeAuth -Name $Authentication)) { + if (!(Test-PodeAuthExists -Name $Authentication)) { throw "Authentication method does not exist: $($Authentication)" } diff --git a/src/Public/Utilities.ps1 b/src/Public/Utilities.ps1 index c40e52b63..cfd4e2d91 100644 --- a/src/Public/Utilities.ps1 +++ b/src/Public/Utilities.ps1 @@ -686,6 +686,14 @@ function Test-PodeIsMacOS return ([bool]$IsMacOS) } +function Test-PodeInRunspace +{ + [CmdletBinding()] + param() + + return ([bool]$PODE_SCOPE_RUNSPACE) +} + <# .SYNOPSIS Outputs an object to the main Host. From e1b601f86c35220fd26077d304717a0a432ebb0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alan=20P=C5=82=C3=B3cieniak?= Date: Mon, 2 Oct 2023 17:16:17 +0200 Subject: [PATCH 31/57] #1142 | Make the Test-PodeJwt function Public --- src/Pode.psd1 | 1 + src/Private/Cryptography.ps1 | 26 ---------------- src/Public/Authentication.ps1 | 46 ++++++++++++++++++++++++++++- tests/unit/Authentication.Tests.ps1 | 16 ++++++++++ 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/Pode.psd1 b/src/Pode.psd1 index 6fdabf613..369adece5 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -218,6 +218,7 @@ 'Add-PodeAuthUserFile', 'ConvertTo-PodeJwt', 'ConvertFrom-PodeJwt', + 'Test-PodeJwt' 'Use-PodeAuth', 'ConvertFrom-PodeOIDCDiscovery', 'Test-PodeAuthUser', diff --git a/src/Private/Cryptography.ps1 b/src/Private/Cryptography.ps1 index 17ef90712..decbad9d8 100644 --- a/src/Private/Cryptography.ps1 +++ b/src/Private/Cryptography.ps1 @@ -253,32 +253,6 @@ function Invoke-PodeValueUnsign return $raw } -function Test-PodeJwt -{ - param( - [Parameter(Mandatory=$true)] - [pscustomobject] - $Payload - ) - - $now = [datetime]::UtcNow - $unixStart = [datetime]::new(1970, 1, 1, 0, 0, [DateTimeKind]::Utc) - - # validate expiry - if (![string]::IsNullOrWhiteSpace($Payload.exp)) { - if ($now -gt $unixStart.AddSeconds($Payload.exp)) { - throw "The JWT has expired" - } - } - - # validate not-before - if (![string]::IsNullOrWhiteSpace($Payload.nbf)) { - if ($now -lt $unixStart.AddSeconds($Payload.nbf)) { - throw "The JWT is not yet valid for use" - } - } -} - function New-PodeJwtSignature { param( diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index a3cc2f172..c96c64652 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -1787,6 +1787,50 @@ function ConvertFrom-PodeJwt return $payload } +<# +.SYNOPSIS +Validates JSON Web Tokens (JWT) claims. + +.DESCRIPTION +Validates JSON Web Tokens (JWT) claims. Checks time related claims: 'exp' and 'nbf'. + +.PARAMETER Payload +Object containing JWT claims. Some of them are: + - exp (expiration time) + - nbf (not before) + +.EXAMPLE +Test-PodeJwt @{exp = 2696258821 } + +.EXAMPLE +Test-PodeJwt -Payload @{nbf = 1696258821 } +#> +function Test-PodeJwt { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [pscustomobject] + $Payload + ) + + $now = [datetime]::UtcNow + $unixStart = [datetime]::new(1970, 1, 1, 0, 0, [DateTimeKind]::Utc) + + # validate expiry + if (![string]::IsNullOrWhiteSpace($Payload.exp)) { + if ($now -gt $unixStart.AddSeconds($Payload.exp)) { + throw "The JWT has expired" + } + } + + # validate not-before + if (![string]::IsNullOrWhiteSpace($Payload.nbf)) { + if ($now -lt $unixStart.AddSeconds($Payload.nbf)) { + throw "The JWT is not yet valid for use" + } + } +} + <# .SYNOPSIS Automatically loads auth ps1 files @@ -2301,7 +2345,7 @@ function Test-PodeAuthAccess return $false } } - + return $true } diff --git a/tests/unit/Authentication.Tests.ps1 b/tests/unit/Authentication.Tests.ps1 index 31d891aac..c180d1480 100644 --- a/tests/unit/Authentication.Tests.ps1 +++ b/tests/unit/Authentication.Tests.ps1 @@ -110,4 +110,20 @@ Describe 'Remove-PodeAuthSession' { Assert-MockCalled Revoke-PodeSession -Times 1 -Scope It } +} + +Describe 'Test-PodeJwt' { + It 'No exception - sucessful validation' { + (Test-PodeJwt @{}) | Should Be $null + } + + It 'Throws exception - the JWT has expired' { + # "exp" (Expiration Time) Claim + { Test-PodeJwt @{exp = 1 } } | Should -Throw -ExceptionType ([System.Exception]) -ExpectedMessage 'The JWT has expired' + } + + It 'Throws exception - the JWT is not yet valid for use' { + # "nbf" (Not Before) Claim + { Test-PodeJwt @{nbf = 99999999999 } } | Should -Throw -ExceptionType ([System.Exception]) -ExpectedMessage 'The JWT is not yet valid for use' + } } \ No newline at end of file From c3683e5d73b4a6ca3fd6a9bcdce45359ba97a3e8 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Mon, 2 Oct 2023 22:34:01 +0100 Subject: [PATCH 32/57] #588: add function summaries --- src/Private/Authentication.ps1 | 144 +++++++++++++++- src/Public/Authentication.ps1 | 251 ++++++++++++++++------------ tests/unit/Authentication.Tests.ps1 | 26 ++- 3 files changed, 310 insertions(+), 111 deletions(-) diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index 50690a43b..509cfe414 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1360,7 +1360,7 @@ function Get-PodeAuthMiddlewareScript return { param($opts) - return (Test-PodeAuth ` + return (Test-PodeAuthInternal ` -Name $opts.Name ` -Login:($opts.Login) ` -Logout:($opts.Logout) ` @@ -1368,6 +1368,148 @@ function Get-PodeAuthMiddlewareScript } } +function Test-PodeAuthInternal +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [switch] + $Login, + + [switch] + $Logout, + + [switch] + $AllowAnon + ) + + # get the auth method + $auth = $PodeContext.Server.Authentications.Methods[$Name] + + # check for logout command + if ($Logout) { + Remove-PodeAuthSession + + if ($PodeContext.Server.Sessions.Info.UseHeaders) { + return (Set-PodeAuthStatus ` + -StatusCode 401 ` + -Name $Name ` + -NoSuccessRedirect) + } + else { + $auth.Failure.Url = (Protect-PodeValue -Value $auth.Failure.Url -Default $WebEvent.Request.Url.AbsolutePath) + return (Set-PodeAuthStatus ` + -StatusCode 302 ` + -Name $Name ` + -NoSuccessRedirect) + } + } + + # if the session already has a user/isAuth'd, then skip auth - or allow anon + if (Test-PodeSessionsInUse) { + # existing session auth'd + if (Test-PodeAuthUser) { + $WebEvent.Auth = $WebEvent.Session.Data.Auth + return (Set-PodeAuthStatus ` + -Name $Name ` + -LoginRoute:($Login) ` + -NoSuccessRedirect) + } + + # if we're allowing anon access, and using sessions, then stop here - as a session will be created from a login route for auth'ing users + if ($AllowAnon) { + if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) { + Revoke-PodeSession + } + + return $true + } + } + + # check if the login flag is set, in which case just return and load a login get-page (allowing anon access) + if ($Login -and !$PodeContext.Server.Sessions.Info.UseHeaders -and ($WebEvent.Method -ieq 'get')) { + if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) { + Revoke-PodeSession + } + + return $true + } + + try { + $result = Invoke-PodeAuthValidation -Name $Name + } + catch { + $_ | Write-PodeErrorLog + return (Set-PodeAuthStatus ` + -StatusCode 500 ` + -Description $_.Exception.Message ` + -Name $Name) + } + + # did the auth force a redirect? + if ($result.Redirected) { + return $false + } + + # if auth failed, are we allowing anon access? + if (!$result.Success -and $AllowAnon) { + return $true + } + + # if auth failed, set appropriate response headers/redirects + if (!$result.Success) { + return (Set-PodeAuthStatus ` + -StatusCode $result.StatusCode ` + -Description $result.Description ` + -Headers $result.Headers ` + -Name $Name ` + -LoginRoute:$Login ` + -NoFailureRedirect:($result.FailureRedirect)) + } + + # if auth passed, assign the user(s) to the session + $user = $result.User + if ($result.Aggregated) { + $user = [ordered]@{} + foreach ($r in $result.Results) { + $user[$r.Auth] = $r.User + } + } + + $WebEvent.Auth = [ordered]@{ + User = $user + IsAuthenticated = $true + IsAuthorised = $true + Store = !$auth.Sessionless + Name = $result.Auth + Multiple = [bool]$result.Aggregated + } + + # check access + foreach ($authName in $result.Auth) { + $WebEvent.Auth.IsAuthorised = Test-PodeAuthValidationAccess -Name $authName + + if (!$WebEvent.Auth.IsAuthorised) { + return (Set-PodeAuthStatus ` + -StatusCode 403 ` + -Description $result.Description ` + -Headers $result.Headers ` + -Name $authName ` + -LoginRoute:$Login ` + -NoFailureRedirect:($result.FailureRedirect)) + } + } + + # successful auth + return (Set-PodeAuthStatus ` + -Headers $result.Headers ` + -Name @($result.Auth)[0] ` + -LoginRoute:$Login) +} + function Get-PodeAuthWwwHeaderValue { param( diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index 99df18543..196b9e55d 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -673,7 +673,7 @@ A unique Name for the Authentication method. The Scheme to use for retrieving credentials (From New-PodeAuthScheme). .PARAMETER Access -One or more optional Access method Names to validate authorisation to Routes (From Add-PodeAuthAccess) +An optional Access method Name to validate authorisation to Routes (From Add-PodeAuthAccess) .PARAMETER ScriptBlock The ScriptBlock defining logic that retrieves and verifys a user. @@ -802,6 +802,57 @@ function Add-PodeAuth } } +<# +.SYNOPSIS +Lets you merge multiple Authentication methods together, into a "single" Authentication method. + +.DESCRIPTION +Lets you merge multiple Authentication methods together, into a "single" Authentication method. +You can specify if only One or All of the methods need to pass to allow access, and you can also +merge other merged Authentication methods for more advanced scenarios. + +.PARAMETER Name +A unique Name for the Authentication method. + +.PARAMETER Authentication +Multiple Autentication method Names to be merged. + +.PARAMETER Valid +How many of the Authentication methods are required to be valid, One or All. (Default: One) + +.PARAMETER Default +The Default Authentication method to use as a fallback for Failure URLs and other settings. + +.PARAMETER Access +An optional Access method Name to validate authorisation to Routes. +This will be used as fallback for the merged Authentication methods if not set on them. + +.PARAMETER FailureUrl +The URL to redirect to when authentication fails. +This will be used as fallback for the merged Authentication methods if not set on them. + +.PARAMETER FailureMessage +An override Message to throw when authentication fails. +This will be used as fallback for the merged Authentication methods if not set on them. + +.PARAMETER SuccessUrl +The URL to redirect to when authentication succeeds when logging in. +This will be used as fallback for the merged Authentication methods if not set on them. + +.PARAMETER Sessionless +If supplied, authenticated users will not be stored in sessions, and sessions will not be used. +This will be used as fallback for the merged Authentication methods if not set on them. + +.PARAMETER SuccessUseOrigin +If supplied, successful authentication from a login page will redirect back to the originating page instead of the FailureUrl. +This will be used as fallback for the merged Authentication methods if not set on them. + +.EXAMPLE +Merge-PodeAuth -Name MergedAuth -Authentication ApiTokenAuth, BasicAuth -Valid All + +.EXAMPLE +Merge-PodeAuth -Name MergedAuth -Authentication ApiTokenAuth, BasicAuth -FailureUrl 'http://localhost:8080/login' +#> function Merge-PodeAuth { [CmdletBinding()] @@ -970,6 +1021,19 @@ function Get-PodeAuth return $PodeContext.Server.Authentications.Methods[$Name] } +<# +.SYNOPSIS +Test if an Authentication method exists. + +.DESCRIPTION +Test if an Authentication method exists. + +.PARAMETER Name +The Name of the Authentication method. + +.EXAMPLE +if (Test-PodeAuthExists -Name BasicAuth) { ... } +#> function Test-PodeAuthExists { [CmdletBinding()] @@ -982,6 +1046,32 @@ function Test-PodeAuthExists return $PodeContext.Server.Authentications.Methods.ContainsKey($Name) } +<# +.SYNOPSIS +Test and invoke an Authentication method to verify a user. + +.DESCRIPTION +Test and invoke an Authentication method to verify a user. This will verify a user's credentials on the request. +When testing OAuth2 methods, the first attempt will trigger a redirect to the provider and $false will be returned. + +.PARAMETER Name +The Name of the Authentication method. + +.PARAMETER IgnoreSession +If supplied, authentication will be re-verified on each call even if a valid session exists on the request. + +.PARAMETER CheckAccess +If supplied, an Authentication method's Access method will also be verified. + +.EXAMPLE +if (Test-PodeAuth -Name 'BasicAuth') { ... } + +.EXAMPLE +if (Test-PodeAuth -Name 'FormAuth' -IgnoreSession) { ... } + +.EXAMPLE +if (Test-PodeAuth -Name 'BasicAuth' -CheckAccess) { ... } +#> function Test-PodeAuth { [CmdletBinding()] @@ -991,65 +1081,15 @@ function Test-PodeAuth $Name, [switch] - $Login, - - [switch] - $Logout, + $IgnoreSession, [switch] - $AllowAnon + $CheckAccess ) - # get the auth method - $auth = $PodeContext.Server.Authentications.Methods[$Name] - - # check for logout command - if ($Logout) { - Remove-PodeAuthSession - - if ($PodeContext.Server.Sessions.Info.UseHeaders) { - return (Set-PodeAuthStatus ` - -StatusCode 401 ` - -Name $Name ` - -NoSuccessRedirect) - } - else { - $auth.Failure.Url = (Protect-PodeValue -Value $auth.Failure.Url -Default $WebEvent.Request.Url.AbsolutePath) - return (Set-PodeAuthStatus ` - -StatusCode 302 ` - -Name $Name ` - -NoSuccessRedirect) - } - } - # if the session already has a user/isAuth'd, then skip auth - or allow anon - if (Test-PodeSessionsInUse) { - # existing session auth'd - if (Test-PodeAuthUser) { - $WebEvent.Auth = $WebEvent.Session.Data.Auth - return (Set-PodeAuthStatus ` - -Name $Name ` - -LoginRoute:($Login) ` - -NoSuccessRedirect) - } - - # if we're allowing anon access, and using sessions, then stop here - as a session will be created from a login route for auth'ing users - if ($AllowAnon) { - if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) { - Revoke-PodeSession - } - - return $true - } - } - - # check if the login flag is set, in which case just return and load a login get-page (allowing anon access) - if ($Login -and !$PodeContext.Server.Sessions.Info.UseHeaders -and ($WebEvent.Method -ieq 'get')) { - if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) { - Revoke-PodeSession - } - - return $true + if (!$IgnoreSession -and (Test-PodeSessionsInUse)) { + return (Test-PodeAuthUser) } try { @@ -1057,10 +1097,7 @@ function Test-PodeAuth } catch { $_ | Write-PodeErrorLog - return (Set-PodeAuthStatus ` - -StatusCode 500 ` - -Description $_.Exception.Message ` - -Name $Name) + return $false } # did the auth force a redirect? @@ -1068,60 +1105,22 @@ function Test-PodeAuth return $false } - # if auth failed, are we allowing anon access? - if (!$result.Success -and $AllowAnon) { - return $true - } - # if auth failed, set appropriate response headers/redirects if (!$result.Success) { - return (Set-PodeAuthStatus ` - -StatusCode $result.StatusCode ` - -Description $result.Description ` - -Headers $result.Headers ` - -Name $Name ` - -LoginRoute:$Login ` - -NoFailureRedirect:($result.FailureRedirect)) - } - - # if auth passed, assign the user(s) to the session - $user = $result.User - if ($result.Aggregated) { - $user = [ordered]@{} - foreach ($r in $result.Results) { - $user[$r.Auth] = $r.User - } - } - - $WebEvent.Auth = [ordered]@{ - User = $user - IsAuthenticated = $true - IsAuthorised = $true - Store = !$auth.Sessionless - Name = $result.Auth - Multiple = [bool]$result.Aggregated + return $false } # check access - foreach ($authName in $result.Auth) { - $WebEvent.Auth.IsAuthorised = Test-PodeAuthValidationAccess -Name $authName - - if (!$WebEvent.Auth.IsAuthorised) { - return (Set-PodeAuthStatus ` - -StatusCode 403 ` - -Description $result.Description ` - -Headers $result.Headers ` - -Name $authName ` - -LoginRoute:$Login ` - -NoFailureRedirect:($result.FailureRedirect)) + if ($CheckAccess) { + foreach ($authName in $result.Auth) { + if (!(Test-PodeAuthValidationAccess -Name $authName)) { + return $false + } } } # successful auth - return (Set-PodeAuthStatus ` - -Headers $result.Headers ` - -Name @($result.Auth)[0] ` - -LoginRoute:$Login) + return $true } <# @@ -2234,6 +2233,9 @@ Test whether the current WebEvent or Session has an authenticated user. .DESCRIPTION Test whether the current WebEvent or Session has an authenticated user. Returns true if there is an authenticated user. +.PARAMETER IgnoreSession +If supplied, only the Auth object in the WebEvent will be checked and the Session will be skipped. + .EXAMPLE if (Test-PodeAuthUser) { ... } #> @@ -2258,6 +2260,19 @@ function Test-PodeAuthUser return $false } +<# +.SYNOPSIS +Get the authenticated user from the WebEvent or Session. + +.DESCRIPTION +Get the authenticated user from the WebEvent or Session. This is similar to calling $Webevent.Auth.User. + +.PARAMETER IgnoreSession +If supplied, only the Auth object in the WebEvent will be used and the Session will be skipped. + +.EXAMPLE +$user = Get-PodeAuthUser +#> function Get-PodeAuthUser { [CmdletBinding()] @@ -2417,6 +2432,27 @@ function Add-PodeAuthAccess } } +<# +.SYNOPSIS +Let's you merge multiple Access methods together, into a "single" Access method. + +.DESCRIPTION +Let's you merge multiple Access methods together, into a "single" Access method. +You can specify if only One or All of the methods need to pass to allow access, and you can also +merge other merged Access methods for more advanced scenarios. + +.PARAMETER Name +A unique Name for the Access method. + +.PARAMETER Access +Mutliple Access method Names to be merged. + +.PARAMETER Valid +How many of the Access methods are required to be valid, One or All. (Default: One) + +.EXAMPLE +Merge-PodeAuthAccess -Name MergedAccess -Access RbacAccess, GbacAccess -Valid All +#> function Merge-PodeAuthAccess { [CmdletBinding()] @@ -2704,8 +2740,15 @@ The Name of the Access method to use to verify the access. .PARAMETER Value An array of access values to pass to the Access method for verification against the User. +.PARAMETER Authentication +Only if multiple merged Authentication methods are being used, supply the Name of the Authentication method's Access methods to use, +and where the User object should be retrieved. + .EXAMPLE if (Test-PodeAuthAccessUser -Name 'Example' -Value 'Developer', 'QA') { } + +.EXAMPLE +if (Test-PodeAuthAccessUser -Name 'Example' -Value 'Developer', 'QA' -Authentication 'BasicAuth') { } #> function Test-PodeAuthAccessUser { diff --git a/tests/unit/Authentication.Tests.ps1 b/tests/unit/Authentication.Tests.ps1 index 31d891aac..f1517c058 100644 --- a/tests/unit/Authentication.Tests.ps1 +++ b/tests/unit/Authentication.Tests.ps1 @@ -8,26 +8,40 @@ Describe 'Set-PodeAuthStatus' { Mock Move-PodeResponseUrl {} Mock Set-PodeResponseStatus {} + $PodeContext = @{ + Server = @{ + Authentications = @{ + Methods = @{ + ExampleAuth = @{ + Failure = @{ Url = '/url' } + Success = @{ Url = '/url' } + Cache = @{} + } + } + } + } + } + It 'Redirects to a failure URL' { - Set-PodeAuthStatus -StatusCode 500 -Failure @{ 'Url' = 'url'} | Should Be $false + Set-PodeAuthStatus -StatusCode 500 -Name ExampleAuth | Should Be $false Assert-MockCalled Move-PodeResponseUrl -Times 1 -Scope It Assert-MockCalled Set-PodeResponseStatus -Times 0 -Scope It } It 'Sets status to failure' { - Set-PodeAuthStatus -StatusCode 500 | Should Be $false - Assert-MockCalled Move-PodeResponseUrl -Times 0 -Scope It - Assert-MockCalled Set-PodeResponseStatus -Times 1 -Scope It + Set-PodeAuthStatus -StatusCode 500 -Name ExampleAuth | Should Be $false + Assert-MockCalled Move-PodeResponseUrl -Times 1 -Scope It + Assert-MockCalled Set-PodeResponseStatus -Times 0 -Scope It } It 'Redirects to a success URL' { - Set-PodeAuthStatus -Success @{ 'Url' = 'url' } -LoginRoute | Should Be $false + Set-PodeAuthStatus -Name ExampleAuth -LoginRoute | Should Be $false Assert-MockCalled Move-PodeResponseUrl -Times 1 -Scope It Assert-MockCalled Set-PodeResponseStatus -Times 0 -Scope It } It 'Returns true for next middleware' { - Set-PodeAuthStatus | Should Be $true + Set-PodeAuthStatus -Name ExampleAuth -NoSuccessRedirect | Should Be $true Assert-MockCalled Move-PodeResponseUrl -Times 0 -Scope It Assert-MockCalled Set-PodeResponseStatus -Times 0 -Scope It } From e362262924435c21f4f980b6afd1df4cc1a81100 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Tue, 3 Oct 2023 22:11:51 +0100 Subject: [PATCH 33/57] #588: fix auth tests and null headers --- examples/web-auth-apikey.ps1 | 2 ++ src/Private/Authentication.ps1 | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/examples/web-auth-apikey.ps1 b/examples/web-auth-apikey.ps1 index c75001b85..580b6c4cc 100644 --- a/examples/web-auth-apikey.ps1 +++ b/examples/web-auth-apikey.ps1 @@ -11,6 +11,8 @@ Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop # or just: # Import-Module Pode +# Invoke-RestMethod -Method Get -Uri 'http://localhost:8085/users' -Headers @{ 'X-API-KEY' = 'test-api-key' } + # create a server, and start listening on port 8085 Start-PodeServer -Threads 2 { diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index 509cfe414..ff154161a 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1212,11 +1212,6 @@ function Test-PodeAuthValidation } } - # headers - if ($null -eq $result.Headers) { - $result.Headers = @{} - } - # if there's no result, or no user, then the auth failed - but allow auth if anon enabled if (($null -eq $result) -or ($result.Count -eq 0) -or (Test-PodeIsEmpty $result.User)) { $code = (Protect-PodeValue -Value $result.Code -Default 401) @@ -1224,9 +1219,19 @@ function Test-PodeAuthValidation # set the www-auth header $validCode = (($code -eq 401) -or ![string]::IsNullOrEmpty($result.Challenge)) - if ($validCode -and !$result.Headers.ContainsKey('WWW-Authenticate')) { - $authHeader = Get-PodeAuthWwwHeaderValue -Name $auth.Scheme.Name -Realm $auth.Scheme.Realm -Challenge $result.Challenge - $result.Headers['WWW-Authenticate'] = $authHeader + if ($validCode) { + if ($null -eq $result) { + $result = @{} + } + + if ($null -eq $result.Headers) { + $result.Headers = @{} + } + + if (!$result.Headers.ContainsKey('WWW-Authenticate')) { + $authHeader = Get-PodeAuthWwwHeaderValue -Name $auth.Scheme.Name -Realm $auth.Scheme.Realm -Challenge $result.Challenge + $result.Headers['WWW-Authenticate'] = $authHeader + } } return @{ @@ -1246,6 +1251,7 @@ function Test-PodeAuthValidation } } catch { + $_ | Write-PodeErrorLog return @{ Success = $false StatusCode = 500 From 0f151b023fed158701c47e242ff530a51a5f9d0a Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Wed, 4 Oct 2023 22:58:34 +0100 Subject: [PATCH 34/57] #588: added test cases, fixed auth scoping issue with access --- examples/web-auth-basic-anon.ps1 | 2 +- examples/web-auth-bearer.ps1 | 2 + examples/web-auth-form-anon.ps1 | 2 +- examples/web-auth-merged.ps1 | 76 ++++++++++++++++++++++++++++++++ src/Private/Authentication.ps1 | 12 +++-- src/Public/Authentication.ps1 | 33 ++++++++++---- 6 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 examples/web-auth-merged.ps1 diff --git a/examples/web-auth-basic-anon.ps1 b/examples/web-auth-basic-anon.ps1 index c3133b26e..024e50d3b 100644 --- a/examples/web-auth-basic-anon.ps1 +++ b/examples/web-auth-basic-anon.ps1 @@ -44,7 +44,7 @@ Start-PodeServer -Threads 2 { } # GET request to get list of users (since there's no session, authentication will always happen, but, we're allowing anon access) - Add-PodeRoute -Method Get -Path '/users' -Authentication 'Validate' -Anon -ScriptBlock { + Add-PodeRoute -Method Get -Path '/users' -Authentication 'Validate' -AllowAnon -ScriptBlock { if (Test-PodeAuthUser) { Write-PodeJsonResponse -Value @{ Users = @( diff --git a/examples/web-auth-bearer.ps1 b/examples/web-auth-bearer.ps1 index e32187bd4..3e8ca4e74 100644 --- a/examples/web-auth-bearer.ps1 +++ b/examples/web-auth-bearer.ps1 @@ -4,6 +4,8 @@ Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop # or just: # Import-Module Pode +# Invoke-RestMethod -Method Get -Uri 'http://localhost:8085/users' -Headers @{ Authorization = 'Bearer test-token' } + # create a server, and start listening on port 8085 Start-PodeServer -Threads 2 { diff --git a/examples/web-auth-form-anon.ps1 b/examples/web-auth-form-anon.ps1 index ab86a6a54..18d25c151 100644 --- a/examples/web-auth-form-anon.ps1 +++ b/examples/web-auth-form-anon.ps1 @@ -50,7 +50,7 @@ Start-PodeServer -Threads 2 { # home page: # redirects to login page if not authenticated - Add-PodeRoute -Method Get -Path '/' -Authentication Login -Anon -ScriptBlock { + Add-PodeRoute -Method Get -Path '/' -Authentication Login -AllowAnon -ScriptBlock { if (Test-PodeAuthUser) { $session:Views++ diff --git a/examples/web-auth-merged.ps1 b/examples/web-auth-merged.ps1 new file mode 100644 index 000000000..23cd775dc --- /dev/null +++ b/examples/web-auth-merged.ps1 @@ -0,0 +1,76 @@ +$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) +Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop + +# or just: +# Import-Module Pode + +# Success +# Invoke-RestMethod -Method Get -Uri 'http://localhost:8085/users' -Headers @{ 'X-API-KEY' = 'test-api-key'; Authorization = 'Basic bW9ydHk6cGlja2xl' } + +# Failure +# Invoke-RestMethod -Method Get -Uri 'http://localhost:8085/users' -Headers @{ 'X-API-KEY' = 'test-api-key'; Authorization = 'Basic bW9ydHk6cmljaw==' } + +# create a server, and start listening on port 8085 +Start-PodeServer -Threads 2 { + + # listen on localhost:8085 + Add-PodeEndpoint -Address * -Port 8085 -Protocol Http + New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging + + # setup access + Add-PodeAuthAccess -Type Role -Name 'Rbac' + Add-PodeAuthAccess -Type Group -Name 'Gbac' + + # setup a merged access + Merge-PodeAuthAccess -Name 'MergedAccess' -Access 'Rbac', 'Gbac' -Valid All + + # setup apikey auth + New-PodeAuthScheme -ApiKey -Location Header | Add-PodeAuth -Name 'ApiKey' -Access 'Gbac' -Sessionless -ScriptBlock { + param($key) + + # here you'd check a real user storage, this is just for example + if ($key -ieq 'test-api-key') { + return @{ + User = @{ + ID ='M0R7Y302' + Name = 'Morty' + Type = 'Human' + Groups = @('Software') + } + } + } + + return $null + } + + # setup basic auth (base64> username:password in header) + New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Basic' -Access 'MergedAccess' -Sessionless -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if ($username -eq 'morty' -and $password -eq 'pickle') { + return @{ + User = @{ + ID ='M0R7Y302' + Name = 'Morty' + Type = 'Human' + Roles = @('Developer') + Groups = @('Software') + } + } + } + + return @{ Message = 'Invalid details supplied' } + } + + # merge the auths together + Merge-PodeAuth -Name 'MergedAuth' -Authentication 'ApiKey', 'Basic' -Valid All + + # GET request to get list of users (since there's no session, authentication will always happen) + Add-PodeRoute -Method Get -Path '/users' -Authentication 'MergedAuth' -Role Developer -Group Software -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = $WebEvent.Auth.User + } + } + +} \ No newline at end of file diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index ff154161a..bbae97780 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1313,7 +1313,7 @@ function Test-PodeAuthValidationAccess } # check access - return ([string]::IsNullOrEmpty($access) -or (Invoke-PodeAuthValidationAccess -Name $access)) + return ([string]::IsNullOrEmpty($access) -or (Invoke-PodeAuthValidationAccess -Name $access -Authentication $BaseName)) } function Invoke-PodeAuthValidationAccess @@ -1321,7 +1321,11 @@ function Invoke-PodeAuthValidationAccess param( [Parameter(Mandatory=$true)] [string] - $Name + $Name, + + [Parameter(Mandatory=$true)] + [string] + $Authentication ) # get the access method @@ -1330,7 +1334,7 @@ function Invoke-PodeAuthValidationAccess # if it's a merged access, re-call this function and check against "succeed" value if ($access.Merged) { foreach ($accName in $access.Access) { - $result = Invoke-PodeAuthValidationAccess -Name $accName + $result = Invoke-PodeAuthValidationAccess -Name $accName -Authentication $Authentication # if the access passed, and we only need one access to pass, return true if ($result -and $access.PassOne) { @@ -1358,7 +1362,7 @@ function Invoke-PodeAuthValidationAccess } # main access validation logic - return (Test-PodeAuthAccessRoute -Name $Name) + return (Test-PodeAuthAccessRoute -Name $Name -Authentication $Authentication) } function Get-PodeAuthMiddlewareScript diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index 196b9e55d..17f6e9abf 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -920,11 +920,6 @@ function Merge-PodeAuth throw "Access method not found: $($Access)" } - # if we're using sessions, ensure sessions have been setup - if (!$Sessionless -and !(Test-PodeSessionsConfigured)) { - throw 'Sessions are required to use session persistent authentication' - } - # set default if ([string]::IsNullOrEmpty($Default)) { $Default = $Authentication[0] @@ -938,6 +933,11 @@ function Merge-PodeAuth $Sessionless = $tmpAuth.Sessionless } + # if we're using sessions, ensure sessions have been setup + if (!$Sessionless -and !(Test-PodeSessionsConfigured)) { + throw 'Sessions are required to use session persistent authentication' + } + # check access from default if (Test-PodeIsEmpty $Access) { $Access = $tmpAuth.Access @@ -2741,7 +2741,7 @@ The Name of the Access method to use to verify the access. An array of access values to pass to the Access method for verification against the User. .PARAMETER Authentication -Only if multiple merged Authentication methods are being used, supply the Name of the Authentication method's Access methods to use, +Only if multiple merged Authentication methods are being used, supply the Name of the Authentication method the Access being checked is for, and where the User object should be retrieved. .EXAMPLE @@ -2810,6 +2810,10 @@ Test the currently authenticated User's access against the access values supplie .PARAMETER Name The Name of the Access method to use to verify the access. +.PARAMETER Authentication +Only if multiple merged Authentication methods are being used, supply the Name of the Authentication method the Access being checked is for, +and where the User object should be retrieved. + .EXAMPLE if (Test-PodeAuthAccessRoute -Name 'Example') { } #> @@ -2818,9 +2822,18 @@ function Test-PodeAuthAccessRoute param( [Parameter(Mandatory=$true)] [string] - $Name + $Name, + + [Parameter()] + [string] + $Authentication ) + # check if an auth name was passed for mutliple auths + if ($WebEvent.Auth.Multiple -and [string]::IsNullOrEmpty($Authentication)) { + throw "No Authentication name supplied to select User for Access testing, when mutliple authentications were used" + } + # get the access method $access = $PodeContext.Server.Authentications.Access[$Name] @@ -2835,5 +2848,9 @@ function Test-PodeAuthAccessRoute } # now test the user's access against the route's access - return (Test-PodeAuthAccessUser -Name $Name -Value $routeAccess -Authentication $WebEvent.Route.Authentication) + if ([string]::IsNullOrEmpty($Authentication)) { + $Authentication = $WebEvent.Route.Authentication + } + + return (Test-PodeAuthAccessUser -Name $Name -Value $routeAccess -Authentication $Authentication) } \ No newline at end of file From cc2b37022a7d207cfe1189b95107f752be205b0c Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Thu, 5 Oct 2023 22:13:46 +0100 Subject: [PATCH 35/57] #588: update docs --- .../Authentication/Inbuilt/AzureAD.md | 6 +- .../Authentication/Methods/Bearer.md | 2 +- docs/Tutorials/Authentication/Overview.md | 151 +++++++++++++++++- docs/Tutorials/Authorisation/Overview.md | 19 ++- .../Routes/Examples/AnonymousAccess.md | 4 +- src/Public/Authentication.ps1 | 58 +++++-- 6 files changed, 211 insertions(+), 29 deletions(-) diff --git a/docs/Tutorials/Authentication/Inbuilt/AzureAD.md b/docs/Tutorials/Authentication/Inbuilt/AzureAD.md index 167942037..0c43fd9d4 100644 --- a/docs/Tutorials/Authentication/Inbuilt/AzureAD.md +++ b/docs/Tutorials/Authentication/Inbuilt/AzureAD.md @@ -41,6 +41,8 @@ To setup and start using Azure AD authentication in Pode you use `New-PodeAuthAz ```powershell Start-PodeServer { + Enable-PodeSessionMiddleware -Duration 120 -Extend + $scheme = New-PodeAuthAzureADScheme -ClientID '' -ClientSecret '' -Tenant '' $scheme | Add-PodeAuth -Name 'Login' -FailureUrl '/login' -SuccessUrl '/' -ScriptBlock { @@ -63,6 +65,8 @@ To setup Azure AD authentcation, but using your own Form login, then you can use ```powershell Start-PodeServer { + Enable-PodeSessionMiddleware -Duration 120 -Extend + $form = New-PodeAuthScheme -Form $scheme = New-PodeAuthAzureADScheme -ClientID '' -ClientSecret '' -Tenant '' -InnerScheme $form @@ -97,7 +101,7 @@ The Pode side needs to be configured to allow basic authentication as well. This $form = New-PodeAuthScheme -Form $schemeForm = New-PodeAuthAzureADScheme -ClientID '' -ClientSecret '' -Tenant '' -InnerScheme $form -$basic = New-PodeAuthSceme -Basic +$basic = New-PodeAuthScheme -Basic $schemeBasic = New-PodeAuthAzureADScheme -ClientID '' -ClientSecret '' -Tenant '' -InnerScheme $basic $authLogin = { diff --git a/docs/Tutorials/Authentication/Methods/Bearer.md b/docs/Tutorials/Authentication/Methods/Bearer.md index 73cb7bc25..77f92abbe 100644 --- a/docs/Tutorials/Authentication/Methods/Bearer.md +++ b/docs/Tutorials/Authentication/Methods/Bearer.md @@ -12,7 +12,7 @@ To start using Bearer authentication in Pode you can use `New-PodeAuthScheme -Be ```powershell Start-PodeServer { - New-PodeAuthScheme -Bearer | Add-PodeAuth -Name 'Authenticate' -ScriptBlock { + New-PodeAuthScheme -Bearer | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock { param($token) # check if the token is valid, and get user diff --git a/docs/Tutorials/Authentication/Overview.md b/docs/Tutorials/Authentication/Overview.md index 3f50298bd..1bdd7f62c 100644 --- a/docs/Tutorials/Authentication/Overview.md +++ b/docs/Tutorials/Authentication/Overview.md @@ -9,9 +9,7 @@ To setup and use authentication in Pode you need to use the [`New-PodeAuthScheme You can also setup [Authorisation](../../Authorisation/Overview) for use with Authentication as well. -## Usage - -### Schemes +## Schemes The [`New-PodeAuthScheme`](../../../Functions/Authentication/New-PodeAuthScheme) function allows you to create and configure authentication schemes, or you can create your own Custom authentication schemes. These schemes can then be piped into [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth). The role of a scheme is to parse the request for any user credentials, or other information, that is required for a user to be authenticated. @@ -31,7 +29,7 @@ Or you can define a custom scheme: * [Custom](../Methods/Custom) -### Validators +## Validators The [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) function allows you to add authentication validators to your server. You can have many methods configured, defining which one to validate against using the `-Authentication` parameter on Routes. Their job is to validate the information parsed from the supplied scheme to ensure a user is valid. @@ -51,7 +49,7 @@ The `-Name` of the authentication method must be unique. The `-Scheme` comes fro The `-ScriptBlock` is used to validate a user, checking if they exist and the password is correct (or checking if they exist in some data store). If the ScriptBlock succeeds, then a `User` object needs to be returned from the script as `@{ User = $user }`. If `$null`, or a null user, is returned then the script is assumed to have failed - meaning the user will have failed authentication, and a 401 response is returned. -#### Custom Status and Headers +### Custom Status and Headers When authenticating a user in Pode, any failures will return a 401 response with a generic message. You can inform Pode to return a custom message/status from [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) by returning the relevant hashtable values. @@ -95,7 +93,7 @@ return @{ } ``` -#### Authenticate Type/Realm +### Authenticate Type/Realm When authentication fails, and a 401 response is returned, then Pode will also attempt to Response back to the client with a `WWW-Authenticate` header (if you've manually set this header using the custom headers from above, then the custom header will be used instead). For the inbuilt types, such as Basic, this Header will always be returned on a 401 response. @@ -116,7 +114,7 @@ WWW-Authenticate: Basic realm="Enter creds to access site" !!! note If no Realm was set then it would just look as follows: `WWW-Authenticate: Basic` -#### Redirecting +### Redirecting When building custom authenticators, it might be required that you have to redirect mid-auth and stop processing the current request. To achieve this you can return the following from the scriptblock of `New-PodeAuthScheme` or `Add-PodeAuth`: @@ -124,7 +122,9 @@ When building custom authenticators, it might be required that you have to redir return @{ IsRedirected = $true } ``` -### Routes/Middleware +An example of this could be OAuth2, where the authentication needs to redirect to the Provider. + +## Routes/Middleware To use an authentication on a specific route, you can use the `-Authentication` parameter on the [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute) function; this takes the Name supplied to the `-Name` parameter on [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth). This will set the authentication up to run before other route middleware. @@ -176,6 +176,141 @@ Add-PodeRoute -Method Get -Path '/' -Authentication 'Login' -Login -ScriptBlock } ``` +## Merging + +For advanced authentication scenarios, you can merge multiple authentication methods together using [`Merge-PodeAuth`](../../../Functions/Authentication/Merge-PodeAuth). This allows you to have an authentication strategy where multiple authentications are required to pass for a user to be fully authenticated, or you could have fallback authentications should the primary authentication fail. + +When you merge authentication methods together, it becomes a new authentication method which you can supply to `-Authentication` on Routes. By default the merged authentications expect just one to pass, but you can state that you require all to pass via the `-Valid` parameter on [`Merge-PodeAuth`](../../../Functions/Authentication/Merge-PodeAuth). + +### All + +For example, you might require an API Key and Basic authentication for a user to view a Route, in which case you would set something up as follows: + +```powershell +# setup apikey auth +New-PodeAuthScheme -ApiKey -Location Header | Add-PodeAuth -Name 'ApiKey' -Sessionless -ScriptBlock { + param($key) + + # here you'd check a real user storage, this is just for example + if ($key -ieq 'test-api-key') { + return @{ User = @{ Name = 'Morty' } } + } + + return $null +} + +# setup basic auth +New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Basic' -Sessionless -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if ($username -eq 'morty' -and $password -eq 'pickle') { + return @{ User = @{ Name = 'Morty' } } + } + + return @{ Message = 'Invalid details supplied' } +} + +# merge the authentications together, and require all to pass +Merge-PodeAuth -Name 'MergedAuth' -Authentication 'ApiKey', 'Basic' -Valid All + +# use the merged auth in a route +Add-PodeRoute -Method Get -Path '/users' -Authentication 'MergedAuth' -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = $WebEvent.Auth.User + } +} +``` + +### One + +Or, you might want to check for a JWT Bearer token, but if one isn't present default to Azure AD authentication so we can store the returned access token and utilise the first Bearer auth later on: + +```powershell +# setup jwt bearer auth +New-PodeAuthScheme -Bearer -AsJWT | Add-PodeAuth -Name 'Bearer' -Sessionless -ScriptBlock { + param($payload) + # check payload + return @{ User = @{ Name = 'Morty' } } +} + +# setup basic azure-ad auth +$basic = New-PodeAuthScheme -Basic +$scheme = New-PodeAuthAzureADScheme -ClientID '' -ClientSecret '' -Tenant '' -InnerScheme $basic +$scheme | Add-PodeAuth -Name 'AzureAD' -Sessionless -ScriptBlock { + param($user, $accessToken, $refreshToken, $response) + # check if the user is valid + return @{ User = $user } +} + +# merge the authentications together, and require just one to pass +Merge-PodeAuth -Name 'MergedAuth' -Authentication 'Bearer', 'AzureAD' -Valid One + +# use the merged auth in a route +Add-PodeRoute -Method Get -Path '/users' -Authentication 'MergedAuth' -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = $WebEvent.Auth.User + } +} +``` + +### Advanced + +You can also merge together other merged authentication methods. This lets you build scenarios where you require an API key, and then need either a JWT Bearer token or OAuth2 to pass. As a very brief example: + +```powershell +# setup apikey auth +New-PodeAuthScheme -ApiKey -Location Header | Add-PodeAuth -Name 'ApiKey' -Sessionless -ScriptBlock { + param($key) + + # here you'd check a real user storage, this is just for example + if ($key -ieq 'test-api-key') { + return @{ User = @{ Name = 'Morty' } } + } + + return $null +} + +# setup jwt bearer auth +New-PodeAuthScheme -Bearer -AsJWT | Add-PodeAuth -Name 'Bearer' -Sessionless -ScriptBlock { + param($payload) + # check payload + return @{ User = @{ Name = 'Morty' } } +} + +# setup basic azure-ad auth +$basic = New-PodeAuthScheme -Basic +$scheme = New-PodeAuthAzureADScheme -ClientID '' -ClientSecret '' -Tenant '' -InnerScheme $basic +$scheme | Add-PodeAuth -Name 'AzureAD' -Sessionless -ScriptBlock { + param($user, $accessToken, $refreshToken, $response) + # check if the user is valid + return @{ User = $user } +} + +# merge the authentications together, and require just one to pass +Merge-PodeAuth -Name 'JwtMergedAuth' -Authentication 'Bearer', 'AzureAD' -Valid One + +# merge the above merged auth with the apikey auth, and require both to pass +Merge-PodeAuth -Name 'ApiMergedAuth' -Authentication 'ApiKey', 'JwtMergedAuth' -Valid All + +# use the merged auth in a route +Add-PodeRoute -Method Get -Path '/users' -Authentication 'ApiMergedAuth' -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = $WebEvent.Auth.User + } +} +``` + +### Users + +When using a single authentication method, the authenticated user's details will be accessible at `$WebEvent.Auth.User`. However, when you're using a merged authentication method you could end up with 2 or more user objects being returned from each authentication method. + +Because of this, when using a merged authentication, the user object will instead be found at `$WebEvent.Auth.User[]`. For example, if you have a authentication method with name "BearerAuth", then the user's details would be at `$WebEvent.Auth.User['BearerAuth']` - not `$WebEvent.Auth.User`. + +### Parameters + +Similar to [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) the [`Merge-PodeAuth`](../../../Functions/Authentication/Merge-PodeAuth) function also supports the `-FailureUrl`, `-SuccessUrl`, etc. parameters. When set on the merged authentication method, these fails will be used as a fallback if the initial authentication methods don't have them set. This means you can setup 2 authentication methods without Failure URLs, and then merge them together with a default one of `/login` on the merged authentication. + ## Inbuilt Authenticators Overtime Pode will start to support inbuilt authentication methods - such as [Windows Active Directory](../Inbuilt/WindowsAD). More information can be found in the Inbuilt section. diff --git a/docs/Tutorials/Authorisation/Overview.md b/docs/Tutorials/Authorisation/Overview.md index dd97d2573..61d80bbd4 100644 --- a/docs/Tutorials/Authorisation/Overview.md +++ b/docs/Tutorials/Authorisation/Overview.md @@ -83,7 +83,7 @@ And Pode will retrieve the appropriate data for you. #### Lookup ScriptBlock -If the access values you require are not stored in the `$WebEvent.Auth.User` object but else where (ie: external source), then you can supply a `-ScriptBlock` on [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess). When Pode attempts to retrieve access values for the User, or another Source, this scriptblock will be invoked. +If the source access values you require are not stored in the `$WebEvent.Auth.User` object but else where (ie: external source), then you can supply a `-ScriptBlock` on [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess). When Pode attempts to retrieve access values for the User, or another Source, this scriptblock will be invoked. !!! note When using this scriptblock with Authentication the currently authenticated User will be supplied as the first parameter, followed by the `-ArgumentList` values. When using the Access methods in a more adhoc manner via [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess), just the `-ArgumentList` values are supplied. @@ -139,13 +139,13 @@ Add-PodeAuthAccess -Name 'ScopeExample' -Type Scope -ScriptBlock { ## Using with Authentication -The Access methods will mostly commonly be used in conjunction with [Authentication](../../Authentication/Overview) and [Routes](../../Routes/Overview). When used together, Pode will automatically validate Route authorised for you as a part of the Authentication flow. If authorisation fails, an HTTP 403 status code will be returned. +The Access methods will mostly commonly be used in conjunction with [Authentication](../../Authentication/Overview) and [Routes](../../Routes/Overview). When used together, Pode will automatically validate Route authorisation for you as a part of the Authentication flow. If authorisation fails, an HTTP 403 status code will be returned. -After creating an Access method as outlined above, you can supply one or more Access method Names to [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) using the `-Access` property. This allows you to check authorisation based on multiple types of Access methods - ie, Roles and Groups, etc. +After creating an Access method as outlined above, you can supply the Access method Name to [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) using the `-Access` property. On [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute) and [`Add-PodeRouteGroup`](../../../Functions/Routes/Add-PodeRouteGroup) there are the following parameters: `-Role`, `-Group`, `-Scope`, and `-User`. You can supply one ore more string values to these parameters, depending on which Access method type you're using. -For example, to verify access to a Route to authorise only Developer accounts: +For example, to verify access to a Route to authorise only Developer role accounts: ```powershell Start-PodeServer { @@ -196,9 +196,11 @@ But calling the following will fail with a 403: Invoke-RestMethod -Uri http://localhost:8080/route2 -Method Get -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' } ``` -### Multiple Access Methods +## Merging -You can configure multiple Access methods on a single Authentication, and each of them will have to succeed for a user to be authorised for access to a Route. +Similar to Authentication methods, you can also merge Access methods using [`Merge-PodeAuthAccess`](../../../Functions/Authentication/Merge-PodeAuthAccess). This allows you to have an access strategy where multiple authorisations are required to pass for a user to be fully authorised, or just one of several possible methods. + +When you merge access methods together, it becomes a new access method which you can supply to `-Access` on [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth). By default the merged access method expects just one to pass, but you can state that you require all to pass via the `-Valid` parameter on [`Merge-PodeAuthAccess`](../../../Functions/Authentication/Merge-PodeAuthAccess). Using the same example above, we could add Group authorisation to this as well so the Developers have to be in a Software Group, and the Admins in a Operations Group: @@ -210,8 +212,11 @@ Start-PodeServer { Add-PodeAuthAccess -Name 'RoleExample' -Type Role Add-PodeAuthAccess -Name 'GroupExample' -Type Group + # setup a merged access + Merge-PodeAuthAccess -Name 'MergedExample' -Access 'RoleExample', 'GroupExample' -Valid All + # setup Basic authentication - New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -Access 'RoleExample', 'GroupExample' -ScriptBlock { + New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -Access 'MergedExample' -ScriptBlock { param($username, $password) # here you'd check a real user storage, this is just for example diff --git a/docs/Tutorials/Routes/Examples/AnonymousAccess.md b/docs/Tutorials/Routes/Examples/AnonymousAccess.md index 48bfdc237..834c327e4 100644 --- a/docs/Tutorials/Routes/Examples/AnonymousAccess.md +++ b/docs/Tutorials/Routes/Examples/AnonymousAccess.md @@ -47,9 +47,9 @@ Add-PodeRoute -Method Get -Path '/' -Authentication 'Login' -AllowAnon -ScriptBl Now, when an authenticated user hits the page, they're shown the original personal greeting page with view counter. However, when an unauthenticated user hits the page they are shown a generic greeting with a login button. -The [`Test-PodeAuthUser`] will check both the `$WebEvent.Auth` and `$WebEvent.Session` objects for an authenticated user. You can force the function to only check the former by supplying `-IgnoreSession`. +The [`Test-PodeAuthUser`](../../../../Functions/Authentication/Test-PodeAuthUser) will check both the `$WebEvent.Auth` and `$WebEvent.Session` objects for an authenticated user. You can force the function to only check the former by supplying `-IgnoreSession`. -You can also retrieve the user object using [`Get-PodeAuthUser`]. Under most circumstances you'll be able to access the authenticated user at `$WebEvent.Auth.User`, however if you're relying on Sessions and a Route without Authentication configured then you'll ave to use this function. Similar to above, you can supply `-IgnoreSession` here as well. +You can also retrieve the user object using [`Get-PodeAuthUser`](../../../../Functions/Authentication/Get-PodeAuthUser). Under most circumstances you'll be able to access the authenticated user at `$WebEvent.Auth.User`, however if you're relying on Sessions and a Route without Authentication configured then you'll ave to use this function. Similar to above, you can supply `-IgnoreSession` here as well. ## Example Code diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index 17f6e9abf..d2696dbc0 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -2233,6 +2233,10 @@ Test whether the current WebEvent or Session has an authenticated user. .DESCRIPTION Test whether the current WebEvent or Session has an authenticated user. Returns true if there is an authenticated user. +.PARAMETER Authentication +Only if multiple merged Authentication methods are being used, supply the Name of the Authentication method where the User object should be checked. +If not supplied, and using mutliple Authentication methods, true will be returned if there is a User object for any Authentication method. + .PARAMETER IgnoreSession If supplied, only the Auth object in the WebEvent will be checked and the Session will be skipped. @@ -2243,21 +2247,36 @@ function Test-PodeAuthUser { [CmdletBinding()] param( + [Parameter()] + [string] + $Authentication, + [switch] $IgnoreSession ) # auth middleware - if (($null -ne $WebEvent.Auth.User) -and $WebEvent.Auth.IsAuthenticated) { - return $true + if (($null -ne $WebEvent.Auth) -and $WebEvent.Auth.IsAuthenticated) { + $auth = $WebEvent.Auth } # session? - if (!$IgnoreSession -and ($null -ne $WebEvent.Session.Data.Auth.User) -and $WebEvent.Session.Data.Auth.IsAuthenticated) { - return $true + elseif (!$IgnoreSession -and ($null -ne $WebEvent.Session.Data.Auth) -and $WebEvent.Session.Data.Auth.IsAuthenticated) { + $auth = $WebEvent.Session.Data.Auth } - return $false + # null? + if (($null -eq $auth) -or ($null -eq $auth.User)) { + return $false + } + + # embedded auth + $user = $auth.User + if ($auth.Multiple -and ![string]::IsNullOrEmpty($Authentication)) { + $user = $user[$Authentication] + } + + return ($null -ne $user) } <# @@ -2267,6 +2286,10 @@ Get the authenticated user from the WebEvent or Session. .DESCRIPTION Get the authenticated user from the WebEvent or Session. This is similar to calling $Webevent.Auth.User. +.PARAMETER Authentication +Only if multiple merged Authentication methods are being used, supply the Name of the Authentication method where the User object should be retrieved. +If not supplied, and using mutliple Authentication methods, all User objects will be returned. + .PARAMETER IgnoreSession If supplied, only the Auth object in the WebEvent will be used and the Session will be skipped. @@ -2277,21 +2300,36 @@ function Get-PodeAuthUser { [CmdletBinding()] param( + [Parameter()] + [string] + $Authentication, + [switch] $IgnoreSession ) # auth middleware - if (($null -ne $WebEvent.Auth.User) -and $WebEvent.Auth.IsAuthenticated) { - return $WebEvent.Auth.User + if (($null -ne $WebEvent.Auth) -and $WebEvent.Auth.IsAuthenticated) { + $auth = $WebEvent.Auth } # session? - if (!$IgnoreSession -and ($null -ne $WebEvent.Session.Data.Auth.User) -and $WebEvent.Session.Data.Auth.IsAuthenticated) { - return $WebEvent.Session.Data.Auth.User + elseif (!$IgnoreSession -and ($null -ne $WebEvent.Session.Data.Auth) -and $WebEvent.Session.Data.Auth.IsAuthenticated) { + $auth = $WebEvent.Session.Data.Auth + } + + # null? + if (($null -eq $auth) -or ($null -eq $auth.User)) { + return $null + } + + # embedded auth + $user = $auth.User + if ($auth.Multiple -and ![string]::IsNullOrEmpty($Authentication)) { + $user = $user[$Authentication] } - return $null + return $user } <# From fe9b7dd4241599e300d697b78ecfb539c511fbed Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 7 Oct 2023 15:19:47 +0100 Subject: [PATCH 36/57] #588: fixes from review --- examples/web-auth-basic-adhoc.ps1 | 67 +++++++++++++++++++++++++++++++ examples/web-auth-basic.ps1 | 29 +------------ src/Public/Authentication.ps1 | 4 +- src/Public/Utilities.ps1 | 10 +++++ 4 files changed, 81 insertions(+), 29 deletions(-) create mode 100644 examples/web-auth-basic-adhoc.ps1 diff --git a/examples/web-auth-basic-adhoc.ps1 b/examples/web-auth-basic-adhoc.ps1 new file mode 100644 index 000000000..ebcb9bb7e --- /dev/null +++ b/examples/web-auth-basic-adhoc.ps1 @@ -0,0 +1,67 @@ +$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) +Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop + +# or just: +# Import-Module Pode + +<# +This example shows how to use sessionless authentication, which will mostly be for +REST APIs. The example used here is adhoc Basic authentication. + +Calling the '[POST] http://localhost:8085/users' endpoint, with an Authorization +header of 'Basic bW9ydHk6cGlja2xl' will display the users. Anything else and +you'll get a 401 status code back. + +Success: +Invoke-RestMethod -Uri http://localhost:8085/users -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' } + +Failure: +Invoke-RestMethod -Uri http://localhost:8085/users -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cmljaw==' } +#> + +# create a server, and start listening on port 8085 +Start-PodeServer -Threads 2 { + + # listen on localhost:8085 + Add-PodeEndpoint -Address * -Port 8085 -Protocol Http + + # setup basic auth (base64> username:password in header) + New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Sessionless -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if ($username -eq 'morty' -and $password -eq 'pickle') { + return @{ + User = @{ + ID ='M0R7Y302' + Name = 'Morty' + Type = 'Human' + } + } + } + + return @{ Message = 'Invalid details supplied' } + } + + # POST request to get list of users (authentication is done adhoc, and not directly using -Authentication on the Route) + Add-PodeRoute -Method Post -Path '/users' -ScriptBlock { + if (!(Test-PodeAuth -Name Validate)) { + Set-PodeResponseStatus -Code 401 + return + } + + Write-PodeJsonResponse -Value @{ + User = @( + @{ + Name = 'Deep Thought' + Age = 42 + }, + @{ + Name = 'Leeroy Jenkins' + Age = 1337 + } + ) + } + } + +} \ No newline at end of file diff --git a/examples/web-auth-basic.ps1 b/examples/web-auth-basic.ps1 index df739269d..d28742089 100644 --- a/examples/web-auth-basic.ps1 +++ b/examples/web-auth-basic.ps1 @@ -43,35 +43,10 @@ Start-PodeServer -Threads 2 { return @{ Message = 'Invalid details supplied' } } - # POST request to get list of users (since there's no session, authentication will always happen) + # POST request to get current user (since there's no session, authentication will always happen) Add-PodeRoute -Method Post -Path '/users' -Authentication 'Validate' -ScriptBlock { Write-PodeJsonResponse -Value @{ - Users = @( - @{ - Name = 'Deep Thought' - Age = 42 - }, - @{ - Name = 'Leeroy Jenkins' - Age = 1337 - } - ) - } - } - - # GET request to get list of users (since there's no session, authentication will always happen) - Add-PodeRoute -Method Get -Path '/users' -Authentication 'Validate' -ScriptBlock { - Write-PodeJsonResponse -Value @{ - Users = @( - @{ - Name = 'Deep Thought' - Age = 42 - }, - @{ - Name = 'Leeroy Jenkins' - Age = 1337 - } - ) + User = (Get-PodeAuthUser) } } diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index d2696dbc0..4aa9dc203 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -1088,8 +1088,8 @@ function Test-PodeAuth ) # if the session already has a user/isAuth'd, then skip auth - or allow anon - if (!$IgnoreSession -and (Test-PodeSessionsInUse)) { - return (Test-PodeAuthUser) + if (!$IgnoreSession -and (Test-PodeSessionsInUse) -and (Test-PodeAuthUser)) { + return $true } try { diff --git a/src/Public/Utilities.ps1 b/src/Public/Utilities.ps1 index cfd4e2d91..800feafad 100644 --- a/src/Public/Utilities.ps1 +++ b/src/Public/Utilities.ps1 @@ -686,6 +686,16 @@ function Test-PodeIsMacOS return ([bool]$IsMacOS) } +<# +.SYNOPSIS +Tests if the scope you're in is currently within a Pode runspace. + +.DESCRIPTION +Tests if the scope you're in is currently within a Pode runspace. + +.EXAMPLE +If (Test-PodeInRunspace) { ... } +#> function Test-PodeInRunspace { [CmdletBinding()] From f68becd5ff6c569accf4798ce0b1cd8f24241406 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 7 Oct 2023 15:55:52 +0100 Subject: [PATCH 37/57] #1137: fix the loading of AutoImport config --- src/Private/Context.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index fa8320fbd..3d7cf1b79 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -787,7 +787,7 @@ function Set-PodeServerConfiguration } # auto-import - $Context.Server.AutoImport = Read-PodeAutoImportConfiguration + $Context.Server.AutoImport = Read-PodeAutoImportConfiguration -Configuration $Configuration # request if ([int]$Configuration.Request.Timeout -gt 0) { From 5e28e2f3493c74723f146acfd2003116f1b98d0a Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 7 Oct 2023 16:49:46 +0100 Subject: [PATCH 38/57] #1101: allow SSL protocols to be set on Add-PodeEndpoint --- docs/Tutorials/Endpoints/Basics.md | 3 ++- src/Private/PodeServer.ps1 | 3 ++- src/Private/SmtpServer.ps1 | 3 ++- src/Private/TcpServer.ps1 | 3 ++- src/Public/Core.ps1 | 20 ++++++++++++++++++-- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/docs/Tutorials/Endpoints/Basics.md b/docs/Tutorials/Endpoints/Basics.md index a2167a411..f2e694ffe 100644 --- a/docs/Tutorials/Endpoints/Basics.md +++ b/docs/Tutorials/Endpoints/Basics.md @@ -128,7 +128,8 @@ The following is the structure of the Endpoint object internally, as well as the | Hostname | string | The hostname of the Endpoint | | FriendlyName | string | A user friendly hostname to use when generating internal URLs | | Url | string | The full base URL of the Endpoint | -| Ssl | bool | Whether or not this Endpoint support support SSL | +| Ssl.Enabled | bool | Whether or not this Endpoint uses SSL | +| Ssl.Protocols | SslProtocols | An aggregated integer which specifies the SSL protocols this endpoints supports | | Protocol | string | The protocol of the Endpoint. Such as: HTTP, HTTPS, WS, etc. | | Type | string | The type of the Endpoint. Such as: HTTP, WS, SMTP, TCP | | Certificate | hashtable | Details about the certificate that will be used for SSL Endpoints | diff --git a/src/Private/PodeServer.ps1 b/src/Private/PodeServer.ps1 index 72b995891..2e19a2788 100644 --- a/src/Private/PodeServer.ps1 +++ b/src/Private/PodeServer.ps1 @@ -44,6 +44,7 @@ function Start-PodeWebServer Protocol = $_.Protocol Type = $_.Type Pool = $_.Runspace.PoolName + SslProtocols = $_.Ssl.Protocols } # add endpoint to list @@ -72,7 +73,7 @@ function Start-PodeWebServer { # register endpoints on the listener $endpoints | ForEach-Object { - $socket = (. ([scriptblock]::Create("New-Pode$($PodeContext.Server.ListenerType)ListenerSocket -Address `$_.Address -Port `$_.Port -SslProtocols `$PodeContext.Server.Sockets.Ssl.Protocols -Type `$endpointsMap[`$_.Key].Type -Certificate `$_.Certificate -AllowClientCertificate `$_.AllowClientCertificate"))) + $socket = (. ([scriptblock]::Create("New-Pode$($PodeContext.Server.ListenerType)ListenerSocket -Address `$_.Address -Port `$_.Port -SslProtocols `$_.SslProtocols -Type `$endpointsMap[`$_.Key].Type -Certificate `$_.Certificate -AllowClientCertificate `$_.AllowClientCertificate"))) $socket.ReceiveTimeout = $PodeContext.Server.Sockets.ReceiveTimeout if (!$_.IsIPAddress) { diff --git a/src/Private/SmtpServer.ps1 b/src/Private/SmtpServer.ps1 index eec5e486d..a71646d29 100644 --- a/src/Private/SmtpServer.ps1 +++ b/src/Private/SmtpServer.ps1 @@ -31,6 +31,7 @@ function Start-PodeSmtpServer Type = $_.Type Pool = $_.Runspace.PoolName Acknowledge = $_.Tcp.Acknowledge + SslProtocols = $_.Ssl.Protocols } # add endpoint to list @@ -48,7 +49,7 @@ function Start-PodeSmtpServer { # register endpoints on the listener $endpoints | ForEach-Object { - $socket = [PodeSocket]::new($_.Address, $_.Port, $PodeContext.Server.Sockets.Ssl.Protocols, [PodeProtocolType]::Smtp, $_.Certificate, $_.AllowClientCertificate, $_.TlsMode) + $socket = [PodeSocket]::new($_.Address, $_.Port, $_.SslProtocols, [PodeProtocolType]::Smtp, $_.Certificate, $_.AllowClientCertificate, $_.TlsMode) $socket.ReceiveTimeout = $PodeContext.Server.Sockets.ReceiveTimeout $socket.AcknowledgeMessage = $_.Acknowledge diff --git a/src/Private/TcpServer.ps1 b/src/Private/TcpServer.ps1 index d506184cb..773284117 100644 --- a/src/Private/TcpServer.ps1 +++ b/src/Private/TcpServer.ps1 @@ -27,6 +27,7 @@ function Start-PodeTcpServer Pool = $_.Runspace.PoolName Acknowledge = $_.Tcp.Acknowledge CRLFMessageEnd = $_.Tcp.CRLFMessageEnd + SslProtocols = $_.Ssl.Protocols } # add endpoint to list @@ -44,7 +45,7 @@ function Start-PodeTcpServer { # register endpoints on the listener $endpoints | ForEach-Object { - $socket = [PodeSocket]::new($_.Address, $_.Port, $PodeContext.Server.Sockets.Ssl.Protocols, [PodeProtocolType]::Tcp, $_.Certificate, $_.AllowClientCertificate, $_.TlsMode) + $socket = [PodeSocket]::new($_.Address, $_.Port, $_.SslProtocols, [PodeProtocolType]::Tcp, $_.Certificate, $_.AllowClientCertificate, $_.TlsMode) $socket.ReceiveTimeout = $PodeContext.Server.Sockets.ReceiveTimeout $socket.AcknowledgeMessage = $_.Acknowledge $socket.CRLFMessageEnd = $_.CRLFMessageEnd diff --git a/src/Public/Core.ps1 b/src/Public/Core.ps1 index cf8871625..a0ea55e0b 100644 --- a/src/Public/Core.ps1 +++ b/src/Public/Core.ps1 @@ -722,6 +722,9 @@ A quick description of the Endpoint - normally used in OpenAPI. .PARAMETER Acknowledge An optional Acknowledge message to send to clients when they first connect, for TCP and SMTP endpoints only. +.PARAMETER SslProtocol +One or more optional SSL Protocols this endpoints supports. (Default: SSL3/TLS12 - Just TLS12 on MacOS). + .PARAMETER CRLFMessageEnd If supplied, TCP endpoints will expect incoming data to end with CRLF. @@ -838,6 +841,11 @@ function Add-PodeEndpoint [string] $Acknowledge, + [Parameter()] + [ValidateSet('Ssl2', 'Ssl3', 'Tls', 'Tls11', 'Tls12', 'Tls13')] + [string[]] + $SslProtocol = $null, + [switch] $CRLFMessageEnd, @@ -946,7 +954,10 @@ function Add-PodeEndpoint HostName = $Hostname FriendlyName = $Hostname Url = $null - Ssl = (@('https', 'wss', 'smtps', 'tcps') -icontains $Protocol) + Ssl = @{ + Enabled = (@('https', 'wss', 'smtps', 'tcps') -icontains $Protocol) + Protocols = $PodeContext.Server.Sockets.Ssl.Protocols + } Protocol = $Protocol.ToLowerInvariant() Type = $type.ToLowerInvariant() Runspace = @{ @@ -965,6 +976,11 @@ function Add-PodeEndpoint } } + # set ssl protocols + if (!(Test-PodeIsEmpty $SslProtocol)) { + $obj.Ssl.Protocols = (ConvertTo-PodeSslProtocols -Protocols $SslProtocol) + } + # set the ip for the context (force to localhost for IIS) $obj.Address = (Get-PodeIPAddress $_endpoint.Host) $obj.IsIPAddress = [string]::IsNullOrWhiteSpace($obj.HostName) @@ -999,7 +1015,7 @@ function Add-PodeEndpoint # has this endpoint been added before? (for http/https we can just not add it again) $exists = ($PodeContext.Server.Endpoints.Values | Where-Object { - ($_.FriendlyName -ieq $obj.FriendlyName) -and ($_.Port -eq $obj.Port) -and ($_.Ssl -eq $obj.Ssl) -and ($_.Type -ieq $obj.Type) + ($_.FriendlyName -ieq $obj.FriendlyName) -and ($_.Port -eq $obj.Port) -and ($_.Ssl.Enabled -eq $obj.Ssl.Enabled) -and ($_.Type -ieq $obj.Type) } | Measure-Object).Count # if we're dealing with a certificate, attempt to import it From 1b01680e8cb5213dcee5e83340f0b3a9e7f6ff5d Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 7 Oct 2023 16:54:34 +0100 Subject: [PATCH 39/57] #1101: update docs to add SSL protos mention in Endpoints docs --- docs/Tutorials/Endpoints/Basics.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/Tutorials/Endpoints/Basics.md b/docs/Tutorials/Endpoints/Basics.md index f2e694ffe..9bd89a81d 100644 --- a/docs/Tutorials/Endpoints/Basics.md +++ b/docs/Tutorials/Endpoints/Basics.md @@ -79,6 +79,13 @@ The below example will create a local self-signed HTTPS endpoint: Add-PodeEndpoint -Address * -Port 8443 -Protocol Https -SelfSigned ``` +### SSL Protocols + +By default Pode will use the SSL3 or TLS12 protocols - or just TLS12 if on MacOS. You can override this default in one of two ways: + +1. Update the global default in Pode's configuration file, as [described here](../../Certificates#ssl-protocols). +2. Specify specific SSL Protocols to use per Endpoints using the `-SslProtocol` parameter on [`Add-PodeEndpoint`](../../../Functions/Core/Add-PodeEndpoint). + ## Endpoint Names You can give endpoints unique names by supplying the `-EndpointName` parameter. This name can then be passed to [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute) or [`Add-PodeStaticRoute`](../../../Functions/Routes/Add-PodeStaticRoute) to bind these routes to that endpoint only. From 36e471106f29ab6955abd9ed30fe7eb9e46319c4 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 7 Oct 2023 17:05:31 +0100 Subject: [PATCH 40/57] #1101: this one test is driving me nuts now --- tests/unit/Schedules.Tests.ps1 | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/unit/Schedules.Tests.ps1 b/tests/unit/Schedules.Tests.ps1 index 6cb0364cd..373dc01bf 100644 --- a/tests/unit/Schedules.Tests.ps1 +++ b/tests/unit/Schedules.Tests.ps1 @@ -220,16 +220,6 @@ Describe 'Get-PodeSchedule' { $schedules.Length | Should Be 0 } - It 'Returns no schedules by where end just before end' { - $PodeContext = @{ Schedules = @{ Items = @{} } } - $start = ([DateTime]::Now.AddHours(3)) - $end = ([DateTime]::Now.AddHours(5)) - - Add-PodeSchedule -Name 'test1' -Cron '@hourly' -ScriptBlock { Write-Host 'hello' } -StartTime $start -EndTime $end - $schedules = Get-PodeSchedule -StartTime $start.AddHours(1).AddMinutes(1) -EndTime $end.AddHours(-1).AddMinutes(-1) - $schedules.Length | Should Be 0 - } - It 'Returns 2 schedules by name' { $PodeContext = @{ Schedules = @{ Items = @{} } } $start = ([DateTime]::Now.AddHours(3)) From 11c90d2d087ce172ceb889a3c6371a8e9b4b8b35 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 7 Oct 2023 18:28:42 +0100 Subject: [PATCH 41/57] #1087: fix parsing of SMTP headers when there are multiple --- examples/mail-server.ps1 | 1 + src/Listener/PodeSmtpRequest.cs | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/examples/mail-server.ps1 b/examples/mail-server.ps1 index 336ac6f03..f08e52b3f 100644 --- a/examples/mail-server.ps1 +++ b/examples/mail-server.ps1 @@ -42,6 +42,7 @@ Start-PodeServer -Threads 2 { Write-Host '|' $SmtpEvent.Email | Out-Default $SmtpEvent.Request | out-default + $SmtpEvent.Email.Headers | out-default Write-Host '- - - - - - - - - - - - - - - - - -' } diff --git a/src/Listener/PodeSmtpRequest.cs b/src/Listener/PodeSmtpRequest.cs index 56582c47d..a2b49eadc 100644 --- a/src/Listener/PodeSmtpRequest.cs +++ b/src/Listener/PodeSmtpRequest.cs @@ -320,14 +320,34 @@ private Hashtable ParseHeaders(string value) if (match.Success) { previousHeader = match.Groups["name"].Value; - headers.Add(match.Groups["name"].Value, match.Groups["value"].Value); + if (headers.ContainsKey(previousHeader)) + { + if (!(headers[previousHeader] is List)) + { + headers[previousHeader] = new List() { headers[previousHeader] }; + } + + ((List)headers[previousHeader]).Add(match.Groups["value"].Value); + } + else + { + headers.Add(previousHeader, match.Groups["value"].Value); + } } else { match = Regex.Match(line, "^(?.*?)\\:\\s+"); if (!match.Success) { - headers[previousHeader] += line; + if (headers[previousHeader] is List) + { + var values = (List)headers[previousHeader]; + values[values.Count - 1] += line; + } + else + { + headers[previousHeader] += line; + } } } From fbcf4b4b0d8c1de82941ba5a279a6d10f4d83a48 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 7 Oct 2023 18:46:51 +0100 Subject: [PATCH 42/57] #1141: update IIS docs for max worker processes --- docs/Hosting/IIS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Hosting/IIS.md b/docs/Hosting/IIS.md index 2620d8549..ef3c68247 100644 --- a/docs/Hosting/IIS.md +++ b/docs/Hosting/IIS.md @@ -129,6 +129,10 @@ This allows you to write a Pode server that works locally, but will also automat !!! note This does mean that Pode will force all endpoints to `127.0.0.1:PORT`. So if you had two different IPs before, they'll be merged into one. Something to be aware of if you assign routes to specific endpoints, as under IIS this won't work. +### Maximum Worker Processes + +Unless you're using an external data store for sessions, ensure the Maximum Worker Processes is 1. Each worker process will spawn a new instance of your Pode server, and if using Pode's inbuilt session storage you'll face authenticated/session timeout issues when one instance doesn't contain the right session. + ### Advanced/Domain/Kerberos The above IIS site setup works, but only for simple sites. If you require the use of the Active Directory module, or your site to be running as a different user then follow the steps below. From 1f72176a01662c514f0ae918e69412461cbd45f2 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 7 Oct 2023 18:54:40 +0100 Subject: [PATCH 43/57] #1123: add link to SecretManagement automation docs --- docs/Tutorials/Secrets/Overview.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Tutorials/Secrets/Overview.md b/docs/Tutorials/Secrets/Overview.md index e652d9e85..2aa9ae7d5 100644 --- a/docs/Tutorials/Secrets/Overview.md +++ b/docs/Tutorials/Secrets/Overview.md @@ -36,6 +36,9 @@ At present there are just two registration types implemented for registering sec * [Secret Management](../Types/SecretManagement) (powershell module) * [Custom](../Types/Custom) +!!! tip + For the SecretManagement module you can read a "getting started" [guide for it here](https://learn.microsoft.com/en-us/powershell/utility-modules/secretmanagement/how-to/using-secrets-in-automation?view=ps-modules) when using the module in automated scenarios. + ### Initialise If there is any logic that needs to be invoked before a vault is registered, such as connecting to a cloud provider first (ie: `Connect-AzAccount`), this can be achieved via the `-InitScriptBlock` parameter on [`Register-PodeSecretVault`](../../../Functions/Secrets/Register-PodeSecretVault). @@ -191,7 +194,6 @@ Mount-PodeSecret -Name 'SecretName2' -Vault 'VaultName' -Key 'SecretKeyNameInVau There is also support for creating/updating, retrieving, and removing secrets in an adhoc manor from registered vaults - without having to mount them. - ### Create To create a new secret, and well as update an existing ones value, you can use [`Set-PodeSecret`](../../../Functions/Secrets/Set-PodeSecret): From 90055d4d09d86d09eb24cc145d6a4dc99695e535 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 7 Oct 2023 18:58:51 +0100 Subject: [PATCH 44/57] #1099: reference the Protected Users group in Windows AD --- docs/Tutorials/Authentication/Inbuilt/WindowsAD.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Tutorials/Authentication/Inbuilt/WindowsAD.md b/docs/Tutorials/Authentication/Inbuilt/WindowsAD.md index 7300ea855..e6a2c77b2 100644 --- a/docs/Tutorials/Authentication/Inbuilt/WindowsAD.md +++ b/docs/Tutorials/Authentication/Inbuilt/WindowsAD.md @@ -149,3 +149,7 @@ New-PodeAuthScheme -Form | Add-PodeAuthWindowsAd -Name 'Login' -ScriptBlock { return @{ Message = 'Authorisation failed' } } ``` + +## Protected Users + +In Windows AD there is a "Protected Users" group that you can assign users into. If users in this group are trying to use your site, then they will fail authentication. Unfortunately, this is just a secure feature of Windows AD, and the only way around this is to take the affected users out of the Protected Users group. From 634258820002a8ffcb9d9be4f9b119661abf634b Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 7 Oct 2023 19:27:11 +0100 Subject: [PATCH 45/57] #1078: add dates to release notes --- docs/release-notes.md | 146 ++++++++++++++++++++++++++++++++++++++++++ docs/roadmap.md | 6 +- 2 files changed, 149 insertions(+), 3 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 3a8fe9f11..3603552dc 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,8 @@ ## v2.8.0 +Date: 2nd February 2023 + ```plain ### Features * #980: Adds support for Secret Management, either via the SecretManagement module or using custom logic @@ -36,6 +38,8 @@ ## v2.7.2 +Date: 18th October 2022 + ```plain ### Enhancements * #1002: Adds a `-KeepCredential` switch for `Add-PodeAuthWindowsAd` (thanks @TheBakaBandit!) @@ -52,6 +56,8 @@ ## v2.7.1 +Date: 21st July 2022 + ```plain ### Bugs * #990: Fix SMTP attachment name parsing, when the name contains a space @@ -62,6 +68,8 @@ ## v2.7.0 +Date: 22nd June 2022 + ```plain ### Features * #895: Add support for server to be able to connect to external WebSockets @@ -91,6 +99,8 @@ ## v2.6.2 +Date: 2nd March 2022 + ```plain ### Bugs * #948: Hotfix to resolve issue with importing ActiveDirectory module into runspaces @@ -98,6 +108,8 @@ ## v2.6.1 +Date: 21st February 2022 + ```plain ### Bugs * #915: Fix regex issue preventing Pode listening on IPv6 addresses @@ -112,6 +124,8 @@ ## v2.6.0 +Date: 10th February 2022 + ```plain ### Features * #893: Add async/sync Task support @@ -138,6 +152,8 @@ ## v2.5.2 +Date: 4th January 2022 + ```plain ### Bugs * #892: Fixes a bug with importing modules, where the wrong file was being used @@ -145,6 +161,8 @@ ## v2.5.1 +Date: 21st December 2021 + ```plain ### Bugs * #877: Fix for `ConvertFrom-PodeJwt` expecting string not byte[] @@ -161,6 +179,8 @@ ## v2.5.0 +Date: 13th November 2021 + ```plain ### Enhancements * #771: Adds more `Use-PodeXYZ` functions for auto-loading scripts @@ -199,6 +219,8 @@ ## v2.4.2 +Date: 13th September 2021 + ```plain ### Bugs * #810: Fixes a Local/UTC datetime issue on Cookies, expiring sessions early @@ -214,6 +236,8 @@ ## v2.4.1 +Date: 9th August 2021 + ```plain ### Enhancements * #801: Add new `-SearchBase` parameter to `Add-PodeAuthWindowsAD` for OpenLDAP @@ -231,6 +255,8 @@ ## v2.4.0 +Date: 21st July 2021 + ```plain ### Features * #766: Add support for server Event Hooks, to run scripts on events like terminating the server @@ -255,6 +281,8 @@ ## v2.3.0 +Date: 1st June 2021 + ```plain ### Features * #723: Add support for logging to Windows Event Viewer @@ -277,6 +305,8 @@ ## v2.2.3 +Date: 10th April 2021 + ```plain ### Bugs * #736: Fix issue with v2.2.2 PowerShell Gallery packaging @@ -284,6 +314,8 @@ ## v2.2.2 +Date: 9th April 2021 + ```plain ### Enhancements * #727: Allow referencing an OpenAPI component schema from another component schema (thanks @glatzert!) @@ -295,6 +327,8 @@ ## v2.2.1 +Date: 27th March 2021 + ```plain ### Bugs * #716: Fix bug with `$TimerEvent` object within Timers @@ -304,6 +338,8 @@ ## v2.2.0 +Date: 21st March 2021 + ```plain ### Features * #682: Add support for the HTTP Range request header @@ -325,6 +361,8 @@ ## v2.1.1 +Date: 19th February 2021 + ```plain ### Enhancements * #693: Add OperationId OpenAPI support on routes (thanks @glatzert) @@ -341,6 +379,8 @@ ## v2.1.0 +Date: 3rd February 2021 + ```plain ### Enhancements * #655: Update the Socket Listener to handle larger request payloads, and fix receiving SSL requests @@ -368,6 +408,8 @@ ## v2.0.3 +Date: 21st December 2020 + ```plain ### Bugs * #641: Fix an issue with Invalid Request Lines being received when running via SSL and using a Proxy @@ -379,6 +421,8 @@ ## v2.0.2 +Date: 5th December 2020 + ```plain ### Bugs * #636: Fixes bug with OAuth2 RedirectUrl when behind IIS @@ -386,6 +430,8 @@ ## v2.0.1 +Date: 29th November 2020 + ```plain ### Bugs * #631: Parse username during Windows AD authentication @@ -394,6 +440,8 @@ ## v2.0.0 +Date: 14th September 2020 + ```plain ### Features * #472: Adds support for client certificate authentication @@ -425,6 +473,8 @@ ## v1.8.4 +Date: 16th October 2020 + ```plain ### Bugs * #615: Fixes a bug with Azure Functions V3, where the sys property has now been removed @@ -432,6 +482,8 @@ ## v1.8.3 +Date: 20th September 2020 + ```plain ### Enhancements * #602: Adds a new `Remove-PodeOAResponse` function to allow removing of default responses @@ -440,6 +492,8 @@ ## v1.8.2 +Date: 31st July 2020 + ```plain ### Bugs * #594: Add `Import-PodeSnapIn` to FunctionsToExport list @@ -447,6 +501,8 @@ ## v1.8.1 +Date: 26th June 2020 + ```plain ### Bugs * #578: Fixes OpenAPI functions with rogue "=" on returning a value @@ -455,6 +511,8 @@ ## v1.8.0 +Date: 24th May 2020 + ```plain ### Enhancements * #533: Support on states for inclusion/exlcusions when saving, and scopes on states @@ -481,6 +539,8 @@ ## v1.7.3 +Date: 10th May 2020 + ```plain ### Bugs * #554: Fixes an issue where HTML static files would be treated as dynamic files @@ -488,6 +548,8 @@ ## v1.7.2 +Date: 27th April 2020 + ```plain ### Bugs * #543: Fixes an internal issue that was causing errors in the SMTP server @@ -495,6 +557,8 @@ ## v1.7.1 +Date: 17th April 2020 + ```plain ### Bugs * #534: Fixes an issue with IIS Windows Authentication when using foreign trusted domains (thanks @RobinBeismann!) @@ -502,6 +566,8 @@ ## v1.7.0 +Date: 10th April 2020 + ```plain ### Features * #504: Support for GZip and Deflate compression on Requests @@ -525,6 +591,8 @@ ## v1.6.1 +Date: 7th March 2020 + ```plain ### Bugs * 495: Fix issue with parsing query strings when using the Pode server type @@ -536,6 +604,8 @@ ## v1.6.0 +Date: 3rd March 2020 + ```plain ### Features * #464: Request metrics for routes for the number of requests @@ -556,6 +626,8 @@ ## v1.5.0 +Date: 2nd February 2020 + ```plain ### Features * #218: Adds OpenAPI with Swagger and ReDoc support @@ -574,6 +646,8 @@ ## v1.4.0 +Date: 10th January 2020 + ```plain ### Enhancements * #447: Sessions can now be used via Headers for better CLI support @@ -589,6 +663,8 @@ ## v1.3.0 +Date: 27th December 2019 + ```plain ### Enhancements * #421: Adds a new `-FilePath` parameter to the `Add-PodeTimer` and `Add-PodeSchedule` functions @@ -606,6 +682,8 @@ ## v1.2.1 +Date: 2nd December 2019 + ```plain ### Enhancements * #415: New functions for invoking Timer and Schedules adhoc @@ -619,6 +697,8 @@ ## v1.2.0 +Date: 13th November 2019 + ```plain ### Features * #395: Built-in support for using Server-to-Client websockets @@ -640,6 +720,8 @@ ## v1.1.0 +Date: 28th September 2019 + ```plain ### Features * #376: *Experimental* support for cross-platform HTTPS! @@ -655,6 +737,8 @@ ## v1.0.1 +Date: 4th September 2019 + ```plain ### Bugs * #367: If a "server.psd1" file is not present, Logging will not work @@ -663,6 +747,8 @@ ## v1.0.0 +Date: 2nd September 2019 + ```plain ### Features * #228: Support for rendering Markdown as HTML (Fully supported in PowerShell 7+) @@ -702,6 +788,8 @@ ## v0.32.0 +Date: 28th June 2019 + ```plain ### Enhancements * #270: Support on `gui` to specify the width and height of the window @@ -717,6 +805,8 @@ ## v0.31.0 +Date: 11th June 2019 + ```plain ### Features * #264: Support for Azure Functions and AWS Lambda @@ -732,6 +822,8 @@ ## v0.30.0 +Date: 26th May 2019 + ```plain ### Enhancements * #245: Support for Windows AD group validation on the inbuilt 'windows-ad' authentication validator @@ -748,6 +840,8 @@ ## v0.29.0 +Date: 10th May 2019 + ```plain ### Enhancements * #216: Multi-content-type support on Error Pages @@ -765,6 +859,8 @@ ## v0.28.1 +Date: 16th April 2019 + ```plain ### Bugs * #226: Adds the "gui" function to export list @@ -772,6 +868,8 @@ ## v0.28.0 +Date: 13th April 2019 + ```plain ### Features * #210: New "cookie" function added, to support setting/getting cookies - including signing them @@ -790,6 +888,8 @@ ## v0.27.3 +Date: 4th April 2019 + ```plain ### Bugs * #217: Binding to hostname throws error @@ -797,6 +897,8 @@ ## v0.27.2 +Date: 27th March 2019 + ```plain ### Bugs * #212: Incorrect variable name used in html, csv, xml and json functions when referencing files @@ -804,6 +906,8 @@ ## v0.27.1 +Date: 16th March 2019 + ```plain ### Bugs * #199: Fix issues with relative paths when running server as a service @@ -812,6 +916,8 @@ ## v0.27.0 +Date: 14th March 2019 + ```plain ### Features * #185: Support for Server Restarts either Periodically or at specific Times, with support for cron expressions @@ -835,6 +941,8 @@ ## v0.26.0 +Date: 17th February 2019 + ```plain ### Features * #162: Basic support for local modules in "package.json" on "pode install" @@ -846,6 +954,8 @@ ## v0.25.0 +Date: 5th February 2019 + ```plain ### Features * #170: Support for Static Content Caching, with ability to include/exclude routes/extensions @@ -864,6 +974,8 @@ ## v0.24.0 +Date: 18th January 2019 + ```plain ### Features * #125: Helper support function for uploading files from a web form @@ -879,6 +991,8 @@ ## v0.23.0 +Date: 24th December 2018 + ```plain ### Features * #77: Ability to run a web server, and view it through a Desktop Application (Windows only) @@ -893,6 +1007,8 @@ ## v0.22.0 +Date: 7th December 2018 + ```plain ### Enhancements * #123: Ability to remove a `route` @@ -904,6 +1020,8 @@ ## v0.21.0 +Date: 2nd November 2018 + ```plain ### Enhancements * #110: Return a 401 for inaccessible files @@ -922,6 +1040,8 @@ ## v0.20.0 +Date: 20th October 2018 + ```plain ### Documentation * Extended documentation for third-party template engines @@ -940,6 +1060,8 @@ ## v0.19.1 +Date: 9th October 2018 + ```plain ### Documentation * #91: This release contains far better documentation for Pode: https://badgerati.github.io/Pode @@ -955,6 +1077,8 @@ ## v0.19.0 +Date: 14th September 2018 + ```plain ### Features * #84: Session cookie support, with in-mem/custom data storage @@ -966,6 +1090,8 @@ ## v0.18.0 +Date: 25th August 2018 + ```plain ### Features * #78: Middleware support for web servers, allowing custom logic and extension modules on web request/responses @@ -976,6 +1102,8 @@ ## v0.17.0 +Date: 19th August 2018 + ```plain ### Features * #43: Ability to generate self-signed certificates, and bind those certs - or pre-installed certs - when using HTTPS @@ -988,6 +1116,8 @@ ## v0.16.0 +Date: 8th August 2018 + ```plain ### Features * #66: Support for basic rate limiting of requests per x seconds from IPs @@ -1000,6 +1130,8 @@ ## v0.15.0 +Date: 13th July 2018 + ```plain ### Features @@ -1013,6 +1145,8 @@ ## v0.14.0 +Date: 6th July 2018 + ```plain ### Features * #21: Ability for Pode to Internally Restart when a File Change is Detected @@ -1028,6 +1162,8 @@ ## v0.13.0 +Date: 23rd June 2018 + ```plain ### Features * #40: Ability to add variables to a shared state, so you can re-use variables in timers, loggers, and routes @@ -1035,6 +1171,8 @@ ## v0.12.0 +Date: 15th June 2018 + ```plain ### Features * #33: Support for logging to the terminal, files, and custom loggers for LogStash/Fluentd/etc @@ -1047,6 +1185,8 @@ ## v0.11.3 +Date: 10th June 2018 + ```plain ### Bugs and Enhancements * #22: Proper fix for high CPU usage, by using `Task.Wait` with `CancellationTokens`; A Runspace is setup to monitor for key presses, and on `Ctrl+C` will `Cancel()` the token and terminate Pode @@ -1054,6 +1194,8 @@ ## v0.11.2 +Date: 8th June 2018 + ```plain ### Bugs * #22: Hot fix patch for reducing high CPU usage when idle @@ -1061,6 +1203,8 @@ ## v0.11.1 +Date: 1st June 2018 + ```plain ### Bugs * #16: Status and Include functions were missing from module export list @@ -1068,6 +1212,8 @@ ## v0.11.0 +Date: 30th May 2018 + ```plain ### Features * #5: Async timers to run tasks and processes in a separate thread (see timers sections in README) diff --git a/docs/roadmap.md b/docs/roadmap.md index 2e2b1049d..426035f3e 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -31,15 +31,15 @@ Sometimes there could be more, if patch releases are needed. But sometimes there - [ ] HTTP/3.0 support - [x] Inbuilt authorization support, on top the current authentications support [#992](https://github.com/Badgerati/Pode/issues/992) - [x] Secret management support [#980](https://github.com/Badgerati/Pode/issues/980) -- [ ] Some way of being able to merge authentication types [588](https://github.com/Badgerati/Pode/issues/588) +- [x] Some way of being able to merge authentication types [588](https://github.com/Badgerati/Pode/issues/588) - [ ] Improved garbage collection in runspaces, to help free up memory - [ ] A Session Pool that can be used to port/re-use PSSessions in Pode more easily - [ ] Further improvements to OIDC, such as HMAC and refresh token support - [ ] Implement an inbuilt FTP(S) server - [ ] Is it possible to implement an inbuilt SFTP server? - [ ] Inbuilt connectors for connecting to message brokers, like Kafka, RabbitMQ, etc. -- [ ] Would is be possible to create an inbuilt pub/sub server? -- [x] An inbuilt FIM server, so we can fun logic on FIM events +- [ ] Would it be possible to create an inbuilt pub/sub server? +- [x] An inbuilt FIM server, so we can run logic on FIM events ### Misc From 9ae1a524acfbc46bd7b12b70ce1e807b08844194 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sun, 8 Oct 2023 16:57:43 +0100 Subject: [PATCH 46/57] #1130: when available, add the username to the request log output --- examples/rest-api.ps1 | 3 +++ examples/web-auth-basic.ps1 | 4 ++++ examples/web-auth-merged.ps1 | 4 ++++ src/Private/Logging.ps1 | 32 +++++++++++++++++++++++++++++++- src/Public/Authentication.ps1 | 2 +- src/Public/Logging.ps1 | 15 +++++++++++++++ 6 files changed, 58 insertions(+), 2 deletions(-) diff --git a/examples/rest-api.ps1 b/examples/rest-api.ps1 index 9599c37bc..1f0793544 100644 --- a/examples/rest-api.ps1 +++ b/examples/rest-api.ps1 @@ -9,6 +9,9 @@ Start-PodeServer { Add-PodeEndpoint -Address * -Port 8086 -Protocol Http + # request logging + New-PodeLoggingMethod -Terminal -Batch 10 -BatchTimeout 10 | Enable-PodeRequestLogging + # can be hit by sending a GET request to "localhost:8086/api/test" Add-PodeRoute -Method Get -Path '/api/test' -ScriptBlock { Write-PodeJsonResponse -Value @{ 'hello' = 'world'; } diff --git a/examples/web-auth-basic.ps1 b/examples/web-auth-basic.ps1 index d28742089..fed990bee 100644 --- a/examples/web-auth-basic.ps1 +++ b/examples/web-auth-basic.ps1 @@ -25,6 +25,9 @@ Start-PodeServer -Threads 2 { # listen on localhost:8085 Add-PodeEndpoint -Address * -Port 8085 -Protocol Http + # request logging + New-PodeLoggingMethod -Terminal -Batch 10 -BatchTimeout 10 | Enable-PodeRequestLogging + # setup basic auth (base64> username:password in header) New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Sessionless -ScriptBlock { param($username, $password) @@ -33,6 +36,7 @@ Start-PodeServer -Threads 2 { if ($username -eq 'morty' -and $password -eq 'pickle') { return @{ User = @{ + Username = 'morty' ID ='M0R7Y302' Name = 'Morty' Type = 'Human' diff --git a/examples/web-auth-merged.ps1 b/examples/web-auth-merged.ps1 index 23cd775dc..e6e664449 100644 --- a/examples/web-auth-merged.ps1 +++ b/examples/web-auth-merged.ps1 @@ -17,6 +17,9 @@ Start-PodeServer -Threads 2 { Add-PodeEndpoint -Address * -Port 8085 -Protocol Http New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging + # request logging + New-PodeLoggingMethod -Terminal -Batch 10 -BatchTimeout 10 | Enable-PodeRequestLogging + # setup access Add-PodeAuthAccess -Type Role -Name 'Rbac' Add-PodeAuthAccess -Type Group -Name 'Gbac' @@ -51,6 +54,7 @@ Start-PodeServer -Threads 2 { if ($username -eq 'morty' -and $password -eq 'pickle') { return @{ User = @{ + Username = 'morty' ID ='M0R7Y302' Name = 'Morty' Type = 'Human' diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 5241e7ce0..ffbd77b37 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -253,7 +253,7 @@ function Test-PodeRequestLoggingEnabled function Write-PodeRequestLog { - param ( + param( [Parameter(Mandatory=$true)] $Request, @@ -291,10 +291,40 @@ function Write-PodeRequestLog } } + # set size if >0 if ($Response.ContentLength64 -gt 0) { $item.Response.Size = $Response.ContentLength64 } + # set username - dot spaces + if (Test-PodeAuthUser -IgnoreSession) { + $userProps = (Get-PodeLogger -Name $name).Properties.Username.Split('.') + $user = $null + + if (!$WebEvent.Auth.Multiple) { + $user = $WebEvent.Auth.User + foreach ($atom in $userProps) { + $user = $user.($atom) + } + } + else { + foreach ($u in $WebEvent.Auth.User.Values) { + $user = $u + foreach ($atom in $userProps) { + $user = $user.($atom) + } + + if (![string]::IsNullOrWhiteSpace($user)) { + break + } + } + } + + if (![string]::IsNullOrWhiteSpace($user)) { + $item.User = $user -ireplace '\s+', '.' + } + } + # add the item to be processed $null = $PodeContext.LogsToProcess.Add(@{ Name = $name diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index d275c7ca0..b4987b541 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -2489,7 +2489,7 @@ function Add-PodeAuthAccess } # default path - if ([string]::IsNullOrEmpty($Path)) { + if ([string]::IsNullOrWhiteSpace($Path)) { if ($Type -ieq 'user') { $Path = 'Username' } diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 820780033..20c32a7d3 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -231,6 +231,9 @@ Enables Request Logging using a supplied output method. .PARAMETER Method The Method to use for output the log entry (From New-PodeLoggingMethod). +.PARAMETER UsernameProperty +An optional property path within the $WebEvent.Auth.User object for the user's Username. (Default: Username). + .PARAMETER Raw If supplied, the log item returned will be the raw Request item as a hashtable and not a string (for Custom methods). @@ -245,6 +248,10 @@ function Enable-PodeRequestLogging [hashtable] $Method, + [Parameter()] + [string] + $UsernameProperty, + [switch] $Raw ) @@ -263,10 +270,18 @@ function Enable-PodeRequestLogging throw "The supplied output Method for Request Logging requires a valid ScriptBlock" } + # username property + if ([string]::IsNullOrWhiteSpace($UsernameProperty)) { + $UsernameProperty = 'Username' + } + # add the request logger $PodeContext.Server.Logging.Types[$name] = @{ Method = $Method ScriptBlock = (Get-PodeLoggingInbuiltType -Type Requests) + Properties = @{ + Username = $UsernameProperty + } Arguments = @{ Raw = $Raw } From c26902e62053a0cfcdda133c4c226718d3c2cf95 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sun, 8 Oct 2023 17:10:49 +0100 Subject: [PATCH 47/57] #1130: update logs --- docs/Tutorials/Logging/Types/Requests.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/Tutorials/Logging/Types/Requests.md b/docs/Tutorials/Logging/Types/Requests.md index 627123c4c..3f31ffbaa 100644 --- a/docs/Tutorials/Logging/Types/Requests.md +++ b/docs/Tutorials/Logging/Types/Requests.md @@ -31,6 +31,22 @@ $method = New-PodeLoggingMethod -Custom -ScriptBlock { $method | Enable-PodeRequestLogging -Raw ``` +### Username + +If you're not using any Authentication then the "user" field in the log will always be "-". However, if you're using Authentication, and it passes, then the Username of the user accessing the Route will attempt to be retrieved from `$WebEvent.Auth.User`. The property within the authenticated user object by default is `Username`, but you can customise this using `-UsernameProperty`. + +For example, if the username was actually user "ID": + +```powershell +Enable-PodeRequestLogging -UsernameProperty 'ID' +``` + +Or if the username was inside another "Meta" property, and then within a "Username" property inside the Meta object: + +```powershell +Enable-PodeRequestLogging -UsernameProperty 'Meta.Username' +``` + ## Raw Request The raw Request hashtable that will be supplied to any Custom logging methods will look as follows: From cd661363000f17ea9acfcbc1a4f58ab85b470134 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sun, 8 Oct 2023 18:34:59 +0100 Subject: [PATCH 48/57] #1163: add initial session auth func --- examples/web-auth-form-session-auth.ps1 | 87 +++++++++++++++ src/Pode.psd1 | 1 + src/Private/Authentication.ps1 | 3 +- src/Private/OpenApi.ps1 | 1 + src/Public/Authentication.ps1 | 142 ++++++++++++++++++++++++ 5 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 examples/web-auth-form-session-auth.ps1 diff --git a/examples/web-auth-form-session-auth.ps1 b/examples/web-auth-form-session-auth.ps1 new file mode 100644 index 000000000..aee830686 --- /dev/null +++ b/examples/web-auth-form-session-auth.ps1 @@ -0,0 +1,87 @@ +$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) +Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop + +# or just: +# Import-Module Pode + +<# +This examples shows how to use session persistant authentication, for things like logins on websites. +The example used here is Form authentication, sent from the in HTML. But also used is Session Authentication +on the main home page route and Form Auth on Login. + +Navigating to the 'http://localhost:8085' endpoint in your browser will auto-rediect you to the '/login' +page. Here, you can type the username (morty) and the password (pickle); clicking 'Login' will take you +back to the home page with a greeting and a view counter. Clicking 'Logout' will purge the session and +take you back to the login page. +#> + +# create a server, and start listening on port 8085 +Start-PodeServer -Threads 2 { + + # listen on localhost:8085 + Add-PodeEndpoint -Address * -Port 8085 -Protocol Http + + # set the view engine + Set-PodeViewEngine -Type Pode + + # enable error logging + New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging + + # setup session details + Enable-PodeSessionMiddleware -Duration 120 -Extend + + # setup form auth ( in HTML) + New-PodeAuthScheme -Form | Add-PodeAuth -Name 'Login' -FailureUrl '/login' -SuccessUrl '/' -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if ($username -eq 'morty' -and $password -eq 'pickle') { + return @{ + User = @{ + ID ='M0R7Y302' + Name = 'Morty' + Type = 'Human' + } + } + } + + return @{ Message = 'Invalid details supplied' } + } + + # setup session auth + Add-PodeAuthSession -Name 'SessionAuth' -FailureUrl '/login' + + + # home page: + # redirects to login page if not authenticated + Add-PodeRoute -Method Get -Path '/' -Authentication SessionAuth -ScriptBlock { + $session:Views++ + + Write-PodeViewResponse -Path 'auth-home' -Data @{ + Username = $WebEvent.Auth.User.Name + Views = $session:Views + Expiry = Get-PodeSessionExpiry + } + } + + + # login page: + # the login flag set below checks if there is already an authenticated session cookie. If there is, then + # the user is redirected to the home page. If there is no session then the login page will load without + # checking user authetication (to prevent a 401 status) + Add-PodeRoute -Method Get -Path '/login' -Authentication Login -Login -ScriptBlock { + Write-PodeViewResponse -Path 'auth-login' -FlashMessages + } + + + # login check: + # this is the endpoint the 's action will invoke. If the user validates then they are set against + # the session as authenticated, and redirect to the home page. If they fail, then the login page reloads + Add-PodeRoute -Method Post -Path '/login' -Authentication Login -Login + + + # logout check: + # when the logout button is click, this endpoint is invoked. The logout flag set below informs this call + # to purge the currently authenticated session, and then redirect back to the login page + Add-PodeRoute -Method Post -Path '/logout' -Authentication SessionAuth -Logout +} \ No newline at end of file diff --git a/src/Pode.psd1 b/src/Pode.psd1 index e5c776f3a..f98c7e204 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -235,6 +235,7 @@ 'Test-PodeAuthExists', 'Merge-PodeAuthAccess', 'Get-PodeAuthUser', + 'Add-PodeAuthSession', # logging 'New-PodeLoggingMethod', diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index bbae97780..4d1af064d 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1228,7 +1228,7 @@ function Test-PodeAuthValidation $result.Headers = @{} } - if (!$result.Headers.ContainsKey('WWW-Authenticate')) { + if (![string]::IsNullOrWhiteSpace($auth.Scheme.Name) -and !$result.Headers.ContainsKey('WWW-Authenticate')) { $authHeader = Get-PodeAuthWwwHeaderValue -Name $auth.Scheme.Name -Realm $auth.Scheme.Realm -Challenge $result.Challenge $result.Headers['WWW-Authenticate'] = $authHeader } @@ -1423,6 +1423,7 @@ function Test-PodeAuthInternal # existing session auth'd if (Test-PodeAuthUser) { $WebEvent.Auth = $WebEvent.Session.Data.Auth + #TODO: ACCESS CHECK HERE return (Set-PodeAuthStatus ` -Name $Name ` -LoginRoute:($Login) ` diff --git a/src/Private/OpenApi.ps1 b/src/Private/OpenApi.ps1 index 68b4d7980..7749152aa 100644 --- a/src/Private/OpenApi.ps1 +++ b/src/Private/OpenApi.ps1 @@ -274,6 +274,7 @@ function Get-PodeOpenApiDefinitionInternal foreach ($authName in $PodeContext.Server.Authentications.Methods.Keys) { $authType = (Find-PodeAuth -Name $authName).Scheme + #TODO: SUPPORT MUTLI-AUTH HERE, IF NO SCHEME SKIP $_authName = ($authName -replace '\s+', '') $_authObj = @{} diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index b4987b541..1f00ed328 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -898,6 +898,11 @@ function Merge-PodeAuth $SuccessUseOrigin ) + #TODO: NEW SCRIPTBLOCK TO MERGE ALL USERS INTO ONE OBJECT + # WAY SIMPLER!! + # ScriptBlock should get @{users} hashtable, and @(authNames) array for the users/auths that passed + # This would remove the ".Multiple" nonsense property in WE.Auth.User + # ensure the name doesn't already exist if (Test-PodeAuthExists -Name $Name) { throw "Authentication method already defined: $($Name)" @@ -1339,6 +1344,139 @@ function Add-PodeAuthWindowsAd } } +<# +.SYNOPSIS +Adds the inbuilt Session Authentication method for verifying an authenticated session is present on Requests. + +.DESCRIPTION +Adds the inbuilt Session Authentication method for verifying an authenticated session is present on Requests. + +.PARAMETER Name +A unique Name for the Authentication method. + +.PARAMETER FailureUrl +The URL to redirect to when authentication fails. + +.PARAMETER FailureMessage +An override Message to throw when authentication fails. + +.PARAMETER SuccessUrl +The URL to redirect to when authentication succeeds when logging in. + +.PARAMETER ScriptBlock +Optional ScriptBlock that is passed the found user object for further validation. + +.PARAMETER Middleware +An array of ScriptBlocks for optional Middleware to run before the Scheme's scriptblock. + +.PARAMETER SuccessUseOrigin +If supplied, successful authentication from a login page will redirect back to the originating page instead of the FailureUrl. + +.EXAMPLE +Add-PodeAuthSession -Name 'SessionAuth' -FailureUrl '/login' +#> +function Add-PodeAuthSession +{ + [CmdletBinding(DefaultParameterSetName='Groups')] + param ( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter()] + [string] + $FailureUrl, + + [Parameter()] + [string] + $FailureMessage, + + [Parameter()] + [string] + $SuccessUrl, + + [Parameter()] + [scriptblock] + $ScriptBlock, + + [Parameter()] + [object[]] + $Middleware, + + [switch] + $SuccessUseOrigin + ) + + # if sessions haven't been setup, error + if (!(Test-PodeSessionsConfigured)) { + throw 'Sessions have not been configured' + } + + # ensure the name doesn't already exist + if (Test-PodeAuthExists -Name $Name) { + throw "Authentication method already defined: $($Name)" + } + + # if we have a scriptblock, deal with using vars + if ($null -ne $ScriptBlock) { + $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + } + + # create the auth scheme for getting the session + $scheme = New-PodeAuthScheme -Custom -Middleware $Middleware -ScriptBlock { + param($options) + + # 401 if sessions not used + if (!(Test-PodeSessionsInUse)) { + Revoke-PodeSession + return @{ + Message = "Sessions are not being used" + Code = 401 + } + } + + # 401 if no authenticated user + if (!(Test-PodeAuthUser)) { + Revoke-PodeSession + return @{ + Message = "Session not authenticated" + Code = 401 + } + } + + # return user + return @($WebEvent.Session.Data.Auth) + } + + # add a custom auth method to return user back + $method = { + param($user, $options) + $result = @{ User = $user } + + # call additional scriptblock if supplied + if ($null -ne $options.ScriptBlock.Script) { + $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $options.ScriptBlock.Script -UsingVariables $options.ScriptBlock.UsingVariables + } + + # return user back + return $result + } + + $scheme | Add-PodeAuth ` + -Name $Name ` + -ScriptBlock $method ` + -FailureUrl $FailureUrl ` + -FailureMessage $FailureMessage ` + -SuccessUrl $SuccessUrl ` + -SuccessUseOrigin:$SuccessUseOrigin ` + -ArgumentList @{ + ScriptBlock = @{ + Script = $ScriptBlock + UsingVariables = $usingVars + } + } +} + <# .SYNOPSIS Remove a specific Authentication method. @@ -2456,6 +2594,10 @@ function Add-PodeAuthAccess $Match = 'One' ) + #TODO: MOVE ALL THESE INTO AN ACCESS.PS1 FILE + # RENAME TO Add-PodeAccess + # THEY SHOULD BE SEPARATE MIDDLEWARE, NOT TIED TO AUTHENTICATION + # check name unique if (Test-PodeAuthAccessExists -Name $Name) { throw "Access method already defined: $($Name)" From 30fe4a9287c10957be4a2e41ceab4402fa3418e9 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Wed, 11 Oct 2023 22:58:20 +0100 Subject: [PATCH 49/57] #1163: slight rewrite of access logic: move to own file out of auth, make own middleware, update docs and examples --- .../Authentication/Inbuilt/Session.md | 59 ++ docs/Tutorials/Authentication/Overview.md | 2 +- docs/Tutorials/Authorisation/Overview.md | 109 +-- examples/public/styles/simple.css | 8 + examples/tcp-server-auth.ps1 | 4 +- examples/views/auth-about.pode | 23 + examples/views/auth-home.pode | 5 + examples/views/auth-register.pode | 23 + examples/web-auth-basic-access.ps1 | 76 +- examples/web-auth-form-access.ps1 | 103 +++ examples/web-auth-merged.ps1 | 6 +- src/Pode.psd1 | 23 +- src/Private/Access.ps1 | 67 ++ src/Private/Authentication.ps1 | 166 +---- src/Private/Context.ps1 | 7 +- src/Private/Logging.ps1 | 21 +- src/Private/OpenApi.ps1 | 5 +- src/Private/Server.ps1 | 2 +- src/Public/Access.ps1 | 705 ++++++++++++++++++ src/Public/Authentication.ps1 | 689 +---------------- src/Public/Responses.ps1 | 2 +- src/Public/Routes.ps1 | 350 ++++++++- tests/unit/Server.Tests.ps1 | 4 +- 23 files changed, 1515 insertions(+), 944 deletions(-) create mode 100644 docs/Tutorials/Authentication/Inbuilt/Session.md create mode 100644 examples/views/auth-about.pode create mode 100644 examples/views/auth-register.pode create mode 100644 examples/web-auth-form-access.ps1 create mode 100644 src/Private/Access.ps1 create mode 100644 src/Public/Access.ps1 diff --git a/docs/Tutorials/Authentication/Inbuilt/Session.md b/docs/Tutorials/Authentication/Inbuilt/Session.md new file mode 100644 index 000000000..809a8a05e --- /dev/null +++ b/docs/Tutorials/Authentication/Inbuilt/Session.md @@ -0,0 +1,59 @@ +# Sessions + +Pode has support for Sessions when using Authentication, by default if you call a Route with authentication and you already have a session on the request then you're "authenticated". If there's no session, then the authentication logic is invoked, and if the details are invalid you're redirected to a login screen. + +If you have a need to use multiple authentication methods for login, and the user can chose the one they want, then on Routes there's no simple way of say which authentication is required. However, under the hood they all create a session object which can be used as a "shared" authentication method. + +This sessions authenticator can be used to pass authentication if a valid session in on the request, or to automatically redirect to a login page if there is no valid session. Useful for if you're using multiple authentication methods the user can choose from. + +## Usage + +To add sessions authentication you can use [`Add-PodeAuthSession`](../../../../Functions/Authentication/Add-PodeAuthSession). The following example will validate a user's credentials on login using Form authentication, but the home page uses session authentication to just verify there's a valid session: + +```powershell +Start-PodeServer { + # endpoint and view engine + Add-PodeEndpoint -Address * -Port 8085 -Protocol Http + Set-PodeViewEngine -Type Pode + + # enable sessions + Enable-PodeSessionMiddleware -Duration 120 -Extend + + # setup form auth for login + New-PodeAuthScheme -Form | Add-PodeAuth -Name 'FormAuth' -FailureUrl '/login' -SuccessUrl '/' -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if ($username -eq 'morty' -and $password -eq 'pickle') { + return @{ User = @{ Name = 'Morty' } } + } + + return @{ Message = 'Invalid details supplied' } + } + + # setup session auth for routes and logout + Add-PodeAuthSession -Name 'SessionAuth' -FailureUrl '/login' + + # home page: use session auth, and redirect to login if no valid session + Add-PodeRoute -Method Get -Path '/' -Authentication SessionAuth -ScriptBlock { + Write-PodeViewResponse -Path 'auth-home' + } + + # login page: use form auth here to actually verify the user's credentials + Add-PodeRoute -Method Get -Path '/login' -Authentication FormAuth -Login -ScriptBlock { + Write-PodeViewResponse -Path 'auth-login' -FlashMessages + } + + # login check: again, use form auth + Add-PodeRoute -Method Post -Path '/login' -Authentication FormAuth -Login + + # logout - use session auth here to purge the session + Add-PodeRoute -Method Post -Path '/logout' -Authentication SessionAuth -Logout +} +``` + +### User Object + +If a valid session is found on the request, then the user object set at `$WebEvent.Auth.User` will take the form of which ever authentication method using for login. + +The user object will simply be loaded from the session. diff --git a/docs/Tutorials/Authentication/Overview.md b/docs/Tutorials/Authentication/Overview.md index 1bdd7f62c..a38b3ba7f 100644 --- a/docs/Tutorials/Authentication/Overview.md +++ b/docs/Tutorials/Authentication/Overview.md @@ -164,7 +164,7 @@ The `Auth` object will also contain: | IsAuthenticated | States if the request is for an authenticated user, can be `$true`, `$false` or `$null` | | Store | States whether the authentication is for a session, and will be stored as a cookie | | IsAuthorised | If using [Authorisation](../../Authorisation/Overview), this value will be `$true` or `$false` depending on whether or not the authenticated user is authorised to access the Route. If not using Authorisation this value will just be `$true` | -| Access | If using [Authorisation](../../Authorisation/Overview), this property will contain the access values for the User per Access method. If not using Authorisation this value will just be an empty hashtable | +| Name | The name(s) of the Authentication methods which passed - useful if you're using merged Authentications and you want to know which one(s) passed | The following example get the user's name from the `Auth` object: diff --git a/docs/Tutorials/Authorisation/Overview.md b/docs/Tutorials/Authorisation/Overview.md index 61d80bbd4..fb59aa807 100644 --- a/docs/Tutorials/Authorisation/Overview.md +++ b/docs/Tutorials/Authorisation/Overview.md @@ -2,31 +2,27 @@ Authorisation can either be used in conjunction with [Authentication](../../Authentication/Overview) and [Routes](../../Routes/Overview), or on it's own for custom scenarios. -When used with Authentication, Pode can automatically authorise access to Routes based on Roles; Groups; Scopes; Users; or custom validation logic for you. When authorisation fails Pode will respond with an HTTP 403 status code. +When used with Authentication, Pode can automatically authorise access to Routes based on Roles; Groups; Scopes; Users; or custom validation logic for you, using the currently authenticated User's details. When authorisation fails Pode will respond with an HTTP 403 status code. With authentication, Pode will set the following properties on the `$WebEvent.Auth` object: | Name | Description | | ---- | ----------- | | IsAuthorised | This value will be `$true` or `$false` depending on whether or not the authenticated user is authorised to access the Route | -| Access | This property will contain the access values for the User per Access method | ## Create an Access Method -To validate authorisation in Pode you'll first need to create an Access method using [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess). At its most simple you'll just need a Name, Type and possibly a Match type. +To validate authorisation in Pode you'll first need to create an Access scheme using [`New-PodeAccessScheme`](../../../Functions/Access/New-PodeAccessScheme), and then an Access method using [`Add-PodeAccess`](../../../Functions/Authentication/Add-PodeAccess). At its most simple you'll just need a Name, Type and possibly a Match type. For example, you can create a simple Access method for any of the inbuilt types as follows: ```powershell -Add-PodeAuthAccess -Name 'RoleExample' -Type Role -Add-PodeAuthAccess -Name 'GroupExample' -Type Group -Add-PodeAuthAccess -Name 'ScopeExample' -Type Scope -Add-PodeAuthAccess -Name 'UserExample' -Type User +New-PodeAccessScheme -Type Role | Add-PodeAccess -Name 'RoleExample' +New-PodeAccessScheme -Type Group | Add-PodeAccess -Name 'GroupExample' +New-PodeAccessScheme -Type Scope | Add-PodeAccess -Name 'ScopeExample' +New-PodeAccessScheme -Type User | Add-PodeAccess -Name 'UserExample' ``` -!!! note - These Types mainly apply when using the Access method with Authentication/Routes. If you're going to be using the Access method in a more adhoc manner via [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess) then the Type doesn't apply. - ### Match Type Pode supports 3 inbuilt "Match" types for validating access to resources: One, All and None. The default Match type is One; each of them are applied as follows: @@ -40,12 +36,12 @@ Pode supports 3 inbuilt "Match" types for validating access to resources: One, A For example, to setup an Access method where a User must be in every Group that a Route specifies: ```powershell -Add-PodeAuthAccess -Name 'GroupExample' -Type Group -Match All +New-PodeAccessScheme -Type Group | Add-PodeAccess -Name 'GroupExample' -Match All ``` ### User Access Lookup -When using Access methods with Authentication, Pode will lookup the User's "access values" from the `$WebEvent.Auth.User` object. The property within this object that Pode uses depends on the `-Type` supplied to [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess): +When using Access methods with Authentication and Routes, Pode will lookup the User's "access values" from the `$WebEvent.Auth.User` object. The property within this object that Pode uses depends on the `-Type` supplied to [`New-PodeAccessScheme`](../../../Functions/Access/New-PodeAccessScheme): | Type | Property | | ---- | -------- | @@ -53,21 +49,21 @@ When using Access methods with Authentication, Pode will lookup the User's "acce | Group | Groups | | Scope | Scopes | | User | Username | -| Custom | Custom.[Name] | +| Custom | n/a - you must supply a `-Path` or `-ScriptBlock` to [`New-PodeAccessScheme`](../../../Functions/Access/New-PodeAccessScheme) | You can override this default lookup in one of two ways, by either supplying a custom property `-Path` or a `-ScriptBlock` for more a more advanced lookup (ie: external sources). !!! note - If you're using Access methods in a more adhoc manner via [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess), the `-Path` property does nothing. However, if you don't supply a `-Source` to this function then the `-ScriptBlock` will be invoked. + If you're using Access methods in a more adhoc manner via [`Test-PodeAccess`](../../../Functions/Authentication/Test-PodeAccess), the `-Path` property does nothing. However, if you don't supply a `-Source` to this function then the `-ScriptBlock` will be invoked. #### Lookup Path -The `-Path` property on [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess) allows you to specify a custom property path within the `$WebEvent.Auth.User` object, which will be used to retrieve the access values for the User. +The `-Path` property on [`New-PodeAccessScheme`](../../../Functions/Access/New-PodeAccessScheme) allows you to specify a custom property path within the `$WebEvent.Auth.User` object, which will be used to retrieve the access values for the User. For example, if you have Roles for the User set in a `Roles` property within a `Metadata` property, then you'd use: ```powershell -Add-PodeAuthAccess -Name 'RoleExample' -Type Role -Path 'Metadata.Roles' +New-PodeAccessScheme -Type Role -Path 'Metadata.Roles' | Add-PodeAccess -Name 'RoleExample' <# $User = @{ @@ -83,44 +79,48 @@ And Pode will retrieve the appropriate data for you. #### Lookup ScriptBlock -If the source access values you require are not stored in the `$WebEvent.Auth.User` object but else where (ie: external source), then you can supply a `-ScriptBlock` on [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess). When Pode attempts to retrieve access values for the User, or another Source, this scriptblock will be invoked. +If the source access values you require are not stored in the `$WebEvent.Auth.User` object but else where (ie: external source), then you can supply a `-ScriptBlock` on [`New-PodeAccessScheme`](../../../Functions/Access/New-PodeAccessScheme). When Pode attempts to retrieve access values for the User, or another Source, this scriptblock will be invoked. !!! note - When using this scriptblock with Authentication the currently authenticated User will be supplied as the first parameter, followed by the `-ArgumentList` values. When using the Access methods in a more adhoc manner via [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess), just the `-ArgumentList` values are supplied. + When using this scriptblock with Authentication the currently authenticated User will be supplied as the first parameter, followed by the `-ArgumentList` values. When using the Access methods in a more adhoc manner via [`Test-PodeAccess`](../../../Functions/Authentication/Test-PodeAccess), just the `-ArgumentList` values are supplied. For example, if the Role values you need to retrieve are stored in some SQL database: ```powershell -Add-PodeAuthAccess -Name 'RoleExample' -Type Role -ScriptBlock { +$scheme = New-PodeAccessScheme -Type Role -ScriptBlock { param($user) return Invoke-Sqlcmd -Query "SELECT Roles FROM UserRoles WHERE Username = '$($user.Username)'" -ServerInstance '(local)' } + +$scheme | Add-PodeAccess -Name 'RoleExample' ``` Or if you need to get the Groups from AD: ```powershell -Add-PodeAuthAccess -Name 'GroupExample' -Type Group -ScriptBlock { +$scheme = New-PodeAccessScheme -Type Group -ScriptBlock { param($user) return Get-ADPrincipalGroupMembership $user.Username | select name } + +$scheme | Add-PodeAccess -Name 'GroupExample' ``` ### Custom Validator -By default Pode will perform basic array contains checks, to see if the Source/Destination access values meet the `-Match` type required. +By default Pode will perform basic array contains checks, to see if the Source/Destination access values meet the `-Match` type required which was set on [`Add-PodeAccess`](../../../Functions/Access/Add-PodeAccess). For example, if the User has just the Role value `Developer`, and Route has `-Role` values of `Developer` and `QA` supplied, and the `-Match` type is left as `One`, then "if the User Role is contained within the Routes Roles" access is authorised. -However, if you require a more custom/advanced validation logic to be applied, you can supply a custom `-Validator` scriptblock to [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess). The scriptblock will be supplied with the "Source" access values as the first parameter; the "Destination" access values as the second parameter; then followed by the `-ArgumentList` values. This scriptblock should return a boolean value: true if authorisation granted, or false otherwise. +However, if you require a more custom/advanced validation logic to be applied, you can supply a `-ScriptBlock` to [`Add-PodeAccess`](../../../Functions/Authentication/Add-PodeAccess). The scriptblock will be supplied with the "Source" access values as the first parameter; the "Destination" access values as the second parameter; and then followed by the `-ArgumentList` values. This scriptblock should return a boolean value: true if authorisation granted, or false otherwise. !!! note - Supplying a `-Validator` scriptblock will override the `-Match` type supplied, as this scriptblock will be used for validation instead of Pode's inbuilt Match logic. + Supplying a `-ScriptBlock` will override the `-Match` type supplied, as this scriptblock will be used for validation instead of Pode's inbuilt Match logic. For example, if you want to validate that the User's Scopes definitely contains a Route's first Scope value and then at least any 1 of the other Scope values: ```powershell -Add-PodeAuthAccess -Name 'ScopeExample' -Type Scope -ScriptBlock { +New-PodeAccessScheme -Type Scope | Add-PodeAccess -Name 'ScopeExample' -ScriptBlock { param($userScopes, $routeScopes) if ($routeScopes[0] -inotin $userScopes) { @@ -137,25 +137,25 @@ Add-PodeAuthAccess -Name 'ScopeExample' -Type Scope -ScriptBlock { } ``` -## Using with Authentication +## Using with Routes -The Access methods will mostly commonly be used in conjunction with [Authentication](../../Authentication/Overview) and [Routes](../../Routes/Overview). When used together, Pode will automatically validate Route authorisation for you as a part of the Authentication flow. If authorisation fails, an HTTP 403 status code will be returned. +The Access methods will most commonly be used in conjunction with [Authentication](../../Authentication/Overview) and [Routes](../../Routes/Overview). When used together, Pode will automatically validate Route Authorisation for after the Authentication flow. If authorisation fails, an HTTP 403 status code will be returned. -After creating an Access method as outlined above, you can supply the Access method Name to [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) using the `-Access` property. +After creating an Access method as outlined above, you can supply the Access method Name to [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute), and other Route functions, using the `-Access` parameter. -On [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute) and [`Add-PodeRouteGroup`](../../../Functions/Routes/Add-PodeRouteGroup) there are the following parameters: `-Role`, `-Group`, `-Scope`, and `-User`. You can supply one ore more string values to these parameters, depending on which Access method type you're using. +On [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute) and [`Add-PodeRouteGroup`](../../../Functions/Routes/Add-PodeRouteGroup) there are also the following parameters: `-Role`, `-Group`, `-Scope`, and `-User`. You can supply one ore more string values to these parameters, depending on which Access method type you're using. -For example, to verify access to a Route to authorise only Developer role accounts: +For example, to verify access to a Route to authorise only Developer role users: ```powershell Start-PodeServer { Add-PodeEndpoint -Address * -Port 8080 -Protocol Http # create a simple role access method - Add-PodeAuthAccess -Name 'RoleExample' -Type Role + New-PodeAccessScheme -Type Role | Add-PodeAccess -Name 'RoleExample' # setup Basic authentication - New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -Access 'RoleExample' -ScriptBlock { + New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -ScriptBlock { param($username, $password) # here you'd check a real user storage, this is just for example @@ -173,12 +173,12 @@ Start-PodeServer { } # create a route which only developers can access - Add-PodeRoute -Method Get -Path '/route1' -Role 'Developer' -Authentication 'AuthExample' -ScriptBlock { + Add-PodeRoute -Method Get -Path '/route1' -Role 'Developer' -Authentication 'AuthExample' -Access 'RoleExample' -ScriptBlock { Write-PodeJsonResponse -Value @{ 'Value' = 'Hello!' } } # create a route which only admins can access - Add-PodeRoute -Method Get -Path '/route2' -Role 'Admin' -Authentication 'AuthExample' -ScriptBlock { + Add-PodeRoute -Method Get -Path '/route2' -Role 'Admin' -Authentication 'AuthExample' -Access 'RoleExample' -ScriptBlock { Write-PodeJsonResponse -Value @{ 'Value' = 'Hi!' } } } @@ -198,9 +198,9 @@ Invoke-RestMethod -Uri http://localhost:8080/route2 -Method Get -Headers @{ Auth ## Merging -Similar to Authentication methods, you can also merge Access methods using [`Merge-PodeAuthAccess`](../../../Functions/Authentication/Merge-PodeAuthAccess). This allows you to have an access strategy where multiple authorisations are required to pass for a user to be fully authorised, or just one of several possible methods. +Similar to Authentication methods, you can also merge Access methods using [`Merge-PodeAccess`](../../../Functions/Authentication/Merge-PodeAccess). This allows you to have an access strategy where multiple authorisations are required to pass for a user to be fully authorised, or just one of several possible methods. -When you merge access methods together, it becomes a new access method which you can supply to `-Access` on [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth). By default the merged access method expects just one to pass, but you can state that you require all to pass via the `-Valid` parameter on [`Merge-PodeAuthAccess`](../../../Functions/Authentication/Merge-PodeAuthAccess). +When you merge access methods together, it becomes a new access method which you can supply to `-Access` on [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute). By default the merged access method expects just one to pass, but you can state that you require all to pass via the `-Valid` parameter on [`Merge-PodeAccess`](../../../Functions/Authentication/Merge-PodeAccess). Using the same example above, we could add Group authorisation to this as well so the Developers have to be in a Software Group, and the Admins in a Operations Group: @@ -209,14 +209,14 @@ Start-PodeServer { Add-PodeEndpoint -Address * -Port 8080 -Protocol Http # create simple role and group access methods - Add-PodeAuthAccess -Name 'RoleExample' -Type Role - Add-PodeAuthAccess -Name 'GroupExample' -Type Group + New-PodeAccessScheme -Type Role | Add-PodeAccess -Name 'RoleExample' + New-PodeAccessScheme -Type Group | Add-PodeAccess -Name 'GroupExample' # setup a merged access - Merge-PodeAuthAccess -Name 'MergedExample' -Access 'RoleExample', 'GroupExample' -Valid All + Merge-PodeAccess -Name 'MergedExample' -Access 'RoleExample', 'GroupExample' -Valid All # setup Basic authentication - New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -Access 'MergedExample' -ScriptBlock { + New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -ScriptBlock { param($username, $password) # here you'd check a real user storage, this is just for example @@ -235,12 +235,12 @@ Start-PodeServer { } # create a route which only developers can access - Add-PodeRoute -Method Get -Path '/route1' -Role 'Developer' -Group 'Software' -Authentication 'AuthExample' -ScriptBlock { + Add-PodeRoute -Method Get -Path '/route1' -Role 'Developer' -Group 'Software' -Authentication 'AuthExample' -Access 'MergedExample' -ScriptBlock { Write-PodeJsonResponse -Value @{ 'Value' = 'Hello!' } } # create a route which only admins can access - Add-PodeRoute -Method Get -Path '/route2' -Role 'Admin' -Group 'Operations' -Authentication 'AuthExample' -ScriptBlock { + Add-PodeRoute -Method Get -Path '/route2' -Role 'Admin' -Group 'Operations' -Authentication 'AuthExample' -Access 'MergedExample' -ScriptBlock { Write-PodeJsonResponse -Value @{ 'Value' = 'Hi!' } } } @@ -248,9 +248,9 @@ Start-PodeServer { ## Custom Access -Pode has inbuilt support for Roles, Groups, Scopes, and Users authorisation on Routes. However, if you need to setup a more Custom authorisation policy on Routes you can create an Access method with `-Type` "Custom", and add custom access values to a Route using [`Add-PodeAuthCustomAccess`](../../../Functions/Authentication/Add-PodeAuthCustomAccess). +Pode has inbuilt support for Roles, Groups, Scopes, and Users authorisation on Routes. However, if you need to setup a more Custom authorisation policy on Routes you can create a custom Access scheme by supplying `-Custom` to [`New-PodeAccessScheme`](../../../Functions/Access/New-PodeAccessScheme), and add custom access values to a Route using [`Add-PodeAccessCustom`](../../../Functions/Authentication/Add-PodeAccessCustom). -Custom access values on a User won't be automatically loaded from the User object, and a `-Path` or `-ScriptBlock` on [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess) will be required. +Custom access values for a User won't be automatically loaded from the authenticated User object, and a `-Path` or `-ScriptBlock` on [`New-PodeAccessScheme`](../../../Functions/Access/New-PodeAccessScheme) will be required. For example, if you wanted to authorise access from a set of user attributes, and based on favourite colour, you could do the following: @@ -259,13 +259,13 @@ Start-PodeServer { Add-PodeEndpoint -Address * -Port 8080 -Protocol Http # create a simple role access method - Add-PodeAuthAccess -Name 'CustomExample' -Type Custom -Path 'Metadata.Attributes' -Validator { + New-PodeAccessScheme -Custom -Path 'Metadata.Attributes' | Add-PodeAccess -Name 'CustomExample' -ScriptBlock { param($userAttrs, $routeAttrs) return ($userAttrs.Colour -ieq $routeAttrs.Colour) } # setup Basic authentication - New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -Access 'CustomExample' -ScriptBlock { + New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'AuthExample' -Sessionless -ScriptBlock { param($username, $password) # here you'd check a real user storage, this is just for example @@ -288,24 +288,24 @@ Start-PodeServer { } # create a route which only users who like the colour blue can access - Add-PodeRoute -Method Get -Path '/blue' -Authentication 'AuthExample' -ScriptBlock { + Add-PodeRoute -Method Get -Path '/blue' -Authentication 'AuthExample' -Access 'CustomExample' -ScriptBlock { Write-PodeJsonResponse -Value @{ 'Value' = 'Hello!' } } -PassThru | - Add-PodeAuthCustomAccess -Name 'CustomExample' -Value @{ Colour = 'Blue' } + Add-PodeAccessCustom -Name 'CustomExample' -Value @{ Colour = 'Blue' } # create a route which only users who like the colour red can access - Add-PodeRoute -Method Get -Path '/red' -Authentication 'AuthExample' -ScriptBlock { + Add-PodeRoute -Method Get -Path '/red' -Authentication 'AuthExample' -Access 'CustomExample' -ScriptBlock { Write-PodeJsonResponse -Value @{ 'Value' = 'Hi!' } } -PassThru | - Add-PodeAuthCustomAccess -Name 'CustomExample' -Value @{ Colour = 'Red' } + Add-PodeAccessCustom -Name 'CustomExample' -Value @{ Colour = 'Red' } } ``` ## Using Adhoc -It is possible to invoke the Access method validation in an adhoc manner, without (or while) using Authentication, using [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess). +It is possible to invoke the Access method validation in an adhoc manner, without (or while) using Authentication, using [`Test-PodeAccess`](../../../Functions/Authentication/Test-PodeAccess). -When using the Access methods outside of Authentication/Routes, the `-Type` doesn't really having any bearing. +When using the Access methods outside of Authentication/Routes, the `-Type` doesn't really have any bearing. For example, you could create a Roles Access method and verify some Users Roles within a TCP Verb: @@ -314,14 +314,15 @@ Start-PodeServer { Add-PodeEndpoint -Address * -Port 9000 -Protocol Tcp -CRLFMessageEnd # create a role access method get retrieves roles from a database - Add-PodeAuthAccess -Name 'RoleExample' -Type Role -ScriptBlock { + $scheme = New-PodeAccessScheme -Type Role -ScriptBlock { param($username) return Invoke-Sqlcmd -Query "SELECT Roles FROM UserRoles WHERE Username = '$($username)'" -ServerInstance '(local)' } + $scheme | Add-PodeAccess -Name 'RoleExample' # setup a Verb that only allows Developers Add-PodeVerb -Verb 'EXAMPLE :username' -ScriptBlock { - if (!(Test-PodeAuthAccess -Name 'RoleExample' -Destination 'Developer' -ArgumentList $TcpEvent.Parameters.username)) { + if (!(Test-PodeAccess -Name 'RoleExample' -Destination 'Developer' -ArgumentList $TcpEvent.Parameters.username)) { Write-PodeTcpClient -Message "Forbidden Access" return } @@ -331,4 +332,4 @@ Start-PodeServer { } ``` -The `-ArgumentList`, on [`Test-PodeAuthAccess`](../../../Functions/Authentication/Test-PodeAuthAccess), will supply values as the first set of parameters to the `-ScriptBlock` defined on [`Add-PodeAuthAccess`](../../../Functions/Authentication/Add-PodeAuthAccess). +The `-ArgumentList`, on [`Test-PodeAccess`](../../../Functions/Authentication/Test-PodeAccess), will supply values as the first set of parameters to the `-ScriptBlock` defined on [`New-PodeAccessScheme`](../../../Functions/Access/New-PodeAccessScheme). diff --git a/examples/public/styles/simple.css b/examples/public/styles/simple.css index 7cf2d40c0..22ff031a3 100644 --- a/examples/public/styles/simple.css +++ b/examples/public/styles/simple.css @@ -1,3 +1,11 @@ body { background-color: rebeccapurple; +} + +a { + color: white; +} + +a:visited { + color: yellow; } \ No newline at end of file diff --git a/examples/tcp-server-auth.ps1 b/examples/tcp-server-auth.ps1 index 1678e8c3c..daea3c39e 100644 --- a/examples/tcp-server-auth.ps1 +++ b/examples/tcp-server-auth.ps1 @@ -14,7 +14,7 @@ Start-PodeServer -Threads 2 { New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging # create a role access method get retrieves roles from a database - Add-PodeAuthAccess -Name 'RoleExample' -Type Role -ScriptBlock { + Add-PodeAccess -Name 'RoleExample' -Type Role -ScriptBlock { param($username) if ($username -ieq 'morty') { return @('Developer') @@ -25,7 +25,7 @@ Start-PodeServer -Threads 2 { # setup a Verb that only allows Developers Add-PodeVerb -Verb 'EXAMPLE :username' -ScriptBlock { - if (!(Test-PodeAuthAccess -Name 'RoleExample' -Destination 'Developer' -ArgumentList $TcpEvent.Parameters.username)) { + if (!(Test-PodeAccess -Name 'RoleExample' -Destination 'Developer' -ArgumentList $TcpEvent.Parameters.username)) { Write-PodeTcpClient -Message "Forbidden Access" 'Forbidden!' | Out-Default return diff --git a/examples/views/auth-about.pode b/examples/views/auth-about.pode new file mode 100644 index 000000000..1df74729c --- /dev/null +++ b/examples/views/auth-about.pode @@ -0,0 +1,23 @@ + + + Auth About + + + + + Hello, this is an about page! +
+ +

+ Home + Register +

+ + +
+ +
+ + + + \ No newline at end of file diff --git a/examples/views/auth-home.pode b/examples/views/auth-home.pode index 34c3c01e1..39af1f672 100644 --- a/examples/views/auth-home.pode +++ b/examples/views/auth-home.pode @@ -10,6 +10,11 @@ Your session will expire on $($data.Expiry)
+

+ About + Register +

+
diff --git a/examples/views/auth-register.pode b/examples/views/auth-register.pode new file mode 100644 index 000000000..d13719c68 --- /dev/null +++ b/examples/views/auth-register.pode @@ -0,0 +1,23 @@ + + + Auth Register + + + + + Hello, this is a register page! +
+ +

+ Home + About +

+ + +
+ +
+ + + + \ No newline at end of file diff --git a/examples/web-auth-basic-access.ps1 b/examples/web-auth-basic-access.ps1 index d6d92aecc..ca6ce591f 100644 --- a/examples/web-auth-basic-access.ps1 +++ b/examples/web-auth-basic-access.ps1 @@ -26,17 +26,20 @@ Start-PodeServer -Threads 2 { Add-PodeEndpoint -Address * -Port 8085 -Protocol Http # setup RBAC - Add-PodeAuthAccess -Type Role -Name 'TestRbac' - Add-PodeAuthAccess -Type Group -Name 'TestGbac' - # Add-PodeAuthAccess -Type Custom -Name 'TestRbac' -Path 'CustomAccess' -Validator { + # Add-PodeAccess -Type Role -Name 'TestRbac' + # Add-PodeAccess -Type Group -Name 'TestGbac' + New-PodeAccessScheme -Type Role | Add-PodeAccess -Name 'TestRbac' + New-PodeAccessScheme -Type Group | Add-PodeAccess -Name 'TestGbac' + # Add-PodeAccess -Type Custom -Name 'TestRbac' -Path 'CustomAccess' -Validator { # param($userRoles, $customValues) # return $userRoles.Example -iin $customValues.Example # } - Merge-PodeAuthAccess -Name 'TestMerged' -Access 'TestRbac', 'TestGbac' -Valid All + Merge-PodeAccess -Name 'TestMergedAll' -Access 'TestRbac', 'TestGbac' -Valid All + Merge-PodeAccess -Name 'TestMergedOne' -Access 'TestRbac', 'TestGbac' -Valid One # setup basic auth (base64> username:password in header) - New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Access 'TestMerged' -Sessionless -ScriptBlock { + New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Sessionless -ScriptBlock { param($username, $password) # here you'd check a real user storage, this is just for example @@ -48,7 +51,7 @@ Start-PodeServer -Threads 2 { Type = 'Human' Username = 'm.orty' Roles = @('Developer') - Groups = @('Software') + Groups = @('Software', 'Admins') CustomAccess = @{ Example = 'test-val-1' } } } @@ -62,40 +65,81 @@ Start-PodeServer -Threads 2 { $WebEvent.Auth | Out-Default } - # POST request to get list of users - there's no Roles, so any auth'd user can access - Add-PodeRoute -Method Post -Path '/users-all' -Authentication 'Validate' -Group 'Ops' -ScriptBlock { + # POST request to get list of users - there's no Access, so any auth'd user can access + Add-PodeRoute -Method Post -Path '/users-all' -Authentication 'Validate' -ScriptBlock { Write-PodeJsonResponse -Value @{ Users = @( @{ Name = 'Deep Thought' - Age = 42 } ) } } # POST request to get list of users - only Developer roles can access - Add-PodeRoute -Method Post -Path '/users-dev' -Authentication 'Validate' -Role Developer -Group Software -ScriptBlock { + Add-PodeRoute -Method Post -Path '/users-dev' -Authentication 'Validate' -Access 'TestRbac' -Role Developer -ScriptBlock { Write-PodeJsonResponse -Value @{ Users = @( @{ Name = 'Leeroy Jenkins' - Age = 1337 } ) } - } -PassThru | Add-PodeAuthCustomAccess -Name 'TestRbac' -Value @{ Example = 'test-val-1' } + } + + # POST request to get list of users - only QA roles can access + Add-PodeRoute -Method Post -Path '/users-qa' -Authentication 'Validate' -Access 'TestRbac' -Role QA -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = @( + @{ + Name = 'Nikola Tesla' + } + ) + } + } + + # POST request to get list of users - only users in the SOftware group can access + Add-PodeRoute -Method Post -Path '/users-soft' -Authentication 'Validate' -Access 'TestGbac' -Group Software -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = @( + @{ + Name = 'Smooth McGroove' + } + ) + } + } - # POST request to get list of users - only Admin roles can access - Add-PodeRoute -Method Post -Path '/users-admin' -Authentication 'Validate' -Role Admin -ScriptBlock { + # POST request to get list of users - only Developer role in the Admins group can access + Add-PodeRoute -Method Post -Path '/users-dev-admin' -Authentication 'Validate' -Access 'TestMergedAll' -Role Developer -Group Admins -ScriptBlock { Write-PodeJsonResponse -Value @{ Users = @( @{ Name = 'Arthur Dent' - Age = 30 } ) } - } -PassThru | Add-PodeAuthCustomAccess -Name 'TestRbac' -Value @{ Example = 'test-val-2' } + } + + # POST request to get list of users - either DevOps role or Admins group can access + Add-PodeRoute -Method Post -Path '/users-devop-admin' -Authentication 'Validate' -Access 'TestMergedOne' -Role DevOps -Group Admins -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = @( + @{ + Name = 'Monkey D. Luffy' + } + ) + } + } + + # POST request to get list of users - either QA role or Support group can access + Add-PodeRoute -Method Post -Path '/users-qa-support' -Authentication 'Validate' -Access 'TestMergedOne' -Role QA -Group Support -ScriptBlock { + Write-PodeJsonResponse -Value @{ + Users = @( + @{ + Name = 'Donald Duck' + } + ) + } + } } \ No newline at end of file diff --git a/examples/web-auth-form-access.ps1 b/examples/web-auth-form-access.ps1 new file mode 100644 index 000000000..a7551dd30 --- /dev/null +++ b/examples/web-auth-form-access.ps1 @@ -0,0 +1,103 @@ +$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) +Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop + +# or just: +# Import-Module Pode + +<# +This examples shows how to use session persistant authentication with access. +The example used here is Form authentication and RBAC access on pages, sent from the
in HTML. + +Navigating to the 'http://localhost:8085' endpoint in your browser will auto-rediect you to the '/login' +page. Here, you can type the username (morty) and the password (pickle); clicking 'Login' will take you +back to the home page with a greeting and a view counter. Clicking 'Logout' will purge the session and +take you back to the login page. + +- The Home and Login pages are accessible by all. +- The About page is only accessible by Developers (for morty it will load) +- The Register page is only accessible by QAs (for morty this will 403) +#> + +# create a server, and start listening on port 8085 +Start-PodeServer -Threads 2 { + + # listen on localhost:8085 + Add-PodeEndpoint -Address * -Port 8085 -Protocol Http + + # set the view engine + Set-PodeViewEngine -Type Pode + + # enable error logging + New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging + + # setup session details + Enable-PodeSessionMiddleware -Duration 120 -Extend + + # setup form auth ( in HTML) + New-PodeAuthScheme -Form | Add-PodeAuth -Name 'Login' -FailureUrl '/login' -SuccessUrl '/' -ScriptBlock { + param($username, $password) + + # here you'd check a real user storage, this is just for example + if ($username -eq 'morty' -and $password -eq 'pickle') { + return @{ + User = @{ + Name = 'Morty' + Roles = @('Developer') + } + } + } + + return @{ Message = 'Invalid details supplied' } + } + + # set RBAC access + New-PodeAccessScheme -Type Role | Add-PodeAccess -Name 'Rbac' -Match One + + + # home page: + # redirects to login page if not authenticated + Add-PodeRoute -Method Get -Path '/' -Authentication Login -ScriptBlock { + $session:Views++ + + Write-PodeViewResponse -Path 'auth-home' -Data @{ + Username = $WebEvent.Auth.User.Name + Views = $session:Views + Expiry = Get-PodeSessionExpiry + } + } + + + # about page: + # only Developers can access this page + Add-PodeRoute -Method Get -Path '/about' -Authentication Login -Access Rbac -Role Developer -ScriptBlock { + Write-PodeViewResponse -Path 'auth-about' + } + + + # register page: + # only QAs can access this page + Add-PodeRoute -Method Get -Path '/register' -Authentication Login -Access Rbac -Role QA -ScriptBlock { + Write-PodeViewResponse -Path 'auth-register' + } + + + # login page: + # the login flag set below checks if there is already an authenticated session cookie. If there is, then + # the user is redirected to the home page. If there is no session then the login page will load without + # checking user authetication (to prevent a 401 status) + Add-PodeRoute -Method Get -Path '/login' -Authentication Login -Login -ScriptBlock { + Write-PodeViewResponse -Path 'auth-login' -FlashMessages + } + + + # login check: + # this is the endpoint the 's action will invoke. If the user validates then they are set against + # the session as authenticated, and redirect to the home page. If they fail, then the login page reloads + Add-PodeRoute -Method Post -Path '/login' -Authentication Login -Login + + + # logout check: + # when the logout button is click, this endpoint is invoked. The logout flag set below informs this call + # to purge the currently authenticated session, and then redirect back to the login page + Add-PodeRoute -Method Post -Path '/logout' -Authentication Login -Logout +} \ No newline at end of file diff --git a/examples/web-auth-merged.ps1 b/examples/web-auth-merged.ps1 index e6e664449..7dd84c36a 100644 --- a/examples/web-auth-merged.ps1 +++ b/examples/web-auth-merged.ps1 @@ -21,11 +21,11 @@ Start-PodeServer -Threads 2 { New-PodeLoggingMethod -Terminal -Batch 10 -BatchTimeout 10 | Enable-PodeRequestLogging # setup access - Add-PodeAuthAccess -Type Role -Name 'Rbac' - Add-PodeAuthAccess -Type Group -Name 'Gbac' + Add-PodeAccess -Type Role -Name 'Rbac' + Add-PodeAccess -Type Group -Name 'Gbac' # setup a merged access - Merge-PodeAuthAccess -Name 'MergedAccess' -Access 'Rbac', 'Gbac' -Valid All + Merge-PodeAccess -Name 'MergedAccess' -Access 'Rbac', 'Gbac' -Valid All # setup apikey auth New-PodeAuthScheme -ApiKey -Location Header | Add-PodeAuth -Name 'ApiKey' -Access 'Gbac' -Sessionless -ScriptBlock { diff --git a/src/Pode.psd1 b/src/Pode.psd1 index f98c7e204..6ff02e503 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -223,20 +223,27 @@ 'Use-PodeAuth', 'ConvertFrom-PodeOIDCDiscovery', 'Test-PodeAuthUser', - 'Add-PodeAuthAccess', - 'Add-PodeAuthCustomAccess', - 'Get-PodeAuthAccess', - 'Test-PodeAuthAccessExists', - 'Test-PodeAuthAccess', - 'Test-PodeAuthAccessUser', - 'Test-PodeAuthAccessRoute', 'Merge-PodeAuth', 'Test-PodeAuth', 'Test-PodeAuthExists', - 'Merge-PodeAuthAccess', 'Get-PodeAuthUser', 'Add-PodeAuthSession', + # access + 'New-PodeAccessScheme', + 'Add-PodeAccess', + 'Add-PodeAccessCustom', + 'Get-PodeAccess', + 'Test-PodeAccessExists', + 'Test-PodeAccess', + 'Test-PodeAccessUser', + 'Test-PodeAccessRoute', + 'Merge-PodeAccess', + 'Remove-PodeAccess', + 'Clear-PodeAccess', + 'Add-PodeAccessMiddleware', + 'Use-PodeAccess', + # logging 'New-PodeLoggingMethod', 'Enable-PodeRequestLogging', diff --git a/src/Private/Access.ps1 b/src/Private/Access.ps1 new file mode 100644 index 000000000..160dbcc5f --- /dev/null +++ b/src/Private/Access.ps1 @@ -0,0 +1,67 @@ +function Get-PodeAccessMiddlewareScript +{ + return { + param($opts) + + if ($null -eq $WebEvent.Auth) { + Set-PodeResponseStatus -Code 403 + return $false + } + + # test access + $WebEvent.Auth.IsAuthorised = Invoke-PodeAccessValidation -Name $opts.Name + + # 403 if unauthorised + if (!$WebEvent.Auth.IsAuthorised) { + Set-PodeResponseStatus -Code 403 + } + + # run next middleware or stop? + return $WebEvent.Auth.IsAuthorised + } +} + +function Invoke-PodeAccessValidation +{ + param( + [Parameter(Mandatory=$true)] + [string] + $Name + ) + + # get the access method + $access = $PodeContext.Server.Authorisations.Methods[$Name] + + # if it's a merged access, re-call this function and check against "succeed" value + if ($access.Merged) { + foreach ($accName in $access.Access) { + $result = Invoke-PodeAccessValidation -Name $accName + + # if the access passed, and we only need one access to pass, return true + if ($result -and $access.PassOne) { + return $true + } + + # if the access failed, but we need all to pass, return false + if (!$result -and !$access.PassOne) { + return $false + } + } + + # if the last access failed, and we only need one access to pass, return false + if (!$result -and $access.PassOne) { + return $false + } + + # if the last access succeeded, and we need all to pass, return true + if ($result -and !$access.PassOne) { + return $true + } + + # default failure + return $false + } + + # main access validation logic + return (Test-PodeAccessRoute -Name $Name) +} \ No newline at end of file diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index 4d1af064d..4adfbfaee 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1072,7 +1072,7 @@ function Invoke-PodeAuthValidation # if it's a merged auth, re-call this function and check against "succeed" value if ($auth.Merged) { - $results = @() + $results = @{} foreach ($authName in $auth.Authentications) { $result = Invoke-PodeAuthValidation -Name $authName @@ -1094,12 +1094,7 @@ function Invoke-PodeAuthValidation # remember result if we need all to pass if (!$auth.PassOne) { - if ($result.Aggregated) { - $results += $result.Results - } - else { - $results += $result - } + $results[$authName] = $result } } @@ -1108,14 +1103,15 @@ function Invoke-PodeAuthValidation return $result } - # if the last auth succeeded, and we need all to pass, return aggregated results + # if the last auth succeeded, and we need all to pass, merge users/headers and return result if ($result.Success -and !$auth.PassOne) { - return @{ - Success = $true - Aggregated = $true - Results = $results - Auth = $results.Auth - } + # invoke scriptblock + $result = Invoke-PodeAuthInbuiltScriptBlock -User $results -ScriptBlock $auth.ScriptBlock.Script -UsingVariables $auth.ScriptBlock.UsingVariables + + # reset default properties and return + $result.Success = $true + $result.Auth = $results.Keys + return $result } # default failure @@ -1260,111 +1256,6 @@ function Test-PodeAuthValidation } } -function Test-PodeAuthValidationAccess -{ - param( - [Parameter(Mandatory=$true)] - [string] - $Name, - - [Parameter()] - [string] - $BaseName - ) - - # base name - if ([string]::IsNullOrEmpty($BaseName)) { - $BaseName = $Name - } - - # get auth method - $auth = $PodeContext.Server.Authentications.Methods[$Name] - $access = $null - - # cached access? - if ($null -ne $auth.Cache.Access) { - $access = $auth.Cache.Access - } - - # recursively find access - else { - # have access and/or parent? - $hasAccess = ![string]::IsNullOrEmpty($auth.Access) - $hasParent = ![string]::IsNullOrEmpty($auth.Parent) - - # no access - if (!$hasAccess) { - $PodeContext.Server.Authentications.Methods[$Name].Cache.Access = [string]::Empty - - # skip if no parent - if (!$hasParent) { - return $true - } - - # re-call on parent - else { - return (Test-PodeAuthValidationAccess -Name $auth.Parent -BaseName $BaseName) - } - } - - # set access - $access = $auth.Access - $PodeContext.Server.Authentications.Methods[$BaseName].Cache.Access = $access - } - - # check access - return ([string]::IsNullOrEmpty($access) -or (Invoke-PodeAuthValidationAccess -Name $access -Authentication $BaseName)) -} - -function Invoke-PodeAuthValidationAccess -{ - param( - [Parameter(Mandatory=$true)] - [string] - $Name, - - [Parameter(Mandatory=$true)] - [string] - $Authentication - ) - - # get the access method - $access = $PodeContext.Server.Authentications.Access[$Name] - - # if it's a merged access, re-call this function and check against "succeed" value - if ($access.Merged) { - foreach ($accName in $access.Access) { - $result = Invoke-PodeAuthValidationAccess -Name $accName -Authentication $Authentication - - # if the access passed, and we only need one access to pass, return true - if ($result -and $access.PassOne) { - return $true - } - - # if the access failed, but we need all to pass, return false - if (!$result -and !$access.PassOne) { - return $false - } - } - - # if the last access failed, and we only need one access to pass, return false - if (!$result -and $access.PassOne) { - return $false - } - - # if the last access succeeded, and we need all to pass, return true - if ($result -and !$access.PassOne) { - return $true - } - - # default failure - return $false - } - - # main access validation logic - return (Test-PodeAuthAccessRoute -Name $Name -Authentication $Authentication) -} - function Get-PodeAuthMiddlewareScript { return { @@ -1423,7 +1314,6 @@ function Test-PodeAuthInternal # existing session auth'd if (Test-PodeAuthUser) { $WebEvent.Auth = $WebEvent.Session.Data.Auth - #TODO: ACCESS CHECK HERE return (Set-PodeAuthStatus ` -Name $Name ` -LoginRoute:($Login) ` @@ -1481,43 +1371,27 @@ function Test-PodeAuthInternal -NoFailureRedirect:($result.FailureRedirect)) } - # if auth passed, assign the user(s) to the session - $user = $result.User - if ($result.Aggregated) { - $user = [ordered]@{} - foreach ($r in $result.Results) { - $user[$r.Auth] = $r.User - } - } - + # if auth passed, assign the user to the session $WebEvent.Auth = [ordered]@{ - User = $user + User = $result.User IsAuthenticated = $true IsAuthorised = $true Store = !$auth.Sessionless Name = $result.Auth - Multiple = [bool]$result.Aggregated } - # check access - foreach ($authName in $result.Auth) { - $WebEvent.Auth.IsAuthorised = Test-PodeAuthValidationAccess -Name $authName - - if (!$WebEvent.Auth.IsAuthorised) { - return (Set-PodeAuthStatus ` - -StatusCode 403 ` - -Description $result.Description ` - -Headers $result.Headers ` - -Name $authName ` - -LoginRoute:$Login ` - -NoFailureRedirect:($result.FailureRedirect)) - } + # successful auth + $authName = $null + if ($auth.Merged -and !$auth.PassOne) { + $authName = $Name + } + else { + $authName = @($result.Auth)[0] } - # successful auth return (Set-PodeAuthStatus ` -Headers $result.Headers ` - -Name @($result.Auth)[0] ` + -Name $authName ` -LoginRoute:$Login) } diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 3d7cf1b79..1d648590c 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -351,10 +351,13 @@ function New-PodeContext } } - # authentication methods and access + # authentication and authorisation methods $ctx.Server.Authentications = @{ Methods = @{} - Access = @{} + } + + $ctx.Server.Authorisations = @{ + Methods = @{} } # create new cancellation tokens diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index ffbd77b37..ef924b43a 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -299,25 +299,10 @@ function Write-PodeRequestLog # set username - dot spaces if (Test-PodeAuthUser -IgnoreSession) { $userProps = (Get-PodeLogger -Name $name).Properties.Username.Split('.') - $user = $null - if (!$WebEvent.Auth.Multiple) { - $user = $WebEvent.Auth.User - foreach ($atom in $userProps) { - $user = $user.($atom) - } - } - else { - foreach ($u in $WebEvent.Auth.User.Values) { - $user = $u - foreach ($atom in $userProps) { - $user = $user.($atom) - } - - if (![string]::IsNullOrWhiteSpace($user)) { - break - } - } + $user = $WebEvent.Auth.User + foreach ($atom in $userProps) { + $user = $user.($atom) } if (![string]::IsNullOrWhiteSpace($user)) { diff --git a/src/Private/OpenApi.ps1 b/src/Private/OpenApi.ps1 index 7749152aa..719921319 100644 --- a/src/Private/OpenApi.ps1 +++ b/src/Private/OpenApi.ps1 @@ -274,7 +274,10 @@ function Get-PodeOpenApiDefinitionInternal foreach ($authName in $PodeContext.Server.Authentications.Methods.Keys) { $authType = (Find-PodeAuth -Name $authName).Scheme - #TODO: SUPPORT MUTLI-AUTH HERE, IF NO SCHEME SKIP + if ([string]::IsNullOrWhiteSpace($authType)) { + continue + } + $_authName = ($authName -replace '\s+', '') $_authObj = @{} diff --git a/src/Private/Server.ps1 b/src/Private/Server.ps1 index 59a4c551d..bd8ae7cd9 100644 --- a/src/Private/Server.ps1 +++ b/src/Private/Server.ps1 @@ -244,7 +244,7 @@ function Restart-PodeInternalServer # clear up authentication methods $PodeContext.Server.Authentications.Methods.Clear() - $PodeContext.Server.Authentications.Access.Clear() + $PodeContext.Server.Authorisations.Methods.Clear() # clear up shared state $PodeContext.Server.State.Clear() diff --git a/src/Public/Access.ps1 b/src/Public/Access.ps1 new file mode 100644 index 000000000..2da389099 --- /dev/null +++ b/src/Public/Access.ps1 @@ -0,0 +1,705 @@ +<# +.SYNOPSIS +Create a new type of Access scheme. + +.DESCRIPTION +Create a new type of Access scheme, which retrieves the destination/resource's authorisation values which a user needs for access. + +.PARAMETER Type +The inbuilt Type of Access this method is for: Role, Group, Scope, User. + +.PARAMETER Custom +If supplied, the access Scheme will be flagged as using Custom logic. + +.PARAMETER ScriptBlock +An optional ScriptBlock for retrieving authorisation values for the authenticated user, useful if the values reside in an external data store. +This, or Path, is mandatory if using a Custom scheme. + +.PARAMETER ArgumentList +An optional array of arguments to supply to the ScriptBlock. + +.PARAMETER Path +An optional property Path within the $WebEvent.Auth.User object to extract authorisation values. +The default Path is based on the Access Type, either Roles; Groups; Scopes; or Username. +This, or ScriptBlock, is mandatory if using a Custom scheme. + +.EXAMPLE +$role_access = New-PodeAccessScheme -Type Role + +.EXAMPLE +$group_access = New-PodeAccessScheme -Type Group -Path 'Metadata.Groups' + +.EXAMPLE +$scope_access = New-PodeAccessScheme -Type Scope -Scriptblock { param($user) return @(Get-ExampleAccess -Username $user.Username) } + +.EXAMPLE +$custom_access = New-PodeAccessScheme -Custom -Path 'CustomProp' +#> +function New-PodeAccessScheme +{ + [CmdletBinding(DefaultParameterSetName='Type_Path')] + param( + [Parameter(Mandatory=$true, ParameterSetName='Type_Scriptblock')] + [Parameter(Mandatory=$true, ParameterSetName='Type_Path')] + [ValidateSet('Role', 'Group', 'Scope', 'User')] + [string] + $Type, + + [Parameter(Mandatory=$true, ParameterSetName='Custom_Scriptblock')] + [Parameter(Mandatory=$true, ParameterSetName='Custom_Path')] + [switch] + $Custom, + + [Parameter(Mandatory=$true, ParameterSetName='Custom_Scriptblock')] + [Parameter(ParameterSetName='Type_Scriptblock')] + [scriptblock] + $ScriptBlock, + + [Parameter(ParameterSetName='Custom_Scriptblock')] + [Parameter(ParameterSetName='Type_Scriptblock')] + [object[]] + $ArgumentList, + + [Parameter(Mandatory=$true, ParameterSetName='Custom_Path')] + [Parameter(ParameterSetName='Type_Path')] + [string] + $Path + ) + + # for custom access a validator is mandatory + if ($Custom) { + if ([string]::IsNullOrWhiteSpace($Path) -and (Test-PodeIsEmpty $ScriptBlock)) { + throw "A Path or ScriptBlock is required for sourcing the Custom access values" + } + } + + # parse using variables in scriptblock + $scriptObj = $null + if (!(Test-PodeIsEmpty $ScriptBlock)) { + $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + $scriptObj = @{ + Script = $ScriptBlock + UsingVariables = $usingScriptVars + } + } + + # default path + if (!$Custom -and (Test-PodeIsEmpty $ScriptBlock) -and [string]::IsNullOrWhiteSpace($Path)) { + if ($Type -ieq 'user') { + $Path = 'Username' + } + else { + $Path = "$($Type)s" + } + } + + # return scheme + return @{ + Type = $Type + IsCustom = $Custom.IsPresent + ScriptBlock = $scriptObj + Arguments = $ArgumentList + Path = $Path + } +} + +<# +.SYNOPSIS +Add an authorisation Access method. + +.DESCRIPTION +Add an authorisation Access method for use with Authentication methods, which will authorise access to Routes. +Or they can be used independant of Authentication/Routes for custom scenarios. + +.PARAMETER Name +A unique Name for the Access method. + +.PARAMETER Scheme +The access Scheme to use for retrieving credentials (From New-PodeAccessScheme). + +.PARAMETER ScriptBlock +An optional Scriptblock, which can be used to invoke custom validation logic to verify authorisation. + +.PARAMETER ArgumentList +An optional array of arguments to supply to the ScriptBlock. + +.PARAMETER Match +An optional inbuilt Match method to use when verifying access to a Route, this only applies when no custom Validator scriptblock is supplied. (Default: One) +"One" will allow access if the User has at least one of the Route's access values. +"All" will allow access only if the User has all the values. +"None" will allow access only if the User has none of the values. + +.EXAMPLE +New-PodeAccessScheme -Type Role | Add-PodeAccess -Name 'Example' + +.EXAMPLE +New-PodeAccessScheme -Type Group -Path 'Metadata.Groups' | Add-PodeAccess -Name 'Example' -Match All + +.EXAMPLE +New-PodeAccessScheme -Type Scope -Scriptblock { param($user) return @(Get-ExampleAccess -Username $user.Username) } | Add-PodeAccess -Name 'Example' + +.EXAMPLE +New-PodeAccessScheme -Custom -Path 'CustomProp' | Add-PodeAccess -Name 'Example' -ScriptBlock { param($userAccess, $customAccess) return $userAccess.Country -ieq $customAccess.Country } +#> +function Add-PodeAccess +{ + [CmdletBinding(DefaultParameterSetName='Match')] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [hashtable] + $Scheme, + + [Parameter(Mandatory=$true, ParameterSetName='ScriptBlock')] + [scriptblock] + $ScriptBlock, + + [Parameter(ParameterSetName='ScriptBlock')] + [object[]] + $ArgumentList, + + [Parameter(ParameterSetName='Match')] + [ValidateSet('All', 'One', 'None')] + [string] + $Match = 'One' + ) + + # check name unique + if (Test-PodeAccessExists -Name $Name) { + throw "Access method already defined: $($Name)" + } + + # parse using variables in validator scriptblock + $scriptObj = $null + if (!(Test-PodeIsEmpty $ScriptBlock)) { + $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + $scriptObj = @{ + Script = $ScriptBlock + UsingVariables = $usingScriptVars + } + } + + # add access object + $PodeContext.Server.Authorisations.Methods[$Name] = @{ + Name = $Name + Scheme = $Scheme + ScriptBlock = $scriptObj + Arguments = $ArgumentList + Match = $Match.ToLowerInvariant() + Cache = @{} + Merged = $false + Parent = $null + } +} + +<# +.SYNOPSIS +Let's you merge multiple Access methods together, into a "single" Access method. + +.DESCRIPTION +Let's you merge multiple Access methods together, into a "single" Access method. +You can specify if only One or All of the methods need to pass to allow access, and you can also +merge other merged Access methods for more advanced scenarios. + +.PARAMETER Name +A unique Name for the Access method. + +.PARAMETER Access +Mutliple Access method Names to be merged. + +.PARAMETER Valid +How many of the Access methods are required to be valid, One or All. (Default: One) + +.EXAMPLE +Merge-PodeAccess -Name MergedAccess -Access RbacAccess, GbacAccess -Valid All +#> +function Merge-PodeAccess +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter(Mandatory=$true)] + [string[]] + $Access, + + [Parameter()] + [ValidateSet('One', 'All')] + [string] + $Valid = 'One' + ) + + # ensure the name doesn't already exist + if (Test-PodeAccessExists -Name $Name) { + throw "Access method already defined: $($Name)" + } + + # ensure all the access methods exist + foreach ($accName in $Access) { + if (!(Test-PodeAccessExists -Name $accName)) { + throw "Access method does not exist for merging: $($accName)" + } + } + + # set parent access + foreach ($accName in $Access) { + $PodeContext.Server.Authorisations.Methods[$accName].Parent = $Name + } + + # add auth method to server + $PodeContext.Server.Authorisations.Methods[$Name] = @{ + Name = $Name + Access = @($Access) + PassOne = ($Valid -ieq 'one') + Cache = @{} + Merged = $true + Parent = $null + } +} + +<# +.SYNOPSIS +Assigns Custom Access value(s) to a Route. + +.DESCRIPTION +Assigns Custom Access value(s) to a Route. + +.PARAMETER Route +The Route to assign the Custom Access value(s). + +.PARAMETER Name +The Name of the Access method the Custom Access value(s) are for. + +.PARAMETER Value +The Custom Access Value(s) + +.EXAMPLE +Add-PodeRoute -Method Get -Path '/users' -ScriptBlock {} -PassThru | Add-PodeAccessCustom -Name 'Example' -Value @{ Country = 'UK' } +#> +function Add-PodeAccessCustom +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [hashtable[]] + $Route, + + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter(Mandatory=$true)] + [object[]] + $Value + ) + + begin { + $routes = @() + } + + process { + $routes += $Route + } + + end { + foreach ($r in $routes) { + if ($r.AccessMeta.Custom.ContainsKey($Name)) { + throw "Route '[$($r.Method)] $($r.Path)' already contains Custom Access with name '$($Name)'" + } + + $r.AccessMeta.Custom[$Name] = $Value + } + } +} + +<# +.SYNOPSIS +Get one or more Access methods. + +.DESCRIPTION +Get one or more Access methods. + +.PARAMETER Name +The Name of the Access method. If no name supplied, all methods will be returned. + +.EXAMPLE +$methods = Get-PodeAccess + +.EXAMPLE +$methods = Get-PodeAccess -Name 'Example' + +.EXAMPLE +$methods = Get-PodeAccess -Name 'Example1', 'Example2' +#> +function Get-PodeAccess +{ + [CmdletBinding()] + param( + [Parameter()] + [string[]] + $Name + ) + + # return all if no Name + if ([string]::IsNullOrEmpty($Name) -or ($Name.Length -eq 0)) { + return $PodeContext.Server.Authorisations.Methods.Values + } + + # return filtered + return @(foreach ($n in $Name) { + $PodeContext.Server.Authorisations.Methods[$n] + }) +} + +<# +.SYNOPSIS +Test if an Access method exists. + +.DESCRIPTION +Test if an Access method exists. + +.PARAMETER Name +The Name of the Access method. + +.EXAMPLE +if (Test-PodeAccessExists -Name 'Example') { } +#> +function Test-PodeAccessExists +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name + ) + + return $PodeContext.Server.Authorisations.Methods.ContainsKey($Name) +} + +<# +.SYNOPSIS +Test access values for a Source/Destination against an Access method. + +.DESCRIPTION +Test access values for a Source/Destination against an Access method. + +.PARAMETER Name +The Name of the Access method to use to verify the access. + +.PARAMETER Source +An array of Source access values to pass to the Access method for verification against the Destination access values. (ie: User) + +.PARAMETER Destination +An array of Destination access values to pass to the Access method for verification. (ie: Route) + +.PARAMETER ArgumentList +An optional array of arguments to supply to the Access Scheme's ScriptBlock for retrieving access values. + +.EXAMPLE +if (Test-PodeAccess -Name 'Example' -Source 'Developer' -Destination 'Admin') { } +#> +function Test-PodeAccess +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter()] + [object[]] + $Source = $null, + + [Parameter()] + [object[]] + $Destination = $null, + + [Parameter()] + [object[]] + $ArgumentList = $null + ) + + # get the access method + $access = $PodeContext.Server.Authorisations.Methods[$Name] + + # authorised if no destination values + if (($null -eq $Destination) -or ($Destination.Length -eq 0)) { + return $true + } + + # if we have no source values, invoke the scriptblock + if (($null -eq $Source) -or ($Source.Length -eq 0)) { + if ($null -ne $access.Scheme.ScriptBlock) { + $_args = $ArgumentList + @($access.Scheme.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Scheme.Scriptblock.UsingVariables) + $Source = Invoke-PodeScriptBlock -ScriptBlock $access.Scheme.Scriptblock.Script -Arguments $_args -Return -Splat + } + } + + # check for custom validator, or use default match logic + if ($null -ne $access.ScriptBlock) { + $_args = @(,$Source) + @(,$Destination) + @($access.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.ScriptBlock.UsingVariables) + return [bool](Invoke-PodeScriptBlock -ScriptBlock $access.ScriptBlock.Script -Arguments $_args -Return -Splat) + } + + # not authorised if no source values + if (($access.Match -ne 'none') -and (($null -eq $Source) -or ($Source.Length -eq 0))) { + return $false + } + + # one or all match? + else { + switch ($access.Match) { + 'one' { + foreach ($item in $Source) { + if ($item -iin $Destination) { + return $true + } + } + } + + 'all' { + foreach ($item in $Destination) { + if ($item -inotin $Source) { + return $false + } + } + + return $true + } + + 'none' { + foreach ($item in $Source) { + if ($item -iin $Destination) { + return $false + } + } + + return $true + } + } + } + + # default is not authorised + return $false +} + +<# +.SYNOPSIS +Test the currently authenticated User's access against the supplied values. + +.DESCRIPTION +Test the currently authenticated User's access against the supplied values. This will be the user in a WebEvent object. + +.PARAMETER Name +The Name of the Access method to use to verify the access. + +.PARAMETER Value +An array of access values to pass to the Access method for verification against the User. + +.EXAMPLE +if (Test-PodeAccessUser -Name 'Example' -Value 'Developer', 'QA') { } +#> +function Test-PodeAccessUser +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter(Mandatory=$true)] + [object[]] + $Value + ) + + # get the access method + $access = $PodeContext.Server.Authorisations.Methods[$Name] + + # get the user + $user = $WebEvent.Auth.User + + # if there's no scriptblock, try the Path fallback + if ($null -eq $access.Scheme.Scriptblock) { + $userAccess = $user + foreach ($atom in $access.Scheme.Path.Split('.')) { + $userAccess = $userAccess.($atom) + } + } + + # otherwise, invoke scriptblock + else { + $_args = @($user) + @($access.Scheme.Arguments) + $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Scheme.Scriptblock.UsingVariables) + $userAccess = Invoke-PodeScriptBlock -ScriptBlock $access.Scheme.Scriptblock.Script -Arguments $_args -Return -Splat + } + + # is the user authorised? + return (Test-PodeAccess -Name $Name -Source $userAccess -Destination $Value) +} + +<# +.SYNOPSIS +Test the currently authenticated User's access against the access values supplied for the current Route. + +.DESCRIPTION +Test the currently authenticated User's access against the access values supplied for the current Route. + +.PARAMETER Name +The Name of the Access method to use to verify the access. + +.EXAMPLE +if (Test-PodeAccessRoute -Name 'Example') { } +#> +function Test-PodeAccessRoute +{ + param( + [Parameter(Mandatory=$true)] + [string] + $Name + ) + + # get the access method + $access = $PodeContext.Server.Authorisations.Methods[$Name] + + # get route access values + if ($access.Scheme.IsCustom) { + $routeAccess = $WebEvent.Route.AccessMeta.Custom[$access.Name] + } + else { + $routeAccess = $WebEvent.Route.AccessMeta[$access.Scheme.Type] + } + + # if no values then skip + if (($null -eq $routeAccess) -or ($routeAccess.Length -eq 0)) { + return $true + } + + # tests values against user + return (Test-PodeAccessUser -Name $Name -Value $routeAccess) +} + +<# +.SYNOPSIS +Remove a specific Access method. + +.DESCRIPTION +Remove a specific Access method. + +.PARAMETER Name +The Name of the Access method. + +.EXAMPLE +Remove-PodeAccess -Name 'RBAC' +#> +function Remove-PodeAccess +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [string] + $Name + ) + + $null = $PodeContext.Server.Authorisations.Methods.Remove($Name) +} + +<# +.SYNOPSIS +Clear all defined Access methods. + +.DESCRIPTION +Clear all defined Access methods. + +.EXAMPLE +Clear-PodeAccess +#> +function Clear-PodeAccess +{ + [CmdletBinding()] + param() + + $PodeContext.Server.Authorisations.Methods.Clear() +} + +<# +.SYNOPSIS +Adds an access method as global middleware. + +.DESCRIPTION +Adds an access method as global middleware. + +.PARAMETER Name +The Name of the Middleware. + +.PARAMETER Access +The Name of the Access method to use. + +.PARAMETER Route +A Route path for which Routes this Middleware should only be invoked against. + +.EXAMPLE +Add-PodeAccessMiddleware -Name 'GlobalAccess' -Access AccessName + +.EXAMPLE +Add-PodeAccessMiddleware -Name 'GlobalAccess' -Access AccessName -Route '/api/*' +#> +function Add-PodeAccessMiddleware +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string] + $Name, + + [Parameter(Mandatory=$true)] + [string] + $Access, + + [Parameter()] + [string] + $Route + ) + + if (!(Test-PodeAccessExists -Name $Access)) { + throw "Access method does not exist: $($Access)" + } + + Get-PodeAccessMiddlewareScript | + New-PodeMiddleware -ArgumentList @{ Name = $Access } | + Add-PodeMiddleware -Name $Name -Route $Route +} + +<# +.SYNOPSIS +Automatically loads access ps1 files + +.DESCRIPTION +Automatically loads access ps1 files from either an /access folder, or a custom folder. Saves space dot-sourcing them all one-by-one. + +.PARAMETER Path +Optional Path to a folder containing ps1 files, can be relative or literal. + +.EXAMPLE +Use-PodeAccess + +.EXAMPLE +Use-PodeAccess -Path './my-access' +#> +function Use-PodeAccess +{ + [CmdletBinding()] + param( + [Parameter()] + [string] + $Path + ) + + Use-PodeFolder -Path $Path -DefaultPath 'access' +} \ No newline at end of file diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index 1f00ed328..e87cbcd4e 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -1,9 +1,9 @@ <# .SYNOPSIS -Create a new type of Authentication. +Create a new type of Authentication scheme. .DESCRIPTION -Create a new type of Authentication, which is used to parse the Request for user credentials for validating. +Create a new type of Authentication scheme, which is used to parse the Request for user credentials for validating. .PARAMETER Basic If supplied, will use the inbuilt Basic Authentication credentials retriever. @@ -123,7 +123,7 @@ function New-PodeAuthScheme { [CmdletBinding(DefaultParameterSetName='Basic')] [OutputType([hashtable])] - param ( + param( [Parameter(ParameterSetName='Basic')] [switch] $Basic, @@ -670,10 +670,7 @@ Adds a custom Authentication method for verifying users. A unique Name for the Authentication method. .PARAMETER Scheme -The Scheme to use for retrieving credentials (From New-PodeAuthScheme). - -.PARAMETER Access -An optional Access method Name to validate authorisation to Routes (From Add-PodeAuthAccess) +The authentication Scheme to use for retrieving credentials (From New-PodeAuthScheme). .PARAMETER ScriptBlock The ScriptBlock defining logic that retrieves and verifys a user. @@ -711,10 +708,6 @@ function Add-PodeAuth [hashtable] $Scheme, - [Parameter()] - [string] - $Access, - [Parameter(Mandatory=$true)] [ValidateScript({ if (Test-PodeIsEmpty $_) { @@ -759,11 +752,6 @@ function Add-PodeAuth throw "The supplied '$($Scheme.Name)' Scheme for the '$($Name)' authentication validator requires a valid ScriptBlock" } - # ensure the Access method exists - if (!(Test-PodeIsEmpty $Access) -and !(Test-PodeAuthAccessExists -Name $Access)) { - throw "Access method not found: $($Access)" - } - # if we're using sessions, ensure sessions have been setup if (!$Sessionless -and !(Test-PodeSessionsConfigured)) { throw 'Sessions are required to use session persistent authentication' @@ -776,7 +764,6 @@ function Add-PodeAuth $PodeContext.Server.Authentications.Methods[$Name] = @{ Name = $Name Scheme = $Scheme - Access = $Access ScriptBlock = $ScriptBlock UsingVariables = $usingVars Arguments = $ArgumentList @@ -820,13 +807,13 @@ Multiple Autentication method Names to be merged. .PARAMETER Valid How many of the Authentication methods are required to be valid, One or All. (Default: One) +.PARAMETER ScriptBlock +This is mandatory when Valid is All. A scriptblock to merge the mutliple users/headers returned by valid authentications into 1 user/header objects. +This scriptblock will receive a hashtable of all result objects returned from Authentication methods. The key for the hashtable will be the authentication names that passed. + .PARAMETER Default The Default Authentication method to use as a fallback for Failure URLs and other settings. -.PARAMETER Access -An optional Access method Name to validate authorisation to Routes. -This will be used as fallback for the merged Authentication methods if not set on them. - .PARAMETER FailureUrl The URL to redirect to when authentication fails. This will be used as fallback for the merged Authentication methods if not set on them. @@ -872,12 +859,12 @@ function Merge-PodeAuth $Valid = 'One', [Parameter()] - [string] - $Default, + [scriptblock] + $ScriptBlock, [Parameter()] [string] - $Access, + $Default, [Parameter()] [string] @@ -898,11 +885,6 @@ function Merge-PodeAuth $SuccessUseOrigin ) - #TODO: NEW SCRIPTBLOCK TO MERGE ALL USERS INTO ONE OBJECT - # WAY SIMPLER!! - # ScriptBlock should get @{users} hashtable, and @(authNames) array for the users/auths that passed - # This would remove the ".Multiple" nonsense property in WE.Auth.User - # ensure the name doesn't already exist if (Test-PodeAuthExists -Name $Name) { throw "Authentication method already defined: $($Name)" @@ -920,11 +902,6 @@ function Merge-PodeAuth throw "the Default Authentication '$($Default)' is not in the Authentication list supplied" } - # ensure the Access methods exists - if (!(Test-PodeIsEmpty $Access) -and !(Test-PodeAuthAccessExists -Name $Access)) { - throw "Access method not found: $($Access)" - } - # set default if ([string]::IsNullOrEmpty($Default)) { $Default = $Authentication[0] @@ -943,11 +920,6 @@ function Merge-PodeAuth throw 'Sessions are required to use session persistent authentication' } - # check access from default - if (Test-PodeIsEmpty $Access) { - $Access = $tmpAuth.Access - } - # check failure url from default if ([string]::IsNullOrEmpty($FailureUrl)) { $FailureUrl = $tmpAuth.Failure.Url @@ -968,6 +940,15 @@ function Merge-PodeAuth $SuccessUseOrigin = $tmpAuth.Success.UseOrigin } + # deal with using vars in scriptblock + if ($Valid -ieq 'all') { + if ($null -eq $ScriptBlock) { + throw "A Scriptblock for merging multiple authenticated users into 1 object is required When Valid is All" + } + + $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + } + # set parent auth foreach ($authName in $Authentication) { $PodeContext.Server.Authentications.Methods[$authName].Parent = $Name @@ -978,8 +959,11 @@ function Merge-PodeAuth Name = $Name Authentications = @($Authentication) PassOne = ($Valid -ieq 'one') + ScriptBlock = @{ + Script = $ScriptBlock + UsingVariables = $usingVars + } Default = $Default - Access = $Access Sessionless = $Sessionless.IsPresent Failure = @{ Url = $FailureUrl @@ -1065,17 +1049,11 @@ The Name of the Authentication method. .PARAMETER IgnoreSession If supplied, authentication will be re-verified on each call even if a valid session exists on the request. -.PARAMETER CheckAccess -If supplied, an Authentication method's Access method will also be verified. - .EXAMPLE if (Test-PodeAuth -Name 'BasicAuth') { ... } .EXAMPLE if (Test-PodeAuth -Name 'FormAuth' -IgnoreSession) { ... } - -.EXAMPLE -if (Test-PodeAuth -Name 'BasicAuth' -CheckAccess) { ... } #> function Test-PodeAuth { @@ -1086,10 +1064,7 @@ function Test-PodeAuth $Name, [switch] - $IgnoreSession, - - [switch] - $CheckAccess + $IgnoreSession ) # if the session already has a user/isAuth'd, then skip auth - or allow anon @@ -1115,15 +1090,6 @@ function Test-PodeAuth return $false } - # check access - if ($CheckAccess) { - foreach ($authName in $result.Auth) { - if (!(Test-PodeAuthValidationAccess -Name $authName)) { - return $false - } - } - } - # successful auth return $true } @@ -1204,7 +1170,7 @@ New-PodeAuthScheme -Form | Add-PodeAuthWindowsAd -Name 'UnixAuth' -Server 'testd function Add-PodeAuthWindowsAd { [CmdletBinding(DefaultParameterSetName='Groups')] - param ( + param( [Parameter(Mandatory=$true)] [string] $Name, @@ -1378,7 +1344,7 @@ Add-PodeAuthSession -Name 'SessionAuth' -FailureUrl '/login' function Add-PodeAuthSession { [CmdletBinding(DefaultParameterSetName='Groups')] - param ( + param( [Parameter(Mandatory=$true)] [string] $Name, @@ -1493,7 +1459,7 @@ Remove-PodeAuth -Name 'Login' function Remove-PodeAuth { [CmdletBinding()] - param ( + param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [string] $Name @@ -1632,7 +1598,7 @@ Add-PodeAuthIIS -Name 'IISAuth' -NoGroups function Add-PodeAuthIIS { [CmdletBinding(DefaultParameterSetName='Groups')] - param ( + param( [Parameter(Mandatory=$true)] [string] $Name, @@ -2415,10 +2381,6 @@ Test whether the current WebEvent or Session has an authenticated user. .DESCRIPTION Test whether the current WebEvent or Session has an authenticated user. Returns true if there is an authenticated user. -.PARAMETER Authentication -Only if multiple merged Authentication methods are being used, supply the Name of the Authentication method where the User object should be checked. -If not supplied, and using mutliple Authentication methods, true will be returned if there is a User object for any Authentication method. - .PARAMETER IgnoreSession If supplied, only the Auth object in the WebEvent will be checked and the Session will be skipped. @@ -2429,10 +2391,6 @@ function Test-PodeAuthUser { [CmdletBinding()] param( - [Parameter()] - [string] - $Authentication, - [switch] $IgnoreSession ) @@ -2452,13 +2410,7 @@ function Test-PodeAuthUser return $false } - # embedded auth - $user = $auth.User - if ($auth.Multiple -and ![string]::IsNullOrEmpty($Authentication)) { - $user = $user[$Authentication] - } - - return ($null -ne $user) + return ($null -ne $auth.User) } <# @@ -2468,10 +2420,6 @@ Get the authenticated user from the WebEvent or Session. .DESCRIPTION Get the authenticated user from the WebEvent or Session. This is similar to calling $Webevent.Auth.User. -.PARAMETER Authentication -Only if multiple merged Authentication methods are being used, supply the Name of the Authentication method where the User object should be retrieved. -If not supplied, and using mutliple Authentication methods, all User objects will be returned. - .PARAMETER IgnoreSession If supplied, only the Auth object in the WebEvent will be used and the Session will be skipped. @@ -2482,10 +2430,6 @@ function Get-PodeAuthUser { [CmdletBinding()] param( - [Parameter()] - [string] - $Authentication, - [switch] $IgnoreSession ) @@ -2505,576 +2449,5 @@ function Get-PodeAuthUser return $null } - # embedded auth - $user = $auth.User - if ($auth.Multiple -and ![string]::IsNullOrEmpty($Authentication)) { - $user = $user[$Authentication] - } - - return $user -} - -<# -.SYNOPSIS -Add an authorisation Access method. - -.DESCRIPTION -Add an authorisation Access method for use with Authentication methods, which will authorise access to Routes. -Or they can be used independant of Authentication/Routes for custom scenarios. - -.PARAMETER Name -A unique Name for the Access method. - -.PARAMETER Type -The Type of Access this method is for: Role, Group, Scope, User, Custom. - -.PARAMETER ScriptBlock -An optional ScriptBlock for retrieving authorisation values for the authenticated user, useful if the values reside in an external data store. - -.PARAMETER ArgumentList -An optional array of arguments to supply to the Access's ScriptBlock and Validator. - -.PARAMETER Validator -An optional Validator scriptblock, which can be used to invoke custom validation logic to verify authorisation. - -.PARAMETER Path -An optional property Path within the $WebEvent.Auth.User object to extract authorisation values. -The default Path is based on the Access Type, either Roles; Groups; Scopes; Username; or Custom. - -.PARAMETER Match -An optional inbuilt Match method to use when verifying access to a Route, this only applies when no custom Validator scriptblock is supplied. (Default: One) -"One" will allow access if the User has at least one of the Route's access values. -"All" will allow access only if the User has all the values. -"None" will allow access only if the User has none of the values. - -.EXAMPLE -Add-PodeAuthAccess -Name 'Example' -Type Role - -.EXAMPLE -Add-PodeAuthAccess -Name 'Example' -Type Group -Path 'Metadata.Groups' -Match All - -.EXAMPLE -Add-PodeAuthAccess -Name 'Example' -Type Scope -Scriptblock { param($user) return @(Get-ExampleAccess -Username $user.Username) } - -.EXAMPLE -Add-PodeAuthAccess -Name 'Example' -Type Custom -Validator { param($userAccess, $customAccess) return $userAccess.Country -ieq $customAccess.Country } -#> -function Add-PodeAuthAccess -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $Name, - - [Parameter(Mandatory=$true)] - [ValidateSet('Role', 'Group', 'Scope', 'User', 'Custom')] - [string] - $Type, - - [Parameter()] - [scriptblock] - $ScriptBlock = $null, - - [Parameter()] - [object[]] - $ArgumentList, - - [Parameter()] - [scriptblock] - $Validator = $null, - - [Parameter()] - [string] - $Path, - - [Parameter()] - [ValidateSet('All', 'One', 'None')] - [string] - $Match = 'One' - ) - - #TODO: MOVE ALL THESE INTO AN ACCESS.PS1 FILE - # RENAME TO Add-PodeAccess - # THEY SHOULD BE SEPARATE MIDDLEWARE, NOT TIED TO AUTHENTICATION - - # check name unique - if (Test-PodeAuthAccessExists -Name $Name) { - throw "Access method already defined: $($Name)" - } - - # for custom access a validator is mandatory - if ($Type -ieq 'custom') { - if ([string]::IsNullOrEmpty($Path) -and ($null -eq $ScriptBlock)) { - throw "A Path or ScriptBlock is required for sourcing the Custom access values, for the Custom Access method '$($Name)'" - } - } - - # parse using variables in scriptblock - $scriptObj = $null - if (!(Test-PodeIsEmpty $ScriptBlock)) { - $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState - $scriptObj = @{ - Script = $ScriptBlock - UsingVariables = $usingScriptVars - } - } - - # parse using variables in validator - $validObj = $null - if (!(Test-PodeIsEmpty $Validator)) { - $Validator, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $Validator -PSSession $PSCmdlet.SessionState - $validObj = @{ - Script = $Validator - UsingVariables = $usingScriptVars - } - } - - # default path - if ([string]::IsNullOrWhiteSpace($Path)) { - if ($Type -ieq 'user') { - $Path = 'Username' - } - else { - $Path = "$($Type)s" - } - } - - # add access object - $PodeContext.Server.Authentications.Access[$Name] = @{ - Name = $Name - Type = $Type - IsCustom = ($Type -ieq 'custom') - ScriptBlock = $scriptObj - Validator = $validObj - Arguments = $ArgumentList - Path = $Path - Match = $Match.ToLowerInvariant() - Cache = @{} - Merged = $false - Parent = $null - } -} - -<# -.SYNOPSIS -Let's you merge multiple Access methods together, into a "single" Access method. - -.DESCRIPTION -Let's you merge multiple Access methods together, into a "single" Access method. -You can specify if only One or All of the methods need to pass to allow access, and you can also -merge other merged Access methods for more advanced scenarios. - -.PARAMETER Name -A unique Name for the Access method. - -.PARAMETER Access -Mutliple Access method Names to be merged. - -.PARAMETER Valid -How many of the Access methods are required to be valid, One or All. (Default: One) - -.EXAMPLE -Merge-PodeAuthAccess -Name MergedAccess -Access RbacAccess, GbacAccess -Valid All -#> -function Merge-PodeAuthAccess -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $Name, - - [Parameter(Mandatory=$true)] - [string[]] - $Access, - - [Parameter()] - [ValidateSet('One', 'All')] - [string] - $Valid = 'One' - ) - - # ensure the name doesn't already exist - if (Test-PodeAuthAccessExists -Name $Name) { - throw "Access method already defined: $($Name)" - } - - # ensure all the access methods exist - foreach ($accName in $Access) { - if (!(Test-PodeAuthAccessExists -Name $accName)) { - throw "Access method does not exist for merging: $($accName)" - } - } - - # set parent access - foreach ($accName in $Access) { - $PodeContext.Server.Authentications.Access[$accName].Parent = $Name - } - - # add auth method to server - $PodeContext.Server.Authentications.Access[$Name] = @{ - Name = $Name - Access = @($Access) - PassOne = ($Valid -ieq 'one') - Cache = @{} - Merged = $true - Parent = $null - } -} - -<# -.SYNOPSIS -Assigns Custom Access value(s) to a Route. - -.DESCRIPTION -Assigns Custom Access value(s) to a Route. - -.PARAMETER Route -The Route to assign the Custom Access value(s). - -.PARAMETER Name -The Name of the Access method the Custom Access value(s) are for. - -.PARAMETER Value -The Custom Access Value(s) - -.EXAMPLE -Add-PodeRoute -Method Get -Path '/users' -ScriptBlock {} -PassThru | Add-PodeAuthCustomAccess -Name 'Example' -Value @{ Country = 'UK' } -#> -function Add-PodeAuthCustomAccess -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] - [hashtable[]] - $Route, - - [Parameter(Mandatory=$true)] - [string] - $Name, - - [Parameter(Mandatory=$true)] - [object[]] - $Value - ) - - begin { - $routes = @() - } - - process { - $routes += $Route - } - - end { - foreach ($r in $routes) { - if ($r.Access.Custom.ContainsKey($Name)) { - throw "Route '[$($r.Method)] $($r.Path)' already contains Custom Access with name '$($Name)'" - } - - $r.Access.Custom[$Name] = $Value - } - } -} - -<# -.SYNOPSIS -Get one or more Access methods. - -.DESCRIPTION -Get one or more Access methods. - -.PARAMETER Name -The Name of the Access method. If no name supplied, all methods will be returned. - -.EXAMPLE -$methods = Get-PodeAuthAccess - -.EXAMPLE -$methods = Get-PodeAuthAccess -Name 'Example' - -.EXAMPLE -$methods = Get-PodeAuthAccess -Name 'Example1', 'Example2' -#> -function Get-PodeAuthAccess -{ - [CmdletBinding()] - param( - [Parameter()] - [string[]] - $Name - ) - - # return all if no Name - if ([string]::IsNullOrEmpty($Name) -or ($Name.Length -eq 0)) { - return $PodeContext.Server.Authentications.Access.Values - } - - # return filtered - return @(foreach ($n in $Name) { - $PodeContext.Server.Authentications.Access[$n] - }) -} - -<# -.SYNOPSIS -Test if an Access method exists. - -.DESCRIPTION -Test if an Access method exists. - -.PARAMETER Name -The Name of the Access method. - -.EXAMPLE -if (Test-PodeAuthAccessExists -Name 'Example') { } -#> -function Test-PodeAuthAccessExists -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $Name - ) - - return $PodeContext.Server.Authentications.Access.ContainsKey($Name) -} - -<# -.SYNOPSIS -Test access values for a Source/Destination against an Access method. - -.DESCRIPTION -Test access values for a Source/Destination against an Access method. - -.PARAMETER Name -The Name of the Access method to use to verify the access. - -.PARAMETER Source -An array of Source access values to pass to the Access method for verification against the Destination access values. (ie: User) - -.PARAMETER Destination -An array of Destination access values to pass to the Access method for verification. (ie: Route) - -.PARAMETER ArgumentList -An optional array of arguments to supply to the Access's methods ScriptBlock for retrieving access values. - -.EXAMPLE -if (Test-PodeAuthAccess -Name 'Example' -Source 'Developer' -Destination 'Admin') { } -#> -function Test-PodeAuthAccess -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $Name, - - [Parameter()] - [object[]] - $Source = $null, - - [Parameter()] - [object[]] - $Destination = $null, - - [Parameter()] - [object[]] - $ArgumentList = $null - ) - - # get the access method - $access = $PodeContext.Server.Authentications.Access[$Name] - - # authorised if no destination values - if (($null -eq $Destination) -or ($Destination.Length -eq 0)) { - return $true - } - - # if we have no source values, invoke the scriptblock - if (($null -eq $Source) -or ($Source.Length -eq 0)) { - if ($null -ne $access.ScriptBlock) { - $_args = $ArgumentList + @($access.Arguments) - $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Scriptblock.UsingVariables) - $Source = Invoke-PodeScriptBlock -ScriptBlock $access.Scriptblock.Script -Arguments $_args -Return -Splat - } - } - - # check for custom validator, or use default match logic - if ($null -ne $access.Validator) { - $_args = @(,$Source) + @(,$Destination) + @($access.Arguments) - $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Validator.UsingVariables) - return [bool](Invoke-PodeScriptBlock -ScriptBlock $access.Validator.Script -Arguments $_args -Return -Splat) - } - - # not authorised if no source values - if (($access.Match -ne 'none') -and (($null -eq $Source) -or ($Source.Length -eq 0))) { - return $false - } - - # one or all match? - else { - switch ($access.Match) { - 'one' { - foreach ($item in $Source) { - if ($item -iin $Destination) { - return $true - } - } - } - - 'all' { - foreach ($item in $Destination) { - if ($item -inotin $Source) { - return $false - } - } - - return $true - } - - 'none' { - foreach ($item in $Source) { - if ($item -iin $Destination) { - return $false - } - } - - return $true - } - } - } - - # default is not authorised - return $false -} - -<# -.SYNOPSIS -Test the currently authenticated User's access against the supplied values. - -.DESCRIPTION -Test the currently authenticated User's access against the supplied values. This will be the user in a WebEvent object. - -.PARAMETER Name -The Name of the Access method to use to verify the access. - -.PARAMETER Value -An array of access values to pass to the Access method for verification against the User. - -.PARAMETER Authentication -Only if multiple merged Authentication methods are being used, supply the Name of the Authentication method the Access being checked is for, -and where the User object should be retrieved. - -.EXAMPLE -if (Test-PodeAuthAccessUser -Name 'Example' -Value 'Developer', 'QA') { } - -.EXAMPLE -if (Test-PodeAuthAccessUser -Name 'Example' -Value 'Developer', 'QA' -Authentication 'BasicAuth') { } -#> -function Test-PodeAuthAccessUser -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string] - $Name, - - [Parameter(Mandatory=$true)] - [object[]] - $Value, - - [Parameter()] - [string] - $Authentication - ) - - # check if an auth name was passed for mutliple auths - if ($WebEvent.Auth.Multiple -and [string]::IsNullOrEmpty($Authentication)) { - throw "No Authentication name supplied to select User for Access testing, when mutliple authentications were used" - } - - # get the access method - $access = $PodeContext.Server.Authentications.Access[$Name] - - # get the user - $user = $WebEvent.Auth.User - if ($WebEvent.Auth.Multiple) { - $user = $user[$Authentication] - } - - # if there's no scriptblock, try the Path fallback - if ($null -eq $access.Scriptblock) { - $userAccess = $user - foreach ($atom in $access.Path.Split('.')) { - $userAccess = $userAccess.($atom) - } - } - - # otherwise, invoke scriptblock - else { - $_args = @($user) + @($access.Arguments) - $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.Scriptblock.UsingVariables) - $userAccess = Invoke-PodeScriptBlock -ScriptBlock $access.Scriptblock.Script -Arguments $_args -Return -Splat - } - - # is the user authorised? - return (Test-PodeAuthAccess -Name $Name -Source $userAccess -Destination $Value) -} - -<# -.SYNOPSIS -Test the currently authenticated User's access against the access values supplied for the current Route. - -.DESCRIPTION -Test the currently authenticated User's access against the access values supplied for the current Route. - -.PARAMETER Name -The Name of the Access method to use to verify the access. - -.PARAMETER Authentication -Only if multiple merged Authentication methods are being used, supply the Name of the Authentication method the Access being checked is for, -and where the User object should be retrieved. - -.EXAMPLE -if (Test-PodeAuthAccessRoute -Name 'Example') { } -#> -function Test-PodeAuthAccessRoute -{ - param( - [Parameter(Mandatory=$true)] - [string] - $Name, - - [Parameter()] - [string] - $Authentication - ) - - # check if an auth name was passed for mutliple auths - if ($WebEvent.Auth.Multiple -and [string]::IsNullOrEmpty($Authentication)) { - throw "No Authentication name supplied to select User for Access testing, when mutliple authentications were used" - } - - # get the access method - $access = $PodeContext.Server.Authentications.Access[$Name] - - # get route access values - if none then skip - $routeAccess = $WebEvent.Route.Access[$access.Type] - if ($access.IsCustom) { - $routeAccess = $routeAccess[$access.Name] - } - - if (($null -eq $routeAccess) -or ($routeAccess.Length -eq 0)) { - return $true - } - - # now test the user's access against the route's access - if ([string]::IsNullOrEmpty($Authentication)) { - $Authentication = $WebEvent.Route.Authentication - } - - return (Test-PodeAuthAccessUser -Name $Name -Value $routeAccess -Authentication $Authentication) + return $auth.User } \ No newline at end of file diff --git a/src/Public/Responses.ps1 b/src/Public/Responses.ps1 index 8f55dad3a..8b05f1886 100644 --- a/src/Public/Responses.ps1 +++ b/src/Public/Responses.ps1 @@ -938,7 +938,7 @@ Set-PodeResponseStatus -Code 500 -Exception $_.Exception -ContentType 'applicati function Set-PodeResponseStatus { [CmdletBinding()] - param ( + param( [Parameter(Mandatory=$true)] [int] $Code, diff --git a/src/Public/Routes.ps1 b/src/Public/Routes.ps1 index cfb4f38bd..d06199ade 100644 --- a/src/Public/Routes.ps1 +++ b/src/Public/Routes.ps1 @@ -38,6 +38,9 @@ An array of arguments to supply to the Route's ScriptBlock. .PARAMETER Authentication The name of an Authentication method which should be used as middleware on this Route. +.PARAMETER Access +The name of an Access method which should be used as middleware on this Route. + .PARAMETER AllowAnon If supplied, the Route will allow anonymous access for non-authenticated users. @@ -137,6 +140,10 @@ function Add-PodeRoute [string] $Authentication, + [Parameter()] + [string] + $Access, + [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] [string] @@ -201,6 +208,10 @@ function Add-PodeRoute $Authentication = $RouteGroup.Authentication } + if ([string]::IsNullOrWhiteSpace($Access)) { + $Access = $RouteGroup.Access + } + if ($RouteGroup.AllowAnon) { $AllowAnon = $RouteGroup.AllowAnon } @@ -209,24 +220,24 @@ function Add-PodeRoute $IfExists = $RouteGroup.IfExists } - if ($null -ne $RouteGroup.Access.Role) { - $Role = $RouteGroup.Access.Role + $Role + if ($null -ne $RouteGroup.AccessMeta.Role) { + $Role = $RouteGroup.AccessMeta.Role + $Role } - if ($null -ne $RouteGroup.Access.Group) { - $Group = $RouteGroup.Access.Group + $Group + if ($null -ne $RouteGroup.AccessMeta.Group) { + $Group = $RouteGroup.AccessMeta.Group + $Group } - if ($null -ne $RouteGroup.Access.Scope) { - $Scope = $RouteGroup.Access.Scope + $Scope + if ($null -ne $RouteGroup.AccessMeta.Scope) { + $Scope = $RouteGroup.AccessMeta.Scope + $Scope } - if ($null -ne $RouteGroup.Access.User) { - $User = $RouteGroup.Access.User + $User + if ($null -ne $RouteGroup.AccessMeta.User) { + $User = $RouteGroup.AccessMeta.User + $User } - if ($null -ne $RouteGroup.Access.Custom) { - $CustomAccess = $RouteGroup.Access.Custom + if ($null -ne $RouteGroup.AccessMeta.Custom) { + $CustomAccess = $RouteGroup.AccessMeta.Custom } } @@ -275,6 +286,23 @@ function Add-PodeRoute # convert any middleware into valid hashtables $Middleware = @(ConvertTo-PodeMiddleware -Middleware $Middleware -PSSession $PSCmdlet.SessionState) + # if an access name was supplied, setup access as middleware first to it's after auth middleware + if (![string]::IsNullOrWhiteSpace($Access)) { + if ([string]::IsNullOrWhiteSpace($Authentication)) { + throw "Access requires Authentication to be supplied on Routes" + } + + if (!(Test-PodeAccessExists -Name $Access)) { + throw "Access method does not exist: $($Access)" + } + + $options = @{ + Name = $Access + } + + $Middleware = (@(Get-PodeAccessMiddlewareScript | New-PodeMiddleware -ArgumentList $options) + $Middleware) + } + # if an auth name was supplied, setup the auth as the first middleware if (![string]::IsNullOrWhiteSpace($Authentication)) { if (!(Test-PodeAuthExists -Name $Authentication)) { @@ -333,7 +361,8 @@ function Add-PodeRoute UsingVariables = $usingVars Middleware = $Middleware Authentication = $Authentication - Access = @{ + Access = $Access + AccessMeta = @{ Role = $Role Group = $Group Scope = $Scope @@ -421,6 +450,9 @@ The content type of any error pages that may get returned. .PARAMETER Authentication The name of an Authentication method which should be used as middleware on this Route. +.PARAMETER Access +The name of an Access method which should be used as middleware on this Route. + .PARAMETER AllowAnon If supplied, the static route will allow anonymous access for non-authenticated users. @@ -433,6 +465,18 @@ If supplied, the static route created will be returned so it can be passed throu .PARAMETER IfExists Specifies what action to take when a Static Route already exists. (Default: Default) +.PARAMETER Role +One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Group +One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Scope +One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER User +One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method. + .EXAMPLE Add-PodeStaticRoute -Path '/assets' -Source './assets' @@ -484,11 +528,31 @@ function Add-PodeStaticRoute [string] $Authentication, + [Parameter()] + [string] + $Access, + [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] [string] $IfExists = 'Default', + [Parameter()] + [string[]] + $Role, + + [Parameter()] + [string[]] + $Group, + + [Parameter()] + [string[]] + $Scope, + + [Parameter()] + [string[]] + $User, + [switch] $AllowAnon, @@ -533,6 +597,10 @@ function Add-PodeStaticRoute $Authentication = $RouteGroup.Authentication } + if ([string]::IsNullOrWhiteSpace($Access)) { + $Access = $RouteGroup.Access + } + if (Test-PodeIsEmpty $Defaults) { $Defaults = $RouteGroup.Defaults } @@ -548,6 +616,26 @@ function Add-PodeStaticRoute if ($RouteGroup.IfExists -ine 'default') { $IfExists = $RouteGroup.IfExists } + + if ($null -ne $RouteGroup.AccessMeta.Role) { + $Role = $RouteGroup.AccessMeta.Role + $Role + } + + if ($null -ne $RouteGroup.AccessMeta.Group) { + $Group = $RouteGroup.AccessMeta.Group + $Group + } + + if ($null -ne $RouteGroup.AccessMeta.Scope) { + $Scope = $RouteGroup.AccessMeta.Scope + $Scope + } + + if ($null -ne $RouteGroup.AccessMeta.User) { + $User = $RouteGroup.AccessMeta.User + $User + } + + if ($null -ne $RouteGroup.AccessMeta.Custom) { + $CustomAccess = $RouteGroup.AccessMeta.Custom + } } # store the route method @@ -617,6 +705,23 @@ function Add-PodeStaticRoute # convert any middleware into valid hashtables $Middleware = @(ConvertTo-PodeMiddleware -Middleware $Middleware -PSSession $PSCmdlet.SessionState) + # if an access name was supplied, setup access as middleware first to it's after auth middleware + if (![string]::IsNullOrWhiteSpace($Access)) { + if ([string]::IsNullOrWhiteSpace($Authentication)) { + throw "Access requires Authentication to be supplied on Static Routes" + } + + if (!(Test-PodeAccessExists -Name $Access)) { + throw "Access method does not exist: $($Access)" + } + + $options = @{ + Name = $Access + } + + $Middleware = (@(Get-PodeAccessMiddlewareScript | New-PodeMiddleware -ArgumentList $options) + $Middleware) + } + # if an auth name was supplied, setup the auth as the first middleware if (![string]::IsNullOrWhiteSpace($Authentication)) { if (!(Test-PodeAuthExists -Name $Authentication)) { @@ -646,6 +751,15 @@ function Add-PodeStaticRoute Method = $Method Defaults = $Defaults Middleware = $Middleware + Authentication = $Authentication + Access = $Access + AccessMeta = @{ + Role = $Role + Group = $Group + Scope = $Scope + User = $User + Custom = $CustomAccess + } Endpoint = @{ Protocol = $_endpoint.Protocol Address = $_endpoint.Address.Trim() @@ -874,6 +988,9 @@ The content type of any error pages that may get returned. .PARAMETER Authentication The name of an Authentication method which should be used as middleware on the Routes. +.PARAMETER Access +The name of an Access method which should be used as middleware on this Route. + .PARAMETER IfExists Specifies what action to take when a Route already exists. (Default: Default) @@ -933,6 +1050,10 @@ function Add-PodeRouteGroup [string] $Authentication, + [Parameter()] + [string] + $Access, + [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] [string] @@ -999,6 +1120,10 @@ function Add-PodeRouteGroup $Authentication = $RouteGroup.Authentication } + if ([string]::IsNullOrWhiteSpace($Access)) { + $Access = $RouteGroup.Access + } + if ($RouteGroup.AllowAnon) { $AllowAnon = $RouteGroup.AllowAnon } @@ -1007,24 +1132,24 @@ function Add-PodeRouteGroup $IfExists = $RouteGroup.IfExists } - if ($null -ne $RouteGroup.Access.Role) { - $Role = $RouteGroup.Access.Role + $Role + if ($null -ne $RouteGroup.AccessMeta.Role) { + $Role = $RouteGroup.AccessMeta.Role + $Role } - if ($null -ne $RouteGroup.Access.Group) { - $Group = $RouteGroup.Access.Group + $Group + if ($null -ne $RouteGroup.AccessMeta.Group) { + $Group = $RouteGroup.AccessMeta.Group + $Group } - if ($null -ne $RouteGroup.Access.Scope) { - $Scope = $RouteGroup.Access.Scope + $Scope + if ($null -ne $RouteGroup.AccessMeta.Scope) { + $Scope = $RouteGroup.AccessMeta.Scope + $Scope } - if ($null -ne $RouteGroup.Access.User) { - $User = $RouteGroup.Access.User + $User + if ($null -ne $RouteGroup.AccessMeta.User) { + $User = $RouteGroup.AccessMeta.User + $User } - if ($null -ne $RouteGroup.Access.Custom) { - $CustomAccess = $RouteGroup.Access.Custom + if ($null -ne $RouteGroup.AccessMeta.Custom) { + $CustomAccess = $RouteGroup.AccessMeta.Custom } } @@ -1036,9 +1161,10 @@ function Add-PodeRouteGroup TransferEncoding = $TransferEncoding ErrorContentType = $ErrorContentType Authentication = $Authentication + Access = $Access AllowAnon = $AllowAnon IfExists = $IfExists - Access = @{ + AccessMeta = @{ Role = $Role Group = $Group Scope = $Scope @@ -1089,6 +1215,9 @@ The content type of any error pages that may get returned. .PARAMETER Authentication The name of an Authentication method which should be used as middleware on the Static Routes. +.PARAMETER Access +The name of an Access method which should be used as middleware on this Route. + .PARAMETER IfExists Specifies what action to take when a Static Route already exists. (Default: Default) @@ -1098,6 +1227,18 @@ If supplied, the Static Routes will allow anonymous access for non-authenticated .PARAMETER DownloadOnly When supplied, all static content on the Routes will be attached as downloads - rather than rendered. +.PARAMETER Role +One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Group +One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Scope +One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER User +One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method. + .EXAMPLE Add-PodeStaticRouteGroup -Path '/static' -Routes { Add-PodeStaticRoute -Path '/images' -Etc } #> @@ -1147,11 +1288,31 @@ function Add-PodeStaticRouteGroup [string] $Authentication, + [Parameter()] + [string] + $Access, + [Parameter()] [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')] [string] $IfExists = 'Default', + [Parameter()] + [string[]] + $Role, + + [Parameter()] + [string[]] + $Group, + + [Parameter()] + [string[]] + $Scope, + + [Parameter()] + [string[]] + $User, + [switch] $AllowAnon, @@ -1204,6 +1365,10 @@ function Add-PodeStaticRouteGroup $Authentication = $RouteGroup.Authentication } + if ([string]::IsNullOrWhiteSpace($Access)) { + $Access = $RouteGroup.Access + } + if (Test-PodeIsEmpty $Defaults) { $Defaults = $RouteGroup.Defaults } @@ -1219,6 +1384,26 @@ function Add-PodeStaticRouteGroup if ($RouteGroup.IfExists -ine 'default') { $IfExists = $RouteGroup.IfExists } + + if ($null -ne $RouteGroup.AccessMeta.Role) { + $Role = $RouteGroup.AccessMeta.Role + $Role + } + + if ($null -ne $RouteGroup.AccessMeta.Group) { + $Group = $RouteGroup.AccessMeta.Group + $Group + } + + if ($null -ne $RouteGroup.AccessMeta.Scope) { + $Scope = $RouteGroup.AccessMeta.Scope + $Scope + } + + if ($null -ne $RouteGroup.AccessMeta.User) { + $User = $RouteGroup.AccessMeta.User + $User + } + + if ($null -ne $RouteGroup.AccessMeta.Custom) { + $CustomAccess = $RouteGroup.AccessMeta.Custom + } } $RouteGroup = @{ @@ -1231,9 +1416,17 @@ function Add-PodeStaticRouteGroup Defaults = $Defaults ErrorContentType = $ErrorContentType Authentication = $Authentication + Access = $Access AllowAnon = $AllowAnon DownloadOnly = $DownloadOnly IfExists = $IfExists + AccessMeta = @{ + Role = $Role + Group = $Group + Scope = $Scope + User = $User + Custom = $CustomAccess + } } # add routes @@ -1586,6 +1779,9 @@ Like normal Routes, an array of Middleware that will be applied to all generated .PARAMETER Authentication The name of an Authentication method which should be used as middleware on this Route. +.PARAMETER Access +The name of an Access method which should be used as middleware on this Route. + .PARAMETER AllowAnon If supplied, the Route will allow anonymous access for non-authenticated users. @@ -1595,6 +1791,18 @@ If supplied, the Command's Verb will not be included in the Route's path. .PARAMETER NoOpenApi If supplied, no OpenAPI definitions will be generated for the routes created. +.PARAMETER Role +One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Group +One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Scope +One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER User +One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method. + .EXAMPLE ConvertTo-PodeRoute -Commands @('Get-ChildItem', 'Get-Host', 'Invoke-Expression') -Middleware { ... } @@ -1637,6 +1845,26 @@ function ConvertTo-PodeRoute [string] $Authentication, + [Parameter()] + [string] + $Access, + + [Parameter()] + [string[]] + $Role, + + [Parameter()] + [string[]] + $Group, + + [Parameter()] + [string[]] + $Scope, + + [Parameter()] + [string[]] + $User, + [switch] $AllowAnon, @@ -1708,7 +1936,22 @@ function ConvertTo-PodeRoute $_path = ("$($Path)/$($Module)/$($name)" -replace '[/]+', '/') # create the route - $route = (Add-PodeRoute -Method $_method -Path $_path -Middleware $Middleware -Authentication $Authentication -AllowAnon:$AllowAnon -ArgumentList $cmd -ScriptBlock { + $params = @{ + Method = $_method + Path = $_path + Middleware = $Middleware + Authentication = $Authentication + Access = $Access + Role = $Role + Group = $Group + Scope = $Scope + User = $User + AllowAnon = $AllowAnon + ArgumentList = $cmd + PassThru = $true + } + + $route = Add-PodeRoute @params -ScriptBlock { param($cmd) # either get params from the QueryString or Payload @@ -1726,7 +1969,7 @@ function ConvertTo-PodeRoute if (!(Test-PodeIsEmpty $result)) { Write-PodeJsonResponse -Value $result -Depth 1 } - } -PassThru) + } # set the openapi metadata of the function, unless told to skip if ($NoOpenApi) { @@ -1790,12 +2033,27 @@ Like normal Routes, an array of Middleware that will be applied to all generated .PARAMETER Authentication The name of an Authentication method which should be used as middleware on this Route. +.PARAMETER Access +The name of an Access method which should be used as middleware on this Route. + .PARAMETER AllowAnon If supplied, the Page will allow anonymous access for non-authenticated users. .PARAMETER FlashMessages If supplied, Views will have any flash messages supplied to them for rendering. +.PARAMETER Role +One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Group +One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER Scope +One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method. + +.PARAMETER User +One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method. + .EXAMPLE Add-PodePage -Name Services -ScriptBlock { Get-Service } @@ -1843,6 +2101,26 @@ function Add-PodePage [string] $Authentication, + [Parameter()] + [string] + $Access, + + [Parameter()] + [string[]] + $Role, + + [Parameter()] + [string[]] + $Group, + + [Parameter()] + [string[]] + $Scope, + + [Parameter()] + [string[]] + $User, + [switch] $AllowAnon, @@ -1912,14 +2190,22 @@ function Add-PodePage $_path = ("$($Path)/$($Name)" -replace '[/]+', '/') # create the route - Add-PodeRoute ` - -Method Get ` - -Path $_path ` - -Middleware $Middleware ` - -Authentication $Authentication ` - -AllowAnon:$AllowAnon ` - -ArgumentList $arg ` - -ScriptBlock $logic + $params = @{ + Method = 'Get' + Path = $_path + Middleware = $Middleware + Authentication = $Authentication + Access = $Access + Role = $Role + Group = $Group + Scope = $Scope + User = $User + AllowAnon = $AllowAnon + ArgumentList = $arg + ScriptBlock = $logic + } + + Add-PodeRoute @params } <# diff --git a/tests/unit/Server.Tests.ps1 b/tests/unit/Server.Tests.ps1 index f30715609..68d207ecb 100644 --- a/tests/unit/Server.Tests.ps1 +++ b/tests/unit/Server.Tests.ps1 @@ -130,7 +130,9 @@ Describe 'Restart-PodeInternalServer' { Sessions = @{ 'key' = 'value' } Authentications = @{ Methods = @{ 'key' = 'value' } - Access = @{ 'key' = 'value' } + } + Authorisations = @{ + Methods = @{ 'key' = 'value' } } State = @{ 'key' = 'value' } Output = @{ From 31c8670e748b56cb2ed7d9b81515a43614220b09 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Thu, 12 Oct 2023 22:32:12 +0100 Subject: [PATCH 50/57] #1030: allow UsersFile, WindowsAd, and WindowsLocal auths work with -AsCredential scheme switch --- src/Private/Authentication.ps1 | 96 +++++++++++++++++++++++----------- src/Public/Authentication.ps1 | 9 ++++ 2 files changed, 75 insertions(+), 30 deletions(-) diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index 4adfbfaee..6f2f5a8bf 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -705,12 +705,24 @@ function Get-PodeAuthUserFileMethod return { param($username, $password, $options) + # using pscreds? + if (($null -eq $options) -and ($username -is [pscredential])) { + $_username = ([pscredential]$username).UserName + $_password = ([pscredential]$username).GetNetworkCredential().Password + $_options = [hashtable]$password + } + else { + $_username = $username + $_password = $password + $_options = $options + } + # load the file - $users = (Get-Content -Path $options.FilePath -Raw | ConvertFrom-Json) + $users = (Get-Content -Path $_options.FilePath -Raw | ConvertFrom-Json) # find the user by username - only use the first one $user = @(foreach ($_user in $users) { - if ($_user.Username -ieq $username) { + if ($_user.Username -ieq $_username) { $_user break } @@ -722,11 +734,11 @@ function Get-PodeAuthUserFileMethod } # check the user's password - if (![string]::IsNullOrWhiteSpace($options.HmacSecret)) { - $hash = Invoke-PodeHMACSHA256Hash -Value $password -Secret $options.HmacSecret + if (![string]::IsNullOrWhiteSpace($_options.HmacSecret)) { + $hash = Invoke-PodeHMACSHA256Hash -Value $_password -Secret $_options.HmacSecret } else { - $hash = Invoke-PodeSHA256Hash -Value $password + $hash = Invoke-PodeSHA256Hash -Value $_password } if ($user.Password -ne $hash) { @@ -743,15 +755,15 @@ function Get-PodeAuthUserFileMethod } # is the user valid for any users/groups? - if (!(Test-PodeAuthUserGroups -User $user -Users $options.Users -Groups $options.Groups)) { + if (!(Test-PodeAuthUserGroups -User $user -Users $_options.Users -Groups $_options.Groups)) { return @{ Message = 'You are not authorised to access this website' } } $result = @{ User = $user } # call additional scriptblock if supplied - if ($null -ne $options.ScriptBlock.Script) { - $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $options.ScriptBlock.Script -UsingVariables $options.ScriptBlock.UsingVariables + if ($null -ne $_options.ScriptBlock.Script) { + $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $_options.ScriptBlock.Script -UsingVariables $_options.ScriptBlock.UsingVariables } # return final result, this could contain a user obj, or an error message from custom scriptblock @@ -764,21 +776,33 @@ function Get-PodeAuthWindowsADMethod return { param($username, $password, $options) + # using pscreds? + if (($null -eq $options) -and ($username -is [pscredential])) { + $_username = ([pscredential]$username).UserName + $_password = ([pscredential]$username).GetNetworkCredential().Password + $_options = [hashtable]$password + } + else { + $_username = $username + $_password = $password + $_options = $options + } + # parse username to remove domains - $username = (($username -split '@')[0] -split '\\')[-1] + $_username = (($_username -split '@')[0] -split '\\')[-1] # validate and retrieve the AD user - $noGroups = $options.NoGroups - $directGroups = $options.DirectGroups - $keepCredential = $options.KeepCredential + $noGroups = $_options.NoGroups + $directGroups = $_options.DirectGroups + $keepCredential = $_options.KeepCredential $result = Get-PodeAuthADResult ` - -Server $options.Server ` - -Domain $options.Domain ` - -SearchBase $options.SearchBase ` - -Username $username ` - -Password $password ` - -Provider $options.Provider ` + -Server $_options.Server ` + -Domain $_options.Domain ` + -SearchBase $_options.SearchBase ` + -Username $_username ` + -Password $_password ` + -Provider $_options.Provider ` -NoGroups:$noGroups ` -DirectGroups:$directGroups ` -KeepCredential:$keepCredential @@ -794,13 +818,13 @@ function Get-PodeAuthWindowsADMethod } # is the user valid for any users/groups - if not, error! - if (!(Test-PodeAuthUserGroups -User $result.User -Users $options.Users -Groups $options.Groups)) { + if (!(Test-PodeAuthUserGroups -User $result.User -Users $_options.Users -Groups $_options.Groups)) { return @{ Message = 'You are not authorised to access this website' } } # call additional scriptblock if supplied - if ($null -ne $options.ScriptBlock.Script) { - $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $options.ScriptBlock.Script -UsingVariables $options.ScriptBlock.UsingVariables + if ($null -ne $_options.ScriptBlock.Script) { + $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $_options.ScriptBlock.Script -UsingVariables $_options.ScriptBlock.UsingVariables } # return final result, this could contain a user obj, or an error message from custom scriptblock @@ -832,10 +856,22 @@ function Get-PodeAuthWindowsLocalMethod return { param($username, $password, $options) + # using pscreds? + if (($null -eq $options) -and ($username -is [pscredential])) { + $_username = ([pscredential]$username).UserName + $_password = ([pscredential]$username).GetNetworkCredential().Password + $_options = [hashtable]$password + } + else { + $_username = $username + $_password = $password + $_options = $options + } + $user = @{ UserType = 'Local' AuthenticationType = 'WinNT' - Username = $username + Username = $_username Name = [string]::Empty Fqdn = $PodeContext.Server.ComputerName Domain = 'localhost' @@ -844,22 +880,22 @@ function Get-PodeAuthWindowsLocalMethod Add-Type -AssemblyName System.DirectoryServices.AccountManagement -ErrorAction Stop $context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Machine', $PodeContext.Server.ComputerName) - $valid = $context.ValidateCredentials($username, $password) + $valid = $context.ValidateCredentials($_username, $_password) if (!$valid) { return @{ Message = 'Invalid credentials supplied' } } try { - $tmpUsername = $username -replace '\\', '/' - if ($username -inotlike "$($PodeContext.Server.ComputerName)*") { - $tmpUsername = "$($PodeContext.Server.ComputerName)/$($username)" + $tmpUsername = $_username -replace '\\', '/' + if ($_username -inotlike "$($PodeContext.Server.ComputerName)*") { + $tmpUsername = "$($PodeContext.Server.ComputerName)/$($_username)" } $ad = [adsi]"WinNT://$($tmpUsername)" $user.Name = @($ad.FullName)[0] - if (!$options.NoGroups) { + if (!$_options.NoGroups) { $cmd = "`$ad = [adsi]'WinNT://$($tmpUsername)'; @(`$ad.Groups() | Foreach-Object { `$_.GetType().InvokeMember('Name', 'GetProperty', `$null, `$_, `$null) })" $user.Groups = [string[]](powershell -c $cmd) } @@ -869,15 +905,15 @@ function Get-PodeAuthWindowsLocalMethod } # is the user valid for any users/groups - if not, error! - if (!(Test-PodeAuthUserGroups -User $user -Users $options.Users -Groups $options.Groups)) { + if (!(Test-PodeAuthUserGroups -User $user -Users $_options.Users -Groups $_options.Groups)) { return @{ Message = 'You are not authorised to access this website' } } $result = @{ User = $user } # call additional scriptblock if supplied - if ($null -ne $options.ScriptBlock.Script) { - $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $options.ScriptBlock.Script -UsingVariables $options.ScriptBlock.UsingVariables + if ($null -ne $_options.ScriptBlock.Script) { + $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $_options.ScriptBlock.Script -UsingVariables $_options.ScriptBlock.UsingVariables } # return final result, this could contain a user obj, or an error message from custom scriptblock diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index e87cbcd4e..59371f37c 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -1307,6 +1307,9 @@ function Add-PodeAuthWindowsAd Url = $SuccessUrl UseOrigin = $SuccessUseOrigin } + Cache = @{} + Merged = $false + Parent = $null } } @@ -1872,6 +1875,9 @@ function Add-PodeAuthUserFile Url = $SuccessUrl UseOrigin = $SuccessUseOrigin } + Cache = @{} + Merged = $false + Parent = $null } } @@ -2018,6 +2024,9 @@ function Add-PodeAuthWindowsLocal Url = $SuccessUrl UseOrigin = $SuccessUseOrigin } + Cache = @{} + Merged = $false + Parent = $null } } From 3a7ce1c41e2ea0370e4f3785b682f9f480ec7347 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Tue, 17 Oct 2023 19:22:42 +0100 Subject: [PATCH 51/57] #1169: add vscode workspace settings for pwsh code formatting --- .vscode/settings.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..abf6faf6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,23 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnType": false, + "editor.minimap.enabled": false, + "powershell.codeFormatting.addWhitespaceAroundPipe": true, + "powershell.codeFormatting.alignPropertyValuePairs": true, + "powershell.codeFormatting.autoCorrectAliases": true, + "powershell.codeFormatting.avoidSemicolonsAsLineTerminators": true, + "powershell.codeFormatting.ignoreOneLineBlock": false, + "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationForFirstPipeline", + "powershell.codeFormatting.trimWhitespaceAroundPipe": true, + "powershell.codeFormatting.useConstantStrings": true, + "powershell.codeFormatting.whitespaceBeforeOpenBrace": true, + "powershell.codeFormatting.whitespaceAroundOperator": true, + "powershell.codeFormatting.whitespaceAfterSeparator": true, + "powershell.codeFormatting.useCorrectCasing": false, + "powershell.codeFormatting.openBraceOnSameLine": true, + "powershell.codeFormatting.newLineAfterOpenBrace": true, + "powershell.codeFormatting.newLineAfterCloseBrace": true, + "powershell.codeFormatting.whitespaceBeforeOpenParen": true, + "powershell.codeFormatting.whitespaceBetweenParameters": false, + "powershell.codeFormatting.whitespaceInsideBrace": false +} \ No newline at end of file From 9388cf5820e0645356a934916770cc0859bc9727 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Tue, 17 Oct 2023 21:09:37 +0100 Subject: [PATCH 52/57] #1169: reformats the src powershell files using the auto code-formatter --- .github/CONTRIBUTING.md | 69 +-- .vscode/settings.json | 5 +- examples/web-csrf.ps1 | 2 +- examples/web-hostname-kestrel.ps1 | 2 +- examples/web-hostname.ps1 | 2 +- examples/web-imports.ps1 | 2 +- examples/web-pages-kestrel.ps1 | 2 +- examples/web-pages-simple.ps1 | 2 +- examples/web-pages.ps1 | 2 +- examples/web-static.ps1 | 2 +- examples/web-upload-kestrel.ps1 | 2 +- examples/web-upload.ps1 | 2 +- pode.build.ps1 | 2 +- src/Pode.Internal.psd1 | 8 +- src/Pode.psd1 | 22 +- src/Private/Access.ps1 | 8 +- src/Private/Authentication.ps1 | 447 ++++++++---------- src/Private/AutoImport.ps1 | 78 ++-- src/Private/Context.ps1 | 257 +++++----- src/Private/Cookies.ps1 | 26 +- src/Private/CronParser.ps1 | 131 +++--- src/Private/Cryptography.ps1 | 149 +++--- src/Private/Endpoints.ps1 | 118 +++-- src/Private/Endware.ps1 | 8 +- src/Private/Events.ps1 | 5 +- src/Private/FileMonitor.ps1 | 26 +- src/Private/FileWatchers.ps1 | 47 +- src/Private/Gui.ps1 | 11 +- src/Private/Helpers.ps1 | 750 +++++++++++++----------------- src/Private/Logging.ps1 | 123 ++--- src/Private/Mappers.ps1 | 16 +- src/Private/Metrics.ps1 | 6 +- src/Private/Middleware.ps1 | 298 ++++++------ src/Private/NameGenerator.ps1 | 121 +++-- src/Private/OpenApi.ps1 | 128 +++-- src/Private/PodeServer.ps1 | 179 ++++--- src/Private/Responses.ps1 | 7 +- src/Private/Routes.ps1 | 140 +++--- src/Private/Schedules.ps1 | 38 +- src/Private/Secrets.ps1 | 139 +++--- src/Private/Security.ps1 | 324 ++++++------- src/Private/Server.ps1 | 36 +- src/Private/Serverless.ps1 | 140 +++--- src/Private/ServiceServer.ps1 | 9 +- src/Private/Sessions.ps1 | 67 ++- src/Private/Setup.ps1 | 10 +- src/Private/SmtpServer.ps1 | 96 ++-- src/Private/Streams.ps1 | 67 ++- src/Private/Tasks.ps1 | 38 +- src/Private/TcpServer.ps1 | 80 ++-- src/Private/Timers.ps1 | 21 +- src/Private/Verbs.ps1 | 26 +- src/Private/WebSockets.ps1 | 40 +- src/Public/Access.ps1 | 143 +++--- src/Public/Authentication.ps1 | 664 +++++++++++++------------- src/Public/AutoImport.ps1 | 20 +- src/Public/Cookies.ps1 | 63 ++- src/Public/Core.ps1 | 220 +++++---- src/Public/Events.ps1 | 44 +- src/Public/FileWatchers.ps1 | 70 ++- src/Public/Flash.ps1 | 36 +- src/Public/Handlers.ps1 | 40 +- src/Public/Headers.ps1 | 45 +- src/Public/Logging.ps1 | 228 +++++---- src/Public/Metrics.ps1 | 24 +- src/Public/Middleware.ps1 | 118 ++--- src/Public/OpenApi.ps1 | 271 +++++------ src/Public/Responses.ps1 | 248 +++++----- src/Public/Routes.ps1 | 615 ++++++++++++------------ src/Public/Schedules.ps1 | 140 +++--- src/Public/Secrets.ps1 | 143 +++--- src/Public/Security.ps1 | 90 ++-- src/Public/Sessions.ps1 | 61 ++- src/Public/State.ps1 | 69 ++- src/Public/Tasks.ps1 | 77 ++- src/Public/Threading.ps1 | 134 +++--- src/Public/Timers.ps1 | 80 ++-- src/Public/Utilities.ps1 | 177 +++---- src/Public/Verbs.ps1 | 73 ++- src/Public/WebSockets.ps1 | 51 +- 80 files changed, 3770 insertions(+), 4510 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 7b346ad61..9122555d5 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,30 +6,34 @@ The following is a set of guidelines for contributing to Pode on GitHub. These a ## Table of Contents -* [Code of Conduct](#code-of-conduct) -* [I just have a Question](#i-just-have-a-question) -* [About Pode](#about-pode) -* [How to Contribute](#how-to-contribute) - * [Issues](#issues) - * [Branch Names](#branch-names) - * [Pull Requests](#pull-requests) - * [Building](#building) - * [Testing](#testing) - * [Documentation](#documentation) -* [Styleguide](#styleguide) - * [Code](#code) - * [Comments](#comments) - * [General](#general) - * [Help](#help) - * [PowerShell Commandlets](#powershell-commandlets) - * [Foreach-Object](#foreach-object) - * [Where-Object](#where-object) - * [Select-Object](#select-object) - * [Measure-Object](#measure-object) +- [Contributing to Pode](#contributing-to-pode) + - [Table of Contents](#table-of-contents) + - [Code of Conduct](#code-of-conduct) + - [I just have a Question](#i-just-have-a-question) + - [About Pode](#about-pode) + - [How to Contribute](#how-to-contribute) + - [Issues](#issues) + - [Branch Names](#branch-names) + - [Pull Requests](#pull-requests) + - [Building](#building) + - [Testing](#testing) + - [Documentation](#documentation) + - [Importing](#importing) + - [Styleguide](#styleguide) + - [Editor](#editor) + - [Code](#code) + - [Comments](#comments) + - [General](#general) + - [Help](#help) + - [PowerShell Commandlets](#powershell-commandlets) + - [Foreach-Object](#foreach-object) + - [Where-Object](#where-object) + - [Select-Object](#select-object) + - [Measure-Object](#measure-object) ## Code of Conduct -This project and everyone participating in it is governed by the Pode's [Code of Conduct](../.github/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. +This project, and everyone participating in it, is governed by the Pode's [Code of Conduct](../.github/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. ## I just have a Question @@ -111,22 +115,20 @@ When editing Pode and you need to import the local dev module for testing, you w ## Styleguide +### Editor + +You can use whatever editor you like, but it's recommended to use Visual Studio Code. To help with this style guide, specifically for PowerShell, Pode has code formatting workspace setting which will automatically format the files on save. + ### Code In general, observe the coding style used within the file/project and mimic that as best as you can. Some standards that are typical are: -* Bracers (`{}`) on the function header should be on a new line, such as: +* Bracers (`{}`) should be on the same line of the statement they following, such as `function`, `foreach`, `if`, etc. ```powershell -function Add-Something -{ - # logic -} -``` - -* Bracers (`{}`) should be on the same line of other calls, such as `foreach`, `if`, etc. -```powershell -foreach ($item in $items) { - # logic +function Add-Something { + foreach ($item in $items) { + # logic + } } ``` @@ -135,8 +137,7 @@ foreach ($item in $items) { * Ensure public functions always declare `[CmdletBinding()]` attribute. * Ensure parameter names, types, and attributes are declared on new lines - not all on one line. ```powershell -function Add-Something -{ +function Add-Something { [CmdletBinding()] param( [Parameter()] diff --git a/.vscode/settings.json b/.vscode/settings.json index abf6faf6b..0eada4b5d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,7 @@ "powershell.codeFormatting.alignPropertyValuePairs": true, "powershell.codeFormatting.autoCorrectAliases": true, "powershell.codeFormatting.avoidSemicolonsAsLineTerminators": true, - "powershell.codeFormatting.ignoreOneLineBlock": false, + "powershell.codeFormatting.ignoreOneLineBlock": true, "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationForFirstPipeline", "powershell.codeFormatting.trimWhitespaceAroundPipe": true, "powershell.codeFormatting.useConstantStrings": true, @@ -19,5 +19,6 @@ "powershell.codeFormatting.newLineAfterCloseBrace": true, "powershell.codeFormatting.whitespaceBeforeOpenParen": true, "powershell.codeFormatting.whitespaceBetweenParameters": false, - "powershell.codeFormatting.whitespaceInsideBrace": false + "powershell.codeFormatting.whitespaceInsideBrace": true, + "files.trimTrailingWhitespace": true } \ No newline at end of file diff --git a/examples/web-csrf.ps1 b/examples/web-csrf.ps1 index 85561a927..e6c8b78f9 100644 --- a/examples/web-csrf.ps1 +++ b/examples/web-csrf.ps1 @@ -1,4 +1,4 @@ -param ( +param( [Parameter()] [ValidateSet('Cookie', 'Session')] [string] diff --git a/examples/web-hostname-kestrel.ps1 b/examples/web-hostname-kestrel.ps1 index e872a1348..0003a355d 100644 --- a/examples/web-hostname-kestrel.ps1 +++ b/examples/web-hostname-kestrel.ps1 @@ -1,4 +1,4 @@ -param ( +param( [int] $Port = 8085 ) diff --git a/examples/web-hostname.ps1 b/examples/web-hostname.ps1 index 64f953c5c..920dfd2d5 100644 --- a/examples/web-hostname.ps1 +++ b/examples/web-hostname.ps1 @@ -1,4 +1,4 @@ -param ( +param( [int] $Port = 8085 ) diff --git a/examples/web-imports.ps1 b/examples/web-imports.ps1 index 4fe9c0cf8..e6017a3cc 100644 --- a/examples/web-imports.ps1 +++ b/examples/web-imports.ps1 @@ -1,4 +1,4 @@ -param ( +param( [int] $Port = 8085 ) diff --git a/examples/web-pages-kestrel.ps1 b/examples/web-pages-kestrel.ps1 index 632f1e5c1..aa73f7801 100644 --- a/examples/web-pages-kestrel.ps1 +++ b/examples/web-pages-kestrel.ps1 @@ -1,4 +1,4 @@ -param ( +param( [int] $Port = 8085 ) diff --git a/examples/web-pages-simple.ps1 b/examples/web-pages-simple.ps1 index e7f846c01..893812910 100644 --- a/examples/web-pages-simple.ps1 +++ b/examples/web-pages-simple.ps1 @@ -1,4 +1,4 @@ -param ( +param( [int] $Port = 8085 ) diff --git a/examples/web-pages.ps1 b/examples/web-pages.ps1 index 90fbf799e..2d1dcb53b 100644 --- a/examples/web-pages.ps1 +++ b/examples/web-pages.ps1 @@ -1,4 +1,4 @@ -param ( +param( [int] $Port = 8085 ) diff --git a/examples/web-static.ps1 b/examples/web-static.ps1 index 3f0bf2472..c6b4a0cc0 100644 --- a/examples/web-static.ps1 +++ b/examples/web-static.ps1 @@ -1,4 +1,4 @@ -param ( +param( [int] $Port = 8085 ) diff --git a/examples/web-upload-kestrel.ps1 b/examples/web-upload-kestrel.ps1 index f021ed274..61560dae2 100644 --- a/examples/web-upload-kestrel.ps1 +++ b/examples/web-upload-kestrel.ps1 @@ -1,4 +1,4 @@ -param ( +param( [int] $Port = 8085 ) diff --git a/examples/web-upload.ps1 b/examples/web-upload.ps1 index 8b0fc7e7a..e652c3ffb 100644 --- a/examples/web-upload.ps1 +++ b/examples/web-upload.ps1 @@ -1,4 +1,4 @@ -param ( +param( [int] $Port = 8085 ) diff --git a/pode.build.ps1 b/pode.build.ps1 index 3b59e8f82..eeb8a7f89 100644 --- a/pode.build.ps1 +++ b/pode.build.ps1 @@ -1,4 +1,4 @@ -param ( +param( [string] $Version = '' ) diff --git a/src/Pode.Internal.psd1 b/src/Pode.Internal.psd1 index 1e295a37f..a7bb4dee5 100644 --- a/src/Pode.Internal.psd1 +++ b/src/Pode.Internal.psd1 @@ -8,16 +8,16 @@ @{ # Script module or binary module file associated with this manifest. - RootModule = 'Pode.Internal.psm1' + RootModule = 'Pode.Internal.psm1' # Version number of this module. - ModuleVersion = '$version$' + ModuleVersion = '$version$' # ID used to uniquely identify this module - GUID = '86b48c1c-8b59-4f3c-80bb-936d6b3218f6' + GUID = '86b48c1c-8b59-4f3c-80bb-936d6b3218f6' # Author of this module - Author = 'Matthew Kelly (Badgerati)' + Author = 'Matthew Kelly (Badgerati)' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '5.0' diff --git a/src/Pode.psd1 b/src/Pode.psd1 index 6ff02e503..cd0ba8d01 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -8,22 +8,22 @@ @{ # Script module or binary module file associated with this manifest. - RootModule = 'Pode.psm1' + RootModule = 'Pode.psm1' # Version number of this module. - ModuleVersion = '$version$' + ModuleVersion = '$version$' # ID used to uniquely identify this module - GUID = 'e3ea217c-fc3d-406b-95d5-4304ab06c6af' + GUID = 'e3ea217c-fc3d-406b-95d5-4304ab06c6af' # Author of this module - Author = 'Matthew Kelly (Badgerati)' + Author = 'Matthew Kelly (Badgerati)' # Copyright statement for this module - Copyright = 'Copyright (c) 2017-2023 Matthew Kelly (Badgerati), licensed under the MIT License.' + Copyright = 'Copyright (c) 2017-2023 Matthew Kelly (Badgerati), licensed under the MIT License.' # Description of the functionality provided by this module - Description = 'A Cross-Platform PowerShell framework for creating web servers to host REST APIs and Websites. Pode also has support for being used in Azure Functions and AWS Lambda.' + Description = 'A Cross-Platform PowerShell framework for creating web servers to host REST APIs and Websites. Pode also has support for being used in Azure Functions and AWS Lambda.' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '5.0' @@ -404,24 +404,24 @@ ) # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. - PrivateData = @{ + PrivateData = @{ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('powershell', 'web', 'server', 'http', 'listener', 'rest', 'api', 'tcp', 'smtp', 'websites', + Tags = @('powershell', 'web', 'server', 'http', 'listener', 'rest', 'api', 'tcp', 'smtp', 'websites', 'powershell-core', 'windows', 'unix', 'linux', 'pode', 'PSEdition_Core', 'cross-platform', 'file-monitoring', 'multithreaded', 'schedule', 'middleware', 'session', 'authentication', 'authorisation', 'arm', 'raspberry-pi', 'aws-lambda', 'azure-functions', 'websockets', 'swagger', 'openapi', 'webserver', 'secrets', 'fim') # A URL to the license for this module. - LicenseUri = 'https://raw.githubusercontent.com/Badgerati/Pode/master/LICENSE.txt' + LicenseUri = 'https://raw.githubusercontent.com/Badgerati/Pode/master/LICENSE.txt' # A URL to the main website for this project. - ProjectUri = 'https://github.com/Badgerati/Pode' + ProjectUri = 'https://github.com/Badgerati/Pode' # A URL to an icon representing this module. - IconUri = 'https://raw.githubusercontent.com/Badgerati/Pode/master/images/icon.png' + IconUri = 'https://raw.githubusercontent.com/Badgerati/Pode/master/images/icon.png' # Release notes for this particular version of the module ReleaseNotes = 'https://github.com/Badgerati/Pode/releases/tag/v$version$' diff --git a/src/Private/Access.ps1 b/src/Private/Access.ps1 index 160dbcc5f..ddbc6c344 100644 --- a/src/Private/Access.ps1 +++ b/src/Private/Access.ps1 @@ -1,5 +1,4 @@ -function Get-PodeAccessMiddlewareScript -{ +function Get-PodeAccessMiddlewareScript { return { param($opts) @@ -21,10 +20,9 @@ function Get-PodeAccessMiddlewareScript } } -function Invoke-PodeAccessValidation -{ +function Invoke-PodeAccessValidation { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) diff --git a/src/Private/Authentication.ps1 b/src/Private/Authentication.ps1 index 6f2f5a8bf..b681d5b0a 100644 --- a/src/Private/Authentication.ps1 +++ b/src/Private/Authentication.ps1 @@ -1,5 +1,4 @@ -function Get-PodeAuthBasicType -{ +function Get-PodeAuthBasicType { return { param($options) @@ -8,7 +7,7 @@ function Get-PodeAuthBasicType if ($null -eq $header) { return @{ Message = 'No Authorization header found' - Code = 401 + Code = 401 } } @@ -17,14 +16,14 @@ function Get-PodeAuthBasicType if ($atoms.Length -lt 2) { return @{ Message = 'Invalid Authorization header' - Code = 400 + Code = 400 } } if ($atoms[0] -ine $options.HeaderTag) { return @{ Message = "Header is not for $($options.HeaderTag) Authorization" - Code = 400 + Code = 400 } } @@ -35,7 +34,7 @@ function Get-PodeAuthBasicType catch { return @{ Message = 'Invalid encoding specified for Authorization' - Code = 400 + Code = 400 } } @@ -45,7 +44,7 @@ function Get-PodeAuthBasicType catch { return @{ Message = 'Invalid Base64 string found in Authorization header' - Code = 400 + Code = 400 } } @@ -69,8 +68,7 @@ function Get-PodeAuthBasicType } } -function Get-PodeAuthOAuth2Type -{ +function Get-PodeAuthOAuth2Type { return { param($options, $schemes) @@ -84,8 +82,8 @@ function Get-PodeAuthOAuth2Type # if there's an error, fail if (![string]::IsNullOrWhiteSpace($WebEvent.Query['error'])) { return @{ - Message = $WebEvent.Query['error'] - Code = 401 + Message = $WebEvent.Query['error'] + Code = 401 IsErrored = $true } } @@ -103,8 +101,8 @@ function Get-PodeAuthOAuth2Type # ensure the state is valid if ((Test-PodeSessionsInUse) -and ($WebEvent.Query['state'] -ne $WebEvent.Session.Data['__pode_oauth_state__'])) { return @{ - Message = "OAuth2 state returned is invalid" - Code = 401 + Message = 'OAuth2 state returned is invalid' + Code = 401 IsErrored = $true } } @@ -148,8 +146,8 @@ function Get-PodeAuthOAuth2Type # was there an error? if (![string]::IsNullOrWhiteSpace($result.error)) { return @{ - Message = "$($result.error): $($result.error_description)" - Code = 401 + Message = "$($result.error): $($result.error_description)" + Code = 401 IsErrored = $true } } @@ -166,8 +164,8 @@ function Get-PodeAuthOAuth2Type if (![string]::IsNullOrWhiteSpace($user.error)) { return @{ - Message = "$($user.error): $($user.error_description)" - Code = 401 + Message = "$($user.error): $($user.error_description)" + Code = 401 IsErrored = $true } } @@ -207,9 +205,9 @@ function Get-PodeAuthOAuth2Type # add authUrl query params $query = "client_id=$($options.Client.ID)" - $query += "&response_type=code" + $query += '&response_type=code' $query += "&redirect_uri=$([System.Web.HttpUtility]::UrlEncode($redirectUrl))" - $query += "&response_mode=query" + $query += '&response_mode=query' $query += "&scope=$([System.Web.HttpUtility]::UrlEncode($scopes))" # add csrf state @@ -250,15 +248,14 @@ function Get-PodeAuthOAuth2Type # hmm, this is unexpected return @{ - Message = 'Well, this is awkward...' - Code = 500 + Message = 'Well, this is awkward...' + Code = 500 IsErrored = $true } } } -function Get-PodeOAuth2RedirectHost -{ +function Get-PodeOAuth2RedirectHost { param( [Parameter()] [string] @@ -284,8 +281,7 @@ function Get-PodeOAuth2RedirectHost return $RedirectUrl } -function Get-PodeAuthClientCertificateType -{ +function Get-PodeAuthClientCertificateType { return { param($options) $cert = $WebEvent.Request.ClientCertificate @@ -294,7 +290,7 @@ function Get-PodeAuthClientCertificateType if ($null -eq $cert) { return @{ Message = 'No client certificate supplied' - Code = 401 + Code = 401 } } @@ -302,7 +298,7 @@ function Get-PodeAuthClientCertificateType if ([string]::IsNullOrWhiteSpace($cert.Thumbprint)) { return @{ Message = 'Invalid client certificate supplied' - Code = 401 + Code = 401 } } @@ -311,7 +307,7 @@ function Get-PodeAuthClientCertificateType if (($cert.NotAfter -lt $now) -or ($cert.NotBefore -gt $now)) { return @{ Message = 'Invalid client certificate supplied' - Code = 401 + Code = 401 } } @@ -320,8 +316,7 @@ function Get-PodeAuthClientCertificateType } } -function Get-PodeAuthApiKeyType -{ +function Get-PodeAuthApiKeyType { return { param($options) @@ -346,7 +341,7 @@ function Get-PodeAuthApiKeyType if ([string]::IsNullOrWhiteSpace($apiKey)) { return @{ Message = "No $($options.LocationName) $($options.Location) found" - Code = 400 + Code = 400 } } @@ -364,7 +359,7 @@ function Get-PodeAuthApiKeyType if ($_.Exception.Message -ilike '*jwt*') { return @{ Message = $_.Exception.Message - Code = 400 + Code = 400 } } @@ -379,8 +374,7 @@ function Get-PodeAuthApiKeyType } } -function Get-PodeAuthBearerType -{ +function Get-PodeAuthBearerType { return { param($options) @@ -388,9 +382,9 @@ function Get-PodeAuthBearerType $header = (Get-PodeHeader -Name 'Authorization') if ($null -eq $header) { return @{ - Message = 'No Authorization header found' + Message = 'No Authorization header found' Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType invalid_request) - Code = 400 + Code = 400 } } @@ -398,17 +392,17 @@ function Get-PodeAuthBearerType $atoms = $header -isplit '\s+' if ($atoms.Length -lt 2) { return @{ - Message = 'Invalid Authorization header' + Message = 'Invalid Authorization header' Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType invalid_request) - Code = 400 + Code = 400 } } if ($atoms[0] -ine $options.HeaderTag) { return @{ - Message = "Authorization header is not $($options.HeaderTag)" + Message = "Authorization header is not $($options.HeaderTag)" Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType invalid_request) - Code = 400 + Code = 400 } } @@ -416,8 +410,8 @@ function Get-PodeAuthBearerType $token = $atoms[1] if ([string]::IsNullOrWhiteSpace($token)) { return @{ - Message = "No Bearer token found" - Code = 400 + Message = 'No Bearer token found' + Code = 400 } } @@ -435,7 +429,7 @@ function Get-PodeAuthBearerType if ($_.Exception.Message -ilike '*jwt*') { return @{ Message = $_.Exception.Message - Code = 400 + Code = 400 } } @@ -450,26 +444,25 @@ function Get-PodeAuthBearerType } } -function Get-PodeAuthBearerPostValidator -{ +function Get-PodeAuthBearerPostValidator { return { param($token, $result, $options) # if there's no user, fail with challenge if (($null -eq $result) -or ($null -eq $result.User)) { return @{ - Message = 'User not found' + Message = 'User not found' Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType invalid_token) - Code = 401 + Code = 401 } } # check for an error and description if (![string]::IsNullOrWhiteSpace($result.Error)) { return @{ - Message = 'Authorization failed' + Message = 'Authorization failed' Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType $result.Error -ErrorDescription $result.ErrorDescription) - Code = 401 + Code = 401 } } @@ -480,18 +473,18 @@ function Get-PodeAuthBearerPostValidator # 403 if we have auth scopes but no token scope if ($hasAuthScopes -and !$hasTokenScope) { return @{ - Message = 'Invalid Scope' + Message = 'Invalid Scope' Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType insufficient_scope) - Code = 403 + Code = 403 } } # 403 if we have both, but token not in auth scope if ($hasAuthScopes -and $hasTokenScope -and ($options.Scopes -notcontains $result.Scope)) { return @{ - Message = 'Invalid Scope' + Message = 'Invalid Scope' Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType insufficient_scope) - Code = 403 + Code = 403 } } @@ -500,8 +493,7 @@ function Get-PodeAuthBearerPostValidator } } -function New-PodeAuthBearerChallenge -{ +function New-PodeAuthBearerChallenge { param( [Parameter()] [string[]] @@ -533,8 +525,7 @@ function New-PodeAuthBearerChallenge return ($items -join ', ') } -function Get-PodeAuthDigestType -{ +function Get-PodeAuthDigestType { return { param($options) @@ -542,9 +533,9 @@ function Get-PodeAuthDigestType $header = (Get-PodeHeader -Name 'Authorization') if ($null -eq $header) { return @{ - Message = 'No Authorization header found' + Message = 'No Authorization header found' Challenge = (New-PodeAuthDigestChallenge) - Code = 401 + Code = 401 } } @@ -553,15 +544,15 @@ function Get-PodeAuthDigestType if ($atoms.Length -lt 2) { return @{ Message = 'Invalid Authorization header' - Code = 400 + Code = 400 } } if ($atoms[0] -ine $options.HeaderTag) { return @{ - Message = "Authorization header is not $($options.HeaderTag)" + Message = "Authorization header is not $($options.HeaderTag)" Challenge = (New-PodeAuthDigestChallenge) - Code = 401 + Code = 401 } } @@ -570,16 +561,16 @@ function Get-PodeAuthDigestType if ($params.Count -eq 0) { return @{ Message = 'Invalid Authorization header' - Code = 400 + Code = 400 } } # if no username then 401 and challenge if ([string]::IsNullOrWhiteSpace($params.username)) { return @{ - Message = 'Authorization header is missing username' + Message = 'Authorization header is missing username' Challenge = (New-PodeAuthDigestChallenge) - Code = 401 + Code = 401 } } @@ -587,7 +578,7 @@ function Get-PodeAuthDigestType if ($WebEvent.Path -ine $params.uri) { return @{ Message = 'Invalid Authorization header' - Code = 400 + Code = 400 } } @@ -596,17 +587,16 @@ function Get-PodeAuthDigestType } } -function Get-PodeAuthDigestPostValidator -{ +function Get-PodeAuthDigestPostValidator { return { param($username, $params, $result, $options) # if there's no user or password, fail with challenge if (($null -eq $result) -or ($null -eq $result.User) -or [string]::IsNullOrWhiteSpace($result.Password)) { return @{ - Message = 'User not found' + Message = 'User not found' Challenge = (New-PodeAuthDigestChallenge) - Code = 401 + Code = 401 } } @@ -622,9 +612,9 @@ function Get-PodeAuthDigestPostValidator # compare final hash to client response if ($final -ne $params.response) { return @{ - Message = 'Hashes failed to match' + Message = 'Hashes failed to match' Challenge = (New-PodeAuthDigestChallenge) - Code = 401 + Code = 401 } } @@ -634,8 +624,7 @@ function Get-PodeAuthDigestPostValidator } } -function ConvertFrom-PodeAuthDigestHeader -{ +function ConvertFrom-PodeAuthDigestHeader { param( [Parameter()] [string[]] @@ -658,14 +647,12 @@ function ConvertFrom-PodeAuthDigestHeader return $obj } -function New-PodeAuthDigestChallenge -{ +function New-PodeAuthDigestChallenge { $items = @('qop="auth"', 'algorithm="MD5"', "nonce=`"$(New-PodeGuid -Secure -NoDashes)`"") return ($items -join ', ') } -function Get-PodeAuthFormType -{ +function Get-PodeAuthFormType { return { param($options) @@ -681,7 +668,7 @@ function Get-PodeAuthFormType if ([string]::IsNullOrWhiteSpace($username) -or [string]::IsNullOrWhiteSpace($password)) { return @{ Message = 'Username or Password not supplied' - Code = 401 + Code = 401 } } @@ -700,8 +687,7 @@ function Get-PodeAuthFormType } } -function Get-PodeAuthUserFileMethod -{ +function Get-PodeAuthUserFileMethod { return { param($username, $password, $options) @@ -722,11 +708,11 @@ function Get-PodeAuthUserFileMethod # find the user by username - only use the first one $user = @(foreach ($_user in $users) { - if ($_user.Username -ieq $_username) { - $_user - break - } - })[0] + if ($_user.Username -ieq $_username) { + $_user + break + } + })[0] # fail if no user if ($null -eq $user) { @@ -747,10 +733,10 @@ function Get-PodeAuthUserFileMethod # convert the user to a hashtable $user = @{ - Name = $user.Name + Name = $user.Name Username = $user.Username - Email = $user.Email - Groups = $user.Groups + Email = $user.Email + Groups = $user.Groups Metadata = $user.Metadata } @@ -771,8 +757,7 @@ function Get-PodeAuthUserFileMethod } } -function Get-PodeAuthWindowsADMethod -{ +function Get-PodeAuthWindowsADMethod { return { param($username, $password, $options) @@ -832,14 +817,13 @@ function Get-PodeAuthWindowsADMethod } } -function Invoke-PodeAuthInbuiltScriptBlock -{ +function Invoke-PodeAuthInbuiltScriptBlock { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [hashtable] $User, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, @@ -851,8 +835,7 @@ function Invoke-PodeAuthInbuiltScriptBlock return (Invoke-PodeScriptBlock -ScriptBlock $ScriptBlock -Arguments $_args -Return -Splat) } -function Get-PodeAuthWindowsLocalMethod -{ +function Get-PodeAuthWindowsLocalMethod { return { param($username, $password, $options) @@ -869,13 +852,13 @@ function Get-PodeAuthWindowsLocalMethod } $user = @{ - UserType = 'Local' + UserType = 'Local' AuthenticationType = 'WinNT' - Username = $_username - Name = [string]::Empty - Fqdn = $PodeContext.Server.ComputerName - Domain = 'localhost' - Groups = @() + Username = $_username + Name = [string]::Empty + Fqdn = $PodeContext.Server.ComputerName + Domain = 'localhost' + Groups = @() } Add-Type -AssemblyName System.DirectoryServices.AccountManagement -ErrorAction Stop @@ -921,8 +904,7 @@ function Get-PodeAuthWindowsLocalMethod } } -function Get-PodeAuthWindowsADIISMethod -{ +function Get-PodeAuthWindowsADIISMethod { return { param($token, $options) @@ -943,18 +925,18 @@ function Get-PodeAuthWindowsADIISMethod # create base user object $user = @{ - UserType = 'Domain' - Identity = @{ + UserType = 'Domain' + Identity = @{ AccessToken = $winIdentity.AccessToken } AuthenticationType = $winIdentity.AuthenticationType - DistinguishedName = [string]::Empty - Username = $username - Name = [string]::Empty - Email = [string]::Empty - Fqdn = [string]::Empty - Domain = $domain - Groups = @() + DistinguishedName = [string]::Empty + Username = $username + Name = [string]::Empty + Email = [string]::Empty + Fqdn = [string]::Empty + Domain = $domain + Groups = @() } # if the domain isn't local, attempt AD user @@ -962,7 +944,7 @@ function Get-PodeAuthWindowsADIISMethod # get the server's fdqn (and name/email) try { # Open ADSISearcher and change context to given domain - $searcher = [adsisearcher]"" + $searcher = [adsisearcher]'' $searcher.SearchRoot = [adsi]"LDAP://$($domain)" $searcher.Filter = "ObjectSid=$($winIdentity.User.Value.ToString())" @@ -1054,10 +1036,9 @@ function Get-PodeAuthWindowsADIISMethod } } -function Test-PodeAuthUserGroups -{ +function Test-PodeAuthUserGroups { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [hashtable] $User, @@ -1095,10 +1076,9 @@ function Test-PodeAuthUserGroups return $false } -function Invoke-PodeAuthValidation -{ +function Invoke-PodeAuthValidation { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -1152,7 +1132,7 @@ function Invoke-PodeAuthValidation # default failure return @{ - Success = $false + Success = $false StatusCode = 500 } } @@ -1163,10 +1143,9 @@ function Invoke-PodeAuthValidation return $result } -function Test-PodeAuthValidation -{ +function Test-PodeAuthValidation { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -1196,24 +1175,24 @@ function Test-PodeAuthValidation $_scheme = $auth.Scheme $_inner = @(while ($null -ne $_scheme.InnerScheme) { - $_scheme = $_scheme.InnerScheme - $_scheme - }) + $_scheme = $_scheme.InnerScheme + $_scheme + }) for ($i = $_inner.Length - 1; $i -ge 0; $i--) { $_tmp_args = @(Get-PodeScriptblockArguments -ArgumentList $_inner[$i].Arguments -UsingVariables $_inner[$i].ScriptBlock.UsingVariables) - $_tmp_args += ,$schemes + $_tmp_args += , $schemes $result = (Invoke-PodeScriptBlock -ScriptBlock $_inner[$i].ScriptBlock.Script -Arguments $_tmp_args -Return -Splat) if ($result -is [hashtable]) { break } - $schemes += ,$result + $schemes += , $result $result = $null } - $_args += ,$schemes + $_args += , $schemes } if ($null -eq $result) { @@ -1239,7 +1218,7 @@ function Test-PodeAuthValidation # is the auth trying to redirect ie: oauth? if ($result.IsRedirected) { return @{ - Success = $false + Success = $false Redirected = $true } } @@ -1267,10 +1246,10 @@ function Test-PodeAuthValidation } return @{ - Success = $false - StatusCode = $code - Description = $result.Message - Headers = $result.Headers + Success = $false + StatusCode = $code + Description = $result.Message + Headers = $result.Headers FailureRedirect = [bool]$result.IsErrored } } @@ -1278,38 +1257,36 @@ function Test-PodeAuthValidation # authentication was successful return @{ Success = $true - User = $result.User + User = $result.User Headers = $result.Headers } } catch { $_ | Write-PodeErrorLog return @{ - Success = $false + Success = $false StatusCode = 500 - Exception = $_ + Exception = $_ } } } -function Get-PodeAuthMiddlewareScript -{ +function Get-PodeAuthMiddlewareScript { return { param($opts) - return (Test-PodeAuthInternal ` + return Test-PodeAuthInternal ` -Name $opts.Name ` -Login:($opts.Login) ` -Logout:($opts.Logout) ` - -AllowAnon:($opts.Anon)) + -AllowAnon:($opts.Anon) } } -function Test-PodeAuthInternal -{ +function Test-PodeAuthInternal { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -1331,17 +1308,17 @@ function Test-PodeAuthInternal Remove-PodeAuthSession if ($PodeContext.Server.Sessions.Info.UseHeaders) { - return (Set-PodeAuthStatus ` + return Set-PodeAuthStatus ` -StatusCode 401 ` -Name $Name ` - -NoSuccessRedirect) + -NoSuccessRedirect } else { $auth.Failure.Url = (Protect-PodeValue -Value $auth.Failure.Url -Default $WebEvent.Request.Url.AbsolutePath) - return (Set-PodeAuthStatus ` + return Set-PodeAuthStatus ` -StatusCode 302 ` -Name $Name ` - -NoSuccessRedirect) + -NoSuccessRedirect } } @@ -1350,10 +1327,10 @@ function Test-PodeAuthInternal # existing session auth'd if (Test-PodeAuthUser) { $WebEvent.Auth = $WebEvent.Session.Data.Auth - return (Set-PodeAuthStatus ` + return Set-PodeAuthStatus ` -Name $Name ` -LoginRoute:($Login) ` - -NoSuccessRedirect) + -NoSuccessRedirect } # if we're allowing anon access, and using sessions, then stop here - as a session will be created from a login route for auth'ing users @@ -1380,10 +1357,10 @@ function Test-PodeAuthInternal } catch { $_ | Write-PodeErrorLog - return (Set-PodeAuthStatus ` + return Set-PodeAuthStatus ` -StatusCode 500 ` -Description $_.Exception.Message ` - -Name $Name) + -Name $Name } # did the auth force a redirect? @@ -1398,22 +1375,22 @@ function Test-PodeAuthInternal # if auth failed, set appropriate response headers/redirects if (!$result.Success) { - return (Set-PodeAuthStatus ` + return Set-PodeAuthStatus ` -StatusCode $result.StatusCode ` -Description $result.Description ` -Headers $result.Headers ` -Name $Name ` -LoginRoute:$Login ` - -NoFailureRedirect:($result.FailureRedirect)) + -NoFailureRedirect:($result.FailureRedirect) } # if auth passed, assign the user to the session $WebEvent.Auth = [ordered]@{ - User = $result.User + User = $result.User IsAuthenticated = $true - IsAuthorised = $true - Store = !$auth.Sessionless - Name = $result.Auth + IsAuthorised = $true + Store = !$auth.Sessionless + Name = $result.Auth } # successful auth @@ -1425,14 +1402,13 @@ function Test-PodeAuthInternal $authName = @($result.Auth)[0] } - return (Set-PodeAuthStatus ` + return Set-PodeAuthStatus ` -Headers $result.Headers ` -Name $authName ` - -LoginRoute:$Login) + -LoginRoute:$Login } -function Get-PodeAuthWwwHeaderValue -{ +function Get-PodeAuthWwwHeaderValue { param( [Parameter()] [string] @@ -1463,8 +1439,7 @@ function Get-PodeAuthWwwHeaderValue return $header } -function Remove-PodeAuthSession -{ +function Remove-PodeAuthSession { # blank out the auth $WebEvent.Auth = @{} @@ -1477,10 +1452,9 @@ function Remove-PodeAuthSession Revoke-PodeSession } -function Get-PodeAuthFailureInfo -{ +function Get-PodeAuthFailureInfo { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -1509,7 +1483,7 @@ function Get-PodeAuthFailureInfo # find failure info if ($null -eq $Info) { $Info = @{ - Url = $auth.Failure.Url + Url = $auth.Failure.Url Message = $auth.Failure.Message } } @@ -1530,10 +1504,9 @@ function Get-PodeAuthFailureInfo return (Get-PodeAuthFailureInfo -Name $auth.Parent -Info $Info -BaseName $BaseName) } -function Get-PodeAuthSuccessInfo -{ +function Get-PodeAuthSuccessInfo { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -1562,7 +1535,7 @@ function Get-PodeAuthSuccessInfo # find success info if ($null -eq $Info) { $Info = @{ - Url = $auth.Success.Url + Url = $auth.Success.Url UseOrigin = $auth.Success.UseOrigin } } @@ -1583,10 +1556,9 @@ function Get-PodeAuthSuccessInfo return (Get-PodeAuthSuccessInfo -Name $auth.Parent -Info $Info -BaseName $BaseName) } -function Set-PodeAuthStatus -{ +function Set-PodeAuthStatus { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -1676,8 +1648,7 @@ function Set-PodeAuthStatus return $true } -function Get-PodeADServerFromDistinguishedName -{ +function Get-PodeADServerFromDistinguishedName { param( [Parameter()] [string] @@ -1700,8 +1671,7 @@ function Get-PodeADServerFromDistinguishedName return ($name -join '.') } -function Get-PodeAuthADResult -{ +function Get-PodeAuthADResult { param( [Parameter()] [string] @@ -1738,8 +1708,7 @@ function Get-PodeAuthADResult $KeepCredential ) - try - { + try { # validate the user's AD creds $result = (Open-PodeAuthADConnection -Server $Server -Domain $Domain -Username $Username -Password $Password -Provider $Provider) if (!$result.Success) { @@ -1762,33 +1731,32 @@ function Get-PodeAuthADResult } # check if we want to keep the credentials in the User object - if($KeepCredential){ - $credential = [pscredential]::new($($Domain+'\'+$Username), (ConvertTo-SecureString -String $Password -AsPlainText -Force)) + if ($KeepCredential) { + $credential = [pscredential]::new($($Domain + '\' + $Username), (ConvertTo-SecureString -String $Password -AsPlainText -Force)) } - else{ + else { $credential = $null } # return the user return @{ User = @{ - UserType = 'Domain' + UserType = 'Domain' AuthenticationType = 'LDAP' - DistinguishedName = $user.DistinguishedName - Username = ($Username -split '\\')[-1] - Name = $user.Name - Email = $user.Email - Fqdn = $Server - Domain = $Domain - Groups = $groups - Credential = $credential + DistinguishedName = $user.DistinguishedName + Username = ($Username -split '\\')[-1] + Name = $user.Name + Email = $user.Email + Fqdn = $Server + Domain = $Domain + Groups = $groups + Credential = $credential } } } finally { if ($null -ne $connection) { - switch ($Provider.ToLowerInvariant()) - { + switch ($Provider.ToLowerInvariant()) { 'openldap' { $connection.Username = $null $connection.Password = $null @@ -1807,10 +1775,9 @@ function Get-PodeAuthADResult } } -function Open-PodeAuthADConnection -{ +function Open-PodeAuthADConnection { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Server, @@ -1845,8 +1812,7 @@ function Open-PodeAuthADConnection $connection = $null # validate the user's AD creds - switch ($Provider.ToLowerInvariant()) - { + switch ($Provider.ToLowerInvariant()) { 'openldap' { if (![string]::IsNullOrWhiteSpace($SearchBase)) { $baseDn = $SearchBase @@ -1871,7 +1837,7 @@ function Open-PodeAuthADConnection $connection = @{ Hostname = $hostname Username = $user - BaseDN = $baseDn + BaseDN = $baseDn Password = $Password } } @@ -1910,15 +1876,14 @@ function Open-PodeAuthADConnection } return @{ - Success = $result + Success = $result Connection = $connection } } -function Get-PodeAuthADQuery -{ +function Get-PodeAuthADQuery { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Username ) @@ -1926,13 +1891,12 @@ function Get-PodeAuthADQuery return "(&(objectCategory=person)(samaccountname=$($Username)))" } -function Get-PodeAuthADUser -{ +function Get-PodeAuthADUser { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Connection, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Username, @@ -1946,8 +1910,7 @@ function Get-PodeAuthADUser $user = $null # generate query to find user - switch ($Provider.ToLowerInvariant()) - { + switch ($Provider.ToLowerInvariant()) { 'openldap' { $result = (ldapsearch -x -LLL -H "$($Connection.Hostname)" -D "$($Connection.Username)" -w "$($Connection.Password)" -b "$($Connection.BaseDN)" -o ldif-wrap=no "$($query)" name mail) if (!$? -or ($LASTEXITCODE -ne 0)) { @@ -1956,8 +1919,8 @@ function Get-PodeAuthADUser $user = @{ DistinguishedName = (Get-PodeOpenLdapValue -Lines $result -Property 'dn') - Name = (Get-PodeOpenLdapValue -Lines $result -Property 'name') - Email = (Get-PodeOpenLdapValue -Lines $result -Property 'mail') + Name = (Get-PodeOpenLdapValue -Lines $result -Property 'name') + Email = (Get-PodeOpenLdapValue -Lines $result -Property 'mail') } } @@ -1965,8 +1928,8 @@ function Get-PodeAuthADUser $result = Get-ADUser -LDAPFilter $query -Credential $Connection.Credential -Properties mail $user = @{ DistinguishedName = $result.DistinguishedName - Name = $result.Name - Email = $result.mail + Name = $result.Name + Email = $result.mail } } @@ -1981,8 +1944,8 @@ function Get-PodeAuthADUser $user = @{ DistinguishedName = @($result.distinguishedname)[0] - Name = @($result.name)[0] - Email = @($result.mail)[0] + Name = @($result.name)[0] + Email = @($result.mail)[0] } } } @@ -1990,8 +1953,7 @@ function Get-PodeAuthADUser return $user } -function Get-PodeOpenLdapValue -{ +function Get-PodeOpenLdapValue { param( [Parameter()] [string[]] @@ -2018,10 +1980,9 @@ function Get-PodeOpenLdapValue } } -function Get-PodeAuthADGroups -{ +function Get-PodeAuthADGroups { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Connection, [Parameter()] @@ -2048,10 +2009,9 @@ function Get-PodeAuthADGroups return (Get-PodeAuthADGroupsAll -Connection $Connection -DistinguishedName $DistinguishedName -Provider $Provider) } -function Get-PodeAuthADGroupsDirect -{ +function Get-PodeAuthADGroupsDirect { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Connection, [Parameter()] @@ -2069,8 +2029,7 @@ function Get-PodeAuthADGroupsDirect $groups = @() # get the groups - switch ($Provider.ToLowerInvariant()) - { + switch ($Provider.ToLowerInvariant()) { 'openldap' { $result = (ldapsearch -x -LLL -H "$($Connection.Hostname)" -D "$($Connection.Username)" -w "$($Connection.Password)" -b "$($Connection.BaseDN)" -o ldif-wrap=no "$($query)" memberof) $groups = (Get-PodeOpenLdapValue -Lines $result -Property 'memberof' -All) @@ -2091,18 +2050,17 @@ function Get-PodeAuthADGroupsDirect } $groups = @(foreach ($group in $groups) { - if ($group -imatch '^CN=(?.+?),') { - $Matches['group'] - } - }) + if ($group -imatch '^CN=(?.+?),') { + $Matches['group'] + } + }) return $groups } -function Get-PodeAuthADGroupsAll -{ +function Get-PodeAuthADGroupsAll { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Connection, [Parameter()] @@ -2120,8 +2078,7 @@ function Get-PodeAuthADGroupsAll $groups = @() # get the groups - switch ($Provider.ToLowerInvariant()) - { + switch ($Provider.ToLowerInvariant()) { 'openldap' { $result = (ldapsearch -x -LLL -H "$($Connection.Hostname)" -D "$($Connection.Username)" -w "$($Connection.Password)" -b "$($Connection.BaseDN)" -o ldif-wrap=no "$($query)" samaccountname) $groups = (Get-PodeOpenLdapValue -Lines $result -Property 'sAMAccountName' -All) @@ -2145,8 +2102,7 @@ function Get-PodeAuthADGroupsAll return $groups } -function Get-PodeAuthDomainName -{ +function Get-PodeAuthDomainName { if (Test-PodeIsUnix) { $dn = (dnsdomainname) if ([string]::IsNullOrWhiteSpace($dn)) { @@ -2165,10 +2121,9 @@ function Get-PodeAuthDomainName } } -function Find-PodeAuth -{ +function Find-PodeAuth { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Name @@ -2177,8 +2132,7 @@ function Find-PodeAuth return $PodeContext.Server.Authentications.Methods[$Name] } -function Import-PodeAuthADModule -{ +function Import-PodeAuthADModule { if (!(Test-PodeIsWindows)) { throw 'Active Directory module only available on Windows' } @@ -2191,8 +2145,7 @@ function Import-PodeAuthADModule Export-PodeModule -Name ActiveDirectory } -function Get-PodeAuthADProvider -{ +function Get-PodeAuthADProvider { param( [switch] $OpenLDAP, diff --git a/src/Private/AutoImport.ps1 b/src/Private/AutoImport.ps1 index d2d9def93..a200cdbc8 100644 --- a/src/Private/AutoImport.ps1 +++ b/src/Private/AutoImport.ps1 @@ -1,11 +1,10 @@ -function Import-PodeFunctionsIntoRunspaceState -{ +function Import-PodeFunctionsIntoRunspaceState { param( - [Parameter(Mandatory=$true, ParameterSetName='Script')] + [Parameter(Mandatory = $true, ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath ) @@ -52,8 +51,7 @@ function Import-PodeFunctionsIntoRunspaceState } } -function Import-PodeModulesIntoRunspaceState -{ +function Import-PodeModulesIntoRunspaceState { # do nothing if disabled if (!$PodeContext.Server.AutoImport.Modules.Enabled) { return @@ -72,8 +70,8 @@ function Import-PodeModulesIntoRunspaceState # work out which order the modules need to be loaded $modulesOrder = @(foreach ($module in $modules) { - Get-PodeModuleDependencies -Module $module - }) | + Get-PodeModuleDependencies -Module $module + }) | Where-Object { ($_.Name -inotin @('pode', 'pode.internal')) -and ($_.Name -inotlike 'microsoft.powershell.*') } | Select-Object -Unique @@ -97,8 +95,7 @@ function Import-PodeModulesIntoRunspaceState } } -function Import-PodeSnapinsIntoRunspaceState -{ +function Import-PodeSnapinsIntoRunspaceState { # if non-windows or core, do nothing if ((Test-PodeIsPSCore) -or (Test-PodeIsUnix)) { return @@ -127,28 +124,27 @@ function Import-PodeSnapinsIntoRunspaceState } } -function Initialize-PodeAutoImportConfiguration -{ +function Initialize-PodeAutoImportConfiguration { return @{ - Modules = @{ - Enabled = $true + Modules = @{ + Enabled = $true ExportList = @() ExportOnly = $false } - Snapins = @{ - Enabled = $true + Snapins = @{ + Enabled = $true ExportList = @() ExportOnly = $false } - Functions = @{ - Enabled = $true + Functions = @{ + Enabled = $true ExportList = @() ExportOnly = $false } SecretVaults = @{ - Enabled = $true + Enabled = $true SecretManagement = @{ - Enabled = $false + Enabled = $false ExportList = @() ExportOnly = $false } @@ -156,8 +152,7 @@ function Initialize-PodeAutoImportConfiguration } } -function Import-PodeSecretVaultsIntoRegistry -{ +function Import-PodeSecretVaultsIntoRegistry { # do nothing if disabled if (!$PodeContext.Server.AutoImport.SecretVaults.Enabled) { return @@ -166,8 +161,7 @@ function Import-PodeSecretVaultsIntoRegistry Import-PodeSecretManagementVaultsIntoRegistry } -function Import-PodeSecretManagementVaultsIntoRegistry -{ +function Import-PodeSecretManagementVaultsIntoRegistry { # do nothing if disabled if (!$PodeContext.Server.AutoImport.SecretVaults.SecretManagement.Enabled) { return @@ -203,22 +197,21 @@ function Import-PodeSecretManagementVaultsIntoRegistry # register the vault $PodeContext.Server.Secrets.Vaults[$vault.Name] = @{ - Name = $vault.Name - Type = 'secretmanagement' - Parameters = $vault.VaultParameters - AutoImported = $true - Unlock = $null - Cache = $null + Name = $vault.Name + Type = 'secretmanagement' + Parameters = $vault.VaultParameters + AutoImported = $true + Unlock = $null + Cache = $null SecretManagement = @{ - VaultName = $vault.Name + VaultName = $vault.Name ModuleName = $vault.ModulePath } } } } -function Read-PodeAutoImportConfiguration -{ +function Read-PodeAutoImportConfiguration { param( [Parameter()] [hashtable] @@ -231,25 +224,25 @@ function Read-PodeAutoImportConfiguration $impSecretVaults = $Configuration.AutoImport.SecretVaults return @{ - Modules = @{ - Enabled = (($null -eq $impModules.Enable) -or [bool]$impModules.Enable) + Modules = @{ + Enabled = (($null -eq $impModules.Enable) -or [bool]$impModules.Enable) ExportList = @() ExportOnly = ([bool]$impModules.ExportOnly) } - Snapins = @{ - Enabled = (($null -eq $impSnapins.Enable) -or [bool]$impSnapins.Enable) + Snapins = @{ + Enabled = (($null -eq $impSnapins.Enable) -or [bool]$impSnapins.Enable) ExportList = @() ExportOnly = ([bool]$impSnapins.ExportOnly) } - Functions = @{ - Enabled = (($null -eq $impFuncs.Enable) -or [bool]$impFuncs.Enable) + Functions = @{ + Enabled = (($null -eq $impFuncs.Enable) -or [bool]$impFuncs.Enable) ExportList = @() ExportOnly = ([bool]$impFuncs.ExportOnly) } SecretVaults = @{ - Enabled = (($null -eq $impSecretVaults.Enable) -or [bool]$impSecretVaults.Enable) + Enabled = (($null -eq $impSecretVaults.Enable) -or [bool]$impSecretVaults.Enable) SecretManagement = @{ - Enabled = ((($null -eq $impSecretVaults.Enable) -and (Test-PodeModuleInstalled -Name Microsoft.PowerShell.SecretManagement)) -or [bool]$impSecretVaults.Enable) + Enabled = ((($null -eq $impSecretVaults.Enable) -and (Test-PodeModuleInstalled -Name Microsoft.PowerShell.SecretManagement)) -or [bool]$impSecretVaults.Enable) ExportList = @() ExportOnly = ([bool]$impSecretVaults.SecretManagement.ExportOnly) } @@ -257,8 +250,7 @@ function Read-PodeAutoImportConfiguration } } -function Reset-PodeAutoImportConfiguration -{ +function Reset-PodeAutoImportConfiguration { $PodeContext.Server.AutoImport.Modules.ExportList = @() $PodeContext.Server.AutoImport.Snapins.ExportList = @() $PodeContext.Server.AutoImport.Functions.ExportList = @() diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 1d648590c..cacdb4cbe 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -1,9 +1,8 @@ using namespace Pode -function New-PodeContext -{ +function New-PodeContext { [CmdletBinding()] - param ( + param( [Parameter()] [scriptblock] $ScriptBlock, @@ -101,24 +100,24 @@ function New-PodeContext # list of timers/schedules/tasks/fim $ctx.Timers = @{ Enabled = ($EnablePool -icontains 'timers') - Items = @{} + Items = @{} } $ctx.Schedules = @{ - Enabled = ($EnablePool -icontains 'schedules') - Items = @{} + Enabled = ($EnablePool -icontains 'schedules') + Items = @{} Processes = @{} } $ctx.Tasks = @{ Enabled = ($EnablePool -icontains 'tasks') - Items = @{} + Items = @{} Results = @{} } $ctx.Fim = @{ Enabled = ($EnablePool -icontains 'files') - Items = @{} + Items = @{} } # auto importing (modules, funcs, snap-ins) @@ -127,28 +126,28 @@ function New-PodeContext # basic logging setup $ctx.Server.Logging = @{ Enabled = $true - Types = @{} + Types = @{} } # set thread counts $ctx.Threads = @{ - General = $Threads - Schedules = 10 - Files = 1 - Tasks = 2 + General = $Threads + Schedules = 10 + Files = 1 + Tasks = 2 WebSockets = 2 } # set socket details for pode server $ctx.Server.Sockets = @{ - Ssl = @{ + Ssl = @{ Protocols = Get-PodeDefaultSslProtocols } ReceiveTimeout = 100 } $ctx.Server.Signals = @{ - Enabled = $false + Enabled = $false Listener = $null } @@ -160,7 +159,7 @@ function New-PodeContext # set default request config $ctx.Server.Request = @{ - Timeout = 30 + Timeout = 30 BodySize = 100MB } @@ -208,11 +207,11 @@ function New-PodeContext # set iis token/settings $ctx.Server.IIS = @{ - Token = $env:ASPNETCORE_TOKEN - Port = $env:ASPNETCORE_PORT - Path = @{ - Raw = '/' - Pattern = '^/' + Token = $env:ASPNETCORE_TOKEN + Port = $env:ASPNETCORE_PORT + Path = @{ + Raw = '/' + Pattern = '^/' IsNonRoot = $false } Shutdown = $false @@ -262,11 +261,11 @@ function New-PodeContext # view engine for rendering pages $ctx.Server.ViewEngine = @{ - Type = 'html' - Extension = 'html' - ScriptBlock = $null + Type = 'html' + Extension = 'html' + ScriptBlock = $null UsingVariables = $null - IsDynamic = $false + IsDynamic = $false } # pode default preferences @@ -278,19 +277,19 @@ function New-PodeContext # routes for pages and api $ctx.Server.Routes = @{ - 'connect' = [ordered]@{} - 'delete' = [ordered]@{} - 'get' = [ordered]@{} - 'head' = [ordered]@{} - 'merge' = [ordered]@{} - 'options' = [ordered]@{} - 'patch' = [ordered]@{} - 'post' = [ordered]@{} - 'put' = [ordered]@{} - 'trace' = [ordered]@{} - 'static' = [ordered]@{} - 'signal' = [ordered]@{} - '*' = [ordered]@{} + 'connect' = [ordered]@{} + 'delete' = [ordered]@{} + 'get' = [ordered]@{} + 'head' = [ordered]@{} + 'merge' = [ordered]@{} + 'options' = [ordered]@{} + 'patch' = [ordered]@{} + 'post' = [ordered]@{} + 'put' = [ordered]@{} + 'trace' = [ordered]@{} + 'static' = [ordered]@{} + 'signal' = [ordered]@{} + '*' = [ordered]@{} } # verbs for tcp @@ -314,18 +313,18 @@ function New-PodeContext # setup basic access placeholders $ctx.Server.Access = @{ Allow = @{} - Deny = @{} + Deny = @{} } # setup basic limit rules $ctx.Server.Limits = @{ - Rules = @{} + Rules = @{} Active = @{} } # cookies and session logic $ctx.Server.Cookies = @{ - Csrf = @{} + Csrf = @{} Secrets = @{} } @@ -337,16 +336,16 @@ function New-PodeContext # server metrics $ctx.Metrics = @{ - Server = @{ + Server = @{ InitialLoadTime = [datetime]::UtcNow - StartTime = [datetime]::UtcNow - RestartCount = 0 + StartTime = [datetime]::UtcNow + RestartCount = 0 } Requests = @{ - Total = 0 + Total = 0 StatusCodes = @{} } - Signals = @{ + Signals = @{ Total = 0 } } @@ -363,7 +362,7 @@ function New-PodeContext # create new cancellation tokens $ctx.Tokens = @{ Cancellation = New-Object System.Threading.CancellationTokenSource - Restart = New-Object System.Threading.CancellationTokenSource + Restart = New-Object System.Threading.CancellationTokenSource } # requests that should be logged @@ -383,15 +382,15 @@ function New-PodeContext # runspace pools $ctx.RunspacePools = @{ - Main = $null - Web = $null - Smtp = $null - Tcp = $null - Signals = $null - Schedules = $null - Gui = $null - Tasks = $null - Files = $null + Main = $null + Web = $null + Smtp = $null + Tcp = $null + Signals = $null + Schedules = $null + Gui = $null + Tasks = $null + Files = $null } # threading locks, etc. @@ -408,13 +407,13 @@ function New-PodeContext # setup events $ctx.Server.Events = @{ - Start = [ordered]@{} + Start = [ordered]@{} Terminate = [ordered]@{} - Restart = [ordered]@{} - Browser = [ordered]@{} - Crash = [ordered]@{} - Stop = [ordered]@{} - Running = [ordered]@{} + Restart = [ordered]@{} + Browser = [ordered]@{} + Crash = [ordered]@{} + Stop = [ordered]@{} + Running = [ordered]@{} } # modules @@ -423,9 +422,9 @@ function New-PodeContext # setup security $ctx.Server.Security = @{ ServerDetails = $true - Headers = @{} - Cache = @{ - ContentSecurity = @{} + Headers = @{} + Cache = @{ + ContentSecurity = @{} PermissionsPolicy = @{} } } @@ -434,8 +433,7 @@ function New-PodeContext return $ctx } -function New-PodeRunspaceState -{ +function New-PodeRunspaceState { # create the state, and add the pode modules $state = [initialsessionstate]::CreateDefault() $state.ImportPSModule($PodeContext.Server.PodeModule.DataPath) @@ -457,19 +455,18 @@ function New-PodeRunspaceState $PodeContext.RunspaceState = $state } -function New-PodeRunspacePools -{ +function New-PodeRunspacePools { if ($PodeContext.Server.IsServerless) { return } # setup main runspace pool $threadsCounts = @{ - Default = 3 - Timer = 1 - Log = 1 + Default = 3 + Timer = 1 + Log = 1 Schedule = 1 - Misc = 1 + Misc = 1 } if (!(Test-PodeTimersExist)) { @@ -487,14 +484,14 @@ function New-PodeRunspacePools # main runspace - for timers, schedules, etc $totalThreadCount = ($threadsCounts.Values | Measure-Object -Sum).Sum $PodeContext.RunspacePools.Main = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, $totalThreadCount, $PodeContext.RunspaceState, $Host) + Pool = [runspacefactory]::CreateRunspacePool(1, $totalThreadCount, $PodeContext.RunspaceState, $Host) State = 'Waiting' } # web runspace - if we have any http/s endpoints if (Test-PodeEndpoints -Type Http) { $PodeContext.RunspacePools.Web = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 1), $PodeContext.RunspaceState, $Host) + Pool = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 1), $PodeContext.RunspaceState, $Host) State = 'Waiting' } } @@ -502,7 +499,7 @@ function New-PodeRunspacePools # smtp runspace - if we have any smtp endpoints if (Test-PodeEndpoints -Type Smtp) { $PodeContext.RunspacePools.Smtp = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 1), $PodeContext.RunspaceState, $Host) + Pool = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 1), $PodeContext.RunspaceState, $Host) State = 'Waiting' } } @@ -510,7 +507,7 @@ function New-PodeRunspacePools # tcp runspace - if we have any tcp endpoints if (Test-PodeEndpoints -Type Tcp) { $PodeContext.RunspacePools.Tcp = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 1), $PodeContext.RunspaceState, $Host) + Pool = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 1), $PodeContext.RunspaceState, $Host) State = 'Waiting' } } @@ -518,7 +515,7 @@ function New-PodeRunspacePools # signals runspace - if we have any ws/s endpoints if (Test-PodeEndpoints -Type Ws) { $PodeContext.RunspacePools.Signals = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 2), $PodeContext.RunspaceState, $Host) + Pool = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 2), $PodeContext.RunspaceState, $Host) State = 'Waiting' } } @@ -526,7 +523,7 @@ function New-PodeRunspacePools # web socket connections runspace - for receiving data for external sockets if (Test-PodeWebSocketsExist) { $PodeContext.RunspacePools.WebSockets = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.WebSockets + 1, $PodeContext.RunspaceState, $Host) + Pool = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.WebSockets + 1, $PodeContext.RunspaceState, $Host) State = 'Waiting' } @@ -536,7 +533,7 @@ function New-PodeRunspacePools # setup schedule runspace pool -if we have any schedules if (Test-PodeSchedulesExist) { $PodeContext.RunspacePools.Schedules = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.Schedules, $PodeContext.RunspaceState, $Host) + Pool = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.Schedules, $PodeContext.RunspaceState, $Host) State = 'Waiting' } } @@ -544,7 +541,7 @@ function New-PodeRunspacePools # setup tasks runspace pool -if we have any tasks if (Test-PodeTasksExist) { $PodeContext.RunspacePools.Tasks = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.Tasks, $PodeContext.RunspaceState, $Host) + Pool = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.Tasks, $PodeContext.RunspaceState, $Host) State = 'Waiting' } } @@ -552,7 +549,7 @@ function New-PodeRunspacePools # setup files runspace pool -if we have any file watchers if (Test-PodeFileWatchersExist) { $PodeContext.RunspacePools.Files = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.Files + 1, $PodeContext.RunspaceState, $Host) + Pool = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.Files + 1, $PodeContext.RunspaceState, $Host) State = 'Waiting' } } @@ -560,7 +557,7 @@ function New-PodeRunspacePools # setup gui runspace pool (only for non-ps-core) - if gui enabled if (Test-PodeGuiEnabled) { $PodeContext.RunspacePools.Gui = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, 1, $PodeContext.RunspaceState, $Host) + Pool = [runspacefactory]::CreateRunspacePool(1, 1, $PodeContext.RunspaceState, $Host) State = 'Waiting' } @@ -568,14 +565,13 @@ function New-PodeRunspacePools } } -function Open-PodeRunspacePools -{ +function Open-PodeRunspacePools { if ($PodeContext.Server.IsServerless) { return } $start = [datetime]::Now - Write-Verbose "Opening RunspacePools" + Write-Verbose 'Opening RunspacePools' # open pools async foreach ($key in $PodeContext.RunspacePools.Keys) { @@ -627,14 +623,13 @@ function Open-PodeRunspacePools Write-Verbose "RunspacePools opened [duration: $(([datetime]::Now - $start).TotalSeconds)s]" } -function Close-PodeRunspacePools -{ +function Close-PodeRunspacePools { if ($PodeContext.Server.IsServerless -or ($null -eq $PodeContext.RunspacePools)) { return } $start = [datetime]::Now - Write-Verbose "Closing RunspacePools" + Write-Verbose 'Closing RunspacePools' # close pools async foreach ($key in $PodeContext.RunspacePools.Keys) { @@ -694,31 +689,29 @@ function Close-PodeRunspacePools Write-Verbose "RunspacePools closed [duration: $(([datetime]::Now - $start).TotalSeconds)s]" } -function New-PodeStateContext -{ - param ( - [Parameter(Mandatory=$true)] +function New-PodeStateContext { + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Context ) return (New-Object -TypeName psobject | - Add-Member -MemberType NoteProperty -Name Threads -Value $Context.Threads -PassThru | - Add-Member -MemberType NoteProperty -Name Timers -Value $Context.Timers -PassThru | - Add-Member -MemberType NoteProperty -Name Schedules -Value $Context.Schedules -PassThru | - Add-Member -MemberType NoteProperty -Name Tasks -Value $Context.Tasks -PassThru | - Add-Member -MemberType NoteProperty -Name Fim -Value $Context.Fim -PassThru | - Add-Member -MemberType NoteProperty -Name RunspacePools -Value $Context.RunspacePools -PassThru | - Add-Member -MemberType NoteProperty -Name Tokens -Value $Context.Tokens -PassThru | - Add-Member -MemberType NoteProperty -Name Metrics -Value $Context.Metrics -PassThru | - Add-Member -MemberType NoteProperty -Name LogsToProcess -Value $Context.LogsToProcess -PassThru | - Add-Member -MemberType NoteProperty -Name Threading -Value $Context.Threading -PassThru | - Add-Member -MemberType NoteProperty -Name Server -Value $Context.Server -PassThru) + Add-Member -MemberType NoteProperty -Name Threads -Value $Context.Threads -PassThru | + Add-Member -MemberType NoteProperty -Name Timers -Value $Context.Timers -PassThru | + Add-Member -MemberType NoteProperty -Name Schedules -Value $Context.Schedules -PassThru | + Add-Member -MemberType NoteProperty -Name Tasks -Value $Context.Tasks -PassThru | + Add-Member -MemberType NoteProperty -Name Fim -Value $Context.Fim -PassThru | + Add-Member -MemberType NoteProperty -Name RunspacePools -Value $Context.RunspacePools -PassThru | + Add-Member -MemberType NoteProperty -Name Tokens -Value $Context.Tokens -PassThru | + Add-Member -MemberType NoteProperty -Name Metrics -Value $Context.Metrics -PassThru | + Add-Member -MemberType NoteProperty -Name LogsToProcess -Value $Context.LogsToProcess -PassThru | + Add-Member -MemberType NoteProperty -Name Threading -Value $Context.Threading -PassThru | + Add-Member -MemberType NoteProperty -Name Server -Value $Context.Server -PassThru) } -function Open-PodeConfiguration -{ - param ( +function Open-PodeConfiguration { + param( [Parameter()] [string] $ServerRoot = $null, @@ -750,8 +743,7 @@ function Open-PodeConfiguration return $config } -function Set-PodeServerConfiguration -{ +function Set-PodeServerConfiguration { param( [Parameter()] [hashtable] @@ -763,11 +755,11 @@ function Set-PodeServerConfiguration # file monitoring $Context.Server.FileMonitor = @{ - Enabled = ([bool]$Configuration.FileMonitor.Enable) - Exclude = (Convert-PodePathPatternsToRegex -Paths @($Configuration.FileMonitor.Exclude)) - Include = (Convert-PodePathPatternsToRegex -Paths @($Configuration.FileMonitor.Include)) + Enabled = ([bool]$Configuration.FileMonitor.Enable) + Exclude = (Convert-PodePathPatternsToRegex -Paths @($Configuration.FileMonitor.Exclude)) + Include = (Convert-PodePathPatternsToRegex -Paths @($Configuration.FileMonitor.Include)) ShowFiles = ([bool]$Configuration.FileMonitor.ShowFiles) - Files = @() + Files = @() } # logging @@ -775,9 +767,9 @@ function Set-PodeServerConfiguration Enabled = (($null -eq $Configuration.Logging.Enable) -or [bool]$Configuration.Logging.Enable) Masking = @{ Patterns = (Remove-PodeEmptyItemsFromArray -Array @($Configuration.Logging.Masking.Patterns)) - Mask = (Protect-PodeValue -Value $Configuration.Logging.Masking.Mask -Default '********') + Mask = (Protect-PodeValue -Value $Configuration.Logging.Masking.Mask -Default '********') } - Types = @{} + Types = @{} } # sockets @@ -802,9 +794,8 @@ function Set-PodeServerConfiguration } } -function Set-PodeWebConfiguration -{ - param ( +function Set-PodeWebConfiguration { + param( [Parameter()] [hashtable] $Configuration, @@ -815,30 +806,30 @@ function Set-PodeWebConfiguration # setup the main web config $Context.Server.Web = @{ - Static = @{ + Static = @{ Defaults = $Configuration.Static.Defaults - Cache = @{ + Cache = @{ Enabled = [bool]$Configuration.Static.Cache.Enable - MaxAge = [int](Protect-PodeValue -Value $Configuration.Static.Cache.MaxAge -Default 3600) + MaxAge = [int](Protect-PodeValue -Value $Configuration.Static.Cache.MaxAge -Default 3600) Include = (Convert-PodePathPatternsToRegex -Paths @($Configuration.Static.Cache.Include) -NotSlashes) Exclude = (Convert-PodePathPatternsToRegex -Paths @($Configuration.Static.Cache.Exclude) -NotSlashes) } } - ErrorPages = @{ - ShowExceptions = [bool]$Configuration.ErrorPages.ShowExceptions + ErrorPages = @{ + ShowExceptions = [bool]$Configuration.ErrorPages.ShowExceptions StrictContentTyping = [bool]$Configuration.ErrorPages.StrictContentTyping - Default = $Configuration.ErrorPages.Default - Routes = @{} + Default = $Configuration.ErrorPages.Default + Routes = @{} } - ContentType = @{ + ContentType = @{ Default = $Configuration.ContentType.Default - Routes = @{} + Routes = @{} } TransferEncoding = @{ Default = $Configuration.TransferEncoding.Default - Routes = @{} + Routes = @{} } - Compression = @{ + Compression = @{ Enabled = [bool]$Configuration.Compression.Enable } } @@ -865,11 +856,10 @@ function Set-PodeWebConfiguration } } -function New-PodeAutoRestartServer -{ +function New-PodeAutoRestartServer { # don't configure if not supplied, or running as serverless $config = (Get-PodeConfig) - if (($null -eq $config) -or ($null -eq $config.Server.Restart) -or $PodeContext.Server.IsServerless) { + if (($null -eq $config) -or ($null -eq $config.Server.Restart) -or $PodeContext.Server.IsServerless) { return } @@ -907,8 +897,7 @@ function New-PodeAutoRestartServer } } -function Set-PodeOutputVariables -{ +function Set-PodeOutputVariables { if (Test-PodeIsEmpty $PodeContext.Server.Output.Variables) { return } diff --git a/src/Private/Cookies.ps1 b/src/Private/Cookies.ps1 index 7917cefae..982a00ba1 100644 --- a/src/Private/Cookies.ps1 +++ b/src/Private/Cookies.ps1 @@ -1,5 +1,4 @@ -function ConvertTo-PodeCookie -{ +function ConvertTo-PodeCookie { param( [Parameter()] [System.Net.Cookie] @@ -11,23 +10,22 @@ function ConvertTo-PodeCookie } return @{ - Name = $Cookie.Name - Value = $Cookie.Value - Expires = $Cookie.Expires - Expired = $Cookie.Expired - Discard = $Cookie.Discard - HttpOnly = $Cookie.HttpOnly - Secure = $Cookie.Secure - Path = $Cookie.Path + Name = $Cookie.Name + Value = $Cookie.Value + Expires = $Cookie.Expires + Expired = $Cookie.Expired + Discard = $Cookie.Discard + HttpOnly = $Cookie.HttpOnly + Secure = $Cookie.Secure + Path = $Cookie.Path TimeStamp = $Cookie.TimeStamp - Signed = $Cookie.Value.StartsWith('s:') + Signed = $Cookie.Value.StartsWith('s:') } } -function ConvertTo-PodeCookieString -{ +function ConvertTo-PodeCookieString { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Cookie ) diff --git a/src/Private/CronParser.ps1 b/src/Private/CronParser.ps1 index 190c405a5..55336397d 100644 --- a/src/Private/CronParser.ps1 +++ b/src/Private/CronParser.ps1 @@ -1,5 +1,4 @@ -function Get-PodeCronFields -{ +function Get-PodeCronFields { return @( 'Minute', 'Hour', @@ -9,10 +8,9 @@ function Get-PodeCronFields ) } -function Get-PodeCronFieldConstraints -{ +function Get-PodeCronFieldConstraints { return @{ - MinMax = @( + MinMax = @( @(0, 59), @(0, 23), @(1, 31), @@ -22,40 +20,38 @@ function Get-PodeCronFieldConstraints DaysInMonths = @( 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ) - Months = @( + Months = @( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ) } } -function Get-PodeCronPredefined -{ +function Get-PodeCronPredefined { return @{ # normal - '@minutely' = '* * * * *'; - '@hourly' = '0 * * * *'; - '@daily' = '0 0 * * *'; - '@weekly' = '0 0 * * 0'; - '@monthly' = '0 0 1 * *'; - '@quarterly' = '0 0 1 1,4,7,10 *'; - '@yearly' = '0 0 1 1 *'; - '@annually' = '0 0 1 1 *'; + '@minutely' = '* * * * *' + '@hourly' = '0 * * * *' + '@daily' = '0 0 * * *' + '@weekly' = '0 0 * * 0' + '@monthly' = '0 0 1 * *' + '@quarterly' = '0 0 1 1,4,7,10 *' + '@yearly' = '0 0 1 1 *' + '@annually' = '0 0 1 1 *' # twice - '@twice-hourly' = '0,30 * * * *'; - '@twice-daily' = '0 0,12 * * *'; - '@twice-weekly' = '0 0 * * 0,4'; - '@twice-monthly' = '0 0 1,15 * *'; - '@twice-yearly' = '0 0 1 1,6 *'; - '@twice-annually' = '0 0 1 1,6 *'; + '@twice-hourly' = '0,30 * * * *' + '@twice-daily' = '0 0,12 * * *' + '@twice-weekly' = '0 0 * * 0,4' + '@twice-monthly' = '0 0 1,15 * *' + '@twice-yearly' = '0 0 1 1,6 *' + '@twice-annually' = '0 0 1 1,6 *' } } -function Get-PodeCronFieldAliases -{ +function Get-PodeCronFieldAliases { return @{ - Month = @{ + Month = @{ Jan = 1 Feb = 2 Mar = 3 @@ -81,24 +77,22 @@ function Get-PodeCronFieldAliases } } -function ConvertFrom-PodeCronExpressions -{ - param ( - [Parameter(Mandatory=$true)] +function ConvertFrom-PodeCronExpressions { + param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string[]] $Expressions ) return @(@($Expressions) | ForEach-Object { - ConvertFrom-PodeCronExpression -Expression $_ - }) + ConvertFrom-PodeCronExpression -Expression $_ + }) } -function ConvertFrom-PodeCronExpression -{ - param ( - [Parameter(Mandatory=$true)] +function ConvertFrom-PodeCronExpression { + param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Expression @@ -127,14 +121,13 @@ function ConvertFrom-PodeCronExpression $aliases = Get-PodeCronFieldAliases $cron = @{} - for ($i = 0; $i -lt $atoms.Length; $i++) - { + for ($i = 0; $i -lt $atoms.Length; $i++) { $_cronExp = @{ - Range = $null - Values = $null + Range = $null + Values = $null Constraints = $null - Random = $false - WildCard = $false + Random = $false + WildCard = $false } $_atom = $atoms[$i] @@ -253,8 +246,7 @@ function ConvertFrom-PodeCronExpression } # post validation for month/days in month - if (($null -ne $cron['Month'].Values) -and ($null -ne $cron['DayOfMonth'].Values)) - { + if (($null -ne $cron['Month'].Values) -and ($null -ne $cron['DayOfMonth'].Values)) { foreach ($mon in $cron['Month'].Values) { foreach ($day in $cron['DayOfMonth'].Values) { if ($day -gt $constraints.DaysInMonths[$mon - 1]) { @@ -271,23 +263,21 @@ function ConvertFrom-PodeCronExpression return $cron } -function Reset-PodeRandomCronExpressions -{ - param ( - [Parameter(Mandatory=$true)] +function Reset-PodeRandomCronExpressions { + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Expressions ) return @(@($Expressions) | ForEach-Object { - Reset-PodeRandomCronExpression -Expression $_ - }) + Reset-PodeRandomCronExpression -Expression $_ + }) } -function Reset-PodeRandomCronExpression -{ - param ( - [Parameter(Mandatory=$true)] +function Reset-PodeRandomCronExpression { + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Expression ) @@ -317,10 +307,9 @@ function Reset-PodeRandomCronExpression return $Expression } -function Test-PodeCronExpressions -{ - param ( - [Parameter(Mandatory=$true)] +function Test-PodeCronExpressions { + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Expressions, @@ -329,14 +318,13 @@ function Test-PodeCronExpressions ) return ((@($Expressions) | Where-Object { - Test-PodeCronExpression -Expression $_ -DateTime $DateTime - } | Measure-Object).Count -gt 0) + Test-PodeCronExpression -Expression $_ -DateTime $DateTime + } | Measure-Object).Count -gt 0) } -function Test-PodeCronExpression -{ - param ( - [Parameter(Mandatory=$true)] +function Test-PodeCronExpression { + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Expression, @@ -386,10 +374,9 @@ function Test-PodeCronExpression return $true } -function Get-PodeCronNextEarliestTrigger -{ +function Get-PodeCronNextEarliestTrigger { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Expressions, @@ -401,14 +388,13 @@ function Get-PodeCronNextEarliestTrigger ) return (@($Expressions) | Foreach-Object { - Get-PodeCronNextTrigger -Expression $_ -StartTime $StartTime -EndTime $EndTime - } | Where-Object { $null -ne $_ } | Sort-Object | Select-Object -First 1) + Get-PodeCronNextTrigger -Expression $_ -StartTime $StartTime -EndTime $EndTime + } | Where-Object { $null -ne $_ } | Sort-Object | Select-Object -First 1) } -function Get-PodeCronNextTrigger -{ +function Get-PodeCronNextTrigger { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Expression, @@ -448,8 +434,7 @@ function Get-PodeCronNextTrigger } # loop until we get a date - while ($true) - { + while ($true) { # check the minute if (!$Expression.Minute.WildCard) { $minute = Get-ClosestValue -AtomContraint $Expression.Minute -NowValue $NextTime.Minute diff --git a/src/Private/Cryptography.ps1 b/src/Private/Cryptography.ps1 index decbad9d8..99a90e34c 100644 --- a/src/Private/Cryptography.ps1 +++ b/src/Private/Cryptography.ps1 @@ -1,16 +1,15 @@ -function Invoke-PodeHMACSHA256Hash -{ - [CmdletBinding(DefaultParameterSetName='String')] +function Invoke-PodeHMACSHA256Hash { + [CmdletBinding(DefaultParameterSetName = 'String')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Value, - [Parameter(Mandatory=$true, ParameterSetName='String')] + [Parameter(Mandatory = $true, ParameterSetName = 'String')] [string] $Secret, - [Parameter(Mandatory=$true, ParameterSetName='Bytes')] + [Parameter(Mandatory = $true, ParameterSetName = 'Bytes')] [byte[]] $SecretBytes ) @@ -20,26 +19,25 @@ function Invoke-PodeHMACSHA256Hash } if ($SecretBytes.Length -eq 0) { - throw "No secret supplied for HMAC256 hash" + throw 'No secret supplied for HMAC256 hash' } $crypto = [System.Security.Cryptography.HMACSHA256]::new($SecretBytes) return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))) } -function Invoke-PodeHMACSHA384Hash -{ - [CmdletBinding(DefaultParameterSetName='String')] +function Invoke-PodeHMACSHA384Hash { + [CmdletBinding(DefaultParameterSetName = 'String')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Value, - [Parameter(Mandatory=$true, ParameterSetName='String')] + [Parameter(Mandatory = $true, ParameterSetName = 'String')] [string] $Secret, - [Parameter(Mandatory=$true, ParameterSetName='Bytes')] + [Parameter(Mandatory = $true, ParameterSetName = 'Bytes')] [byte[]] $SecretBytes ) @@ -49,26 +47,25 @@ function Invoke-PodeHMACSHA384Hash } if ($SecretBytes.Length -eq 0) { - throw "No secret supplied for HMAC384 hash" + throw 'No secret supplied for HMAC384 hash' } $crypto = [System.Security.Cryptography.HMACSHA384]::new($SecretBytes) return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))) } -function Invoke-PodeHMACSHA512Hash -{ - [CmdletBinding(DefaultParameterSetName='String')] +function Invoke-PodeHMACSHA512Hash { + [CmdletBinding(DefaultParameterSetName = 'String')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Value, - [Parameter(Mandatory=$true, ParameterSetName='String')] + [Parameter(Mandatory = $true, ParameterSetName = 'String')] [string] $Secret, - [Parameter(Mandatory=$true, ParameterSetName='Bytes')] + [Parameter(Mandatory = $true, ParameterSetName = 'Bytes')] [byte[]] $SecretBytes ) @@ -78,17 +75,16 @@ function Invoke-PodeHMACSHA512Hash } if ($SecretBytes.Length -eq 0) { - throw "No secret supplied for HMAC512 hash" + throw 'No secret supplied for HMAC512 hash' } $crypto = [System.Security.Cryptography.HMACSHA512]::new($SecretBytes) return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))) } -function Invoke-PodeSHA256Hash -{ - param ( - [Parameter(Mandatory=$true)] +function Invoke-PodeSHA256Hash { + param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Value @@ -98,10 +94,9 @@ function Invoke-PodeSHA256Hash return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))) } -function Invoke-PodeSHA1Hash -{ - param ( - [Parameter(Mandatory=$true)] +function Invoke-PodeSHA1Hash { + param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Value @@ -111,14 +106,13 @@ function Invoke-PodeSHA1Hash return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))) } -function ConvertTo-PodeBase64Auth -{ +function ConvertTo-PodeBase64Auth { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Username, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Password ) @@ -126,10 +120,9 @@ function ConvertTo-PodeBase64Auth return [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($Username):$($Password)")) } -function Invoke-PodeMD5Hash -{ - param ( - [Parameter(Mandatory=$true)] +function Invoke-PodeMD5Hash { + param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Value @@ -139,25 +132,23 @@ function Invoke-PodeMD5Hash return [System.BitConverter]::ToString($crypto.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($Value))).Replace('-', '').ToLowerInvariant() } -function Get-PodeRandomBytes -{ - param ( +function Get-PodeRandomBytes { + param( [Parameter()] [int] $Length = 16 ) return (Use-PodeStream -Stream ([System.Security.Cryptography.RandomNumberGenerator]::Create()) { - param($p) - $bytes = [byte[]]::new($Length) - $p.GetBytes($bytes) - return $bytes - }) + param($p) + $bytes = [byte[]]::new($Length) + $p.GetBytes($bytes) + return $bytes + }) } -function New-PodeSalt -{ - param ( +function New-PodeSalt { + param( [Parameter()] [int] $Length = 8 @@ -167,9 +158,8 @@ function New-PodeSalt return [System.Convert]::ToBase64String($bytes) } -function New-PodeGuid -{ - param ( +function New-PodeGuid { + param( [Parameter()] [int] $Length = 16, @@ -199,15 +189,14 @@ function New-PodeGuid return $guid } -function Invoke-PodeValueSign -{ - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] +function Invoke-PodeValueSign { + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [string] $Value, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Secret @@ -216,15 +205,14 @@ function Invoke-PodeValueSign return "s:$($Value).$(Invoke-PodeHMACSHA256Hash -Value $Value -Secret $Secret)" } -function Invoke-PodeValueUnsign -{ - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] +function Invoke-PodeValueUnsign { + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [string] $Value, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Secret @@ -253,14 +241,13 @@ function Invoke-PodeValueUnsign return $raw } -function New-PodeJwtSignature -{ +function New-PodeJwtSignature { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Algorithm, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Token, @@ -270,11 +257,11 @@ function New-PodeJwtSignature ) if (($Algorithm -ine 'none') -and (($null -eq $SecretBytes) -or ($SecretBytes.Length -eq 0))) { - throw "No Secret supplied for JWT signature" + throw 'No Secret supplied for JWT signature' } if (($Algorithm -ieq 'none') -and (($null -ne $secretBytes) -and ($SecretBytes.Length -gt 0))) { - throw "Expected no secret to be supplied for no signature" + throw 'Expected no secret to be supplied for no signature' } $sig = $null @@ -307,10 +294,9 @@ function New-PodeJwtSignature return $sig } -function ConvertTo-PodeBase64UrlValue -{ +function ConvertTo-PodeBase64UrlValue { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Value, @@ -329,10 +315,9 @@ function ConvertTo-PodeBase64UrlValue return $Value } -function ConvertFrom-PodeJwtBase64Value -{ +function ConvertFrom-PodeJwtBase64Value { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Value ) @@ -343,9 +328,17 @@ function ConvertFrom-PodeJwtBase64Value # add padding switch ($Value.Length % 4) { - 1 { $Value = $Value.Substring(0, $Value.Length - 1) } - 2 { $Value += '==' } - 3 { $Value += '=' } + 1 { + $Value = $Value.Substring(0, $Value.Length - 1) + } + + 2 { + $Value += '==' + } + + 3 { + $Value += '=' + } } # convert base64 to string @@ -353,7 +346,7 @@ function ConvertFrom-PodeJwtBase64Value $Value = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Value)) } catch { - throw "Invalid Base64 encoded value found in JWT" + throw 'Invalid Base64 encoded value found in JWT' } # return json @@ -361,6 +354,6 @@ function ConvertFrom-PodeJwtBase64Value return ($Value | ConvertFrom-Json) } catch { - throw "Invalid JSON value found in JWT" + throw 'Invalid JSON value found in JWT' } } \ No newline at end of file diff --git a/src/Private/Endpoints.ps1 b/src/Private/Endpoints.ps1 index 33b624ca7..61e2f1e15 100644 --- a/src/Private/Endpoints.ps1 +++ b/src/Private/Endpoints.ps1 @@ -1,5 +1,4 @@ -function Find-PodeEndpoints -{ +function Find-PodeEndpoints { param( [Parameter()] [ValidateSet('', 'Http', 'Https')] @@ -21,8 +20,8 @@ function Find-PodeEndpoints if ([string]::IsNullOrWhiteSpace($EndpointName)) { $endpoints += @{ Protocol = $Protocol - Address = $Address - Name = [string]::Empty + Address = $Address + Name = [string]::Empty } } @@ -33,8 +32,8 @@ function Find-PodeEndpoints if ($null -ne $_endpoint) { $endpoints += @{ Protocol = $_endpoint.Protocol - Address = $_endpoint.RawAddress - Name = $name + Address = $_endpoint.RawAddress + Name = $name } } } @@ -51,10 +50,9 @@ function Find-PodeEndpoints return $endpoints } -function Get-PodeEndpoints -{ +function Get-PodeEndpoints { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Http', 'Ws', 'Smtp', 'Tcp')] [string[]] $Type @@ -85,10 +83,9 @@ function Get-PodeEndpoints return $endpoints } -function Test-PodeEndpointProtocol -{ +function Test-PodeEndpointProtocol { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Http', 'Https', 'Ws', 'Wss', 'Smtp', 'Smtps', 'Tcp', 'Tcps')] [string] $Protocol @@ -98,8 +95,7 @@ function Test-PodeEndpointProtocol return ($null -ne $endpoint) } -function Get-PodeEndpointType -{ +function Get-PodeEndpointType { param( [Parameter()] [ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Tcps', 'Ws', 'Wss')] @@ -108,16 +104,29 @@ function Get-PodeEndpointType ) switch ($Protocol) { - { $_ -iin @('http', 'https') } { 'Http' } - { $_ -iin @('ws', 'wss') } { 'Ws' } - { $_ -iin @('smtp', 'smtps') } { 'Smtp' } - { $_ -iin @('tcp', 'tcps') } { 'Tcp' } - default { $Protocol } + { $_ -iin @('http', 'https') } { + 'Http' + } + + { $_ -iin @('ws', 'wss') } { + 'Ws' + } + + { $_ -iin @('smtp', 'smtps') } { + 'Smtp' + } + + { $_ -iin @('tcp', 'tcps') } { + 'Tcp' + } + + default { + $Protocol + } } } -function Get-PodeEndpointRunspacePoolName -{ +function Get-PodeEndpointRunspacePoolName { param( [Parameter()] [ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Tcps', 'Ws', 'Wss')] @@ -126,18 +135,31 @@ function Get-PodeEndpointRunspacePoolName ) switch ($Protocol) { - { $_ -iin @('http', 'https') } { 'Web' } - { $_ -iin @('ws', 'wss') } { 'Signals' } - { $_ -iin @('smtp', 'smtps') } { 'Smtp' } - { $_ -iin @('tcp', 'tcps') } { 'Tcp' } - default { $Protocol } + { $_ -iin @('http', 'https') } { + 'Web' + } + + { $_ -iin @('ws', 'wss') } { + 'Signals' + } + + { $_ -iin @('smtp', 'smtps') } { + 'Smtp' + } + + { $_ -iin @('tcp', 'tcps') } { + 'Tcp' + } + + default { + $Protocol + } } } -function Test-PodeEndpoints -{ +function Test-PodeEndpoints { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Http', 'Ws', 'Smtp', 'Tcp')] [string] $Type @@ -148,8 +170,7 @@ function Test-PodeEndpoints } -function Find-PodeEndpointName -{ +function Find-PodeEndpointName { param( [Parameter()] [string] @@ -206,11 +227,11 @@ function Find-PodeEndpointName # try and find endpoint for address $key = @(foreach ($k in $PodeContext.Server.EndpointsMap.Keys) { - if ($k -imatch $key) { - $k - break - } - })[0] + if ($k -imatch $key) { + $k + break + } + })[0] if (![string]::IsNullOrWhiteSpace($key) -and $PodeContext.Server.EndpointsMap.ContainsKey($key)) { return $PodeContext.Server.EndpointsMap[$key] @@ -229,11 +250,11 @@ function Find-PodeEndpointName # try and find endpoint for local address $key = @(foreach ($k in $PodeContext.Server.EndpointsMap.Keys) { - if ($k -imatch $key) { - $k - break - } - })[0] + if ($k -imatch $key) { + $k + break + } + })[0] if (![string]::IsNullOrWhiteSpace($key) -and $PodeContext.Server.EndpointsMap.ContainsKey($key)) { return $PodeContext.Server.EndpointsMap[$key] @@ -249,11 +270,11 @@ function Find-PodeEndpointName # try and find endpoint for any address $key = @(foreach ($k in $PodeContext.Server.EndpointsMap.Keys) { - if ($k -imatch $key) { - $k - break - } - })[0] + if ($k -imatch $key) { + $k + break + } + })[0] if (![string]::IsNullOrWhiteSpace($key) -and $PodeContext.Server.EndpointsMap.ContainsKey($key)) { return $PodeContext.Server.EndpointsMap[$key] @@ -267,9 +288,8 @@ function Find-PodeEndpointName return $null } -function Get-PodeEndpointByName -{ - param ( +function Get-PodeEndpointByName { + param( [Parameter()] [string] $Name, diff --git a/src/Private/Endware.ps1 b/src/Private/Endware.ps1 index e5a829133..508842b0c 100644 --- a/src/Private/Endware.ps1 +++ b/src/Private/Endware.ps1 @@ -1,6 +1,5 @@ -function Invoke-PodeEndware -{ - param ( +function Invoke-PodeEndware { + param( [Parameter()] $Endware ) @@ -11,8 +10,7 @@ function Invoke-PodeEndware } # loop through each of the endware, invoking the next if it returns true - foreach ($eware in @($Endware)) - { + foreach ($eware in @($Endware)) { if (($null -eq $eware) -or ($null -eq $eware.Logic)) { continue } diff --git a/src/Private/Events.ps1 b/src/Private/Events.ps1 index 7654ad3aa..2025bb508 100644 --- a/src/Private/Events.ps1 +++ b/src/Private/Events.ps1 @@ -1,7 +1,6 @@ -function Invoke-PodeEvent -{ +function Invoke-PodeEvent { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')] [string] $Type diff --git a/src/Private/FileMonitor.ps1 b/src/Private/FileMonitor.ps1 index 46fbdd4d1..deee38e46 100644 --- a/src/Private/FileMonitor.ps1 +++ b/src/Private/FileMonitor.ps1 @@ -1,5 +1,4 @@ -function Start-PodeFileMonitor -{ +function Start-PodeFileMonitor { # don't configure if not supplied, or we're running as serverless if (!$PodeContext.Server.FileMonitor.Enabled -or $PodeContext.Server.IsServerless) { return @@ -11,8 +10,8 @@ function Start-PodeFileMonitor # setup the file monitor $watcher = New-Object System.IO.FileSystemWatcher $folder, $filter -Property @{ - IncludeSubdirectories = $true; - NotifyFilter = [System.IO.NotifyFilters]'FileName,LastWrite,CreationTime'; + IncludeSubdirectories = $true + NotifyFilter = [System.IO.NotifyFilters]'FileName,LastWrite,CreationTime' } $watcher.EnableRaisingEvents = $true @@ -24,7 +23,7 @@ function Start-PodeFileMonitor # setup the message data for the events $msgData = @{ - Timer = $timer + Timer = $timer Settings = $PodeContext.Server.FileMonitor } @@ -83,14 +82,13 @@ function Start-PodeFileMonitor $Event.MessageData.Tokens.Restart.Cancel() $Event.Sender.Stop() } -MessageData @{ - Tokens = $PodeContext.Tokens + Tokens = $PodeContext.Tokens FileSettings = $PodeContext.Server.FileMonitor - Quiet = $PodeContext.Server.Quiet + Quiet = $PodeContext.Server.Quiet } -SupportEvent } -function Stop-PodeFileMonitor -{ +function Stop-PodeFileMonitor { if ($PodeContext.Server.IsServerless) { return } @@ -103,10 +101,9 @@ function Stop-PodeFileMonitor } } -function Get-PodeFileMonitorName -{ - param ( - [Parameter(Mandatory=$true)] +function Get-PodeFileMonitorName { + param( + [Parameter(Mandatory = $true)] [ValidateSet('Create', 'Delete', 'Update')] [string] $Type @@ -115,7 +112,6 @@ function Get-PodeFileMonitorName return "PodeFileMonitor$($Type)" } -function Get-PodeFileMonitorTimerName -{ +function Get-PodeFileMonitorTimerName { return 'PodeFileMonitorTimer' } \ No newline at end of file diff --git a/src/Private/FileWatchers.ps1 b/src/Private/FileWatchers.ps1 index 0ad5b848b..7446ddb90 100644 --- a/src/Private/FileWatchers.ps1 +++ b/src/Private/FileWatchers.ps1 @@ -1,20 +1,17 @@ using namespace Pode -function Test-PodeFileWatchersExist -{ +function Test-PodeFileWatchersExist { return (($null -ne $PodeContext.Fim) -and (($PodeContext.Fim.Enabled) -or ($PodeContext.Fim.Items.Count -gt 0))) } -function New-PodeFileWatcher -{ +function New-PodeFileWatcher { $watcher = [PodeWatcher]::new($PodeContext.Tokens.Cancellation.Token) $watcher.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) $watcher.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevels) return $watcher } -function Start-PodeFileWatcherRunspace -{ +function Start-PodeFileWatcherRunspace { if (!(Test-PodeFileWatchersExist)) { return } @@ -50,24 +47,20 @@ function Start-PodeFileWatcherRunspace $watchScript = { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Watcher, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $ThreadId ) - try - { - while ($Watcher.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + try { + while ($Watcher.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) { $evt = (Wait-PodeTask -Task $Watcher.GetFileEventAsync($PodeContext.Tokens.Cancellation.Token)) - try - { - try - { + try { + try { # get file watcher $fileWatcher = $PodeContext.Fim.Items[$evt.FileWatcher.Name] if ($null -eq $fileWatcher) { @@ -88,16 +81,16 @@ function Start-PodeFileWatcherRunspace # set file event object $FileEvent = @{ - Type = $evt.ChangeType - FullPath = $evt.FullPath - Name = $evt.Name - Old = @{ + Type = $evt.ChangeType + FullPath = $evt.FullPath + Name = $evt.Name + Old = @{ FullPath = $evt.OldFullPath - Name = $evt.OldName + Name = $evt.OldName } Parameters = @{} - Lockable = $PodeContext.Threading.Lockables.Global - Timestamp = [datetime]::UtcNow + Lockable = $PodeContext.Threading.Lockables.Global + Timestamp = [datetime]::UtcNow } # do we have any parameters? @@ -109,7 +102,8 @@ function Start-PodeFileWatcherRunspace $_args = @(Get-PodeScriptblockArguments -ArgumentList $fileWatcher.Arguments -UsingVariables $fileWatcher.UsingVariables) Invoke-PodeScriptBlock -ScriptBlock $fileWatcher.Script -Arguments $_args -Scoped -Splat } - catch [System.OperationCanceledException] {} + catch [System.OperationCanceledException] { + } catch { $_ | Write-PodeErrorLog $_.Exception | Write-PodeErrorLog -CheckInnerException @@ -121,7 +115,8 @@ function Start-PodeFileWatcherRunspace } } } - catch [System.OperationCanceledException] {} + catch [System.OperationCanceledException] { + } catch { $_ | Write-PodeErrorLog $_.Exception | Write-PodeErrorLog -CheckInnerException @@ -136,7 +131,7 @@ function Start-PodeFileWatcherRunspace # script to keep file watcher server alive until cancelled $waitScript = { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Watcher ) diff --git a/src/Private/Gui.ps1 b/src/Private/Gui.ps1 index b27a41f43..ec5592abf 100644 --- a/src/Private/Gui.ps1 +++ b/src/Private/Gui.ps1 @@ -1,5 +1,4 @@ -function Test-PodeGuiEnabled -{ +function Test-PodeGuiEnabled { return ($PodeContext.Server.Gui.Enabled -and !$PodeContext.Server.IsServerless -and !$PodeContext.Server.IsIIS -and @@ -17,7 +16,7 @@ function Start-PodeGuiRunspace { # if there are multiple endpoints, flag warning we're only using the first - unless explicitly set if ($null -eq $PodeContext.Server.Gui.Endpoint) { if ($PodeContext.Server.Endpoints.Values.Count -gt 1) { - Write-PodeHost "Multiple endpoints defined, only the first will be used for the GUI" -ForegroundColor Yellow + Write-PodeHost 'Multiple endpoints defined, only the first will be used for the GUI' -ForegroundColor Yellow } } @@ -52,7 +51,7 @@ function Start-PodeGuiRunspace { $null = [System.Reflection.Assembly]::LoadWithPartialName('PresentationCore') # Check for CefSharp - $loadCef = [bool]([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.FullName.StartsWith("CefSharp.Wpf,") }) + $loadCef = [bool]([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.FullName.StartsWith('CefSharp.Wpf,') }) # setup the WPF XAML for the server # Check for CefSharp and used Chromium based WPF if Modules exists @@ -117,11 +116,11 @@ function Start-PodeGuiRunspace { # get the browser object from XAML and navigate to base page if Cef is not loaded if (!$loadCef) { - $form.FindName("WebBrowser").Navigate($uri) + $form.FindName('WebBrowser').Navigate($uri) } # display the form - Write-PodeHost "Opening GUI" -ForegroundColor Yellow + Write-PodeHost 'Opening GUI' -ForegroundColor Yellow $null = $form.ShowDialog() Start-Sleep -Seconds 1 } diff --git a/src/Private/Helpers.ps1 b/src/Private/Helpers.ps1 index baefbede3..b9805ace1 100644 --- a/src/Private/Helpers.ps1 +++ b/src/Private/Helpers.ps1 @@ -1,10 +1,9 @@ using namespace Pode # read in the content from a dynamic pode file and invoke its content -function ConvertFrom-PodeFile -{ +function ConvertFrom-PodeFile { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Content, @@ -24,10 +23,9 @@ function ConvertFrom-PodeFile return (Invoke-PodeScriptBlock -ScriptBlock ([scriptblock]::Create($Content)) -Arguments $Data -Return) } -function Get-PodeViewEngineType -{ +function Get-PodeViewEngineType { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path ) @@ -43,10 +41,9 @@ function Get-PodeViewEngineType return $type } -function Get-PodeFileContentUsingViewEngine -{ +function Get-PodeFileContentUsingViewEngine { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -62,8 +59,7 @@ function Get-PodeFileContentUsingViewEngine $content = [string]::Empty # run the relevant engine logic - switch ($engine.ToLowerInvariant()) - { + switch ($engine.ToLowerInvariant()) { 'html' { $content = Get-Content -Path $Path -Raw -Encoding utf8 } @@ -93,10 +89,9 @@ function Get-PodeFileContentUsingViewEngine return $content } -function Get-PodeFileContent -{ +function Get-PodeFileContent { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path ) @@ -104,8 +99,7 @@ function Get-PodeFileContent return (Get-Content -Path $Path -Raw -Encoding utf8) } -function Get-PodeType -{ +function Get-PodeType { param( [Parameter()] $Value @@ -122,13 +116,11 @@ function Get-PodeType } } -function Get-PodePSVersionTable -{ +function Get-PodePSVersionTable { return $PSVersionTable } -function Test-PodeIsAdminUser -{ +function Test-PodeIsAdminUser { # check the current platform, if it's unix then return true if (Test-PodeIsUnix) { return $true @@ -149,10 +141,9 @@ function Test-PodeIsAdminUser } } -function Get-PodeHostIPRegex -{ +function Get-PodeHostIPRegex { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Both', 'Hostname', 'IP')] [string] $Type @@ -161,8 +152,7 @@ function Get-PodeHostIPRegex $ip_rgx = '\[[a-f0-9\:]+\]|((\d+\.){3}\d+)|\:\:\d*|\*|all' $host_rgx = '([a-z]|\*\.)(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])+' - switch ($Type.ToLowerInvariant()) - { + switch ($Type.ToLowerInvariant()) { 'both' { return "(?($($ip_rgx)|$($host_rgx)))" } @@ -177,13 +167,11 @@ function Get-PodeHostIPRegex } } -function Get-PortRegex -{ +function Get-PortRegex { return '(?\d+)' } -function Get-PodeEndpointInfo -{ +function Get-PodeEndpointInfo { param( [Parameter()] [string] @@ -235,8 +223,7 @@ function Get-PodeEndpointInfo } } -function Test-PodeIPAddress -{ +function Test-PodeIPAddress { param( [Parameter()] [string] @@ -263,8 +250,7 @@ function Test-PodeIPAddress } } -function Test-PodeHostname -{ +function Test-PodeHostname { param( [Parameter()] [string] @@ -274,10 +260,9 @@ function Test-PodeHostname return ($Hostname -imatch "^$(Get-PodeHostIPRegex -Type Hostname)$") } -function ConvertTo-PodeIPAddress -{ +function ConvertTo-PodeIPAddress { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Address ) @@ -285,14 +270,13 @@ function ConvertTo-PodeIPAddress return [System.Net.IPAddress]::Parse(([System.Net.IPEndPoint]$Address).Address.ToString()) } -function Get-PodeIPAddressesForHostname -{ +function Get-PodeIPAddressesForHostname { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Hostname, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('All', 'IPv4', 'IPv6')] [string] $Type @@ -311,32 +295,30 @@ function Get-PodeIPAddressesForHostname } # return ips based on type - switch ($Type.ToLowerInvariant()) - { + switch ($Type.ToLowerInvariant()) { 'ipv4' { $ips = @(foreach ($ip in $ips) { - if ($ip.AddressFamily -ieq 'InterNetwork') { - $ip - } - }) + if ($ip.AddressFamily -ieq 'InterNetwork') { + $ip + } + }) } 'ipv6' { $ips = @(foreach ($ip in $ips) { - if ($ip.AddressFamily -ieq 'InterNetworkV6') { - $ip - } - }) + if ($ip.AddressFamily -ieq 'InterNetworkV6') { + $ip + } + }) } } return (@($ips)).IPAddressToString } -function Test-PodeIPAddressLocal -{ +function Test-PodeIPAddressLocal { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $IP ) @@ -344,10 +326,9 @@ function Test-PodeIPAddressLocal return (@('127.0.0.1', '::1', '[::1]', 'localhost') -icontains $IP) } -function Test-PodeIPAddressAny -{ +function Test-PodeIPAddressAny { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $IP ) @@ -355,10 +336,9 @@ function Test-PodeIPAddressAny return (@('0.0.0.0', '*', 'all', '::', '[::]') -icontains $IP) } -function Test-PodeIPAddressLocalOrAny -{ +function Test-PodeIPAddressLocalOrAny { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $IP ) @@ -366,8 +346,7 @@ function Test-PodeIPAddressLocalOrAny return ((Test-PodeIPAddressLocal -IP $IP) -or (Test-PodeIPAddressAny -IP $IP)) } -function Get-PodeIPAddress -{ +function Get-PodeIPAddress { param( [Parameter()] [string] @@ -398,16 +377,15 @@ function Get-PodeIPAddress return [System.Net.IPAddress]::Parse($IP) } -function Test-PodeIPAddressInRange -{ +function Test-PodeIPAddressInRange { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $IP, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $LowerIP, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $UpperIP ) @@ -427,10 +405,9 @@ function Test-PodeIPAddressInRange return $valid } -function Test-PodeIPAddressIsSubnetMask -{ +function Test-PodeIPAddressIsSubnetMask { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $IP @@ -439,10 +416,9 @@ function Test-PodeIPAddressIsSubnetMask return (($IP -split '/').Length -gt 1) } -function Get-PodeSubnetRange -{ +function Get-PodeSubnetRange { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SubnetMask @@ -458,7 +434,7 @@ function Get-PodeSubnetRange $bits = [int]$split[1] # generate the netmask - $network = @("", "", "", "") + $network = @('', '', '', '') $count = 0 foreach ($i in 0..3) { @@ -466,10 +442,10 @@ function Get-PodeSubnetRange $count++ if ($count -le $bits) { - $network[$i] += "1" + $network[$i] += '1' } else { - $network[$i] += "0" + $network[$i] += '0' } } } @@ -481,37 +457,36 @@ function Get-PodeSubnetRange # calculate the bottom range $bottom = @(foreach ($i in 0..3) { - [byte]([byte]$network[$i] -band [byte]$ip_parts[$i]) - }) + [byte]([byte]$network[$i] -band [byte]$ip_parts[$i]) + }) # calculate the range $range = @(foreach ($i in 0..3) { - 256 + (-bnot [byte]$network[$i]) - }) + 256 + (-bnot [byte]$network[$i]) + }) # calculate the top range $top = @(foreach ($i in 0..3) { - [byte]([byte]$ip_parts[$i] + [byte]$range[$i]) - }) + [byte]([byte]$ip_parts[$i] + [byte]$range[$i]) + }) return @{ - 'Lower' = ($bottom -join '.'); - 'Upper' = ($top -join '.'); - 'Range' = ($range -join '.'); - 'Netmask' = ($network -join '.'); - 'IP' = ($ip_parts -join '.'); + 'Lower' = ($bottom -join '.') + 'Upper' = ($top -join '.') + 'Range' = ($range -join '.') + 'Netmask' = ($network -join '.') + 'IP' = ($ip_parts -join '.') } } -function Add-PodeRunspace -{ +function Add-PodeRunspace { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Main', 'Signals', 'Schedules', 'Gui', 'Web', 'Smtp', 'Tcp', 'Tasks', 'WebSockets', 'Files')] [string] $Type, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] [scriptblock] $ScriptBlock, @@ -533,8 +508,7 @@ function Add-PodeRunspace $PassThru ) - try - { + try { # create powershell pipelines $ps = [powershell]::Create() $ps.RunspacePool = $PodeContext.RunspacePools[$Type].Pool @@ -571,17 +545,17 @@ function Add-PodeRunspace elseif ($PassThru) { return @{ Pipeline = $ps - Handler = $pipeline + Handler = $pipeline } } # or store it here for later clean-up else { $PodeContext.Runspaces += @{ - Pool = $Type + Pool = $Type Pipeline = $ps - Handler = $pipeline - Stopped = $false + Handler = $pipeline + Stopped = $false } } } @@ -591,10 +565,9 @@ function Add-PodeRunspace } } -function Open-PodeRunspace -{ +function Open-PodeRunspace { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Type ) @@ -615,8 +588,7 @@ function Open-PodeRunspace } } -function Close-PodeRunspaces -{ +function Close-PodeRunspaces { param( [switch] $ClosePool @@ -628,7 +600,7 @@ function Close-PodeRunspaces try { if (!(Test-PodeIsEmpty $PodeContext.Runspaces)) { - Write-Verbose "Waiting until all Listeners are disposed" + Write-Verbose 'Waiting until all Listeners are disposed' $count = 0 $continue = $false @@ -665,28 +637,28 @@ function Close-PodeRunspaces break } - Write-Verbose "All Listeners disposed" + Write-Verbose 'All Listeners disposed' # now dispose runspaces - Write-Verbose "Disposing Runspaces" + Write-Verbose 'Disposing Runspaces' $runspaceErrors = @(foreach ($item in $PodeContext.Runspaces) { - if ($item.Stopped) { - continue - } + if ($item.Stopped) { + continue + } - try { - # only do this, if the pool is in error - if ($PodeContext.RunspacePools[$item.Pool].State -ieq 'error') { - $item.Pipeline.EndInvoke($item.Handler) + try { + # only do this, if the pool is in error + if ($PodeContext.RunspacePools[$item.Pool].State -ieq 'error') { + $item.Pipeline.EndInvoke($item.Handler) + } + } + catch { + "$($item.Pool) runspace failed to load: $($_.Exception.InnerException.Message)" } - } - catch { - "$($item.Pool) runspace failed to load: $($_.Exception.InnerException.Message)" - } - Close-PodeDisposable -Disposable $item.Pipeline - $item.Stopped = $true - }) + Close-PodeDisposable -Disposable $item.Pipeline + $item.Stopped = $true + }) # dispose of schedule runspaces if ($PodeContext.Schedules.Processes.Count -gt 0) { @@ -703,7 +675,7 @@ function Close-PodeRunspaces } $PodeContext.Runspaces = @() - Write-Verbose "Runspaces disposed" + Write-Verbose 'Runspaces disposed' } # close/dispose the runspace pools @@ -731,8 +703,7 @@ function Close-PodeRunspaces } } -function Get-PodeConsoleKey -{ +function Get-PodeConsoleKey { if ([Console]::IsInputRedirected -or ![Console]::KeyAvailable) { return $null } @@ -740,8 +711,7 @@ function Get-PodeConsoleKey return [Console]::ReadKey($true) } -function Test-PodeTerminationPressed -{ +function Test-PodeTerminationPressed { param( [Parameter()] $Key = $null @@ -754,8 +724,7 @@ function Test-PodeTerminationPressed return (Test-PodeKeyPressed -Key $Key -Character 'c') } -function Test-PodeRestartPressed -{ +function Test-PodeRestartPressed { param( [Parameter()] $Key = $null @@ -764,8 +733,7 @@ function Test-PodeRestartPressed return (Test-PodeKeyPressed -Key $Key -Character 'r') } -function Test-PodeOpenBrowserPressed -{ +function Test-PodeOpenBrowserPressed { param( [Parameter()] $Key = $null @@ -774,13 +742,12 @@ function Test-PodeOpenBrowserPressed return (Test-PodeKeyPressed -Key $Key -Character 'b') } -function Test-PodeKeyPressed -{ +function Test-PodeKeyPressed { param( [Parameter()] $Key = $null, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Character ) @@ -793,8 +760,7 @@ function Test-PodeKeyPressed (($Key.Modifiers -band [ConsoleModifiers]::Control) -or ((Test-PodeIsUnix) -and ($Key.Modifiers -band [ConsoleModifiers]::Shift)))) } -function Close-PodeServerInternal -{ +function Close-PodeServerInternal { param( [switch] $ShowDoneMessage @@ -802,26 +768,26 @@ function Close-PodeServerInternal # ensure the token is cancelled if ($null -ne $PodeContext.Tokens.Cancellation) { - Write-Verbose "Cancelling main cancellation token" + Write-Verbose 'Cancelling main cancellation token' $PodeContext.Tokens.Cancellation.Cancel() } # stop all current runspaces - Write-Verbose "Closing runspaces" + Write-Verbose 'Closing runspaces' Close-PodeRunspaces -ClosePool # stop the file monitor if it's running - Write-Verbose "Stopping file monitor" + Write-Verbose 'Stopping file monitor' Stop-PodeFileMonitor try { # remove all the cancellation tokens - Write-Verbose "Disposing cancellation tokens" + Write-Verbose 'Disposing cancellation tokens' Close-PodeDisposable -Disposable $PodeContext.Tokens.Cancellation Close-PodeDisposable -Disposable $PodeContext.Tokens.Restart # dispose mutex/semaphores - Write-Verbose "Diposing mutex and semaphores" + Write-Verbose 'Diposing mutex and semaphores' Clear-PodeMutexes Clear-PodeSemaphores } @@ -830,18 +796,17 @@ function Close-PodeServerInternal } # remove all of the pode temp drives - Write-Verbose "Removing internal PSDrives" + Write-Verbose 'Removing internal PSDrives' Remove-PodePSDrives if ($ShowDoneMessage -and ($PodeContext.Server.Types.Length -gt 0) -and !$PodeContext.Server.IsServerless) { - Write-PodeHost " Done" -ForegroundColor Green + Write-PodeHost ' Done' -ForegroundColor Green } } -function New-PodePSDrive -{ +function New-PodePSDrive { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -884,10 +849,9 @@ function New-PodePSDrive return "$($drive.Name):$([System.IO.Path]::DirectorySeparatorChar)" } -function Get-PodePSDrive -{ +function Get-PodePSDrive { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -895,10 +859,9 @@ function Get-PodePSDrive return (Get-PSDrive -Name $Name -PSProvider FileSystem -Scope Global -ErrorAction Ignore) } -function Test-PodePSDrive -{ +function Test-PodePSDrive { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -919,23 +882,20 @@ function Test-PodePSDrive return $true } -function Add-PodePSDrives -{ +function Add-PodePSDrives { foreach ($key in $PodeContext.Server.Drives.Keys) { $null = New-PodePSDrive -Path $PodeContext.Server.Drives[$key] -Name $key } } -function Import-PodeModules -{ +function Import-PodeModules { # import other modules in the session foreach ($path in $PodeContext.Server.Modules.Values) { $null = Import-Module $path -DisableNameChecking -Scope Global -ErrorAction Stop } } -function Add-PodePSInbuiltDrives -{ +function Add-PodePSInbuiltDrives { # create drive for views, if path exists $path = (Join-PodeServerRoot 'views') if (Test-Path $path) { @@ -955,15 +915,13 @@ function Add-PodePSInbuiltDrives } } -function Remove-PodePSDrives -{ +function Remove-PodePSDrives { $null = Get-PSDrive PodeDir* | Remove-PSDrive } -function Join-PodeServerRoot -{ +function Join-PodeServerRoot { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Folder, @@ -986,10 +944,9 @@ function Join-PodeServerRoot return [System.IO.Path]::Combine($Root, $Folder, $FilePath) } -function Remove-PodeEmptyItemsFromArray -{ +function Remove-PodeEmptyItemsFromArray { param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] $Array ) @@ -1000,10 +957,9 @@ function Remove-PodeEmptyItemsFromArray return @(@($Array -ne ([string]::Empty)) -ne $null) } -function Remove-PodeNullKeysFromHashtable -{ +function Remove-PodeNullKeysFromHashtable { param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [hashtable] $Hashtable ) @@ -1041,8 +997,7 @@ function Remove-PodeNullKeysFromHashtable } } -function Get-PodeFileExtension -{ +function Get-PodeFileExtension { param( [Parameter()] [string] @@ -1060,8 +1015,7 @@ function Get-PodeFileExtension return $ext } -function Get-PodeFileName -{ +function Get-PodeFileName { param( [Parameter()] [string] @@ -1078,8 +1032,7 @@ function Get-PodeFileName return [System.IO.Path]::GetFileName($Path) } -function Test-PodeValidNetworkFailure -{ +function Test-PodeValidNetworkFailure { param( [Parameter()] $Exception @@ -1093,18 +1046,17 @@ function Test-PodeValidNetworkFailure ) $match = @(foreach ($msg in $msgs) { - if ($Exception.Message -ilike $msg) { - $msg - } - })[0] + if ($Exception.Message -ilike $msg) { + $msg + } + })[0] return ($null -ne $match) } -function ConvertFrom-PodeHeaderQValue -{ +function ConvertFrom-PodeHeaderQValue { param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [string] $Value ) @@ -1135,8 +1087,7 @@ function ConvertFrom-PodeHeaderQValue return $qs } -function Get-PodeAcceptEncoding -{ +function Get-PodeAcceptEncoding { param( [Parameter()] [string] @@ -1222,8 +1173,7 @@ function Get-PodeAcceptEncoding return $found.Name } -function Get-PodeRanges -{ +function Get-PodeRanges { param( [Parameter()] [string] @@ -1277,8 +1227,7 @@ function Get-PodeRanges return $ranges } -function Get-PodeTransferEncoding -{ +function Get-PodeTransferEncoding { param( [Parameter()] [string] @@ -1333,8 +1282,7 @@ function Get-PodeTransferEncoding return [string]::Empty } -function Get-PodeEncodingFromContentType -{ +function Get-PodeEncodingFromContentType { param( [Parameter()] [string] @@ -1356,10 +1304,9 @@ function Get-PodeEncodingFromContentType return [System.Text.Encoding]::UTF8 } -function New-PodeRequestException -{ +function New-PodeRequestException { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $StatusCode ) @@ -1369,10 +1316,9 @@ function New-PodeRequestException return $err } -function ConvertTo-PodeResponseContent -{ +function ConvertTo-PodeResponseContent { param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] $InputObject, [Parameter()] @@ -1419,8 +1365,8 @@ function ConvertTo-PodeResponseContent { $_ -ilike '*/xml' } { if ($InputObject -isnot [string]) { $temp = @(foreach ($item in $InputObject) { - New-Object psobject -Property $item - }) + New-Object psobject -Property $item + }) return ($temp | ConvertTo-Xml -Depth $Depth -As String -NoTypeInformation) } @@ -1433,8 +1379,8 @@ function ConvertTo-PodeResponseContent { $_ -ilike '*/csv' } { if ($InputObject -isnot [string]) { $temp = @(foreach ($item in $InputObject) { - New-Object psobject -Property $item - }) + New-Object psobject -Property $item + }) if (Test-PodeIsPSCore) { $temp = ($temp | ConvertTo-Csv -Delimiter $Delimiter -IncludeTypeInformation:$false) @@ -1471,8 +1417,7 @@ function ConvertTo-PodeResponseContent return ([string]$InputObject) } -function ConvertFrom-PodeRequestContent -{ +function ConvertFrom-PodeRequestContent { param( [Parameter()] $Request, @@ -1491,7 +1436,7 @@ function ConvertFrom-PodeRequestContent # result object for data/files $Result = @{ - Data = @{} + Data = @{} Files = @{} } @@ -1617,8 +1562,7 @@ function ConvertFrom-PodeRequestContent return $Result } -function Split-PodeContentType -{ +function Split-PodeContentType { param( [Parameter()] [string] @@ -1632,8 +1576,7 @@ function Split-PodeContentType return @($ContentType -isplit ';')[0].Trim() } -function ConvertFrom-PodeNameValueToHashTable -{ +function ConvertFrom-PodeNameValueToHashTable { param( [Parameter()] [System.Collections.Specialized.NameValueCollection] @@ -1657,8 +1600,7 @@ function ConvertFrom-PodeNameValueToHashTable return $ht } -function Get-PodeCount -{ +function Get-PodeCount { param( [Parameter()] $Object @@ -1668,7 +1610,7 @@ function Get-PodeCount return 0 } - if ($Object -is [string]){ + if ($Object -is [string]) { return $Object.Length } @@ -1679,10 +1621,9 @@ function Get-PodeCount return $Object.Count } -function Test-PodePathAccess -{ +function Test-PodePathAccess { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path ) @@ -1697,8 +1638,7 @@ function Test-PodePathAccess return $true } -function Test-PodePath -{ +function Test-PodePath { param( [Parameter()] $Path, @@ -1740,8 +1680,7 @@ function Test-PodePath return $true } -function Test-PodePathIsFile -{ +function Test-PodePathIsFile { param( [Parameter()] [string] @@ -1762,8 +1701,7 @@ function Test-PodePathIsFile return (![string]::IsNullOrWhiteSpace([System.IO.Path]::GetExtension($Path))) } -function Test-PodePathIsWildcard -{ +function Test-PodePathIsWildcard { param( [Parameter()] [string] @@ -1777,10 +1715,9 @@ function Test-PodePathIsWildcard return $Path.Contains('*') } -function Test-PodePathIsDirectory -{ +function Test-PodePathIsDirectory { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Path, @@ -1796,22 +1733,20 @@ function Test-PodePathIsDirectory return ([string]::IsNullOrWhiteSpace([System.IO.Path]::GetExtension($Path))) } -function Convert-PodePathSeparators -{ +function Convert-PodePathSeparators { param( [Parameter()] $Paths ) return @($Paths | ForEach-Object { - if (![string]::IsNullOrWhiteSpace($_)) { - $_ -ireplace '[\\/]', [System.IO.Path]::DirectorySeparatorChar - } - }) + if (![string]::IsNullOrWhiteSpace($_)) { + $_ -ireplace '[\\/]', [System.IO.Path]::DirectorySeparatorChar + } + }) } -function Convert-PodePathPatternToRegex -{ +function Convert-PodePathPatternToRegex { param( [Parameter()] [string] @@ -1842,8 +1777,7 @@ function Convert-PodePathPatternToRegex return "^$($Path)$" } -function Convert-PodePathPatternsToRegex -{ +function Convert-PodePathPatternsToRegex { param( [Parameter()] [string[]] @@ -1858,10 +1792,10 @@ function Convert-PodePathPatternsToRegex # replace certain chars $Paths = @(foreach ($path in $Paths) { - if (![string]::IsNullOrEmpty($path)) { - Convert-PodePathPatternToRegex -Path $path -NotStrict -NotSlashes:$NotSlashes - } - }) + if (![string]::IsNullOrEmpty($path)) { + Convert-PodePathPatternToRegex -Path $path -NotStrict -NotSlashes:$NotSlashes + } + }) # if no paths, return null if (($null -eq $Paths) -or ($Paths.Length -eq 0)) { @@ -1878,8 +1812,7 @@ function Convert-PodePathPatternsToRegex return "^$($joined)$" } -function Get-PodeDefaultSslProtocols -{ +function Get-PodeDefaultSslProtocols { if (Test-PodeIsMacOS) { return (ConvertTo-PodeSslProtocols -Protocols Tls12) } @@ -1887,8 +1820,7 @@ function Get-PodeDefaultSslProtocols return (ConvertTo-PodeSslProtocols -Protocols Ssl3, Tls12) } -function ConvertTo-PodeSslProtocols -{ +function ConvertTo-PodeSslProtocols { param( [Parameter()] [ValidateSet('Ssl2', 'Ssl3', 'Tls', 'Tls11', 'Tls12', 'Tls13')] @@ -1904,8 +1836,7 @@ function ConvertTo-PodeSslProtocols return [System.Security.Authentication.SslProtocols]($protos) } -function Get-PodeModuleDetails -{ +function Get-PodeModuleDetails { # if there's 1 module imported already, use that $importedModule = @(Get-Module -Name Pode) if (($importedModule | Measure-Object).Count -eq 1) { @@ -1919,7 +1850,8 @@ function Get-PodeModuleDetails return (Convert-PodeModuleDetails -Module $usedModule) } } - catch {} + catch { + } # if there were multiple to begin with, use the newest version if (($importedModule | Measure-Object).Count -gt 1) { @@ -1930,31 +1862,29 @@ function Get-PodeModuleDetails return (Convert-PodeModuleDetails -Module @(Get-Module -ListAvailable -Name Pode | Sort-Object -Property Version)[-1]) } -function Convert-PodeModuleDetails -{ +function Convert-PodeModuleDetails { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [psmoduleinfo] $Module ) $details = @{ - Name = $Module.Name - Path = $Module.Path - BasePath = $Module.ModuleBase - DataPath = (Find-PodeModuleFile -Module $Module -CheckVersion) + Name = $Module.Name + Path = $Module.Path + BasePath = $Module.ModuleBase + DataPath = (Find-PodeModuleFile -Module $Module -CheckVersion) InternalPath = $null - InPath = (Test-PodeModuleInPath -Module $Module) + InPath = (Test-PodeModuleInPath -Module $Module) } $details.InternalPath = $details.DataPath -ireplace 'Pode\.(ps[md]1)', 'Pode.Internal.$1' return $details } -function Test-PodeModuleInPath -{ +function Test-PodeModuleInPath { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [psmoduleinfo] $Module ) @@ -1975,10 +1905,9 @@ function Test-PodeModuleInPath return $false } -function Get-PodeModuleDependencies -{ +function Get-PodeModuleDependencies { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [psmoduleinfo] $Module ) @@ -1995,23 +1924,19 @@ function Get-PodeModuleDependencies return ($mods + $module) } -function Get-PodeModuleRootPath -{ +function Get-PodeModuleRootPath { return (Split-Path -Parent -Path $PodeContext.Server.PodeModule.Path) } -function Get-PodeModuleMiscPath -{ +function Get-PodeModuleMiscPath { return [System.IO.Path]::Combine((Get-PodeModuleRootPath), 'Misc') } -function Get-PodeUrl -{ +function Get-PodeUrl { return "$($WebEvent.Endpoint.Protocol)://$($WebEvent.Endpoint.Address)$($WebEvent.Path)" } -function Find-PodeErrorPage -{ +function Find-PodeErrorPage { param( [Parameter()] [int] @@ -2042,10 +1967,10 @@ function Find-PodeErrorPage if (!(Test-PodeIsEmpty $PodeContext.Server.Web.ErrorPages.Routes)) { # find type by pattern $matched = @(foreach ($key in $PodeContext.Server.Web.ErrorPages.Routes.Keys) { - if ($WebEvent.Path -imatch $key) { - $key - } - })[0] + if ($WebEvent.Path -imatch $key) { + $key + } + })[0] # if we have a match, see if a page exists if (!(Test-PodeIsEmpty $matched)) { @@ -2084,8 +2009,7 @@ function Find-PodeErrorPage return $null } -function Get-PodeErrorPage -{ +function Get-PodeErrorPage { param( [Parameter()] [int] @@ -2119,8 +2043,7 @@ function Get-PodeErrorPage return $path } -function Find-PodeCustomErrorPage -{ +function Find-PodeCustomErrorPage { param( [Parameter()] [int] @@ -2155,8 +2078,7 @@ function Find-PodeCustomErrorPage return $null } -function Find-PodeFileForContentType -{ +function Find-PodeFileForContentType { param( [Parameter()] [string] @@ -2194,28 +2116,27 @@ function Find-PodeFileForContentType } $engineFiles = @(foreach ($file in $files) { - if ($file.Name -imatch "\.$($Engine)$") { - $file - } - }) + if ($file.Name -imatch "\.$($Engine)$") { + $file + } + }) $files = @(foreach ($file in $files) { - if ($file.Name -inotmatch "\.$($Engine)$") { - $file - } - }) + if ($file.Name -inotmatch "\.$($Engine)$") { + $file + } + }) # only attempt static files if we still have files after any engine filtering - if ($null -ne $files -and $files.Length -gt 0) - { + if ($null -ne $files -and $files.Length -gt 0) { # get files of the format '.' $file = @(foreach ($f in $files) { - if ($f.Name -imatch "^$($Name)\.(?.*?)$") { - if (($ContentType -ieq (Get-PodeContentType -Extension $Matches['ext']))) { - $f.FullName + if ($f.Name -imatch "^$($Name)\.(?.*?)$") { + if (($ContentType -ieq (Get-PodeContentType -Extension $Matches['ext']))) { + $f.FullName + } } - } - })[0] + })[0] if (![string]::IsNullOrWhiteSpace($file)) { return $file @@ -2223,16 +2144,15 @@ function Find-PodeFileForContentType } # only attempt these formats if we have a files for the view engine - if ($null -ne $engineFiles -and $engineFiles.Length -gt 0) - { + if ($null -ne $engineFiles -and $engineFiles.Length -gt 0) { # get files of the format '..' $file = @(foreach ($f in $engineFiles) { - if ($f.Name -imatch "^$($Name)\.(?.*?)\.$($engine)$") { - if ($ContentType -ieq (Get-PodeContentType -Extension $Matches['ext'])) { - $f.FullName + if ($f.Name -imatch "^$($Name)\.(?.*?)\.$($engine)$") { + if ($ContentType -ieq (Get-PodeContentType -Extension $Matches['ext'])) { + $f.FullName + } } - } - })[0] + })[0] if (![string]::IsNullOrWhiteSpace($file)) { return $file @@ -2240,10 +2160,10 @@ function Find-PodeFileForContentType # get files of the format '.' $file = @(foreach ($f in $engineFiles) { - if ($f.Name -imatch "^$($Name)\.$($engine)$") { - $f.FullName - } - })[0] + if ($f.Name -imatch "^$($Name)\.$($engine)$") { + $f.FullName + } + })[0] if (![string]::IsNullOrWhiteSpace($file)) { return $file @@ -2254,10 +2174,9 @@ function Find-PodeFileForContentType return $null } -function Get-PodeRelativePath -{ +function Get-PodeRelativePath { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -2298,10 +2217,9 @@ function Get-PodeRelativePath return $Path } -function Get-PodeWildcardFiles -{ +function Get-PodeWildcardFiles { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -2328,8 +2246,7 @@ function Get-PodeWildcardFiles return $null } -function Test-PodeIsServerless -{ +function Test-PodeIsServerless { param( [Parameter()] [string] @@ -2348,8 +2265,7 @@ function Test-PodeIsServerless } } -function Get-PodeEndpointUrl -{ +function Get-PodeEndpointUrl { param( [Parameter()] $Endpoint @@ -2371,8 +2287,7 @@ function Get-PodeEndpointUrl return $url } -function Get-PodeDefaultPort -{ +function Get-PodeDefaultPort { param( [Parameter()] [ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Tcps', 'Ws', 'Wss')] @@ -2391,15 +2306,15 @@ function Get-PodeDefaultPort # are we after the real default ports? if ($Real) { return (@{ - Http = @{ Implicit = 80 } - Https = @{ Implicit = 443 } - Smtp = @{ Implicit = 25 } - Smtps = @{ Implicit = 465; Explicit = 587 } - Tcp = @{ Implicit = 9001 } - Tcps = @{ Implicit = 9002; Explicit = 9003 } - Ws = @{ Implicit = 80 } - Wss = @{ Implicit = 443 } - })[$Protocol.ToLowerInvariant()][$TlsMode.ToLowerInvariant()] + Http = @{ Implicit = 80 } + Https = @{ Implicit = 443 } + Smtp = @{ Implicit = 25 } + Smtps = @{ Implicit = 465; Explicit = 587 } + Tcp = @{ Implicit = 9001 } + Tcps = @{ Implicit = 9002; Explicit = 9003 } + Ws = @{ Implicit = 80 } + Wss = @{ Implicit = 443 } + })[$Protocol.ToLowerInvariant()][$TlsMode.ToLowerInvariant()] } # if we running as iis, return the ASPNET port @@ -2414,19 +2329,18 @@ function Get-PodeDefaultPort # otherwise, get the port for the protocol return (@{ - Http = @{ Implicit = 8080 } - Https = @{ Implicit = 8443 } - Smtp = @{ Implicit = 25 } - Smtps = @{ Implicit = 465; Explicit = 587 } - Tcp = @{ Implicit = 9001 } - Tcps = @{ Implicit = 9002; Explicit = 9003 } - Ws = @{ Implicit = 9080 } - Wss = @{ Implicit = 9443 } - })[$Protocol.ToLowerInvariant()][$TlsMode.ToLowerInvariant()] + Http = @{ Implicit = 8080 } + Https = @{ Implicit = 8443 } + Smtp = @{ Implicit = 25 } + Smtps = @{ Implicit = 465; Explicit = 587 } + Tcp = @{ Implicit = 9001 } + Tcps = @{ Implicit = 9002; Explicit = 9003 } + Ws = @{ Implicit = 9080 } + Wss = @{ Implicit = 9443 } + })[$Protocol.ToLowerInvariant()][$TlsMode.ToLowerInvariant()] } -function Set-PodeServerHeader -{ +function Set-PodeServerHeader { param( [Parameter()] [string] @@ -2444,10 +2358,9 @@ function Set-PodeServerHeader Set-PodeHeader -Name 'Server' -Value $name } -function Get-PodeHandler -{ +function Get-PodeHandler { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Service', 'Smtp')] [string] $Type, @@ -2464,10 +2377,9 @@ function Get-PodeHandler return $PodeContext.Server.Handlers[$Type][$Name] } -function Convert-PodeFileToScriptBlock -{ +function Convert-PodeFileToScriptBlock { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $FilePath ) @@ -2488,8 +2400,7 @@ function Convert-PodeFileToScriptBlock return ([scriptblock](Use-PodeScript -Path $FilePath)) } -function Convert-PodeQueryStringToHashTable -{ +function Convert-PodeQueryStringToHashTable { param( [Parameter()] [string] @@ -2513,8 +2424,7 @@ function Convert-PodeQueryStringToHashTable return (ConvertFrom-PodeNameValueToHashTable -Collection $tmpQuery) } -function Convert-PodeScopedVariables -{ +function Convert-PodeScopedVariables { param( [Parameter()] [scriptblock] @@ -2566,8 +2476,7 @@ function Convert-PodeScopedVariables } } -function Invoke-PodeStateScriptConversion -{ +function Invoke-PodeStateScriptConversion { param( [Parameter()] [scriptblock] @@ -2600,8 +2509,7 @@ function Invoke-PodeStateScriptConversion return $ScriptBlock } -function Invoke-PodeSecretScriptConversion -{ +function Invoke-PodeSecretScriptConversion { param( [Parameter()] [scriptblock] @@ -2634,8 +2542,7 @@ function Invoke-PodeSecretScriptConversion return $ScriptBlock } -function Invoke-PodeSessionScriptConversion -{ +function Invoke-PodeSessionScriptConversion { param( [Parameter()] [scriptblock] @@ -2663,14 +2570,13 @@ function Invoke-PodeSessionScriptConversion return $ScriptBlock } -function Invoke-PodeUsingScriptConversion -{ +function Invoke-PodeUsingScriptConversion { param( [Parameter()] [scriptblock] $ScriptBlock, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $PSSession ) @@ -2709,10 +2615,9 @@ function Invoke-PodeUsingScriptConversion return @($newScriptBlock, $usingVars) } -function Get-PodeScriptUsingVariables -{ +function Get-PodeScriptUsingVariables { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) @@ -2720,13 +2625,12 @@ function Get-PodeScriptUsingVariables return $ScriptBlock.Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.UsingExpressionAst] }, $true) } -function ConvertTo-PodeUsingVariables -{ +function ConvertTo-PodeUsingVariables { param( [Parameter()] $UsingVariables, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $PSSession ) @@ -2755,11 +2659,11 @@ function ConvertTo-PodeUsingVariables # add to mapped $mapped[$varName] = @{ - OldName = $usingVar.SubExpression.Extent.Text - NewName = "__using_$($varName)" + OldName = $usingVar.SubExpression.Extent.Text + NewName = "__using_$($varName)" NewNameWithDollar = "`$__using_$($varName)" - SubExpressions = @() - Value = $value.Value + SubExpressions = @() + Value = $value.Value } } @@ -2770,10 +2674,9 @@ function ConvertTo-PodeUsingVariables return @($mapped.Values) } -function ConvertTo-PodeUsingScript -{ +function ConvertTo-PodeUsingScript { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, @@ -2818,10 +2721,9 @@ function ConvertTo-PodeUsingScript return $convertedScriptBlock } -function Get-PodeDotSourcedFiles -{ +function Get-PodeDotSourcedFiles { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Management.Automation.Language.Ast] $Ast, @@ -2841,7 +2743,7 @@ function Get-PodeDotSourcedFiles ($args[0] -is [System.Management.Automation.Language.CommandAst]) -and ($args[0].InvocationOperator -iin $cmdTypes) -and ($args[0].CommandElements.StaticType.Name -ieq 'string') - }, $false)).CommandElements.Value + }, $false)).CommandElements.Value $fileOrder = @() @@ -2867,10 +2769,9 @@ function Get-PodeDotSourcedFiles return $fileOrder } -function Get-PodeAstFromFile -{ +function Get-PodeAstFromFile { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $FilePath ) @@ -2882,10 +2783,9 @@ function Get-PodeAstFromFile return [System.Management.Automation.Language.Parser]::ParseFile($FilePath, [ref]$null, [ref]$null) } -function Get-PodeFunctionsFromFile -{ +function Get-PodeFunctionsFromFile { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $FilePath ) @@ -2894,10 +2794,9 @@ function Get-PodeFunctionsFromFile return @(Get-PodeFunctionsFromAst -Ast $ast) } -function Get-PodeFunctionsFromAst -{ +function Get-PodeFunctionsFromAst { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Management.Automation.Language.Ast] $Ast ) @@ -2905,34 +2804,33 @@ function Get-PodeFunctionsFromAst $funcs = @(($Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $false))) return @(foreach ($func in $funcs) { - # skip null - if ($null -eq $func) { - continue - } + # skip null + if ($null -eq $func) { + continue + } - # skip pode funcs - if ($func.Name -ilike '*-Pode*') { - continue - } + # skip pode funcs + if ($func.Name -ilike '*-Pode*') { + continue + } - # definition - $def = "$($func.Body)".Trim('{}').Trim() - if (($null -ne $func.Parameters) -and ($func.Parameters.Count -gt 0)) { - $def = "param($($func.Parameters.Name -join ','))`n$($def)" - } + # definition + $def = "$($func.Body)".Trim('{}').Trim() + if (($null -ne $func.Parameters) -and ($func.Parameters.Count -gt 0)) { + $def = "param($($func.Parameters.Name -join ','))`n$($def)" + } - # the found func - @{ - Name = $func.Name - Definition = $def - } - }) + # the found func + @{ + Name = $func.Name + Definition = $def + } + }) } -function Get-PodeFunctionsFromScriptBlock -{ +function Get-PodeFunctionsFromScriptBlock { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) @@ -2946,8 +2844,7 @@ function Get-PodeFunctionsFromScriptBlock $callstack = ($callstack | Select-Object -Skip 4) $bindingFlags = [System.Reflection.BindingFlags]'NonPublic, Instance, Static' - foreach ($call in $callstack) - { + foreach ($call in $callstack) { $_funcContext = $call.GetType().GetProperty('FunctionContext', $bindingFlags).GetValue($call, $null) $_scriptBlock = $_funcContext.GetType().GetField('_scriptBlock', $bindingFlags).GetValue($_funcContext) $foundFuncs += @(Get-PodeFunctionsFromAst -Ast $_scriptBlock.Ast) @@ -2961,10 +2858,9 @@ function Get-PodeFunctionsFromScriptBlock return $foundFuncs } -function Read-PodeWebExceptionDetails -{ +function Read-PodeWebExceptionDetails { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Management.Automation.ErrorRecord] $ErrorRecord ) @@ -2992,21 +2888,20 @@ function Read-PodeWebExceptionDetails return @{ Status = @{ - Code = $code + Code = $code Description = $desc } - Body = $body + Body = $body } } -function Use-PodeFolder -{ +function Use-PodeFolder { param( [Parameter()] [string] $Path, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $DefaultPath ) @@ -3030,14 +2925,13 @@ function Use-PodeFolder } } -function Find-PodeModuleFile -{ +function Find-PodeModuleFile { param( - [Parameter(Mandatory=$true, ParameterSetName='Name')] + [Parameter(Mandatory = $true, ParameterSetName = 'Name')] [string] $Name, - [Parameter(Mandatory=$true, ParameterSetName='Module')] + [Parameter(Mandatory = $true, ParameterSetName = 'Module')] [psmoduleinfo] $Module, @@ -3086,8 +2980,7 @@ function Find-PodeModuleFile return $path } -function Get-PodeScriptblockArguments -{ +function Get-PodeScriptblockArguments { param( [Parameter()] [object[]] @@ -3108,16 +3001,15 @@ function Get-PodeScriptblockArguments $_vars = @() foreach ($_var in $UsingVariables) { - $_vars += ,$_var.Value + $_vars += , $_var.Value } return ($_vars + $ArgumentList) } -function Clear-PodeHashtableInnerKeys -{ +function Clear-PodeHashtableInnerKeys { param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [hashtable] $InputObject ) @@ -3131,8 +3023,7 @@ function Clear-PodeHashtableInnerKeys } } -function Set-PodeCronInterval -{ +function Set-PodeCronInterval { param( [Parameter()] [hashtable] @@ -3167,10 +3058,9 @@ function Set-PodeCronInterval return ($Value.Length -eq 1) } -function Test-PodeModuleInstalled -{ +function Test-PodeModuleInstalled { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -3178,15 +3068,13 @@ function Test-PodeModuleInstalled return ($null -ne (Get-Module -Name $Name -ListAvailable -ErrorAction Ignore -Verbose:$false)) } -function Get-PodePlaceholderRegex -{ +function Get-PodePlaceholderRegex { return '\:(?[\w]+)' } -function Resolve-PodePlaceholders -{ +function Resolve-PodePlaceholders { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -3222,10 +3110,9 @@ function Resolve-PodePlaceholders return (Convert-PodePlaceholders -Path $Path -Pattern $Pattern -Prepend $Prepend -Append $Append) } -function Convert-PodePlaceholders -{ +function Convert-PodePlaceholders { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -3253,10 +3140,9 @@ function Convert-PodePlaceholders return $Path } -function Test-PodePlaceholders -{ +function Test-PodePlaceholders { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index ef924b43a..24f8f393f 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -1,5 +1,4 @@ -function Get-PodeLoggingTerminalMethod -{ +function Get-PodeLoggingTerminalMethod { return { param($item, $options) @@ -18,8 +17,7 @@ function Get-PodeLoggingTerminalMethod } } -function Get-PodeLoggingFileMethod -{ +function Get-PodeLoggingFileMethod { return { param($item, $options) @@ -77,8 +75,7 @@ function Get-PodeLoggingFileMethod } } -function Get-PodeLoggingEventViewerMethod -{ +function Get-PodeLoggingEventViewerMethod { return { param($item, $options, $rawItem) @@ -106,13 +103,13 @@ function Get-PodeLoggingEventViewerMethod $message = ($item[$i] | Protect-PodeLogItem) $entryLog.WriteEvent($entryInstance, $message) } - catch {} + catch { + } } } } -function ConvertTo-PodeEventViewerLevel -{ +function ConvertTo-PodeEventViewerLevel { param( [Parameter()] [string] @@ -134,17 +131,15 @@ function ConvertTo-PodeEventViewerLevel return [System.Diagnostics.EventLogEntryType]::Information } -function Get-PodeLoggingInbuiltType -{ - param ( - [Parameter(Mandatory=$true)] +function Get-PodeLoggingInbuiltType { + param( + [Parameter(Mandatory = $true)] [ValidateSet('Errors', 'Requests')] [string] $Type ) - switch ($Type.ToLowerInvariant()) - { + switch ($Type.ToLowerInvariant()) { 'requests' { $script = { param($item, $options) @@ -204,20 +199,17 @@ function Get-PodeLoggingInbuiltType return $script } -function Get-PodeRequestLoggingName -{ +function Get-PodeRequestLoggingName { return '__pode_log_requests__' } -function Get-PodeErrorLoggingName -{ +function Get-PodeErrorLoggingName { return '__pode_log_errors__' } -function Get-PodeLogger -{ - param ( - [Parameter(Mandatory=$true)] +function Get-PodeLogger { + param( + [Parameter(Mandatory = $true)] [string] $Name ) @@ -225,10 +217,9 @@ function Get-PodeLogger return $PodeContext.Server.Logging.Types[$Name] } -function Test-PodeLoggerEnabled -{ - param ( - [Parameter(Mandatory=$true)] +function Test-PodeLoggerEnabled { + param( + [Parameter(Mandatory = $true)] [string] $Name ) @@ -236,28 +227,24 @@ function Test-PodeLoggerEnabled return ($PodeContext.Server.Logging.Enabled -and $PodeContext.Server.Logging.Types.ContainsKey($Name)) } -function Get-PodeErrorLoggingLevels -{ +function Get-PodeErrorLoggingLevels { return (Get-PodeLogger -Name (Get-PodeErrorLoggingName)).Arguments.Levels } -function Test-PodeErrorLoggingEnabled -{ +function Test-PodeErrorLoggingEnabled { return (Test-PodeLoggerEnabled -Name (Get-PodeErrorLoggingName)) } -function Test-PodeRequestLoggingEnabled -{ +function Test-PodeRequestLoggingEnabled { return (Test-PodeLoggerEnabled -Name (Get-PodeRequestLoggingName)) } -function Write-PodeRequestLog -{ +function Write-PodeRequestLog { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Request, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Response, [Parameter()] @@ -273,21 +260,21 @@ function Write-PodeRequestLog # build a request object $item = @{ - Host = $Request.RemoteEndPoint.Address.IPAddressToString + Host = $Request.RemoteEndPoint.Address.IPAddressToString RfcUserIdentity = '-' - User = '-' - Date = [DateTime]::Now.ToString('dd/MMM/yyyy:HH:mm:ss zzz') - Request = @{ - Method = $Request.HttpMethod.ToUpperInvariant() + User = '-' + Date = [DateTime]::Now.ToString('dd/MMM/yyyy:HH:mm:ss zzz') + Request = @{ + Method = $Request.HttpMethod.ToUpperInvariant() Resource = $Path Protocol = "HTTP/$($Request.ProtocolVersion)" Referrer = $Request.UrlReferrer - Agent = $Request.UserAgent + Agent = $Request.UserAgent } - Response = @{ - StatusCode = $Response.StatusCode + Response = @{ + StatusCode = $Response.StatusCode StatusDescription = $Response.StatusDescription - Size = '-' + Size = '-' } } @@ -312,15 +299,14 @@ function Write-PodeRequestLog # add the item to be processed $null = $PodeContext.LogsToProcess.Add(@{ - Name = $name - Item = $item - }) + Name = $name + Item = $item + }) } -function Add-PodeRequestLogEndware -{ - param ( - [Parameter(Mandatory=$true)] +function Add-PodeRequestLogEndware { + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] $WebEvent ) @@ -339,8 +325,7 @@ function Add-PodeRequestLogEndware } } -function Test-PodeLoggersExist -{ +function Test-PodeLoggersExist { if (($null -eq $PodeContext.Server.Logging) -or ($null -eq $PodeContext.Server.Logging.Types)) { return $false } @@ -348,16 +333,14 @@ function Test-PodeLoggersExist return (($PodeContext.Server.Logging.Types.Count -gt 0) -or ($PodeContext.Server.Logging.Enabled)) } -function Start-PodeLoggingRunspace -{ +function Start-PodeLoggingRunspace { # skip if there are no loggers configured, or logging is disabled if (!(Test-PodeLoggersExist)) { return } $script = { - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { # if there are no logs to process, just sleep for a few seconds - but after checking the batch if ($PodeContext.LogsToProcess.Count -eq 0) { Test-PodeLoggerBatches @@ -367,10 +350,10 @@ function Start-PodeLoggingRunspace # safely pop off the first log from the array $log = (Lock-PodeObject -Return -Object $PodeContext.LogsToProcess -ScriptBlock { - $log = $PodeContext.LogsToProcess[0] - $null = $PodeContext.LogsToProcess.RemoveAt(0) - return $log - }) + $log = $PodeContext.LogsToProcess[0] + $null = $PodeContext.LogsToProcess.RemoveAt(0) + return $log + }) # run the log item through the appropriate method $logger = Get-PodeLogger -Name $log.Name @@ -413,7 +396,7 @@ function Start-PodeLoggingRunspace # send the writable log item off to the log writer if ($null -ne $result) { - $_args = @(,$result) + @($logger.Method.Arguments) + @(,$rawItems) + $_args = @(, $result) + @($logger.Method.Arguments) + @(, $rawItems) $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $logger.Method.UsingVariables) Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -Splat } @@ -426,24 +409,20 @@ function Start-PodeLoggingRunspace Add-PodeRunspace -Type Main -ScriptBlock $script } -function Test-PodeLoggerBatches -{ +function Test-PodeLoggerBatches { $now = [datetime]::Now # check each logger, and see if its batch needs to be written - foreach ($logger in $PodeContext.Server.Logging.Types.Values) - { + foreach ($logger in $PodeContext.Server.Logging.Types.Values) { $batch = $logger.Method.Batch - if (($batch.Size -gt 1) -and ($batch.Items.Length -gt 0) -and - ($batch.Timeout -gt 0) -and ($null -ne $batch.LastUpdate) -and ($batch.LastUpdate.AddSeconds($batch.Timeout) -le $now)) - { + if (($batch.Size -gt 1) -and ($batch.Items.Length -gt 0) -and ($batch.Timeout -gt 0) -and ($null -ne $batch.LastUpdate) -and ($batch.LastUpdate.AddSeconds($batch.Timeout) -le $now)) { $result = $batch.Items $rawItems = $batch.RawItems $batch.Items = @() $batch.RawItems = @() - $_args = @(,$result) + @($logger.Method.Arguments) + @(,$rawItems) + $_args = @(, $result) + @($logger.Method.Arguments) + @(, $rawItems) $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $logger.Method.UsingVariables) Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -Splat } diff --git a/src/Private/Mappers.ps1 b/src/Private/Mappers.ps1 index 976312a5a..a16b6c05d 100644 --- a/src/Private/Mappers.ps1 +++ b/src/Private/Mappers.ps1 @@ -1,6 +1,5 @@ -function Get-PodeContentType -{ - param ( +function Get-PodeContentType { + param( [Parameter()] [string] $Extension, @@ -18,8 +17,7 @@ function Get-PodeContentType } # Sourced from https://github.com/samuelneff/MimeTypeMap - switch ($Extension.ToLowerInvariant()) - { + switch ($Extension.ToLowerInvariant()) { '.323' { return 'text/h323' } '.3g2' { return 'video/3gpp2' } '.3gp' { return 'video/3gpp' } @@ -644,16 +642,14 @@ function Get-PodeContentType } } -function Get-PodeStatusDescription -{ - param ( +function Get-PodeStatusDescription { + param( [Parameter()] [int] $StatusCode ) - switch ($StatusCode) - { + switch ($StatusCode) { 100 { return 'Continue' } 101 { return 'Switching Protocols' } 102 { return 'Processing' } diff --git a/src/Private/Metrics.ps1 b/src/Private/Metrics.ps1 index 975593432..d510ddd29 100644 --- a/src/Private/Metrics.ps1 +++ b/src/Private/Metrics.ps1 @@ -1,5 +1,4 @@ -function Update-PodeServerRequestMetrics -{ +function Update-PodeServerRequestMetrics { param( [Parameter()] [hashtable] @@ -33,8 +32,7 @@ function Update-PodeServerRequestMetrics } } -function Update-PodeServerSignalMetrics -{ +function Update-PodeServerSignalMetrics { param( [Parameter()] [hashtable] diff --git a/src/Private/Middleware.ps1 b/src/Private/Middleware.ps1 index 59b41ccb0..a0cb93b6b 100644 --- a/src/Private/Middleware.ps1 +++ b/src/Private/Middleware.ps1 @@ -1,8 +1,7 @@ using namespace System.Security.Cryptography -function Invoke-PodeMiddleware -{ - param ( +function Invoke-PodeMiddleware { + param( [Parameter()] $Middleware, @@ -17,25 +16,23 @@ function Invoke-PodeMiddleware } # filter the middleware down by route (retaining order) - if (![string]::IsNullOrWhiteSpace($Route)) - { + if (![string]::IsNullOrWhiteSpace($Route)) { $Middleware = @(foreach ($mware in $Middleware) { - if ($null -eq $mware) { - continue - } + if ($null -eq $mware) { + continue + } - if ([string]::IsNullOrWhiteSpace($mware.Route) -or ($mware.Route -ieq '/') -or ($mware.Route -ieq $Route) -or ($Route -imatch "^$($mware.Route)$")) { - $mware - } - }) + if ([string]::IsNullOrWhiteSpace($mware.Route) -or ($mware.Route -ieq '/') -or ($mware.Route -ieq $Route) -or ($Route -imatch "^$($mware.Route)$")) { + $mware + } + }) } # continue or halt? $continue = $true # loop through each of the middleware, invoking the next if it returns true - foreach ($midware in @($Middleware)) - { + foreach ($midware in @($Middleware)) { if (($null -eq $midware) -or ($null -eq $midware.Logic)) { continue } @@ -61,11 +58,10 @@ function Invoke-PodeMiddleware return $continue } -function New-PodeMiddlewareInternal -{ +function New-PodeMiddlewareInternal { [OutputType([hashtable])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, @@ -77,13 +73,13 @@ function New-PodeMiddlewareInternal [object[]] $ArgumentList, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $PSSession ) if (Test-PodeIsEmpty $ScriptBlock) { - throw "[Middleware]: No ScriptBlock supplied" + throw '[Middleware]: No ScriptBlock supplied' } # if route is empty, set it to root @@ -94,9 +90,9 @@ function New-PodeMiddlewareInternal # create the middleware hashtable from a scriptblock $HashTable = @{ - Route = $Route - Logic = $ScriptBlock - Arguments = $ArgumentList + Route = $Route + Logic = $ScriptBlock + Arguments = $ArgumentList UsingVariables = $usingVars } @@ -104,15 +100,14 @@ function New-PodeMiddlewareInternal return $HashTable } -function Get-PodeInbuiltMiddleware -{ - param ( - [Parameter(Mandatory=$true)] +function Get-PodeInbuiltMiddleware { + param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] [scriptblock] $ScriptBlock @@ -129,85 +124,81 @@ function Get-PodeInbuiltMiddleware # return the script return @{ - Name = $Name + Name = $Name Logic = $ScriptBlock } } -function Get-PodeAccessMiddleware -{ +function Get-PodeAccessMiddleware { return (Get-PodeInbuiltMiddleware -Name '__pode_mw_access__' -ScriptBlock { - # are there any rules? - if (($PodeContext.Server.Access.Allow.Count -eq 0) -and ($PodeContext.Server.Access.Deny.Count -eq 0)) { - return $true - } + # are there any rules? + if (($PodeContext.Server.Access.Allow.Count -eq 0) -and ($PodeContext.Server.Access.Deny.Count -eq 0)) { + return $true + } - # ensure the request IP address is allowed - if (!(Test-PodeIPAccess -IP $WebEvent.Request.RemoteEndPoint.Address)) { - Set-PodeResponseStatus -Code 403 - return $false - } + # ensure the request IP address is allowed + if (!(Test-PodeIPAccess -IP $WebEvent.Request.RemoteEndPoint.Address)) { + Set-PodeResponseStatus -Code 403 + return $false + } - # request is allowed - return $true - }) + # request is allowed + return $true + }) } -function Get-PodeLimitMiddleware -{ +function Get-PodeLimitMiddleware { return (Get-PodeInbuiltMiddleware -Name '__pode_mw_rate_limit__' -ScriptBlock { - # are there any rules? - if ($PodeContext.Server.Limits.Rules.Count -eq 0) { - return $true - } + # are there any rules? + if ($PodeContext.Server.Limits.Rules.Count -eq 0) { + return $true + } - # check the request IP address has not hit a rate limit - if (!(Test-PodeIPLimit -IP $WebEvent.Request.RemoteEndPoint.Address)) { - Set-PodeResponseStatus -Code 429 - return $false - } + # check the request IP address has not hit a rate limit + if (!(Test-PodeIPLimit -IP $WebEvent.Request.RemoteEndPoint.Address)) { + Set-PodeResponseStatus -Code 429 + return $false + } - # check the route - if (!(Test-PodeRouteLimit -Path $WebEvent.Path)) { - Set-PodeResponseStatus -Code 429 - return $false - } + # check the route + if (!(Test-PodeRouteLimit -Path $WebEvent.Path)) { + Set-PodeResponseStatus -Code 429 + return $false + } - # check the endpoint - if (!(Test-PodeEndpointLimit -EndpointName $WebEvent.Endpoint.Name)) { - Set-PodeResponseStatus -Code 429 - return $false - } + # check the endpoint + if (!(Test-PodeEndpointLimit -EndpointName $WebEvent.Endpoint.Name)) { + Set-PodeResponseStatus -Code 429 + return $false + } - # request is allowed - return $true - }) + # request is allowed + return $true + }) } -function Get-PodePublicMiddleware -{ +function Get-PodePublicMiddleware { return (Get-PodeInbuiltMiddleware -Name '__pode_mw_static_content__' -ScriptBlock { - # only find public static content here - $path = Find-PodePublicRoute -Path $WebEvent.Path - if ([string]::IsNullOrWhiteSpace($path)) { - return $true - } + # only find public static content here + $path = Find-PodePublicRoute -Path $WebEvent.Path + if ([string]::IsNullOrWhiteSpace($path)) { + return $true + } - # check current state of caching - $cachable = Test-PodeRouteValidForCaching -Path $WebEvent.Path + # check current state of caching + $cachable = Test-PodeRouteValidForCaching -Path $WebEvent.Path - # write the file to the response - Write-PodeFileResponse -Path $path -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge -Cache:$cachable + # write the file to the response + Write-PodeFileResponse -Path $path -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge -Cache:$cachable - # public static content found, stop - return $false - }) + # public static content found, stop + return $false + }) } -function Get-PodeRouteValidateMiddleware -{ +function Get-PodeRouteValidateMiddleware { return @{ - Name = '__pode_mw_route_validation__' + Name = '__pode_mw_route_validation__' Logic = { # check if the path is static route first, then check the main routes $route = Find-PodeStaticRoute -Path $WebEvent.Path -EndpointName $WebEvent.Endpoint.Name @@ -220,12 +211,12 @@ function Get-PodeRouteValidateMiddleware # check if a route exists for another method $methods = @('CONNECT', 'DELETE', 'GET', 'HEAD', 'MERGE', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE') $diff_route = @(foreach ($method in $methods) { - $r = Find-PodeRoute -Method $method -Path $WebEvent.Path -EndpointName $WebEvent.Endpoint.Name - if ($null -ne $r) { - $r - break - } - })[0] + $r = Find-PodeRoute -Method $method -Path $WebEvent.Path -EndpointName $WebEvent.Endpoint.Name + if ($null -ne $r) { + $r + break + } + })[0] if ($null -ne $diff_route) { Set-PodeResponseStatus -Code 405 @@ -271,95 +262,90 @@ function Get-PodeRouteValidateMiddleware } } -function Get-PodeBodyMiddleware -{ +function Get-PodeBodyMiddleware { return (Get-PodeInbuiltMiddleware -Name '__pode_mw_body_parsing__' -ScriptBlock { - try { - # attempt to parse that data - $result = ConvertFrom-PodeRequestContent -Request $WebEvent.Request -ContentType $WebEvent.ContentType -TransferEncoding $WebEvent.TransferEncoding + try { + # attempt to parse that data + $result = ConvertFrom-PodeRequestContent -Request $WebEvent.Request -ContentType $WebEvent.ContentType -TransferEncoding $WebEvent.TransferEncoding - # set session data - $WebEvent.Data = $result.Data - $WebEvent.Files = $result.Files + # set session data + $WebEvent.Data = $result.Data + $WebEvent.Files = $result.Files - # payload parsed - return $true - } - catch { - Set-PodeResponseStatus -Code 400 -Exception $_ - return $false - } - }) + # payload parsed + return $true + } + catch { + Set-PodeResponseStatus -Code 400 -Exception $_ + return $false + } + }) } -function Get-PodeQueryMiddleware -{ +function Get-PodeQueryMiddleware { return (Get-PodeInbuiltMiddleware -Name '__pode_mw_query_parsing__' -ScriptBlock { - try { - # set the query string from the request - $WebEvent.Query = (ConvertFrom-PodeNameValueToHashTable -Collection $WebEvent.Request.QueryString) - return $true - } - catch { - Set-PodeResponseStatus -Code 400 -Exception $_ - return $false - } - }) + try { + # set the query string from the request + $WebEvent.Query = (ConvertFrom-PodeNameValueToHashTable -Collection $WebEvent.Request.QueryString) + return $true + } + catch { + Set-PodeResponseStatus -Code 400 -Exception $_ + return $false + } + }) } -function Get-PodeCookieMiddleware -{ +function Get-PodeCookieMiddleware { return (Get-PodeInbuiltMiddleware -Name '__pode_mw_cookie_parsing__' -ScriptBlock { - # if cookies already set, return - if ($WebEvent.Cookies.Count -gt 0) { - return $true - } + # if cookies already set, return + if ($WebEvent.Cookies.Count -gt 0) { + return $true + } - # if the request's header has no cookies, return - $h_cookie = (Get-PodeHeader -Name 'Cookie') - if ([string]::IsNullOrWhiteSpace($h_cookie)) { - return $true - } + # if the request's header has no cookies, return + $h_cookie = (Get-PodeHeader -Name 'Cookie') + if ([string]::IsNullOrWhiteSpace($h_cookie)) { + return $true + } - # parse the cookies from the header - $cookies = @($h_cookie -split '; ') - $WebEvent.Cookies = @{} + # parse the cookies from the header + $cookies = @($h_cookie -split '; ') + $WebEvent.Cookies = @{} - foreach ($cookie in $cookies) { - $atoms = $cookie.Split('=', 2) + foreach ($cookie in $cookies) { + $atoms = $cookie.Split('=', 2) - $value = [string]::Empty - if ($atoms.Length -gt 1) { - foreach ($atom in $atoms[1..($atoms.Length - 1)]) { - $value += $atom + $value = [string]::Empty + if ($atoms.Length -gt 1) { + foreach ($atom in $atoms[1..($atoms.Length - 1)]) { + $value += $atom + } } - } - $WebEvent.Cookies[$atoms[0]] = [System.Net.Cookie]::new($atoms[0], $value) - } + $WebEvent.Cookies[$atoms[0]] = [System.Net.Cookie]::new($atoms[0], $value) + } - return $true - }) + return $true + }) } -function Get-PodeSecurityMiddleware -{ +function Get-PodeSecurityMiddleware { return (Get-PodeInbuiltMiddleware -Name '__pode_mw_security__' -ScriptBlock { - # are there any security headers setup? - if ($PodeContext.Server.Security.Headers.Count -eq 0) { - return $true - } + # are there any security headers setup? + if ($PodeContext.Server.Security.Headers.Count -eq 0) { + return $true + } - # add security headers - Set-PodeHeaderBulk -Value $PodeContext.Server.Security.Headers + # add security headers + Set-PodeHeaderBulk -Value $PodeContext.Server.Security.Headers - # continue to next middleware/route - return $true - }) + # continue to next middleware/route + return $true + }) } -function Initialize-PodeIISMiddleware -{ +function Initialize-PodeIISMiddleware { # do nothing if not iis if (!$PodeContext.Server.IsIIS) { return @@ -367,7 +353,7 @@ function Initialize-PodeIISMiddleware # fail if no iis token - because there should be! if ([string]::IsNullOrWhiteSpace($PodeContext.Server.IIS.Token)) { - throw "IIS ASPNETCORE_TOKEN is missing" + throw 'IIS ASPNETCORE_TOKEN is missing' } # add middleware to check every request has the token diff --git a/src/Private/NameGenerator.ps1 b/src/Private/NameGenerator.ps1 index 20cf697cc..352c24f40 100644 --- a/src/Private/NameGenerator.ps1 +++ b/src/Private/NameGenerator.ps1 @@ -1,68 +1,67 @@ -function Get-PodeRandomName -{ +function Get-PodeRandomName { $adjs = @( - "admiring", - "agitated", - "blissful", - "dazzling", - "ecstatic", - "eloquent", - "friendly", - "gracious", - "hardcore", - "laughing", - "peaceful", - "pedantic", - "reverent", - "romantic", - "trusting", - "vigilant", - "vigorous", - "wizardly", - "youthful" + 'admiring', + 'agitated', + 'blissful', + 'dazzling', + 'ecstatic', + 'eloquent', + 'friendly', + 'gracious', + 'hardcore', + 'laughing', + 'peaceful', + 'pedantic', + 'reverent', + 'romantic', + 'trusting', + 'vigilant', + 'vigorous', + 'wizardly', + 'youthful' ) $names = @( - "almeida", - "babbage", - "bardeen", - "shannon", - "davinci", - "feynman", - "galileo", - "goodall", - "hawking", - "hermann", - "hodgkin", - "hypatia", - "jackson", - "johnson", - "kapitsa", - "keldysh", - "khorana", - "lalande", - "lamport", - "leavitt", - "lumiere", - "mcnulty", - "meitner", - "mestorf", - "murdock", - "neumann", - "noether", - "pasteur", - "perlman", - "poitras", - "ptolemy", - "ritchie", - "shirley", - "swanson", - "swirles", - "vaughan", - "volhard", - "villani", - "wescoff", - "wozniak" + 'almeida', + 'babbage', + 'bardeen', + 'shannon', + 'davinci', + 'feynman', + 'galileo', + 'goodall', + 'hawking', + 'hermann', + 'hodgkin', + 'hypatia', + 'jackson', + 'johnson', + 'kapitsa', + 'keldysh', + 'khorana', + 'lalande', + 'lamport', + 'leavitt', + 'lumiere', + 'mcnulty', + 'meitner', + 'mestorf', + 'murdock', + 'neumann', + 'noether', + 'pasteur', + 'perlman', + 'poitras', + 'ptolemy', + 'ritchie', + 'shirley', + 'swanson', + 'swirles', + 'vaughan', + 'volhard', + 'villani', + 'wescoff', + 'wozniak' ) $adjsRand = (Get-Random -Minimum 0 -Maximum $adjs.Length) diff --git a/src/Private/OpenApi.ps1 b/src/Private/OpenApi.ps1 index 719921319..9c85b4d88 100644 --- a/src/Private/OpenApi.ps1 +++ b/src/Private/OpenApi.ps1 @@ -1,7 +1,6 @@ -function ConvertTo-PodeOAContentTypeSchema -{ +function ConvertTo-PodeOAContentTypeSchema { param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [hashtable] $Schemas ) @@ -21,10 +20,9 @@ function ConvertTo-PodeOAContentTypeSchema return (ConvertTo-PodeOAObjectSchema -Schemas $Schemas) } -function ConvertTo-PodeOAHeaderSchema -{ +function ConvertTo-PodeOAHeaderSchema { param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [hashtable] $Schemas ) @@ -37,10 +35,9 @@ function ConvertTo-PodeOAHeaderSchema return (ConvertTo-PodeOAObjectSchema -Schemas $Schemas) } -function ConvertTo-PodeOAObjectSchema -{ +function ConvertTo-PodeOAObjectSchema { param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [hashtable] $Schemas ) @@ -72,10 +69,9 @@ function ConvertTo-PodeOAObjectSchema return $obj } -function Test-PodeOAComponentSchema -{ +function Test-PodeOAComponentSchema { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -83,10 +79,9 @@ function Test-PodeOAComponentSchema return $PodeContext.Server.OpenAPI.components.schemas.ContainsKey($Name) } -function Test-PodeOAComponentResponse -{ +function Test-PodeOAComponentResponse { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -94,10 +89,9 @@ function Test-PodeOAComponentResponse return $PodeContext.Server.OpenAPI.components.responses.ContainsKey($Name) } -function Test-PodeOAComponentRequestBody -{ +function Test-PodeOAComponentRequestBody { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -105,10 +99,9 @@ function Test-PodeOAComponentRequestBody return $PodeContext.Server.OpenAPI.components.requestBodies.ContainsKey($Name) } -function Test-PodeOAComponentParameter -{ +function Test-PodeOAComponentParameter { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -116,10 +109,9 @@ function Test-PodeOAComponentParameter return $PodeContext.Server.OpenAPI.components.parameters.ContainsKey($Name) } -function ConvertTo-PodeOASchemaProperty -{ +function ConvertTo-PodeOASchemaProperty { param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Property, @@ -129,10 +121,10 @@ function ConvertTo-PodeOASchemaProperty # base schema type $schema = @{ - type = $Property.type - format = $Property.format + type = $Property.type + format = $Property.format description = $Property.description - default = $Property.default + default = $Property.default } if ($Property.deprecated) { @@ -161,7 +153,7 @@ function ConvertTo-PodeOASchemaProperty $Property.array = $false $schema = @{ - type = 'array' + type = 'array' items = ($Property | ConvertTo-PodeOASchemaProperty) } } @@ -171,7 +163,7 @@ function ConvertTo-PodeOASchemaProperty $Property.object = $false $schema = @{ - type = 'object' + type = 'object' properties = (ConvertTo-PodeOASchemaObjectProperty -Properties $Property) } @@ -188,10 +180,9 @@ function ConvertTo-PodeOASchemaProperty return $schema } -function ConvertTo-PodeOASchemaObjectProperty -{ +function ConvertTo-PodeOASchemaObjectProperty { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [hashtable[]] $Properties ) @@ -205,10 +196,9 @@ function ConvertTo-PodeOASchemaObjectProperty return $schema } -function Get-PodeOpenApiDefinitionInternal -{ +function Get-PodeOpenApiDefinitionInternal { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Title, @@ -247,8 +237,8 @@ function Get-PodeOpenApiDefinitionInternal # metadata $def['info'] = @{ - title = $Title - version = $Version + title = $Title + version = $Version description = $Description } @@ -256,11 +246,11 @@ function Get-PodeOpenApiDefinitionInternal $def['servers'] = $null if (!$RestrictRoutes -and ($PodeContext.Server.Endpoints.Count -gt 1)) { $def.servers = @(foreach ($endpoint in $PodeContext.Server.Endpoints.Values) { - @{ - url = $endpoint.Url - description = (Protect-PodeValue -Value $endpoint.Description -Default $endpoint.Name) - } - }) + @{ + url = $endpoint.Url + description = (Protect-PodeValue -Value $endpoint.Description -Default $endpoint.Name) + } + }) } # components @@ -284,13 +274,13 @@ function Get-PodeOpenApiDefinitionInternal if ($authType.Scheme -ieq 'apikey') { $_authObj = @{ type = $authType.Scheme - in = $authType.Arguments.Location.ToLowerInvariant() + in = $authType.Arguments.Location.ToLowerInvariant() name = $authType.Arguments.LocationName } } else { $_authObj = @{ - type = $authType.Scheme.ToLowerInvariant() + type = $authType.Scheme.ToLowerInvariant() scheme = $authType.Name.ToLowerInvariant() } } @@ -340,15 +330,15 @@ function Get-PodeOpenApiDefinitionInternal # add path's http method to defintition $def.paths[$_route.OpenApi.Path][$method] = @{ - summary = $_route.OpenApi.Summary + summary = $_route.OpenApi.Summary description = $_route.OpenApi.Description operationId = $_route.OpenApi.OperationId - tags = @($_route.OpenApi.Tags) - responses = $_route.OpenApi.Responses - parameters = $_route.OpenApi.Parameters + tags = @($_route.OpenApi.Tags) + responses = $_route.OpenApi.Responses + parameters = $_route.OpenApi.Parameters requestBody = $_route.OpenApi.RequestBody - servers = $null - security = @($_route.OpenApi.Authentication) + servers = $null + security = @($_route.OpenApi.Authentication) } if ($_route.OpenApi.Deprecated) { @@ -402,10 +392,9 @@ function Get-PodeOpenApiDefinitionInternal return $def } -function ConvertTo-PodeOAPropertyFromCmdletParameter -{ +function ConvertTo-PodeOAPropertyFromCmdletParameter { param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.Management.Automation.ParameterMetadata] $Parameter ) @@ -428,25 +417,23 @@ function ConvertTo-PodeOAPropertyFromCmdletParameter New-PodeOAStringProperty -Name $Parameter.Name } -function Get-PodeOABaseObject -{ +function Get-PodeOABaseObject { return @{ - Path = $null - Title = $null + Path = $null + Title = $null components = @{ - schemas = @{} - responses = @{} + schemas = @{} + responses = @{} requestBodies = @{} - parameters = @{} + parameters = @{} } - Security = @() + Security = @() } } -function Set-PodeOAAuth -{ +function Set-PodeOAAuth { param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, @@ -464,15 +451,14 @@ function Set-PodeOAAuth foreach ($r in @($Route)) { $r.OpenApi.Authentication = @(foreach ($n in @($Name)) { - @{ - "$($n -replace '\s+', '')" = @() - } - }) + @{ + "$($n -replace '\s+', '')" = @() + } + }) } } -function Set-PodeOAGlobalAuth -{ +function Set-PodeOAGlobalAuth { param( [Parameter()] [string] @@ -495,6 +481,6 @@ function Set-PodeOAGlobalAuth Definition = @{ "$($Name -replace '\s+', '')" = @() } - Route = (ConvertTo-PodeRouteRegex -Path $Route) + Route = (ConvertTo-PodeRouteRegex -Path $Route) } } \ No newline at end of file diff --git a/src/Private/PodeServer.ps1 b/src/Private/PodeServer.ps1 index 2e19a2788..ad10d44cd 100644 --- a/src/Private/PodeServer.ps1 +++ b/src/Private/PodeServer.ps1 @@ -1,8 +1,7 @@ using namespace Pode -function Start-PodeWebServer -{ - param ( +function Start-PodeWebServer { + param( [switch] $Browse ) @@ -33,18 +32,18 @@ function Start-PodeWebServer # the endpoint $_endpoint = @{ - Key = "$($_ip):$($_.Port)" - Address = $_ip - Hostname = $_.HostName - IsIPAddress = $_.IsIPAddress - Port = $_.Port - Certificate = $_.Certificate.Raw + Key = "$($_ip):$($_.Port)" + Address = $_ip + Hostname = $_.HostName + IsIPAddress = $_.IsIPAddress + Port = $_.Port + Certificate = $_.Certificate.Raw AllowClientCertificate = $_.Certificate.AllowClientCertificate - Url = $_.Url - Protocol = $_.Protocol - Type = $_.Type - Pool = $_.Runspace.PoolName - SslProtocols = $_.Ssl.Protocols + Url = $_.Url + Protocol = $_.Protocol + Type = $_.Type + Pool = $_.Runspace.PoolName + SslProtocols = $_.Ssl.Protocols } # add endpoint to list @@ -69,8 +68,7 @@ function Start-PodeWebServer $listener.RequestBodySize = $PodeContext.Server.Request.BodySize $listener.ShowServerDetails = [bool]$PodeContext.Server.Security.ServerDetails - try - { + try { # register endpoints on the listener $endpoints | ForEach-Object { $socket = (. ([scriptblock]::Create("New-Pode$($PodeContext.Server.ListenerType)ListenerSocket -Address `$_.Address -Port `$_.Port -SslProtocols `$_.SslProtocols -Type `$endpointsMap[`$_.Key].Type -Certificate `$_.Certificate -AllowClientCertificate `$_.AllowClientCertificate"))) @@ -100,57 +98,53 @@ function Start-PodeWebServer # script for listening out for incoming requests $listenScript = { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Listener, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $ThreadId ) - try - { - while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + try { + while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) { # get request and response $context = (Wait-PodeTask -Task $Listener.GetContextAsync($PodeContext.Tokens.Cancellation.Token)) - try - { - try - { + try { + try { $Request = $context.Request $Response = $context.Response # reset with basic event data $WebEvent = @{ - OnEnd = @() - Auth = @{} - Response = $Response - Request = $Request - Lockable = $PodeContext.Threading.Lockables.Global - Path = [System.Web.HttpUtility]::UrlDecode($Request.Url.AbsolutePath) - Method = $Request.HttpMethod.ToLowerInvariant() - Query = $null - Endpoint = @{ + OnEnd = @() + Auth = @{} + Response = $Response + Request = $Request + Lockable = $PodeContext.Threading.Lockables.Global + Path = [System.Web.HttpUtility]::UrlDecode($Request.Url.AbsolutePath) + Method = $Request.HttpMethod.ToLowerInvariant() + Query = $null + Endpoint = @{ Protocol = $Request.Url.Scheme - Address = $Request.Host - Name = $null + Address = $Request.Host + Name = $null } - ContentType = $Request.ContentType - ErrorType = $null - Cookies = @{} - PendingCookies = @{} - Parameters = $null - Data = $null - Files = $null - Streamed = $true - Route = $null - StaticContent = $null - Timestamp = [datetime]::UtcNow + ContentType = $Request.ContentType + ErrorType = $null + Cookies = @{} + PendingCookies = @{} + Parameters = $null + Data = $null + Files = $null + Streamed = $true + Route = $null + StaticContent = $null + Timestamp = [datetime]::UtcNow TransferEncoding = $null - AcceptEncoding = $null - Ranges = $null + AcceptEncoding = $null + Ranges = $null } # if iis, and we have an app path, alter it @@ -184,8 +178,7 @@ function Start-PodeWebServer throw $Request.Error } - if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) - { + if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) { # has the request been aborted if ($Request.IsAborted) { throw $Request.Error @@ -259,17 +252,15 @@ function Start-PodeWebServer # script to write messages back to the client(s) $signalScript = { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Listener ) try { - while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) { $message = (Wait-PodeTask -Task $Listener.GetServerSignalAsync($PodeContext.Tokens.Cancellation.Token)) - try - { + try { # get the sockets for the message $sockets = @() @@ -283,11 +274,11 @@ function Start-PodeWebServer # by path if (![string]::IsNullOrWhiteSpace($message.Path)) { $sockets = @(foreach ($socket in $sockets) { - if ($socket.Path -ieq $message.Path) { - $socket - break - } - }) + if ($socket.Path -ieq $message.Path) { + $socket + break + } + }) } } @@ -332,45 +323,43 @@ function Start-PodeWebServer # script to queue messages from clients to send back to other clients from the server $clientScript = { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Listener, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $ThreadId ) try { - while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) { $context = (Wait-PodeTask -Task $Listener.GetClientSignalAsync($PodeContext.Tokens.Cancellation.Token)) - try - { + try { $payload = ($context.Message | ConvertFrom-Json) $Request = $context.Signal.Context.Request $Response = $context.Signal.Context.Response $SignalEvent = @{ - Response = $Response - Request = $Request - Lockable = $PodeContext.Threading.Lockables.Global - Path = [System.Web.HttpUtility]::UrlDecode($Request.Url.AbsolutePath) - Data = @{ - Path = [System.Web.HttpUtility]::UrlDecode($payload.path) - Message = $payload.message + Response = $Response + Request = $Request + Lockable = $PodeContext.Threading.Lockables.Global + Path = [System.Web.HttpUtility]::UrlDecode($Request.Url.AbsolutePath) + Data = @{ + Path = [System.Web.HttpUtility]::UrlDecode($payload.path) + Message = $payload.message ClientId = $payload.clientId - Direct = [bool]$payload.direct + Direct = [bool]$payload.direct } - Endpoint = @{ + Endpoint = @{ Protocol = $Request.Url.Scheme - Address = $Request.Host - Name = $null + Address = $Request.Host + Name = $null } - Route = $null - ClientId = $context.Signal.ClientId + Route = $null + ClientId = $context.Signal.ClientId Timestamp = $context.Timestamp - Streamed = $true + Streamed = $true } # endpoint name @@ -415,7 +404,7 @@ function Start-PodeWebServer # script to keep web server listening until cancelled $waitScript = { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Listener ) @@ -449,18 +438,17 @@ function Start-PodeWebServer } return @(foreach ($endpoint in $endpoints) { - @{ - Url = $endpoint.Url - Pool = $endpoint.Pool - } - }) + @{ + Url = $endpoint.Url + Pool = $endpoint.Pool + } + }) } -function New-PodeListener -{ +function New-PodeListener { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Threading.CancellationToken] $CancellationToken ) @@ -468,15 +456,14 @@ function New-PodeListener return [PodeListener]::new($CancellationToken) } -function New-PodeListenerSocket -{ +function New-PodeListenerSocket { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ipaddress] $Address, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Port, @@ -484,7 +471,7 @@ function New-PodeListenerSocket [System.Security.Authentication.SslProtocols] $SslProtocols, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [PodeProtocolType] $Type, diff --git a/src/Private/Responses.ps1 b/src/Private/Responses.ps1 index 7e8f955ca..e350c8d5a 100644 --- a/src/Private/Responses.ps1 +++ b/src/Private/Responses.ps1 @@ -1,5 +1,4 @@ -function Show-PodeErrorPage -{ +function Show-PodeErrorPage { param( [Parameter()] [int] @@ -38,8 +37,8 @@ function Show-PodeErrorPage # setup the data object for dynamic pages $data = @{ - Url = [System.Web.HttpUtility]::HtmlEncode((Get-PodeUrl)) - Status = @{ + Url = [System.Web.HttpUtility]::HtmlEncode((Get-PodeUrl)) + Status = @{ Code = $Code Description = $Description } diff --git a/src/Private/Routes.ps1 b/src/Private/Routes.ps1 index c46ea6d8e..427b115d2 100644 --- a/src/Private/Routes.ps1 +++ b/src/Private/Routes.ps1 @@ -1,12 +1,11 @@ -function Test-PodeRouteFromRequest -{ +function Test-PodeRouteFromRequest { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('CONNECT', 'DELETE', 'GET', 'HEAD', 'MERGE', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE', 'STATIC', 'SIGNAL', '*')] [string] $Method, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Path, @@ -23,15 +22,14 @@ function Test-PodeRouteFromRequest return ($null -ne $route) } -function Find-PodeRoute -{ +function Find-PodeRoute { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('CONNECT', 'DELETE', 'GET', 'HEAD', 'MERGE', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE', 'STATIC', 'SIGNAL', '*')] [string] $Method, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Path, @@ -69,11 +67,11 @@ function Find-PodeRoute # otherwise, match the path to routes on regex (first match only) $valid = @(foreach ($key in $_method.Keys) { - if ($Path -imatch "^$($key)$") { - $key - break - } - })[0] + if ($Path -imatch "^$($key)$") { + $key + break + } + })[0] if ($null -eq $valid) { return $null @@ -88,10 +86,9 @@ function Find-PodeRoute return $found } -function Find-PodePublicRoute -{ +function Find-PodePublicRoute { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path ) @@ -116,10 +113,9 @@ function Find-PodePublicRoute return $source } -function Find-PodeStaticRoute -{ +function Find-PodeStaticRoute { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -145,8 +141,7 @@ function Find-PodeStaticRoute } # if there's no file, we need to check defaults - if (!$found.Download -and !(Test-PodePathIsFile $file) -and (Get-PodeCount @($found.Defaults)) -gt 0) - { + if (!$found.Download -and !(Test-PodePathIsFile $file) -and (Get-PodeCount @($found.Defaults)) -gt 0) { if ((Get-PodeCount @($found.Defaults)) -eq 1) { $file = [System.IO.Path]::Combine($file, @($found.Defaults)[0]) } @@ -178,18 +173,17 @@ function Find-PodeStaticRoute # return the route details return @{ Content = @{ - Source = $source + Source = $source IsDownload = $download IsCachable = (Test-PodeRouteValidForCaching -Path $Path) } - Route = $found + Route = $found } } -function Find-PodeSignalRoute -{ +function Find-PodeSignalRoute { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -202,10 +196,9 @@ function Find-PodeSignalRoute return (Find-PodeRoute -Method 'signal' -Path $Path -EndpointName $EndpointName) } -function Test-PodeRouteValidForCaching -{ +function Test-PodeRouteValidForCaching { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path ) @@ -228,8 +221,7 @@ function Test-PodeRouteValidForCaching return $caching } -function Get-PodeRouteByUrl -{ +function Get-PodeRouteByUrl { param( [Parameter()] [hashtable[]] @@ -249,8 +241,7 @@ function Get-PodeRouteByUrl return (Get-PodeRoutesByUrl -Routes $Routes -EndpointName $EndpointName) } -function Get-PodeRoutesByUrl -{ +function Get-PodeRoutesByUrl { param( [Parameter()] [hashtable[]] @@ -280,10 +271,9 @@ function Get-PodeRoutesByUrl return $null } -function ConvertTo-PodeOpenApiRoutePath -{ +function ConvertTo-PodeOpenApiRoutePath { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path ) @@ -291,10 +281,9 @@ function ConvertTo-PodeOpenApiRoutePath return (Resolve-PodePlaceholders -Path $Path -Pattern '\:(?[\w]+)' -Prepend '{' -Append '}') } -function Update-PodeRouteSlashes -{ +function Update-PodeRouteSlashes { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -321,19 +310,17 @@ function Update-PodeRouteSlashes return $Path } -function Split-PodeRouteQuery -{ +function Split-PodeRouteQuery { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path ) - return ($Path -isplit "\?")[0] + return ($Path -isplit '\?')[0] } -function ConvertTo-PodeRouteRegex -{ +function ConvertTo-PodeRouteRegex { param( [Parameter()] [string] @@ -353,8 +340,7 @@ function ConvertTo-PodeRouteRegex return $Path } -function Get-PodeStaticRouteDefaults -{ +function Get-PodeStaticRouteDefaults { if (!(Test-PodeIsEmpty $PodeContext.Server.Web.Static.Defaults)) { return @($PodeContext.Server.Web.Static.Defaults) } @@ -367,14 +353,13 @@ function Get-PodeStaticRouteDefaults ) } -function Test-PodeRouteInternal -{ +function Test-PodeRouteInternal { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Method, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -427,8 +412,7 @@ function Test-PodeRouteInternal throw "[$($Method)] $($Path): Already defined for $($_url)" } -function Convert-PodeFunctionVerbToHttpMethod -{ +function Convert-PodeFunctionVerbToHttpMethod { param( [Parameter()] [string] @@ -445,10 +429,9 @@ function Convert-PodeFunctionVerbToHttpMethod } } -function Find-PodeRouteTransferEncoding -{ +function Find-PodeRouteTransferEncoding { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -467,8 +450,8 @@ function Find-PodeRouteTransferEncoding # find type by pattern from settings $matched = ($PodeContext.Server.Web.TransferEncoding.Routes.Keys | Where-Object { - $Path -imatch $_ - } | Select-Object -First 1) + $Path -imatch $_ + } | Select-Object -First 1) # if we get a match, set it if (!(Test-PodeIsEmpty $matched)) { @@ -478,10 +461,9 @@ function Find-PodeRouteTransferEncoding return $TransferEncoding } -function Find-PodeRouteContentType -{ +function Find-PodeRouteContentType { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -500,8 +482,8 @@ function Find-PodeRouteContentType # find type by pattern from settings $matched = ($PodeContext.Server.Web.ContentType.Routes.Keys | Where-Object { - $Path -imatch $_ - } | Select-Object -First 1) + $Path -imatch $_ + } | Select-Object -First 1) # if we get a match, set it if (!(Test-PodeIsEmpty $matched)) { @@ -511,15 +493,14 @@ function Find-PodeRouteContentType return $ContentType } -function ConvertTo-PodeMiddleware -{ +function ConvertTo-PodeMiddleware { [OutputType([hashtable[]])] param( [Parameter()] [object[]] $Middleware, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $PSSession ) @@ -545,7 +526,7 @@ function ConvertTo-PodeMiddleware # if middleware is hashtable, ensure the keys are valid (logic is a scriptblock) if ($mid -is [hashtable]) { if ($null -eq $mid.Logic) { - throw "A Hashtable Middleware supplied has no Logic defined" + throw 'A Hashtable Middleware supplied has no Logic defined' } if ($mid.Logic -isnot [scriptblock]) { @@ -556,27 +537,26 @@ function ConvertTo-PodeMiddleware # if we have middleware, convert scriptblocks to hashtables $converted = @(for ($i = 0; $i -lt $Middleware.Length; $i++) { - if ($null -eq $Middleware[$i]) { - continue - } + if ($null -eq $Middleware[$i]) { + continue + } - if ($Middleware[$i] -is [scriptblock]) { - $_script, $_usingVars = Convert-PodeScopedVariables -ScriptBlock $Middleware[$i] -PSSession $PSSession + if ($Middleware[$i] -is [scriptblock]) { + $_script, $_usingVars = Convert-PodeScopedVariables -ScriptBlock $Middleware[$i] -PSSession $PSSession - $Middleware[$i] = @{ - Logic = $_script - UsingVariables = $_usingVars + $Middleware[$i] = @{ + Logic = $_script + UsingVariables = $_usingVars + } } - } - $Middleware[$i] - }) + $Middleware[$i] + }) return $converted } -function Get-PodeRouteIfExistsPreference -{ +function Get-PodeRouteIfExistsPreference { # from route groups $groupPref = $RouteGroup.IfExists if (![string]::IsNullOrWhiteSpace($groupPref) -and ($groupPref -ine 'default')) { diff --git a/src/Private/Schedules.ps1 b/src/Private/Schedules.ps1 index 1cdfe2354..bcdea1092 100644 --- a/src/Private/Schedules.ps1 +++ b/src/Private/Schedules.ps1 @@ -1,7 +1,6 @@ -function Find-PodeSchedule -{ +function Find-PodeSchedule { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Name @@ -10,13 +9,11 @@ function Find-PodeSchedule return $PodeContext.Schedules.Items[$Name] } -function Test-PodeSchedulesExist -{ +function Test-PodeSchedulesExist { return (($null -ne $PodeContext.Schedules) -and (($PodeContext.Schedules.Enabled) -or ($PodeContext.Schedules.Items.Count -gt 0))) } -function Start-PodeScheduleRunspace -{ +function Start-PodeScheduleRunspace { if (!(Test-PodeSchedulesExist)) { return } @@ -58,8 +55,7 @@ function Start-PodeScheduleRunspace # first, sleep for a period of time to get to 00 seconds (start of minute) Start-Sleep -Seconds (60 - [DateTime]::Now.Second) - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { $_now = [DateTime]::Now # select the schedules that need triggering @@ -84,8 +80,7 @@ function Start-PodeScheduleRunspace Add-PodeRunspace -Type Main -ScriptBlock $script -NoProfile } -function Close-PodeScheduleInternal -{ +function Close-PodeScheduleInternal { param( [Parameter()] [hashtable] @@ -100,17 +95,16 @@ function Close-PodeScheduleInternal $null = $PodeContext.Schedules.Processes.Remove($Process.ID) } -function Complete-PodeInternalSchedules -{ +function Complete-PodeInternalSchedules { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [datetime] $Now ) # add any schedules to remove that have exceeded their end time $Schedules = @($PodeContext.Schedules.Items.Values | - Where-Object { (($null -ne $_.EndTime) -and ($_.EndTime -lt $Now)) }) + Where-Object { (($null -ne $_.EndTime) -and ($_.EndTime -lt $Now)) }) if (($null -eq $Schedules) -or ($Schedules.Length -eq 0)) { return @@ -122,10 +116,9 @@ function Complete-PodeInternalSchedules } } -function Invoke-PodeInternalSchedule -{ +function Invoke-PodeInternalSchedule { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Schedule ) @@ -160,10 +153,9 @@ function Invoke-PodeInternalSchedule Invoke-PodeInternalScheduleLogic -Schedule $Schedule } -function Invoke-PodeInternalScheduleLogic -{ +function Invoke-PodeInternalScheduleLogic { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Schedule, [Parameter()] @@ -176,7 +168,7 @@ function Invoke-PodeInternalScheduleLogic $parameters = @{ Event = @{ Lockable = $PodeContext.Threading.Lockables.Global - Sender = $Schedule + Sender = $Schedule } } @@ -203,7 +195,7 @@ function Invoke-PodeInternalScheduleLogic $runspace = Add-PodeRunspace -Type Schedules -ScriptBlock (($Schedule.Script).GetNewClosure()) -Parameters $parameters -PassThru $PodeContext.Schedules.Processes[$name] = @{ - ID = $name + ID = $name Schedule = $Schedule.Name Runspace = $runspace } diff --git a/src/Private/Secrets.ps1 b/src/Private/Secrets.ps1 index 596f1610b..ebf15abce 100644 --- a/src/Private/Secrets.ps1 +++ b/src/Private/Secrets.ps1 @@ -1,11 +1,10 @@ -function Initialize-PodeSecretVault -{ +function Initialize-PodeSecretVault { param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $VaultConfig, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) @@ -13,10 +12,9 @@ function Initialize-PodeSecretVault Invoke-PodeScriptBlock -ScriptBlock $ScriptBlock -Splat -Arguments @($VaultConfig.Parameters) } -function Register-PodeSecretManagementVault -{ +function Register-PodeSecretManagementVault { param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $VaultConfig, @@ -24,7 +22,7 @@ function Register-PodeSecretManagementVault [string] $VaultName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $ModuleName ) @@ -43,19 +41,18 @@ function Register-PodeSecretManagementVault # all is good, so set the config $VaultConfig['SecretManagement'] = @{ - VaultName = $VaultName + VaultName = $VaultName ModuleName = $ModuleName } } -function Register-PodeSecretCustomVault -{ +function Register-PodeSecretCustomVault { param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $VaultConfig, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, @@ -83,18 +80,17 @@ function Register-PodeSecretCustomVault # all is good, so set the config $VaultConfig['Custom'] = @{ - Read = $ScriptBlock - Unlock = $UnlockScriptBlock - Remove = $RemoveScriptBlock - Set = $SetScriptBlock + Read = $ScriptBlock + Unlock = $UnlockScriptBlock + Remove = $RemoveScriptBlock + Set = $SetScriptBlock Unregister = $UnregisterScriptBlock } } -function Unlock-PodeSecretManagementVault -{ +function Unlock-PodeSecretManagementVault { param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $VaultConfig ) @@ -115,10 +111,9 @@ function Unlock-PodeSecretManagementVault return $null } -function Unlock-PodeSecretCustomVault -{ +function Unlock-PodeSecretCustomVault { param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $VaultConfig ) @@ -135,9 +130,9 @@ function Unlock-PodeSecretCustomVault # unlock the vault, and get back an expiry $expiry = (Invoke-PodeScriptBlock -ScriptBlock $VaultConfig.Custom.Unlock -Splat -Return -Arguments @( - $VaultConfig.Parameters, + $VaultConfig.Parameters, (ConvertFrom-SecureString -SecureString $VaultConfig.Unlock.Secret -AsPlainText) - )) + )) # return expiry if given, otherwise check interval if ($null -ne $expiry) { @@ -151,10 +146,9 @@ function Unlock-PodeSecretCustomVault return $null } -function Unregister-PodeSecretManagementVault -{ +function Unregister-PodeSecretManagementVault { param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $VaultConfig ) @@ -168,10 +162,9 @@ function Unregister-PodeSecretManagementVault $null = Unregister-SecretVault -Name $VaultConfig.SecretManagement.VaultName -Confirm:$false -ErrorAction Stop } -function Unregister-PodeSecretCustomVault -{ +function Unregister-PodeSecretCustomVault { param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $VaultConfig ) @@ -192,14 +185,13 @@ function Unregister-PodeSecretCustomVault ) } -function Get-PodeSecretManagementKey -{ +function Get-PodeSecretManagementKey { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Vault, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key ) @@ -211,14 +203,13 @@ function Get-PodeSecretManagementKey return (Get-Secret -Name $Key -Vault $_vault.SecretManagement.VaultName -AsPlainText -ErrorAction Stop) } -function Get-PodeSecretCustomKey -{ +function Get-PodeSecretCustomKey { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Vault, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key, @@ -232,23 +223,22 @@ function Get-PodeSecretCustomKey # fetch the secret return (Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Read -Splat -Return -Arguments (@( - $_vault.Parameters, - $Key - ) + $ArgumentList)) + $_vault.Parameters, + $Key + ) + $ArgumentList)) } -function Set-PodeSecretManagementKey -{ +function Set-PodeSecretManagementKey { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Vault, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [object] $Value, @@ -264,18 +254,17 @@ function Set-PodeSecretManagementKey $null = Set-Secret -Name $Key -Secret $Value -Vault $_vault.SecretManagement.VaultName -Metadata $Metadata -Confirm:$false -ErrorAction Stop } -function Set-PodeSecretCustomKey -{ +function Set-PodeSecretCustomKey { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Vault, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [object] $Value, @@ -298,21 +287,20 @@ function Set-PodeSecretCustomKey # set the secret Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Set -Splat -Arguments (@( - $_vault.Parameters, - $Key, - $Value, - $Metadata - ) + $ArgumentList) + $_vault.Parameters, + $Key, + $Value, + $Metadata + ) + $ArgumentList) } -function Remove-PodeSecretManagementKey -{ +function Remove-PodeSecretManagementKey { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Vault, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key ) @@ -324,14 +312,13 @@ function Remove-PodeSecretManagementKey $null = Remove-Secret -Name $Key -Vault $_vault.SecretManagement.VaultName -Confirm:$false -ErrorAction Stop } -function Remove-PodeSecretCustomKey -{ +function Remove-PodeSecretCustomKey { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Vault, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key, @@ -350,13 +337,12 @@ function Remove-PodeSecretCustomKey # remove the secret Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Remove -Splat -Arguments (@( - $_vault.Parameters, - $Key - ) + $ArgumentList) + $_vault.Parameters, + $Key + ) + $ArgumentList) } -function Start-PodeSecretCacheHousekeeper -{ +function Start-PodeSecretCacheHousekeeper { if (Test-PodeTimer -Name '__pode_secrets_cache_expiry__') { return } @@ -375,8 +361,7 @@ function Start-PodeSecretCacheHousekeeper } } -function Start-PodeSecretVaultUnlocker -{ +function Start-PodeSecretVaultUnlocker { if (Test-PodeTimer -Name '__pode_secrets_vault_unlock__') { return } @@ -394,8 +379,7 @@ function Start-PodeSecretVaultUnlocker } } -function Unregister-PodeSecretVaults -{ +function Unregister-PodeSecretVaults { param( [switch] $ThrowError @@ -424,10 +408,9 @@ function Unregister-PodeSecretVaults } } -function Protect-PodeSecretValueType -{ +function Protect-PodeSecretValueType { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [object] $Value ) @@ -451,7 +434,7 @@ function Protect-PodeSecretValueType ($Value -is [byte[]]) -or ($Value -is [pscredential]) -or ($Value -is [System.Management.Automation.OrderedHashtable]) - )) { + )) { throw "Value to set secret to is of an invalid type. Expected either String, SecureString, HashTable, Byte[], or PSCredential. But got: $($Value.GetType().Name)" } diff --git a/src/Private/Security.ps1 b/src/Private/Security.ps1 index 17291deee..d8521cfb7 100644 --- a/src/Private/Security.ps1 +++ b/src/Private/Security.ps1 @@ -1,9 +1,8 @@ using namespace System.Security.Cryptography -function Test-PodeIPLimit -{ +function Test-PodeIPLimit { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] $IP ) @@ -23,7 +22,7 @@ function Test-PodeIPLimit $IP = @{ String = $IP.IPAddressToString Family = $IP.AddressFamily - Bytes = $IP.GetAddressBytes() + Bytes = $IP.GetAddressBytes() } # now @@ -33,17 +32,17 @@ function Test-PodeIPLimit $_active_ip = $active[$IP.String] if ($null -eq $_active_ip) { $_groups = @(foreach ($key in $active.Keys) { - if ($active[$key].Rule.Grouped) { - $active[$key] - } - }) + 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] + 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 @@ -73,11 +72,11 @@ function Test-PodeIPLimit 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 (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) @@ -95,8 +94,8 @@ function Test-PodeIPLimit $_ip = (Resolve-PodeValue -Check $_rule_ip.Grouped -TrueValue $_rule_ip.IP -FalseValue $IP.String) $active[$_ip] = @{ - Rule = $_rule_ip - Rate = 1 + Rule = $_rule_ip + Rate = 1 Expire = $now.AddSeconds($_rule_ip.Seconds) } @@ -105,10 +104,9 @@ function Test-PodeIPLimit } } -function Test-PodeRouteLimit -{ - param ( - [Parameter(Mandatory=$true)] +function Test-PodeRouteLimit { + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] [string] $Path @@ -172,8 +170,8 @@ function Test-PodeRouteLimit # add route to active list $active[$Path] = @{ - Rule = $_rule_route - Rate = 1 + Rule = $_rule_route + Rate = 1 Expire = $now.AddSeconds($_rule_route.Seconds) } @@ -182,9 +180,8 @@ function Test-PodeRouteLimit } } -function Test-PodeEndpointLimit -{ - param ( +function Test-PodeEndpointLimit { + param( [Parameter()] [string] $EndpointName @@ -252,8 +249,8 @@ function Test-PodeEndpointLimit # add endpoint to active list $active[$EndpointName] = @{ - Rule = $_rule_endpoint - Rate = 1 + Rule = $_rule_endpoint + Rate = 1 Expire = $now.AddSeconds($_rule_endpoint.Seconds) } @@ -262,10 +259,9 @@ function Test-PodeEndpointLimit } } -function Test-PodeIPAccess -{ +function Test-PodeIPAccess { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] $IP ) @@ -288,17 +284,17 @@ function Test-PodeIPAccess # get the ip address in bytes $IP = @{ Family = $IP.AddressFamily - Bytes = $IP.GetAddressBytes() + 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 (Test-PodeIPAddressInRange -IP $IP -LowerIP $value.Lower -UpperIP $value.Upper) { + $value + break + } + })[0] if ($null -ne $match) { return $true @@ -308,11 +304,11 @@ function Test-PodeIPAccess # 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 (Test-PodeIPAddressInRange -IP $IP -LowerIP $value.Lower -UpperIP $value.Upper) { + $value + break + } + })[0] if ($null -ne $match) { return $false @@ -328,19 +324,18 @@ function Test-PodeIPAccess return $true } -function Add-PodeIPLimit -{ - param ( - [Parameter(Mandatory=$true)] +function Add-PodeIPLimit { + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] [string] $IP, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Limit, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Seconds, @@ -392,34 +387,33 @@ function Add-PodeIPLimit # 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() - } - }) + 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)] +function Add-PodeRouteLimit { + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] [string] $Path, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Limit, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Seconds, @@ -456,26 +450,25 @@ function Add-PodeRouteLimit # add limit rule for the route $rules.Add($Path, @{ - Limit = $Limit - Seconds = $Seconds - Grouped = [bool]$Group - Path = $Path - }) + Limit = $Limit + Seconds = $Seconds + Grouped = [bool]$Group + Path = $Path + }) } -function Add-PodeEndpointLimit -{ +function Add-PodeEndpointLimit { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] [string] $EndpointName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Limit, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Seconds, @@ -537,22 +530,21 @@ function Add-PodeEndpointLimit # add limit rule for the endpoint $rules.Add($EndpointName, @{ - Limit = $Limit - Seconds = $Seconds - Grouped = [bool]$Group - EndpointName = $EndpointName - }) + Limit = $Limit + Seconds = $Seconds + Grouped = [bool]$Group + EndpointName = $EndpointName + }) } -function Add-PodeIPAccess -{ - param ( - [Parameter(Mandatory=$true)] +function Add-PodeIPAccess { + param( + [Parameter(Mandatory = $true)] [ValidateSet('Allow', 'Deny')] [string] $Access, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $IP ) @@ -600,19 +592,18 @@ function Add-PodeIPAccess # add access rule for ip $permType.Add($IP, @{ - Lower = @{ - Family = $_tmpLo.AddressFamily - Bytes = $_tmpLo.GetAddressBytes() - } - Upper = @{ - Family = $_tmpHi.AddressFamily - Bytes = $_tmpHi.GetAddressBytes() - } - }) + Lower = @{ + Family = $_tmpLo.AddressFamily + Bytes = $_tmpLo.GetAddressBytes() + } + Upper = @{ + Family = $_tmpHi.AddressFamily + Bytes = $_tmpHi.GetAddressBytes() + } + }) } -function Get-PodeCsrfToken -{ +function Get-PodeCsrfToken { # key name to search $key = $PodeContext.Server.Cookies.Csrf.Name @@ -635,9 +626,8 @@ function Get-PodeCsrfToken return $null } -function Test-PodeCsrfToken -{ - param ( +function Test-PodeCsrfToken { + param( [Parameter()] [string] $Secret, @@ -674,8 +664,7 @@ function Test-PodeCsrfToken return $true } -function New-PodeCsrfSecret -{ +function New-PodeCsrfSecret { # see if there's already a secret in session/cookie $secret = (Get-PodeCsrfSecret) if (!(Test-PodeIsEmpty $secret)) { @@ -688,16 +677,16 @@ function New-PodeCsrfSecret return $secret } -function Get-PodeCsrfSecret -{ +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 ` + $cookie = Get-PodeCookie ` -Name $PodeContext.Server.Cookies.Csrf.Name ` - -Secret $PodeContext.Server.Cookies.Csrf.Secret).Value + -Secret $PodeContext.Server.Cookies.Csrf.Secret + return $cookie.Value } # on session @@ -706,10 +695,9 @@ function Get-PodeCsrfSecret } } -function Set-PodeCsrfSecret -{ - param ( - [Parameter(Mandatory=$true)] +function Set-PodeCsrfSecret { + param( + [Parameter(Mandatory = $true)] [string] $Secret ) @@ -731,14 +719,13 @@ function Set-PodeCsrfSecret } } -function Restore-PodeCsrfToken -{ - param ( - [Parameter(Mandatory=$true)] +function Restore-PodeCsrfToken { + param( + [Parameter(Mandatory = $true)] [string] $Secret, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Salt ) @@ -746,15 +733,13 @@ function Restore-PodeCsrfToken return "t:$($Salt).$(Invoke-PodeSHA256Hash -Value "$($Salt)-$($Secret)")" } -function Test-PodeCsrfConfigured -{ +function Test-PodeCsrfConfigured { return (!(Test-PodeIsEmpty $PodeContext.Server.Cookies.Csrf)) } -function Get-PodeCertificateByFile -{ +function Get-PodeCertificateByFile { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Certificate, @@ -783,10 +768,9 @@ function Get-PodeCertificateByFile return [X509Certificates.X509Certificate2]::new($path) } -function Get-PodeCertificateByPemFile -{ +function Get-PodeCertificateByPemFile { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Certificate, @@ -864,29 +848,28 @@ function Get-PodeCertificateByPemFile return $cert } -function Find-PodeCertificateInCertStore -{ +function Find-PodeCertificateInCertStore { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [X509Certificates.X509FindType] $FindType, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Query, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [X509Certificates.StoreName] $StoreName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [X509Certificates.StoreLocation] $StoreLocation ) # fail if not windows if (!(Test-PodeIsWindows)) { - throw "Certificate Thumbprints/Name are only supported on Windows" + throw 'Certificate Thumbprints/Name are only supported on Windows' } # open the currentuser\my store @@ -912,54 +895,51 @@ function Find-PodeCertificateInCertStore return ([X509Certificates.X509Certificate2]($x509certs[0])) } -function Get-PodeCertificateByThumbprint -{ +function Get-PodeCertificateByThumbprint { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Thumbprint, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [X509Certificates.StoreName] $StoreName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [X509Certificates.StoreLocation] $StoreLocation ) - return (Find-PodeCertificateInCertStore ` + return Find-PodeCertificateInCertStore ` -FindType ([X509Certificates.X509FindType]::FindByThumbprint) ` -Query $Thumbprint ` -StoreName $StoreName ` - -StoreLocation $StoreLocation) + -StoreLocation $StoreLocation } -function Get-PodeCertificateByName -{ +function Get-PodeCertificateByName { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [X509Certificates.StoreName] $StoreName, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [X509Certificates.StoreLocation] $StoreLocation ) - return (Find-PodeCertificateInCertStore ` + return Find-PodeCertificateInCertStore ` -FindType ([X509Certificates.X509FindType]::FindBySubjectName) ` -Query $Name ` -StoreName $StoreName ` - -StoreLocation $StoreLocation) + -StoreLocation $StoreLocation } -function New-PodeSelfSignedCertificate -{ +function New-PodeSelfSignedCertificate { $sanBuilder = [X509Certificates.SubjectAlternativeNameBuilder]::new() $null = $sanBuilder.AddIpAddress([ipaddress]::Loopback) $null = $sanBuilder.AddIpAddress([ipaddress]::IPv6Loopback) @@ -970,7 +950,7 @@ function New-PodeSelfSignedCertificate } $rsa = [RSA]::Create(2048) - $distinguishedName = [X500DistinguishedName]::new("CN=localhost") + $distinguishedName = [X500DistinguishedName]::new('CN=localhost') $req = [X509Certificates.CertificateRequest]::new( $distinguishedName, @@ -1021,10 +1001,9 @@ function New-PodeSelfSignedCertificate return $cert } -function Protect-PodeContentSecurityKeyword -{ +function Protect-PodeContentSecurityKeyword { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -1069,26 +1048,25 @@ function Protect-PodeContentSecurityKeyword # build the value $values = @(foreach ($v in $Value) { - if ($keywords -icontains $v) { - "'$($v.ToLowerInvariant())'" - continue - } + if ($keywords -icontains $v) { + "'$($v.ToLowerInvariant())'" + continue + } - if ($schemes -icontains $v) { - "$($v.ToLowerInvariant()):" - continue - } + if ($schemes -icontains $v) { + "$($v.ToLowerInvariant()):" + continue + } - $v - }) + $v + }) return "$($Name) $($values -join ' ')" } -function Protect-PodePermissionsPolicyKeyword -{ +function Protect-PodePermissionsPolicyKeyword { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -1126,13 +1104,13 @@ function Protect-PodePermissionsPolicyKeyword ) $values = @(foreach ($v in $Value) { - if ($keywords -icontains $v) { - $v - continue - } + if ($keywords -icontains $v) { + $v + continue + } - "`"$($v)`"" - }) + "`"$($v)`"" + }) return "$($Name)=($($values -join ' '))" } \ No newline at end of file diff --git a/src/Private/Server.ps1 b/src/Private/Server.ps1 index bd8ae7cd9..295a62c25 100644 --- a/src/Private/Server.ps1 +++ b/src/Private/Server.ps1 @@ -1,6 +1,5 @@ -function Start-PodeInternalServer -{ - param ( +function Start-PodeInternalServer { + param( [Parameter()] $Request, @@ -8,8 +7,7 @@ function Start-PodeInternalServer $Browse ) - try - { + try { # setup temp drives for internal dirs Add-PodePSInbuiltDrives @@ -50,8 +48,7 @@ function Start-PodeInternalServer New-PodeRunspacePools Open-PodeRunspacePools - if (!$PodeContext.Server.IsServerless) - { + if (!$PodeContext.Server.IsServerless) { # start runspace for loggers Start-PodeLoggingRunspace @@ -81,8 +78,7 @@ function Start-PodeInternalServer # - serverless elseif ($PodeContext.Server.IsServerless) { - switch ($PodeContext.Server.ServerlessType.ToUpperInvariant()) - { + switch ($PodeContext.Server.ServerlessType.ToUpperInvariant()) { 'AZUREFUNCTIONS' { Start-PodeAzFuncServer -Data $Request } @@ -97,8 +93,7 @@ function Start-PodeInternalServer else { # start each server type foreach ($_type in $PodeContext.Server.Types) { - switch ($_type.ToUpperInvariant()) - { + switch ($_type.ToUpperInvariant()) { 'SMTP' { $endpoints += (Start-PodeSmtpServer) } @@ -151,10 +146,8 @@ function Start-PodeInternalServer } } -function Restart-PodeInternalServer -{ - try - { +function Restart-PodeInternalServer { + try { # inform restart Write-PodeHost 'Restarting server...' -NoNewline -ForegroundColor Cyan @@ -232,11 +225,11 @@ function Restart-PodeInternalServer # set view engine back to default $PodeContext.Server.ViewEngine = @{ - Type = 'html' - Extension = 'html' - ScriptBlock = $null + Type = 'html' + Extension = 'html' + ScriptBlock = $null UsingVariables = $null - IsDynamic = $false + IsDynamic = $false } # clear up cookie sessions @@ -276,7 +269,7 @@ function Restart-PodeInternalServer $PodeContext.Server.Configuration = Open-PodeConfiguration -Context $PodeContext # done message - Write-PodeHost " Done" -ForegroundColor Green + Write-PodeHost ' Done' -ForegroundColor Green # restart the server $PodeContext.Metrics.Server.RestartCount++ @@ -288,8 +281,7 @@ function Restart-PodeInternalServer } } -function Test-PodeServerKeepOpen -{ +function Test-PodeServerKeepOpen { # if we have any timers/schedules/fim - keep open if ((Test-PodeTimersExist) -or (Test-PodeSchedulesExist) -or (Test-PodeFileWatchersExist)) { return $true diff --git a/src/Private/Serverless.ps1 b/src/Private/Serverless.ps1 index 9d0b56de3..a7aac81ca 100644 --- a/src/Private/Serverless.ps1 +++ b/src/Private/Serverless.ps1 @@ -1,7 +1,6 @@ -function Start-PodeAzFuncServer -{ - param ( - [Parameter(Mandatory=$true)] +function Start-PodeAzFuncServer { + param( + [Parameter(Mandatory = $true)] $Data ) @@ -16,10 +15,8 @@ function Start-PodeAzFuncServer $PodeContext.Server.Middleware = ($inbuilt_middleware + $PodeContext.Server.Middleware) - try - { - try - { + try { + try { # get the request $request = $Data.Request @@ -30,33 +27,33 @@ function Start-PodeAzFuncServer # reset event data $global:WebEvent = @{ - OnEnd = @() - Auth = @{} - Response = $response - Request = $request - Lockable = $PodeContext.Threading.Lockables.Global - Path = [string]::Empty - Method = $request.Method.ToLowerInvariant() - Query = $request.Query - Endpoint = @{ + OnEnd = @() + Auth = @{} + Response = $response + Request = $request + Lockable = $PodeContext.Threading.Lockables.Global + Path = [string]::Empty + Method = $request.Method.ToLowerInvariant() + Query = $request.Query + Endpoint = @{ Protocol = ($request.Url -split '://')[0] - Address = $null - Name = $null + Address = $null + Name = $null } - ContentType = $null - ErrorType = $null - Cookies = @{} - PendingCookies = @{} - Parameters = $null - Data = $null - Files = $null - Streamed = $false - Route = $null - StaticContent = $null - Timestamp = [datetime]::UtcNow + ContentType = $null + ErrorType = $null + Cookies = @{} + PendingCookies = @{} + Parameters = $null + Data = $null + Files = $null + Streamed = $false + Route = $null + StaticContent = $null + Timestamp = [datetime]::UtcNow TransferEncoding = $null - AcceptEncoding = $null - Ranges = $null + AcceptEncoding = $null + Ranges = $null } $WebEvent.Endpoint.Address = ((Get-PodeHeader -Name 'host') -split ':')[0] @@ -82,8 +79,7 @@ function Start-PodeAzFuncServer # invoke global and route middleware if ((Invoke-PodeMiddleware -Middleware $PodeContext.Server.Middleware -Route $WebEvent.Path)) { - if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) - { + if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) { # invoke the route if ($null -ne $WebEvent.StaticContent) { if ($WebEvent.StaticContent.IsDownload) { @@ -123,10 +119,9 @@ function Start-PodeAzFuncServer } } -function Start-PodeAwsLambdaServer -{ - param ( - [Parameter(Mandatory=$true)] +function Start-PodeAwsLambdaServer { + param( + [Parameter(Mandatory = $true)] $Data ) @@ -141,49 +136,47 @@ function Start-PodeAwsLambdaServer $PodeContext.Server.Middleware = ($inbuilt_middleware + $PodeContext.Server.Middleware) - try - { - try - { + try { + try { # get the request $request = $Data # setup the response $response = @{ StatusCode = 200 - Headers = @{} - Body = [string]::Empty + Headers = @{} + Body = [string]::Empty } # reset event data $global:WebEvent = @{ - OnEnd = @() - Auth = @{} - Response = $response - Request = $request - Lockable = $PodeContext.Threading.Lockables.Global - Path = [System.Web.HttpUtility]::UrlDecode($request.path) - Method = $request.httpMethod.ToLowerInvariant() - Query = $request.queryStringParameters - Endpoint = @{ + OnEnd = @() + Auth = @{} + Response = $response + Request = $request + Lockable = $PodeContext.Threading.Lockables.Global + Path = [System.Web.HttpUtility]::UrlDecode($request.path) + Method = $request.httpMethod.ToLowerInvariant() + Query = $request.queryStringParameters + Endpoint = @{ Protocol = $null - Address = $null - Name = $null + Address = $null + Name = $null } - ContentType = $null - ErrorType = $null - Cookies = @{} - PendingCookies = @{} - Parameters = $null - Data = $null - Files = $null - Streamed = $false - Route = $null - StaticContent = $null - Timestamp = [datetime]::UtcNow + ContentType = $null + ErrorType = $null + Cookies = @{} + PendingCookies = @{} + Parameters = $null + Data = $null + Files = $null + Streamed = $false + Route = $null + StaticContent = $null + Timestamp = [datetime]::UtcNow TransferEncoding = $null - AcceptEncoding = $null - Ranges = $null + AcceptEncoding = $null + Ranges = $null } $WebEvent.Endpoint.Protocol = (Get-PodeHeader -Name 'X-Forwarded-Proto') @@ -195,8 +188,7 @@ function Start-PodeAwsLambdaServer # invoke global and route middleware if ((Invoke-PodeMiddleware -Middleware $PodeContext.Server.Middleware -Route $WebEvent.Path)) { - if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) - { + if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) { # invoke the route if ($null -ne $WebEvent.StaticContent) { if ($WebEvent.StaticContent.IsDownload) { @@ -233,10 +225,10 @@ function Start-PodeAwsLambdaServer } return (@{ - 'statusCode' = $response.StatusCode; - 'headers' = $response.Headers; - 'body' = $response.Body; - } | ConvertTo-Json -Depth 10 -Compress) + 'statusCode' = $response.StatusCode + 'headers' = $response.Headers + 'body' = $response.Body + } | ConvertTo-Json -Depth 10 -Compress) } catch { $_ | Write-PodeErrorLog diff --git a/src/Private/ServiceServer.ps1 b/src/Private/ServiceServer.ps1 index 540f93b7f..736f610fa 100644 --- a/src/Private/ServiceServer.ps1 +++ b/src/Private/ServiceServer.ps1 @@ -1,5 +1,4 @@ -function Start-PodeServiceServer -{ +function Start-PodeServiceServer { # ensure we have service handlers if (Test-PodeIsEmpty (Get-PodeHandler -Type Service)) { throw 'No Service handlers have been defined' @@ -10,10 +9,8 @@ function Start-PodeServiceServer # script for the looping server $serverScript = { - try - { - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + try { + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { # the event object $ServiceEvent = @{ Lockable = $PodeContext.Threading.Lockables.Global diff --git a/src/Private/Sessions.ps1 b/src/Private/Sessions.ps1 index 61ca339f4..61ed58f28 100644 --- a/src/Private/Sessions.ps1 +++ b/src/Private/Sessions.ps1 @@ -1,18 +1,16 @@ -function New-PodeSession -{ +function New-PodeSession { return @{ - Name = $PodeContext.Server.Sessions.Name - Id = (Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Sessions.GenerateId -Return) - Extend = $PodeContext.Server.Sessions.Info.Extend + Name = $PodeContext.Server.Sessions.Name + Id = (Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Sessions.GenerateId -Return) + Extend = $PodeContext.Server.Sessions.Info.Extend TimeStamp = [datetime]::UtcNow - Data = @{} + Data = @{} } } -function ConvertTo-PodeSessionStrictSecret -{ +function ConvertTo-PodeSessionStrictSecret { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Secret ) @@ -20,8 +18,7 @@ function ConvertTo-PodeSessionStrictSecret return "$($Secret);$($WebEvent.Request.UserAgent);$($WebEvent.Request.RemoteEndPoint.Address.IPAddressToString)" } -function Set-PodeSession -{ +function Set-PodeSession { if ($null -eq $WebEvent.Session) { throw 'there is no session available to set on the response' } @@ -50,8 +47,7 @@ function Set-PodeSession } } -function Get-PodeSession -{ +function Get-PodeSession { $secret = $PodeContext.Server.Sessions.Secret $value = $null $name = $PodeContext.Server.Sessions.Name @@ -95,18 +91,17 @@ function Get-PodeSession # generate the session data $data = @{ - Name = $name - Id = $value - Extend = $PodeContext.Server.Sessions.Info.Extend + Name = $name + Id = $value + Extend = $PodeContext.Server.Sessions.Info.Extend TimeStamp = $null - Data = @{} + Data = @{} } return $data } -function Revoke-PodeSession -{ +function Revoke-PodeSession { # do nothing if no current session if ($null -eq $WebEvent.Session) { return @@ -124,8 +119,7 @@ function Revoke-PodeSession $WebEvent.Session.Clear() } -function Set-PodeSessionDataHash -{ +function Set-PodeSessionDataHash { if ($null -eq $WebEvent.Session) { throw 'No session available to calculate data hash' } @@ -137,8 +131,7 @@ function Set-PodeSessionDataHash $WebEvent.Session.DataHash = (Invoke-PodeSHA256Hash -Value (ConvertTo-Json -InputObject $WebEvent.Session.Data.Clone() -Depth 10 -Compress)) } -function Test-PodeSessionDataHash -{ +function Test-PodeSessionDataHash { if ($null -eq $WebEvent.Session) { return $false } @@ -155,8 +148,7 @@ function Test-PodeSessionDataHash return ($WebEvent.Session.DataHash -eq $hash) } -function Set-PodeSessionHelpers -{ +function Set-PodeSessionHelpers { if ($null -eq $WebEvent.Session) { throw 'No session available to set helpers' } @@ -186,7 +178,7 @@ function Set-PodeSessionHelpers Metadata = @{ TimeStamp = $session.TimeStamp } - Data = $session.Data + Data = $session.Data } # save session data to store @@ -209,8 +201,7 @@ function Set-PodeSessionHelpers } } -function Get-PodeSessionInMemStore -{ +function Get-PodeSessionInMemStore { $store = New-Object -TypeName psobject # add in-mem storage @@ -242,7 +233,7 @@ function Get-PodeSessionInMemStore param($sessionId, $data, $expiry) $PodeContext.Server.Sessions.Store.Memory[$sessionId] = @{ - Data = $data + Data = $data Expiry = $expiry } } @@ -250,8 +241,7 @@ function Get-PodeSessionInMemStore return $store } -function Set-PodeSessionInMemClearDown -{ +function Set-PodeSessionInMemClearDown { # don't setup if serverless - as memory is short lived anyway if ($PodeContext.Server.IsServerless) { return @@ -274,18 +264,15 @@ function Set-PodeSessionInMemClearDown } } -function Test-PodeSessionsConfigured -{ +function Test-PodeSessionsConfigured { return (($null -ne $PodeContext.Server.Sessions) -and ($PodeContext.Server.Sessions.Count -gt 0)) } -function Test-PodeSessionsInUse -{ +function Test-PodeSessionsInUse { return (($null -ne $WebEvent.Session) -and ($WebEvent.Session.Count -gt 0)) } -function Get-PodeSessionData -{ +function Get-PodeSessionData { param( [Parameter()] [string] @@ -295,16 +282,14 @@ function Get-PodeSessionData return (Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Sessions.Store.Get -Arguments $SessionId -Return) } -function Get-PodeSessionMiddleware -{ +function Get-PodeSessionMiddleware { return { # if session already set, return if ($WebEvent.Session) { return $true } - try - { + try { # retrieve the current session from cookie/header $WebEvent.Session = Get-PodeSession diff --git a/src/Private/Setup.ps1 b/src/Private/Setup.ps1 index 24aa23643..340706864 100644 --- a/src/Private/Setup.ps1 +++ b/src/Private/Setup.ps1 @@ -1,6 +1,5 @@ -function Invoke-PodePackageScript -{ - param ( +function Invoke-PodePackageScript { + param( [Parameter()] [string] $ActionScript @@ -13,9 +12,8 @@ function Invoke-PodePackageScript Invoke-Expression -Command $ActionScript } -function Install-PodeLocalModules -{ - param ( +function Install-PodeLocalModules { + param( [Parameter()] $Modules = $null ) diff --git a/src/Private/SmtpServer.ps1 b/src/Private/SmtpServer.ps1 index a71646d29..66ddf908c 100644 --- a/src/Private/SmtpServer.ps1 +++ b/src/Private/SmtpServer.ps1 @@ -1,7 +1,6 @@ using namespace Pode -function Start-PodeSmtpServer -{ +function Start-PodeSmtpServer { # ensure we have smtp handlers if (Test-PodeIsEmpty (Get-PodeHandler -Type Smtp)) { throw 'No SMTP handlers have been defined' @@ -18,20 +17,20 @@ function Start-PodeSmtpServer # the endpoint $_endpoint = @{ - Key = "$($_ip):$($_.Port)" - Address = $_ip - Hostname = $_.HostName - IsIPAddress = $_.IsIPAddress - Port = $_.Port - Certificate = $_.Certificate.Raw + Key = "$($_ip):$($_.Port)" + Address = $_ip + Hostname = $_.HostName + IsIPAddress = $_.IsIPAddress + Port = $_.Port + Certificate = $_.Certificate.Raw AllowClientCertificate = $_.Certificate.AllowClientCertificate - TlsMode = $_.Certificate.TlsMode - Url = $_.Url - Protocol = $_.Protocol - Type = $_.Type - Pool = $_.Runspace.PoolName - Acknowledge = $_.Tcp.Acknowledge - SslProtocols = $_.Ssl.Protocols + TlsMode = $_.Certificate.TlsMode + Url = $_.Url + Protocol = $_.Protocol + Type = $_.Type + Pool = $_.Runspace.PoolName + Acknowledge = $_.Tcp.Acknowledge + SslProtocols = $_.Ssl.Protocols } # add endpoint to list @@ -45,8 +44,7 @@ function Start-PodeSmtpServer $listener.RequestTimeout = $PodeContext.Server.Request.Timeout $listener.RequestBodySize = $PodeContext.Server.Request.BodySize - try - { + try { # register endpoints on the listener $endpoints | ForEach-Object { $socket = [PodeSocket]::new($_.Address, $_.Port, $_.SslProtocols, [PodeProtocolType]::Smtp, $_.Certificate, $_.AllowClientCertificate, $_.TlsMode) @@ -72,50 +70,46 @@ function Start-PodeSmtpServer # script for listening out of for incoming requests $listenScript = { - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Listener, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $ThreadId ) - try - { - while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + try { + while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) { # get email $context = (Wait-PodeTask -Task $Listener.GetContextAsync($PodeContext.Tokens.Cancellation.Token)) - try - { - try - { + try { + try { $Request = $context.Request $Response = $context.Response $SmtpEvent = @{ - Response = $Response - Request = $Request - Lockable = $PodeContext.Threading.Lockables.Global - Email = @{ - From = $Request.From - To = $Request.To - Data = $Request.RawBody - Headers = $Request.Headers - Subject = $Request.Subject - IsUrgent = $Request.IsUrgent - ContentType = $Request.ContentType + Response = $Response + Request = $Request + Lockable = $PodeContext.Threading.Lockables.Global + Email = @{ + From = $Request.From + To = $Request.To + Data = $Request.RawBody + Headers = $Request.Headers + Subject = $Request.Subject + IsUrgent = $Request.IsUrgent + ContentType = $Request.ContentType ContentEncoding = $Request.ContentEncoding - Attachments = $Request.Attachments - Body = $Request.Body + Attachments = $Request.Attachments + Body = $Request.Body } - Endpoint = @{ + Endpoint = @{ Protocol = $Request.Scheme - Address = $Request.Address - Name = $null + Address = $Request.Address + Name = $null } Timestamp = [datetime]::UtcNow } @@ -178,8 +172,8 @@ function Start-PodeSmtpServer # script to keep smtp server listening until cancelled $waitScript = { - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Listener ) @@ -204,9 +198,9 @@ function Start-PodeSmtpServer # state where we're running return @(foreach ($endpoint in $endpoints) { - @{ - Url = $endpoint.Url - Pool = $endpoint.Pool - } - }) + @{ + Url = $endpoint.Url + Pool = $endpoint.Pool + } + }) } \ No newline at end of file diff --git a/src/Private/Streams.ps1 b/src/Private/Streams.ps1 index ac40aadcc..6e44da324 100644 --- a/src/Private/Streams.ps1 +++ b/src/Private/Streams.ps1 @@ -1,6 +1,5 @@ -function Read-PodeStreamToEnd -{ - param ( +function Read-PodeStreamToEnd { + param( [Parameter()] $Stream, @@ -13,14 +12,13 @@ function Read-PodeStreamToEnd } return (Use-PodeStream -Stream ([System.IO.StreamReader]::new($Stream, $Encoding)) { - return $args[0].ReadToEnd() - }) + return $args[0].ReadToEnd() + }) } -function Read-PodeByteLineFromByteArray -{ - param ( - [Parameter(Mandatory=$true)] +function Read-PodeByteLineFromByteArray { + param( + [Parameter(Mandatory = $true)] [byte[]] $Bytes, @@ -52,16 +50,15 @@ function Read-PodeByteLineFromByteArray # grab the portion of the bytes array - which is our line return @{ - Bytes = $Bytes[$StartIndex..$fIndex]; - StartIndex = $StartIndex; - EndIndex = $index; + Bytes = $Bytes[$StartIndex..$fIndex] + StartIndex = $StartIndex + EndIndex = $index } } -function Get-PodeByteLinesFromByteArray -{ - param ( - [Parameter(Mandatory=$true)] +function Get-PodeByteLinesFromByteArray { + param( + [Parameter(Mandatory = $true)] [byte[]] $Bytes, @@ -90,17 +87,16 @@ function Get-PodeByteLinesFromByteArray } # add the line, and get the next one - $lines += ,$Bytes[$index..$fIndex] + $lines += , $Bytes[$index..$fIndex] $index = $nextIndex + 1 } return $lines } -function ConvertFrom-PodeStreamToBytes -{ - param ( - [Parameter(Mandatory=$true)] +function ConvertFrom-PodeStreamToBytes { + param( + [Parameter(Mandatory = $true)] $Stream ) @@ -116,9 +112,8 @@ function ConvertFrom-PodeStreamToBytes return $ms.ToArray() } -function ConvertFrom-PodeValueToBytes -{ - param ( +function ConvertFrom-PodeValueToBytes { + param( [Parameter()] [string] $Value, @@ -130,9 +125,8 @@ function ConvertFrom-PodeValueToBytes return $Encoding.GetBytes($Value) } -function ConvertFrom-PodeBytesToString -{ - param ( +function ConvertFrom-PodeBytesToString { + param( [Parameter()] [byte[]] $Bytes, @@ -156,22 +150,20 @@ function ConvertFrom-PodeBytesToString return $value } -function Get-PodeNewLineBytes -{ - param ( +function Get-PodeNewLineBytes { + param( [Parameter()] $Encoding = [System.Text.Encoding]::UTF8 ) return @{ - NewLine = @($Encoding.GetBytes("`n"))[0]; - Return = @($Encoding.GetBytes("`r"))[0]; + NewLine = @($Encoding.GetBytes("`n"))[0] + Return = @($Encoding.GetBytes("`r"))[0] } } -function Test-PodeByteArrayIsBoundary -{ - param ( +function Test-PodeByteArrayIsBoundary { + param( [Parameter()] [byte[]] $Bytes, @@ -198,9 +190,8 @@ function Test-PodeByteArrayIsBoundary return (ConvertFrom-PodeBytesToString $Bytes $Encoding).StartsWith($Boundary) } -function Remove-PodeNewLineBytesFromArray -{ - param ( +function Remove-PodeNewLineBytesFromArray { + param( [Parameter()] $Bytes, diff --git a/src/Private/Tasks.ps1 b/src/Private/Tasks.ps1 index e12e377d8..2ac4245a0 100644 --- a/src/Private/Tasks.ps1 +++ b/src/Private/Tasks.ps1 @@ -1,10 +1,8 @@ -function Test-PodeTasksExist -{ +function Test-PodeTasksExist { return (($null -ne $PodeContext.Tasks) -and (($PodeContext.Tasks.Enabled) -or ($PodeContext.Tasks.Items.Count -gt 0))) } -function Start-PodeTaskHousekeeper -{ +function Start-PodeTaskHousekeeper { if (!(Test-PodeTasksExist)) { return } @@ -46,8 +44,7 @@ function Start-PodeTaskHousekeeper } } -function Close-PodeTaskInternal -{ +function Close-PodeTaskInternal { param( [Parameter()] [hashtable] @@ -63,10 +60,9 @@ function Close-PodeTaskInternal $null = $PodeContext.Tasks.Results.Remove($Result.ID) } -function Invoke-PodeInternalTask -{ +function Invoke-PodeInternalTask { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Task, [Parameter()] @@ -83,7 +79,7 @@ function Invoke-PodeInternalTask $parameters = @{ Event = @{ Lockable = $PodeContext.Threading.Lockables.Global - Sender = $Task + Sender = $Task } } @@ -118,13 +114,13 @@ function Invoke-PodeInternalTask } $PodeContext.Tasks.Results[$name] = @{ - ID = $name - Task = $Task.Name - Runspace = $runspace - Result = $result + ID = $name + Task = $Task.Name + Runspace = $runspace + Result = $result CompletedTime = $null - ExpireTime = $expireTime - Timeout = $Timeout + ExpireTime = $expireTime + Timeout = $Timeout } return $PodeContext.Tasks.Results[$name] @@ -134,12 +130,11 @@ function Invoke-PodeInternalTask } } -function Wait-PodeNetTaskInternal -{ +function Wait-PodeNetTaskInternal { [CmdletBinding()] [OutputType([object])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.Threading.Tasks.Task] $Task, @@ -181,12 +176,11 @@ function Wait-PodeNetTaskInternal } } -function Wait-PodeTaskInternal -{ +function Wait-PodeTaskInternal { [CmdletBinding()] [OutputType([object])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [hashtable] $Task, diff --git a/src/Private/TcpServer.ps1 b/src/Private/TcpServer.ps1 index 773284117..640e6ca26 100644 --- a/src/Private/TcpServer.ps1 +++ b/src/Private/TcpServer.ps1 @@ -1,7 +1,6 @@ using namespace Pode -function Start-PodeTcpServer -{ +function Start-PodeTcpServer { # work out which endpoints to listen on $endpoints = @() @@ -13,21 +12,21 @@ function Start-PodeTcpServer # the endpoint $_endpoint = @{ - Key = "$($_ip):$($_.Port)" - Address = $_ip - Hostname = $_.HostName - IsIPAddress = $_.IsIPAddress - Port = $_.Port - Certificate = $_.Certificate.Raw + Key = "$($_ip):$($_.Port)" + Address = $_ip + Hostname = $_.HostName + IsIPAddress = $_.IsIPAddress + Port = $_.Port + Certificate = $_.Certificate.Raw AllowClientCertificate = $_.Certificate.AllowClientCertificate - TlsMode = $_.Certificate.TlsMode - Url = $_.Url - Protocol = $_.Protocol - Type = $_.Type - Pool = $_.Runspace.PoolName - Acknowledge = $_.Tcp.Acknowledge - CRLFMessageEnd = $_.Tcp.CRLFMessageEnd - SslProtocols = $_.Ssl.Protocols + TlsMode = $_.Certificate.TlsMode + Url = $_.Url + Protocol = $_.Protocol + Type = $_.Type + Pool = $_.Runspace.PoolName + Acknowledge = $_.Tcp.Acknowledge + CRLFMessageEnd = $_.Tcp.CRLFMessageEnd + SslProtocols = $_.Ssl.Protocols } # add endpoint to list @@ -41,8 +40,7 @@ function Start-PodeTcpServer $listener.RequestTimeout = $PodeContext.Server.Request.Timeout $listener.RequestBodySize = $PodeContext.Server.Request.BodySize - try - { + try { # register endpoints on the listener $endpoints | ForEach-Object { $socket = [PodeSocket]::new($_.Address, $_.Port, $_.SslProtocols, [PodeProtocolType]::Tcp, $_.Certificate, $_.AllowClientCertificate, $_.TlsMode) @@ -69,41 +67,37 @@ function Start-PodeTcpServer # script for listening out of for incoming requests $listenScript = { - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Listener, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $ThreadId ) - try - { - while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + try { + while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) { # get email $context = (Wait-PodeTask -Task $Listener.GetContextAsync($PodeContext.Tokens.Cancellation.Token)) - try - { - try - { + try { + try { $Request = $context.Request $Response = $context.Response $TcpEvent = @{ - Response = $Response - Request = $Request - Lockable = $PodeContext.Threading.Lockables.Global - Endpoint = @{ + Response = $Response + Request = $Request + Lockable = $PodeContext.Threading.Lockables.Global + Endpoint = @{ Protocol = $Request.Scheme - Address = $Request.Address - Name = $null + Address = $Request.Address + Name = $null } Parameters = $null - Timestamp = [datetime]::UtcNow + Timestamp = [datetime]::UtcNow } # endpoint name @@ -197,8 +191,8 @@ function Start-PodeTcpServer # script to keep tcp server listening until cancelled $waitScript = { - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Listener ) @@ -223,9 +217,9 @@ function Start-PodeTcpServer # state where we're running return @(foreach ($endpoint in $endpoints) { - @{ - Url = $endpoint.Url - Pool = $endpoint.Pool - } - }) + @{ + Url = $endpoint.Url + Pool = $endpoint.Pool + } + }) } diff --git a/src/Private/Timers.ps1 b/src/Private/Timers.ps1 index c7181ae89..191d49685 100644 --- a/src/Private/Timers.ps1 +++ b/src/Private/Timers.ps1 @@ -1,7 +1,6 @@ -function Find-PodeTimer -{ +function Find-PodeTimer { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Name @@ -10,20 +9,17 @@ function Find-PodeTimer return $PodeContext.Timers.Items[$Name] } -function Test-PodeTimersExist -{ +function Test-PodeTimersExist { return (($null -ne $PodeContext.Timers) -and (($PodeContext.Timers.Enabled) -or ($PodeContext.Timers.Items.Count -gt 0))) } -function Start-PodeTimerRunspace -{ +function Start-PodeTimerRunspace { if (!(Test-PodeTimersExist)) { return } $script = { - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { $_now = [DateTime]::Now # only run timers that haven't completed, and have a next trigger in the past @@ -65,10 +61,9 @@ function Start-PodeTimerRunspace Add-PodeRunspace -Type Main -ScriptBlock $script } -function Invoke-PodeInternalTimer -{ +function Invoke-PodeInternalTimer { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] $Timer, [Parameter()] @@ -79,7 +74,7 @@ function Invoke-PodeInternalTimer try { $global:TimerEvent = @{ Lockable = $PodeContext.Threading.Lockables.Global - Sender = $Timer + Sender = $Timer } # add main timer args diff --git a/src/Private/Verbs.ps1 b/src/Private/Verbs.ps1 index af077b1b2..002e61571 100644 --- a/src/Private/Verbs.ps1 +++ b/src/Private/Verbs.ps1 @@ -1,7 +1,6 @@ -function Find-PodeVerb -{ +function Find-PodeVerb { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Verb, @@ -19,11 +18,11 @@ function Find-PodeVerb # otherwise, match regex on the verbs (first match only) $valid = @(foreach ($key in $PodeContext.Server.Verbs.Keys) { - if (($key -ine '*') -and ($Verb -imatch "^$($key)$")) { - $key - break - } - })[0] + if (($key -ine '*') -and ($Verb -imatch "^$($key)$")) { + $key + break + } + })[0] if ($null -eq $valid) { return $null @@ -38,8 +37,7 @@ function Find-PodeVerb return $found } -function Get-PodeVerbByLiteral -{ +function Get-PodeVerbByLiteral { param( [Parameter()] [hashtable[]] @@ -59,8 +57,7 @@ function Get-PodeVerbByLiteral return (Get-PodeVerbsByLiteral -Verbs $Verbs -EndpointName $EndpointName) } -function Get-PodeVerbsByLiteral -{ +function Get-PodeVerbsByLiteral { param( [Parameter()] [hashtable[]] @@ -90,10 +87,9 @@ function Get-PodeVerbsByLiteral return $null } -function Test-PodeVerbAndError -{ +function Test-PodeVerbAndError { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Verb, diff --git a/src/Private/WebSockets.ps1 b/src/Private/WebSockets.ps1 index fc21c187d..df3beff39 100644 --- a/src/Private/WebSockets.ps1 +++ b/src/Private/WebSockets.ps1 @@ -1,14 +1,12 @@ using namespace Pode -function Test-PodeWebSocketsExist -{ +function Test-PodeWebSocketsExist { return (($null -ne $PodeContext.Server.WebSockets) -and (($PodeContext.Server.WebSockets.Enabled) -or ($PodeContext.Server.WebSockets.Connections.Count -gt 0))) } -function Find-PodeWebSocket -{ +function Find-PodeWebSocket { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -16,8 +14,7 @@ function Find-PodeWebSocket return $PodeContext.Server.WebSockets.Connections[$Name] } -function New-PodeWebSocketReceiver -{ +function New-PodeWebSocketReceiver { if ($null -ne $PodeContext.Server.WebSockets.Receiver) { return } @@ -37,8 +34,7 @@ function New-PodeWebSocketReceiver } } -function Start-PodeWebSocketRunspace -{ +function Start-PodeWebSocketRunspace { if (!(Test-PodeWebSocketsExist)) { return } @@ -46,31 +42,27 @@ function Start-PodeWebSocketRunspace # script for listening out of for incoming requests $receiveScript = { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Receiver, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $ThreadId ) - try - { - while ($Receiver.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) - { + try { + while ($Receiver.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) { # get request $request = (Wait-PodeTask -Task $Receiver.GetWebSocketRequestAsync($PodeContext.Tokens.Cancellation.Token)) - try - { - try - { + try { + try { $WsEvent = @{ - Request = $request - Data = $null - Files = $null - Lockable = $PodeContext.Threading.Lockables.Global + Request = $request + Data = $null + Files = $null + Lockable = $PodeContext.Threading.Lockables.Global Timestamp = [datetime]::UtcNow } @@ -117,7 +109,7 @@ function Start-PodeWebSocketRunspace # script to keep websocket server receiving until cancelled $waitScript = { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNull()] $Receiver ) diff --git a/src/Public/Access.ps1 b/src/Public/Access.ps1 index 2da389099..90bed1981 100644 --- a/src/Public/Access.ps1 +++ b/src/Public/Access.ps1 @@ -35,33 +35,32 @@ $scope_access = New-PodeAccessScheme -Type Scope -Scriptblock { param($user) ret .EXAMPLE $custom_access = New-PodeAccessScheme -Custom -Path 'CustomProp' #> -function New-PodeAccessScheme -{ - [CmdletBinding(DefaultParameterSetName='Type_Path')] +function New-PodeAccessScheme { + [CmdletBinding(DefaultParameterSetName = 'Type_Path')] param( - [Parameter(Mandatory=$true, ParameterSetName='Type_Scriptblock')] - [Parameter(Mandatory=$true, ParameterSetName='Type_Path')] + [Parameter(Mandatory = $true, ParameterSetName = 'Type_Scriptblock')] + [Parameter(Mandatory = $true, ParameterSetName = 'Type_Path')] [ValidateSet('Role', 'Group', 'Scope', 'User')] [string] $Type, - [Parameter(Mandatory=$true, ParameterSetName='Custom_Scriptblock')] - [Parameter(Mandatory=$true, ParameterSetName='Custom_Path')] + [Parameter(Mandatory = $true, ParameterSetName = 'Custom_Scriptblock')] + [Parameter(Mandatory = $true, ParameterSetName = 'Custom_Path')] [switch] $Custom, - [Parameter(Mandatory=$true, ParameterSetName='Custom_Scriptblock')] - [Parameter(ParameterSetName='Type_Scriptblock')] + [Parameter(Mandatory = $true, ParameterSetName = 'Custom_Scriptblock')] + [Parameter(ParameterSetName = 'Type_Scriptblock')] [scriptblock] $ScriptBlock, - [Parameter(ParameterSetName='Custom_Scriptblock')] - [Parameter(ParameterSetName='Type_Scriptblock')] + [Parameter(ParameterSetName = 'Custom_Scriptblock')] + [Parameter(ParameterSetName = 'Type_Scriptblock')] [object[]] $ArgumentList, - [Parameter(Mandatory=$true, ParameterSetName='Custom_Path')] - [Parameter(ParameterSetName='Type_Path')] + [Parameter(Mandatory = $true, ParameterSetName = 'Custom_Path')] + [Parameter(ParameterSetName = 'Type_Path')] [string] $Path ) @@ -69,7 +68,7 @@ function New-PodeAccessScheme # for custom access a validator is mandatory if ($Custom) { if ([string]::IsNullOrWhiteSpace($Path) -and (Test-PodeIsEmpty $ScriptBlock)) { - throw "A Path or ScriptBlock is required for sourcing the Custom access values" + throw 'A Path or ScriptBlock is required for sourcing the Custom access values' } } @@ -78,7 +77,7 @@ function New-PodeAccessScheme if (!(Test-PodeIsEmpty $ScriptBlock)) { $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState $scriptObj = @{ - Script = $ScriptBlock + Script = $ScriptBlock UsingVariables = $usingScriptVars } } @@ -95,11 +94,11 @@ function New-PodeAccessScheme # return scheme return @{ - Type = $Type - IsCustom = $Custom.IsPresent + Type = $Type + IsCustom = $Custom.IsPresent ScriptBlock = $scriptObj - Arguments = $ArgumentList - Path = $Path + Arguments = $ArgumentList + Path = $Path } } @@ -141,27 +140,26 @@ New-PodeAccessScheme -Type Scope -Scriptblock { param($user) return @(Get-Exampl .EXAMPLE New-PodeAccessScheme -Custom -Path 'CustomProp' | Add-PodeAccess -Name 'Example' -ScriptBlock { param($userAccess, $customAccess) return $userAccess.Country -ieq $customAccess.Country } #> -function Add-PodeAccess -{ - [CmdletBinding(DefaultParameterSetName='Match')] +function Add-PodeAccess { + [CmdletBinding(DefaultParameterSetName = 'Match')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Scheme, - [Parameter(Mandatory=$true, ParameterSetName='ScriptBlock')] + [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')] [scriptblock] $ScriptBlock, - [Parameter(ParameterSetName='ScriptBlock')] + [Parameter(ParameterSetName = 'ScriptBlock')] [object[]] $ArgumentList, - [Parameter(ParameterSetName='Match')] + [Parameter(ParameterSetName = 'Match')] [ValidateSet('All', 'One', 'None')] [string] $Match = 'One' @@ -177,21 +175,21 @@ function Add-PodeAccess if (!(Test-PodeIsEmpty $ScriptBlock)) { $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState $scriptObj = @{ - Script = $ScriptBlock + Script = $ScriptBlock UsingVariables = $usingScriptVars } } # add access object $PodeContext.Server.Authorisations.Methods[$Name] = @{ - Name = $Name - Scheme = $Scheme + Name = $Name + Scheme = $Scheme ScriptBlock = $scriptObj - Arguments = $ArgumentList - Match = $Match.ToLowerInvariant() - Cache = @{} - Merged = $false - Parent = $null + Arguments = $ArgumentList + Match = $Match.ToLowerInvariant() + Cache = @{} + Merged = $false + Parent = $null } } @@ -216,15 +214,14 @@ How many of the Access methods are required to be valid, One or All. (Default: O .EXAMPLE Merge-PodeAccess -Name MergedAccess -Access RbacAccess, GbacAccess -Valid All #> -function Merge-PodeAccess -{ +function Merge-PodeAccess { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string[]] $Access, @@ -253,12 +250,12 @@ function Merge-PodeAccess # add auth method to server $PodeContext.Server.Authorisations.Methods[$Name] = @{ - Name = $Name - Access = @($Access) + Name = $Name + Access = @($Access) PassOne = ($Valid -ieq 'one') - Cache = @{} - Merged = $true - Parent = $null + Cache = @{} + Merged = $true + Parent = $null } } @@ -281,19 +278,18 @@ The Custom Access Value(s) .EXAMPLE Add-PodeRoute -Method Get -Path '/users' -ScriptBlock {} -PassThru | Add-PodeAccessCustom -Name 'Example' -Value @{ Country = 'UK' } #> -function Add-PodeAccessCustom -{ +function Add-PodeAccessCustom { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable[]] $Route, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [object[]] $Value ) @@ -336,8 +332,7 @@ $methods = Get-PodeAccess -Name 'Example' .EXAMPLE $methods = Get-PodeAccess -Name 'Example1', 'Example2' #> -function Get-PodeAccess -{ +function Get-PodeAccess { [CmdletBinding()] param( [Parameter()] @@ -352,8 +347,8 @@ function Get-PodeAccess # return filtered return @(foreach ($n in $Name) { - $PodeContext.Server.Authorisations.Methods[$n] - }) + $PodeContext.Server.Authorisations.Methods[$n] + }) } <# @@ -369,11 +364,10 @@ The Name of the Access method. .EXAMPLE if (Test-PodeAccessExists -Name 'Example') { } #> -function Test-PodeAccessExists -{ +function Test-PodeAccessExists { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -403,11 +397,10 @@ An optional array of arguments to supply to the Access Scheme's ScriptBlock for .EXAMPLE if (Test-PodeAccess -Name 'Example' -Source 'Developer' -Destination 'Admin') { } #> -function Test-PodeAccess -{ +function Test-PodeAccess { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -443,7 +436,7 @@ function Test-PodeAccess # check for custom validator, or use default match logic if ($null -ne $access.ScriptBlock) { - $_args = @(,$Source) + @(,$Destination) + @($access.Arguments) + $_args = @(, $Source) + @(, $Destination) + @($access.Arguments) $_args = @(Get-PodeScriptblockArguments -ArgumentList $_args -UsingVariables $access.ScriptBlock.UsingVariables) return [bool](Invoke-PodeScriptBlock -ScriptBlock $access.ScriptBlock.Script -Arguments $_args -Return -Splat) } @@ -506,15 +499,14 @@ An array of access values to pass to the Access method for verification against .EXAMPLE if (Test-PodeAccessUser -Name 'Example' -Value 'Developer', 'QA') { } #> -function Test-PodeAccessUser -{ +function Test-PodeAccessUser { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [object[]] $Value ) @@ -557,10 +549,9 @@ The Name of the Access method to use to verify the access. .EXAMPLE if (Test-PodeAccessRoute -Name 'Example') { } #> -function Test-PodeAccessRoute -{ +function Test-PodeAccessRoute { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -598,11 +589,10 @@ The Name of the Access method. .EXAMPLE Remove-PodeAccess -Name 'RBAC' #> -function Remove-PodeAccess -{ +function Remove-PodeAccess { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name ) @@ -620,8 +610,7 @@ Clear all defined Access methods. .EXAMPLE Clear-PodeAccess #> -function Clear-PodeAccess -{ +function Clear-PodeAccess { [CmdletBinding()] param() @@ -650,15 +639,14 @@ Add-PodeAccessMiddleware -Name 'GlobalAccess' -Access AccessName .EXAMPLE Add-PodeAccessMiddleware -Name 'GlobalAccess' -Access AccessName -Route '/api/*' #> -function Add-PodeAccessMiddleware -{ +function Add-PodeAccessMiddleware { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Access, @@ -692,8 +680,7 @@ Use-PodeAccess .EXAMPLE Use-PodeAccess -Path './my-access' #> -function Use-PodeAccess -{ +function Use-PodeAccess { [CmdletBinding()] param( [Parameter()] diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index 59371f37c..0451f5a80 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -119,57 +119,56 @@ $form_auth = New-PodeAuthScheme -Form -UsernameField 'Email' .EXAMPLE $custom_auth = New-PodeAuthScheme -Custom -ScriptBlock { /* logic */ } #> -function New-PodeAuthScheme -{ - [CmdletBinding(DefaultParameterSetName='Basic')] +function New-PodeAuthScheme { + [CmdletBinding(DefaultParameterSetName = 'Basic')] [OutputType([hashtable])] param( - [Parameter(ParameterSetName='Basic')] + [Parameter(ParameterSetName = 'Basic')] [switch] $Basic, - [Parameter(ParameterSetName='Basic')] + [Parameter(ParameterSetName = 'Basic')] [string] $Encoding = 'ISO-8859-1', - [Parameter(ParameterSetName='Basic')] - [Parameter(ParameterSetName='Bearer')] - [Parameter(ParameterSetName='Digest')] + [Parameter(ParameterSetName = 'Basic')] + [Parameter(ParameterSetName = 'Bearer')] + [Parameter(ParameterSetName = 'Digest')] [string] $HeaderTag, - [Parameter(ParameterSetName='Form')] + [Parameter(ParameterSetName = 'Form')] [switch] $Form, - [Parameter(ParameterSetName='Form')] + [Parameter(ParameterSetName = 'Form')] [string] $UsernameField = 'username', - [Parameter(ParameterSetName='Form')] + [Parameter(ParameterSetName = 'Form')] [string] $PasswordField = 'password', - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [switch] $Custom, - [Parameter(Mandatory=$true, ParameterSetName='Custom')] + [Parameter(Mandatory = $true, ParameterSetName = 'Custom')] [ValidateScript({ - if (Test-PodeIsEmpty $_) { - throw "A non-empty ScriptBlock is required for the Custom authentication scheme" - } + if (Test-PodeIsEmpty $_) { + throw 'A non-empty ScriptBlock is required for the Custom authentication scheme' + } - return $true - })] + return $true + })] [scriptblock] $ScriptBlock, - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [hashtable] $ArgumentList, - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [string] $Name, @@ -177,7 +176,7 @@ function New-PodeAuthScheme [string] $Realm, - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [ValidateSet('ApiKey', 'Http', 'OAuth2', 'OpenIdConnect')] [string] $Type = 'Http', @@ -186,98 +185,98 @@ function New-PodeAuthScheme [object[]] $Middleware, - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [scriptblock] $PostValidator = $null, - [Parameter(ParameterSetName='Digest')] + [Parameter(ParameterSetName = 'Digest')] [switch] $Digest, - [Parameter(ParameterSetName='Bearer')] + [Parameter(ParameterSetName = 'Bearer')] [switch] $Bearer, - [Parameter(ParameterSetName='ClientCertificate')] + [Parameter(ParameterSetName = 'ClientCertificate')] [switch] $ClientCertificate, - [Parameter(ParameterSetName='OAuth2', Mandatory=$true)] + [Parameter(ParameterSetName = 'OAuth2', Mandatory = $true)] [string] $ClientId, - [Parameter(ParameterSetName='OAuth2')] + [Parameter(ParameterSetName = 'OAuth2')] [string] $ClientSecret, - [Parameter(ParameterSetName='OAuth2')] + [Parameter(ParameterSetName = 'OAuth2')] [string] $RedirectUrl, - [Parameter(ParameterSetName='OAuth2')] + [Parameter(ParameterSetName = 'OAuth2')] [string] $AuthoriseUrl, - [Parameter(ParameterSetName='OAuth2', Mandatory=$true)] + [Parameter(ParameterSetName = 'OAuth2', Mandatory = $true)] [string] $TokenUrl, - [Parameter(ParameterSetName='OAuth2')] + [Parameter(ParameterSetName = 'OAuth2')] [string] $UserUrl, - [Parameter(ParameterSetName='OAuth2')] + [Parameter(ParameterSetName = 'OAuth2')] [ValidateSet('Get', 'Post')] [string] $UserUrlMethod = 'Post', - [Parameter(ParameterSetName='OAuth2')] + [Parameter(ParameterSetName = 'OAuth2')] [ValidateSet('plain', 'S256')] [string] $CodeChallengeMethod = 'S256', - [Parameter(ParameterSetName='OAuth2')] + [Parameter(ParameterSetName = 'OAuth2')] [switch] $UsePKCE, - [Parameter(ParameterSetName='OAuth2')] + [Parameter(ParameterSetName = 'OAuth2')] [switch] $OAuth2, - [Parameter(ParameterSetName='ApiKey')] + [Parameter(ParameterSetName = 'ApiKey')] [switch] $ApiKey, - [Parameter(ParameterSetName='ApiKey')] + [Parameter(ParameterSetName = 'ApiKey')] [ValidateSet('Header', 'Query', 'Cookie')] [string] $Location = 'Header', - [Parameter(ParameterSetName='ApiKey')] + [Parameter(ParameterSetName = 'ApiKey')] [string] $LocationName, - [Parameter(ParameterSetName='Bearer')] - [Parameter(ParameterSetName='OAuth2')] + [Parameter(ParameterSetName = 'Bearer')] + [Parameter(ParameterSetName = 'OAuth2')] [string[]] $Scope, - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [hashtable] $InnerScheme, - [Parameter(ParameterSetName='Basic')] - [Parameter(ParameterSetName='Form')] + [Parameter(ParameterSetName = 'Basic')] + [Parameter(ParameterSetName = 'Form')] [switch] $AsCredential, - [Parameter(ParameterSetName='Bearer')] - [Parameter(ParameterSetName='ApiKey')] + [Parameter(ParameterSetName = 'Bearer')] + [Parameter(ParameterSetName = 'ApiKey')] [switch] $AsJWT, - [Parameter(ParameterSetName='Bearer')] - [Parameter(ParameterSetName='ApiKey')] + [Parameter(ParameterSetName = 'Bearer')] + [Parameter(ParameterSetName = 'ApiKey')] [string] $Secret ) @@ -292,19 +291,19 @@ function New-PodeAuthScheme switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'basic' { return @{ - Name = (Protect-PodeValue -Value $HeaderTag -Default 'Basic') - Realm = (Protect-PodeValue -Value $Realm -Default $_realm) - ScriptBlock = @{ - Script = (Get-PodeAuthBasicType) + Name = (Protect-PodeValue -Value $HeaderTag -Default 'Basic') + Realm = (Protect-PodeValue -Value $Realm -Default $_realm) + ScriptBlock = @{ + Script = (Get-PodeAuthBasicType) UsingVariables = $null } PostValidator = $null - Middleware = $Middleware - InnerScheme = $InnerScheme - Scheme = 'http' - Arguments = @{ - HeaderTag = (Protect-PodeValue -Value $HeaderTag -Default 'Basic') - Encoding = (Protect-PodeValue -Value $Encoding -Default 'ISO-8859-1') + Middleware = $Middleware + InnerScheme = $InnerScheme + Scheme = 'http' + Arguments = @{ + HeaderTag = (Protect-PodeValue -Value $HeaderTag -Default 'Basic') + Encoding = (Protect-PodeValue -Value $Encoding -Default 'ISO-8859-1') AsCredential = $AsCredential } } @@ -312,36 +311,36 @@ function New-PodeAuthScheme 'clientcertificate' { return @{ - Name = 'Mutual' - Realm = (Protect-PodeValue -Value $Realm -Default $_realm) - ScriptBlock = @{ - Script = (Get-PodeAuthClientCertificateType) + Name = 'Mutual' + Realm = (Protect-PodeValue -Value $Realm -Default $_realm) + ScriptBlock = @{ + Script = (Get-PodeAuthClientCertificateType) UsingVariables = $null } PostValidator = $null - Middleware = $Middleware - InnerScheme = $InnerScheme - Scheme = 'http' - Arguments = @{} + Middleware = $Middleware + InnerScheme = $InnerScheme + Scheme = 'http' + Arguments = @{} } } 'digest' { return @{ - Name = 'Digest' - Realm = (Protect-PodeValue -Value $Realm -Default $_realm) - ScriptBlock = @{ - Script = (Get-PodeAuthDigestType) + Name = 'Digest' + Realm = (Protect-PodeValue -Value $Realm -Default $_realm) + ScriptBlock = @{ + Script = (Get-PodeAuthDigestType) UsingVariables = $null } PostValidator = @{ - Script = (Get-PodeAuthDigestPostValidator) + Script = (Get-PodeAuthDigestPostValidator) UsingVariables = $null } - Middleware = $Middleware - InnerScheme = $InnerScheme - Scheme = 'http' - Arguments = @{ + Middleware = $Middleware + InnerScheme = $InnerScheme + Scheme = 'http' + Arguments = @{ HeaderTag = (Protect-PodeValue -Value $HeaderTag -Default 'Digest') } } @@ -354,42 +353,42 @@ function New-PodeAuthScheme } return @{ - Name = 'Bearer' - Realm = (Protect-PodeValue -Value $Realm -Default $_realm) - ScriptBlock = @{ - Script = (Get-PodeAuthBearerType) + Name = 'Bearer' + Realm = (Protect-PodeValue -Value $Realm -Default $_realm) + ScriptBlock = @{ + Script = (Get-PodeAuthBearerType) UsingVariables = $null } PostValidator = @{ - Script = (Get-PodeAuthBearerPostValidator) + Script = (Get-PodeAuthBearerPostValidator) UsingVariables = $null } - Middleware = $Middleware - Scheme = 'http' - InnerScheme = $InnerScheme - Arguments = @{ + Middleware = $Middleware + Scheme = 'http' + InnerScheme = $InnerScheme + Arguments = @{ HeaderTag = (Protect-PodeValue -Value $HeaderTag -Default 'Bearer') - Scopes = $Scope - AsJWT = $AsJWT - Secret = $secretBytes + Scopes = $Scope + AsJWT = $AsJWT + Secret = $secretBytes } } } 'form' { return @{ - Name = 'Form' - Realm = (Protect-PodeValue -Value $Realm -Default $_realm) - ScriptBlock = @{ - Script = (Get-PodeAuthFormType) + Name = 'Form' + Realm = (Protect-PodeValue -Value $Realm -Default $_realm) + ScriptBlock = @{ + Script = (Get-PodeAuthFormType) UsingVariables = $null } PostValidator = $null - Middleware = $Middleware - InnerScheme = $InnerScheme - Scheme = 'http' - Arguments = @{ - Fields = @{ + Middleware = $Middleware + InnerScheme = $InnerScheme + Scheme = 'http' + Arguments = @{ + Fields = @{ Username = (Protect-PodeValue -Value $UsernameField -Default 'username') Password = (Protect-PodeValue -Value $PasswordField -Default 'password') } @@ -404,7 +403,7 @@ function New-PodeAuthScheme } if (($null -eq $InnerScheme) -and [string]::IsNullOrWhiteSpace($AuthoriseUrl)) { - throw "OAuth2 requires an Authorise URL to be supplied" + throw 'OAuth2 requires an Authorise URL to be supplied' } if ($UsePKCE -and !(Test-PodeSessionsConfigured)) { @@ -412,38 +411,38 @@ function New-PodeAuthScheme } if (!$UsePKCE -and [string]::IsNullOrEmpty($ClientSecret)) { - throw "OAuth2 requires a Client Secret when not using PKCE" + throw 'OAuth2 requires a Client Secret when not using PKCE' } return @{ - Name = 'OAuth2' - Realm = (Protect-PodeValue -Value $Realm -Default $_realm) - ScriptBlock = @{ - Script = (Get-PodeAuthOAuth2Type) + Name = 'OAuth2' + Realm = (Protect-PodeValue -Value $Realm -Default $_realm) + ScriptBlock = @{ + Script = (Get-PodeAuthOAuth2Type) UsingVariables = $null } PostValidator = $null - Middleware = $Middleware - Scheme = 'oauth2' - InnerScheme = $InnerScheme - Arguments = @{ + Middleware = $Middleware + Scheme = 'oauth2' + InnerScheme = $InnerScheme + Arguments = @{ Scopes = $Scope - PKCE = @{ - Enabled = $UsePKCE + PKCE = @{ + Enabled = $UsePKCE CodeChallenge = @{ Method = $CodeChallengeMethod } } Client = @{ - ID = $ClientId + ID = $ClientId Secret = $ClientSecret } - Urls = @{ - Redirect = $RedirectUrl + Urls = @{ + Redirect = $RedirectUrl Authorise = $AuthoriseUrl - Token = $TokenUrl - User = @{ - Url = $UserUrl + Token = $TokenUrl + User = @{ + Url = $UserUrl Method = (Protect-PodeValue -Value $UserUrlMethod -Default 'Post') } } @@ -455,10 +454,10 @@ function New-PodeAuthScheme # set default location name if ([string]::IsNullOrWhiteSpace($LocationName)) { $LocationName = (@{ - Header = 'X-API-KEY' - Query = 'api_key' - Cookie = 'X-API-KEY' - })[$Location] + Header = 'X-API-KEY' + Query = 'api_key' + Cookie = 'X-API-KEY' + })[$Location] } $secretBytes = $null @@ -467,21 +466,21 @@ function New-PodeAuthScheme } return @{ - Name = 'ApiKey' - Realm = (Protect-PodeValue -Value $Realm -Default $_realm) - ScriptBlock = @{ - Script = (Get-PodeAuthApiKeyType) + Name = 'ApiKey' + Realm = (Protect-PodeValue -Value $Realm -Default $_realm) + ScriptBlock = @{ + Script = (Get-PodeAuthApiKeyType) UsingVariables = $null } PostValidator = $null - Middleware = $Middleware - InnerScheme = $InnerScheme - Scheme = 'apiKey' - Arguments = @{ - Location = $Location + Middleware = $Middleware + InnerScheme = $InnerScheme + Scheme = 'apiKey' + Arguments = @{ + Location = $Location LocationName = $LocationName - AsJWT = $AsJWT - Secret = $secretBytes + AsJWT = $AsJWT + Secret = $secretBytes } } } @@ -494,20 +493,20 @@ function New-PodeAuthScheme } return @{ - Name = $Name - Realm = (Protect-PodeValue -Value $Realm -Default $_realm) - InnerScheme = $InnerScheme - Scheme = $Type.ToLowerInvariant() - ScriptBlock = @{ - Script = $ScriptBlock + Name = $Name + Realm = (Protect-PodeValue -Value $Realm -Default $_realm) + InnerScheme = $InnerScheme + Scheme = $Type.ToLowerInvariant() + ScriptBlock = @{ + Script = $ScriptBlock UsingVariables = $usingScriptVars } PostValidator = @{ - Script = $PostValidator + Script = $PostValidator UsingVariables = $usingPostVars } - Middleware = $Middleware - Arguments = $ArgumentList + Middleware = $Middleware + Arguments = $ArgumentList } } } @@ -547,8 +546,7 @@ New-PodeAuthAzureADScheme -Tenant 123-456-678 -ClientId some_id -ClientSecret 12 .EXAMPLE New-PodeAuthAzureADScheme -Tenant 123-456-678 -ClientId some_id -UsePKCE #> -function New-PodeAuthAzureADScheme -{ +function New-PodeAuthAzureADScheme { [CmdletBinding()] param( [Parameter()] @@ -556,7 +554,7 @@ function New-PodeAuthAzureADScheme [string] $Tenant = 'common', - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $ClientId, @@ -568,7 +566,7 @@ function New-PodeAuthAzureADScheme [string] $RedirectUrl, - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [hashtable] $InnerScheme, @@ -580,17 +578,17 @@ function New-PodeAuthAzureADScheme $UsePKCE ) - return (New-PodeAuthScheme ` + return New-PodeAuthScheme ` -OAuth2 ` -ClientId $ClientId ` -ClientSecret $ClientSecret ` -AuthoriseUrl "https://login.microsoftonline.com/$($Tenant)/oauth2/v2.0/authorize" ` -TokenUrl "https://login.microsoftonline.com/$($Tenant)/oauth2/v2.0/token" ` - -UserUrl "https://graph.microsoft.com/oidc/userinfo" ` + -UserUrl 'https://graph.microsoft.com/oidc/userinfo' ` -RedirectUrl $RedirectUrl ` -InnerScheme $InnerScheme ` -Middleware $Middleware ` - -UsePKCE:$UsePKCE) + -UsePKCE:$UsePKCE } <# @@ -621,11 +619,10 @@ New-PodeAuthTwitterScheme -ClientId some_id -ClientSecret 1234.abc .EXAMPLE New-PodeAuthTwitterScheme -ClientId some_id -UsePKCE #> -function New-PodeAuthTwitterScheme -{ +function New-PodeAuthTwitterScheme { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $ClientId, @@ -645,18 +642,18 @@ function New-PodeAuthTwitterScheme $UsePKCE ) - return (New-PodeAuthScheme ` + return New-PodeAuthScheme ` -OAuth2 ` -ClientId $ClientId ` -ClientSecret $ClientSecret ` - -AuthoriseUrl "https://twitter.com/i/oauth2/authorize" ` - -TokenUrl "https://api.twitter.com/2/oauth2/token" ` - -UserUrl "https://api.twitter.com/2/users/me" ` + -AuthoriseUrl 'https://twitter.com/i/oauth2/authorize' ` + -TokenUrl 'https://api.twitter.com/2/oauth2/token' ` + -UserUrl 'https://api.twitter.com/2/users/me' ` -UserUrlMethod 'Get' ` -RedirectUrl $RedirectUrl ` -Middleware $Middleware ` -Scope 'tweet.read', 'users.read' ` - -UsePKCE:$UsePKCE) + -UsePKCE:$UsePKCE } <# @@ -696,26 +693,25 @@ If supplied, successful authentication from a login page will redirect back to t .EXAMPLE New-PodeAuthScheme -Form | Add-PodeAuth -Name 'Main' -ScriptBlock { /* logic */ } #> -function Add-PodeAuth -{ +function Add-PodeAuth { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Scheme, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateScript({ - if (Test-PodeIsEmpty $_) { - throw "A non-empty ScriptBlock is required for the authentication method" - } + if (Test-PodeIsEmpty $_) { + throw 'A non-empty ScriptBlock is required for the authentication method' + } - return $true - })] + return $true + })] [scriptblock] $ScriptBlock, @@ -762,27 +758,27 @@ function Add-PodeAuth # add auth method to server $PodeContext.Server.Authentications.Methods[$Name] = @{ - Name = $Name - Scheme = $Scheme - ScriptBlock = $ScriptBlock + Name = $Name + Scheme = $Scheme + ScriptBlock = $ScriptBlock UsingVariables = $usingVars - Arguments = $ArgumentList - Sessionless = $Sessionless.IsPresent - Failure = @{ - Url = $FailureUrl + Arguments = $ArgumentList + Sessionless = $Sessionless.IsPresent + Failure = @{ + Url = $FailureUrl Message = $FailureMessage } - Success = @{ - Url = $SuccessUrl + Success = @{ + Url = $SuccessUrl UseOrigin = $SuccessUseOrigin.IsPresent } - Cache = @{} - Merged = $false - Parent = $null + Cache = @{} + Merged = $false + Parent = $null } # if the scheme is oauth2, and there's no redirect, set up a default one - if (($Scheme.Name -ieq 'oauth2') -and ($null -eq $Scheme.InnerScheme) -and [string]::IsNullOrWhiteSpace($Scheme.Arguments.Urls.Redirect)) { + if (($Scheme.Name -ieq 'oauth2') -and ($null -eq $Scheme.InnerScheme) -and [string]::IsNullOrWhiteSpace($Scheme.Arguments.Urls.Redirect)) { $path = '/oauth2/callback' $Scheme.Arguments.Urls.Redirect = $path Add-PodeRoute -Method Get -Path $path -Authentication $Name @@ -840,15 +836,14 @@ Merge-PodeAuth -Name MergedAuth -Authentication ApiTokenAuth, BasicAuth -Valid A .EXAMPLE Merge-PodeAuth -Name MergedAuth -Authentication ApiTokenAuth, BasicAuth -FailureUrl 'http://localhost:8080/login' #> -function Merge-PodeAuth -{ +function Merge-PodeAuth { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [Alias('Auth')] [string[]] $Authentication, @@ -943,7 +938,7 @@ function Merge-PodeAuth # deal with using vars in scriptblock if ($Valid -ieq 'all') { if ($null -eq $ScriptBlock) { - throw "A Scriptblock for merging multiple authenticated users into 1 object is required When Valid is All" + throw 'A Scriptblock for merging multiple authenticated users into 1 object is required When Valid is All' } $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState @@ -956,26 +951,26 @@ function Merge-PodeAuth # add auth method to server $PodeContext.Server.Authentications.Methods[$Name] = @{ - Name = $Name + Name = $Name Authentications = @($Authentication) - PassOne = ($Valid -ieq 'one') - ScriptBlock = @{ - Script = $ScriptBlock + PassOne = ($Valid -ieq 'one') + ScriptBlock = @{ + Script = $ScriptBlock UsingVariables = $usingVars } - Default = $Default - Sessionless = $Sessionless.IsPresent - Failure = @{ - Url = $FailureUrl + Default = $Default + Sessionless = $Sessionless.IsPresent + Failure = @{ + Url = $FailureUrl Message = $FailureMessage } - Success = @{ - Url = $SuccessUrl + Success = @{ + Url = $SuccessUrl UseOrigin = $SuccessUseOrigin.IsPresent } - Cache = @{} - Merged = $true - Parent = $null + Cache = @{} + Merged = $true + Parent = $null } } @@ -992,11 +987,10 @@ The Name of an Authentication method. .EXAMPLE Get-PodeAuth -Name 'Main' #> -function Get-PodeAuth -{ +function Get-PodeAuth { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -1023,11 +1017,10 @@ The Name of the Authentication method. .EXAMPLE if (Test-PodeAuthExists -Name BasicAuth) { ... } #> -function Test-PodeAuthExists -{ +function Test-PodeAuthExists { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -1055,11 +1048,10 @@ if (Test-PodeAuth -Name 'BasicAuth') { ... } .EXAMPLE if (Test-PodeAuth -Name 'FormAuth' -IgnoreSession) { ... } #> -function Test-PodeAuth -{ +function Test-PodeAuth { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -1167,15 +1159,14 @@ New-PodeAuthScheme -Form | Add-PodeAuthWindowsAd -Name 'WinAuth' -NoGroups .EXAMPLE New-PodeAuthScheme -Form | Add-PodeAuthWindowsAd -Name 'UnixAuth' -Server 'testdomain.company.com' -Domain 'testdomain' #> -function Add-PodeAuthWindowsAd -{ - [CmdletBinding(DefaultParameterSetName='Groups')] +function Add-PodeAuthWindowsAd { + [CmdletBinding(DefaultParameterSetName = 'Groups')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Scheme, @@ -1192,7 +1183,7 @@ function Add-PodeAuthWindowsAd [string] $SearchBase, - [Parameter(ParameterSetName='Groups')] + [Parameter(ParameterSetName = 'Groups')] [string[]] $Groups, @@ -1219,11 +1210,11 @@ function Add-PodeAuthWindowsAd [switch] $Sessionless, - [Parameter(ParameterSetName='NoGroups')] + [Parameter(ParameterSetName = 'NoGroups')] [switch] $NoGroups, - [Parameter(ParameterSetName='Groups')] + [Parameter(ParameterSetName = 'Groups')] [switch] $DirectGroups, @@ -1281,35 +1272,35 @@ function Add-PodeAuthWindowsAd # add Windows AD auth method to server $PodeContext.Server.Authentications.Methods[$Name] = @{ - Scheme = $Scheme + Scheme = $Scheme ScriptBlock = (Get-PodeAuthWindowsADMethod) - Arguments = @{ - Server = $Fqdn - Domain = $Domain - SearchBase = $SearchBase - Users = $Users - Groups = $Groups - NoGroups = $NoGroups - DirectGroups = $DirectGroups + Arguments = @{ + Server = $Fqdn + Domain = $Domain + SearchBase = $SearchBase + Users = $Users + Groups = $Groups + NoGroups = $NoGroups + DirectGroups = $DirectGroups KeepCredential = $KeepCredential - Provider = (Get-PodeAuthADProvider -OpenLDAP:$OpenLDAP -ADModule:$ADModule) - ScriptBlock = @{ - Script = $ScriptBlock + Provider = (Get-PodeAuthADProvider -OpenLDAP:$OpenLDAP -ADModule:$ADModule) + ScriptBlock = @{ + Script = $ScriptBlock UsingVariables = $usingVars } } Sessionless = $Sessionless - Failure = @{ - Url = $FailureUrl + Failure = @{ + Url = $FailureUrl Message = $FailureMessage } - Success = @{ - Url = $SuccessUrl + Success = @{ + Url = $SuccessUrl UseOrigin = $SuccessUseOrigin } - Cache = @{} - Merged = $false - Parent = $null + Cache = @{} + Merged = $false + Parent = $null } } @@ -1344,11 +1335,10 @@ If supplied, successful authentication from a login page will redirect back to t .EXAMPLE Add-PodeAuthSession -Name 'SessionAuth' -FailureUrl '/login' #> -function Add-PodeAuthSession -{ - [CmdletBinding(DefaultParameterSetName='Groups')] +function Add-PodeAuthSession { + [CmdletBinding(DefaultParameterSetName = 'Groups')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -1399,8 +1389,8 @@ function Add-PodeAuthSession if (!(Test-PodeSessionsInUse)) { Revoke-PodeSession return @{ - Message = "Sessions are not being used" - Code = 401 + Message = 'Sessions are not being used' + Code = 401 } } @@ -1408,8 +1398,8 @@ function Add-PodeAuthSession if (!(Test-PodeAuthUser)) { Revoke-PodeSession return @{ - Message = "Session not authenticated" - Code = 401 + Message = 'Session not authenticated' + Code = 401 } } @@ -1439,11 +1429,11 @@ function Add-PodeAuthSession -SuccessUrl $SuccessUrl ` -SuccessUseOrigin:$SuccessUseOrigin ` -ArgumentList @{ - ScriptBlock = @{ - Script = $ScriptBlock - UsingVariables = $usingVars - } + ScriptBlock = @{ + Script = $ScriptBlock + UsingVariables = $usingVars } + } } <# @@ -1459,11 +1449,10 @@ The Name of the Authentication method. .EXAMPLE Remove-PodeAuth -Name 'Login' #> -function Remove-PodeAuth -{ +function Remove-PodeAuth { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name ) @@ -1481,8 +1470,7 @@ Clear all defined Authentication methods. .EXAMPLE Clear-PodeAuth #> -function Clear-PodeAuth -{ +function Clear-PodeAuth { [CmdletBinding()] param() @@ -1511,15 +1499,14 @@ Add-PodeAuthMiddleware -Name 'GlobalAuth' -Authentication AuthName .EXAMPLE Add-PodeAuthMiddleware -Name 'GlobalAuth' -Authentication AuthName -Route '/api/*' #> -function Add-PodeAuthMiddleware -{ +function Add-PodeAuthMiddleware { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [Alias('Auth')] [string] $Authentication, @@ -1598,15 +1585,14 @@ Add-PodeAuthIIS -Name 'IISAuth' -Groups @('Developers') .EXAMPLE Add-PodeAuthIIS -Name 'IISAuth' -NoGroups #> -function Add-PodeAuthIIS -{ - [CmdletBinding(DefaultParameterSetName='Groups')] +function Add-PodeAuthIIS { + [CmdletBinding(DefaultParameterSetName = 'Groups')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(ParameterSetName='Groups')] + [Parameter(ParameterSetName = 'Groups')] [string[]] $Groups, @@ -1637,11 +1623,11 @@ function Add-PodeAuthIIS [switch] $Sessionless, - [Parameter(ParameterSetName='NoGroups')] + [Parameter(ParameterSetName = 'NoGroups')] [switch] $NoGroups, - [Parameter(ParameterSetName='Groups')] + [Parameter(ParameterSetName = 'Groups')] [switch] $DirectGroups, @@ -1657,7 +1643,7 @@ function Add-PodeAuthIIS # ensure we're on Windows! if (!(Test-PodeIsWindows)) { - throw "IIS Authentication support is for Windows only" + throw 'IIS Authentication support is for Windows only' } # ensure the name doesn't already exist @@ -1685,7 +1671,7 @@ function Add-PodeAuthIIS if (!(Test-PodeHeader -Name $header)) { return @{ Message = "No $($header) header found" - Code = 401 + Code = 401 } } @@ -1706,17 +1692,17 @@ function Add-PodeAuthIIS -Sessionless:$Sessionless ` -SuccessUseOrigin:$SuccessUseOrigin ` -ArgumentList @{ - Users = $Users - Groups = $Groups - NoGroups = $NoGroups - DirectGroups = $DirectGroups - Provider = (Get-PodeAuthADProvider -ADModule:$ADModule) - NoLocalCheck = $NoLocalCheck - ScriptBlock = @{ - Script = $ScriptBlock - UsingVariables = $usingVars - } + Users = $Users + Groups = $Groups + NoGroups = $NoGroups + DirectGroups = $DirectGroups + Provider = (Get-PodeAuthADProvider -ADModule:$ADModule) + NoLocalCheck = $NoLocalCheck + ScriptBlock = @{ + Script = $ScriptBlock + UsingVariables = $usingVars } + } } <# @@ -1768,15 +1754,14 @@ New-PodeAuthScheme -Form | Add-PodeAuthUserFile -Name 'Login' .EXAMPLE New-PodeAuthScheme -Form | Add-PodeAuthUserFile -Name 'Login' -FilePath './custom/path/users.json' #> -function Add-PodeAuthUserFile -{ +function Add-PodeAuthUserFile { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Scheme, @@ -1792,7 +1777,7 @@ function Add-PodeAuthUserFile [string[]] $Users, - [Parameter(ParameterSetName='Hmac')] + [Parameter(ParameterSetName = 'Hmac')] [string] $HmacSecret, @@ -1854,30 +1839,30 @@ function Add-PodeAuthUserFile # add Windows AD auth method to server $PodeContext.Server.Authentications.Methods[$Name] = @{ - Scheme = $Scheme + Scheme = $Scheme ScriptBlock = (Get-PodeAuthUserFileMethod) - Arguments = @{ - FilePath = $FilePath - Users = $Users - Groups = $Groups - HmacSecret = $HmacSecret + Arguments = @{ + FilePath = $FilePath + Users = $Users + Groups = $Groups + HmacSecret = $HmacSecret ScriptBlock = @{ - Script = $ScriptBlock + Script = $ScriptBlock UsingVariables = $usingVars } } Sessionless = $Sessionless - Failure = @{ - Url = $FailureUrl + Failure = @{ + Url = $FailureUrl Message = $FailureMessage } - Success = @{ - Url = $SuccessUrl + Success = @{ + Url = $SuccessUrl UseOrigin = $SuccessUseOrigin } - Cache = @{} - Merged = $false - Parent = $null + Cache = @{} + Merged = $false + Parent = $null } } @@ -1930,19 +1915,18 @@ New-PodeAuthScheme -Basic | Add-PodeAuthWindowsLocal -Name 'WinAuth' -Groups @(' .EXAMPLE New-PodeAuthScheme -Form | Add-PodeAuthWindowsLocal -Name 'WinAuth' -NoGroups #> -function Add-PodeAuthWindowsLocal -{ - [CmdletBinding(DefaultParameterSetName='Groups')] +function Add-PodeAuthWindowsLocal { + [CmdletBinding(DefaultParameterSetName = 'Groups')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Scheme, - [Parameter(ParameterSetName='Groups')] + [Parameter(ParameterSetName = 'Groups')] [string[]] $Groups, @@ -1969,7 +1953,7 @@ function Add-PodeAuthWindowsLocal [switch] $Sessionless, - [Parameter(ParameterSetName='NoGroups')] + [Parameter(ParameterSetName = 'NoGroups')] [switch] $NoGroups, @@ -1979,7 +1963,7 @@ function Add-PodeAuthWindowsLocal # ensure we're on Windows! if (!(Test-PodeIsWindows)) { - throw "Windows Local Authentication support is for Windows only" + throw 'Windows Local Authentication support is for Windows only' } # ensure the name doesn't already exist @@ -2004,29 +1988,29 @@ function Add-PodeAuthWindowsLocal # add Windows Local auth method to server $PodeContext.Server.Authentications.Methods[$Name] = @{ - Scheme = $Scheme + Scheme = $Scheme ScriptBlock = (Get-PodeAuthWindowsLocalMethod) - Arguments = @{ - Users = $Users - Groups = $Groups - NoGroups = $NoGroups + Arguments = @{ + Users = $Users + Groups = $Groups + NoGroups = $NoGroups ScriptBlock = @{ - Script = $ScriptBlock + Script = $ScriptBlock UsingVariables = $usingVars } } Sessionless = $Sessionless - Failure = @{ - Url = $FailureUrl + Failure = @{ + Url = $FailureUrl Message = $FailureMessage } - Success = @{ - Url = $SuccessUrl + Success = @{ + Url = $SuccessUrl UseOrigin = $SuccessUseOrigin } - Cache = @{} - Merged = $false - Parent = $null + Cache = @{} + Merged = $false + Parent = $null } } @@ -2052,15 +2036,14 @@ ConvertTo-PodeJwt -Header @{ alg = 'none' } -Payload @{ sub = '123'; name = 'Joh .EXAMPLE ConvertTo-PodeJwt -Header @{ alg = 'hs256' } -Payload @{ sub = '123'; name = 'John' } -Secret 'abc' #> -function ConvertTo-PodeJwt -{ +function ConvertTo-PodeJwt { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [hashtable] $Header, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [hashtable] $Payload, @@ -2070,7 +2053,7 @@ function ConvertTo-PodeJwt # validate header if ([string]::IsNullOrWhiteSpace($Header.alg)) { - throw "No algorithm supplied in JWT Header" + throw 'No algorithm supplied in JWT Header' } # convert the header @@ -2114,18 +2097,17 @@ Skip signature verification, and return the decoded payload. .EXAMPLE ConvertFrom-PodeJwt -Token "eyJ0eXAiOiJKV1QiLCJhbGciOiJoczI1NiJ9.eyJleHAiOjE2MjI1NTMyMTQsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMyJ9.LP-O8OKwix91a-SZwVK35gEClLZQmsORbW0un2Z4RkY" #> -function ConvertFrom-PodeJwt -{ - [CmdletBinding(DefaultParameterSetName='Secret')] +function ConvertFrom-PodeJwt { + [CmdletBinding(DefaultParameterSetName = 'Secret')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Token, - [Parameter(ParameterSetName='Signed')] + [Parameter(ParameterSetName = 'Signed')] $Secret = $null, - [Parameter(ParameterSetName='Ignore')] + [Parameter(ParameterSetName = 'Ignore')] [switch] $IgnoreSignature ) @@ -2135,13 +2117,13 @@ function ConvertFrom-PodeJwt # check number of parts (should be 3) if ($parts.Length -ne 3) { - throw "Invalid JWT supplied" + throw 'Invalid JWT supplied' } # convert to header $header = ConvertFrom-PodeJwtBase64Value -Value $parts[0] if ([string]::IsNullOrWhiteSpace($header.alg)) { - throw "Invalid JWT header algorithm supplied" + throw 'Invalid JWT header algorithm supplied' } # convert to payload @@ -2162,7 +2144,7 @@ function ConvertFrom-PodeJwt } if (![string]::IsNullOrWhiteSpace($signature) -and $isNoneAlg) { - throw "Expected no JWT signature to be supplied" + throw 'Expected no JWT signature to be supplied' } if ($isNoneAlg -and ($null -ne $Secret) -and ($Secret.Length -gt 0)) { @@ -2182,7 +2164,7 @@ function ConvertFrom-PodeJwt $sig = New-PodeJwtSignature -Algorithm $header.alg -Token $sig -SecretBytes $Secret if ($sig -ne $parts[2]) { - throw "Invalid JWT signature supplied" + throw 'Invalid JWT signature supplied' } # it's valid return the payload! @@ -2221,14 +2203,14 @@ function Test-PodeJwt { # validate expiry if (![string]::IsNullOrWhiteSpace($Payload.exp)) { if ($now -gt $unixStart.AddSeconds($Payload.exp)) { - throw "The JWT has expired" + throw 'The JWT has expired' } } # validate not-before if (![string]::IsNullOrWhiteSpace($Payload.nbf)) { if ($now -lt $unixStart.AddSeconds($Payload.nbf)) { - throw "The JWT is not yet valid for use" + throw 'The JWT is not yet valid for use' } } } @@ -2249,8 +2231,7 @@ Use-PodeAuth .EXAMPLE Use-PodeAuth -Path './my-auth' #> -function Use-PodeAuth -{ +function Use-PodeAuth { [CmdletBinding()] param( [Parameter()] @@ -2298,11 +2279,10 @@ ConvertFrom-PodeOIDCDiscovery -Url 'https://accounts.google.com/.well-known/open .EXAMPLE ConvertFrom-PodeOIDCDiscovery -Url 'https://accounts.google.com' -ClientId some_id -UsePKCE #> -function ConvertFrom-PodeOIDCDiscovery -{ +function ConvertFrom-PodeOIDCDiscovery { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Url, @@ -2310,7 +2290,7 @@ function ConvertFrom-PodeOIDCDiscovery [string[]] $Scope, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $ClientId, @@ -2322,7 +2302,7 @@ function ConvertFrom-PodeOIDCDiscovery [string] $RedirectUrl, - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [hashtable] $InnerScheme, @@ -2356,10 +2336,10 @@ function ConvertFrom-PodeOIDCDiscovery if (($null -ne $Scope) -and ($Scope.Length -gt 0)) { $scopes = @(foreach ($s in $Scope) { - if ($s -iin $config.scopes_supported) { - $s - } - }) + if ($s -iin $config.scopes_supported) { + $s + } + }) } # pkce code challenge method @@ -2368,7 +2348,7 @@ function ConvertFrom-PodeOIDCDiscovery $codeMethod = 'plain' } - return (New-PodeAuthScheme ` + return New-PodeAuthScheme ` -OAuth2 ` -ClientId $ClientId ` -ClientSecret $ClientSecret ` @@ -2380,7 +2360,7 @@ function ConvertFrom-PodeOIDCDiscovery -InnerScheme $InnerScheme ` -Middleware $Middleware ` -CodeChallengeMethod $codeMethod ` - -UsePKCE:$UsePKCE) + -UsePKCE:$UsePKCE } <# @@ -2396,8 +2376,7 @@ If supplied, only the Auth object in the WebEvent will be checked and the Sessio .EXAMPLE if (Test-PodeAuthUser) { ... } #> -function Test-PodeAuthUser -{ +function Test-PodeAuthUser { [CmdletBinding()] param( [switch] @@ -2435,8 +2414,7 @@ If supplied, only the Auth object in the WebEvent will be used and the Session w .EXAMPLE $user = Get-PodeAuthUser #> -function Get-PodeAuthUser -{ +function Get-PodeAuthUser { [CmdletBinding()] param( [switch] diff --git a/src/Public/AutoImport.ps1 b/src/Public/AutoImport.ps1 index 6fef4b004..a02005c60 100644 --- a/src/Public/AutoImport.ps1 +++ b/src/Public/AutoImport.ps1 @@ -11,11 +11,10 @@ The Name(s) of modules to export. .EXAMPLE Export-PodeModule -Name Mod1, Mod2 #> -function Export-PodeModule -{ +function Export-PodeModule { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string[]] $Name ) @@ -36,11 +35,10 @@ The Name(s) of snapins to export. .EXAMPLE Export-PodeSnapin -Name Mod1, Mod2 #> -function Export-PodeSnapin -{ +function Export-PodeSnapin { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string[]] $Name ) @@ -66,11 +64,10 @@ The Name(s) of functions to export. .EXAMPLE Export-PodeFunction -Name Mod1, Mod2 #> -function Export-PodeFunction -{ +function Export-PodeFunction { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string[]] $Name ) @@ -94,11 +91,10 @@ The Type of the Secret Vault to import - only option currently is SecretManageme .EXAMPLE Export-PodeSecretVault -Name Vault1, Vault2 #> -function Export-PodeSecretVault -{ +function Export-PodeSecretVault { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string[]] $Name, diff --git a/src/Public/Cookies.ps1 b/src/Public/Cookies.ps1 index edb51c37d..d319d442a 100644 --- a/src/Public/Cookies.ps1 +++ b/src/Public/Cookies.ps1 @@ -38,12 +38,11 @@ Set-PodeCookie -Name 'Views' -Value 2 -Secret 'hunter2' .EXAMPLE Set-PodeCookie -Name 'Views' -Value 2 -Duration 3600 #> -function Set-PodeCookie -{ - [CmdletBinding(DefaultParameterSetName='Duration')] +function Set-PodeCookie { + [CmdletBinding(DefaultParameterSetName = 'Duration')] [OutputType([hashtable])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -55,11 +54,11 @@ function Set-PodeCookie [string] $Secret, - [Parameter(ParameterSetName='Duration')] + [Parameter(ParameterSetName = 'Duration')] [int] $Duration = 0, - [Parameter(ParameterSetName='ExpiryDate')] + [Parameter(ParameterSetName = 'ExpiryDate')] [datetime] $ExpiryDate, @@ -124,12 +123,11 @@ Get-PodeCookie -Name 'Views' .EXAMPLE Get-PodeCookie -Name 'Views' -Secret 'hunter2' #> -function Get-PodeCookie -{ +function Get-PodeCookie { [CmdletBinding()] [OutputType([hashtable])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -181,11 +179,10 @@ Get-PodeCookieValue -Name 'Views' .EXAMPLE Get-PodeCookieValue -Name 'Views' -Secret 'hunter2' #> -function Get-PodeCookieValue -{ +function Get-PodeCookieValue { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -215,12 +212,11 @@ The name of the cookie to test for on the Request. .EXAMPLE Test-PodeCookie -Name 'Views' #> -function Test-PodeCookie -{ +function Test-PodeCookie { [CmdletBinding()] [OutputType([bool])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -242,11 +238,10 @@ The name of the cookie to be removed. .EXAMPLE Remove-PodeCookie -Name 'Views' #> -function Remove-PodeCookie -{ +function Remove-PodeCookie { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -283,12 +278,11 @@ A secret to use for attempting to unsign the cookie's value. .EXAMPLE Test-PodeCookieSigned -Name 'Views' -Secret 'hunter2' #> -function Test-PodeCookieSigned -{ +function Test-PodeCookieSigned { [CmdletBinding()] [OutputType([bool])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -328,20 +322,19 @@ Update-PodeCookieExpiry -Name 'Views' -Duration 1800 .EXAMPLE Update-PodeCookieExpiry -Name 'Views' -ExpiryDate ([datetime]::UtcNow.AddSeconds(1800)) #> -function Update-PodeCookieExpiry -{ - [CmdletBinding(DefaultParameterSetName='Duration')] +function Update-PodeCookieExpiry { + [CmdletBinding(DefaultParameterSetName = 'Duration')] [OutputType([hashtable])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(ParameterSetName='Duration')] + [Parameter(ParameterSetName = 'Duration')] [int] $Duration = 0, - [Parameter(ParameterSetName='ExpiryDate')] + [Parameter(ParameterSetName = 'ExpiryDate')] [datetime] $ExpiryDate ) @@ -394,19 +387,18 @@ Set-PodeCookieSecret -Name 'my-secret' -Value 'shhhh!' .EXAMPLE Set-PodeCookieSecret -Value 'hunter2' -Global #> -function Set-PodeCookieSecret -{ +function Set-PodeCookieSecret { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ParameterSetName='General')] + [Parameter(Mandatory = $true, ParameterSetName = 'General')] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Value, - [Parameter(ParameterSetName='Global')] + [Parameter(ParameterSetName = 'Global')] [switch] $Global ) @@ -437,16 +429,15 @@ Get-PodeCookieSecret -Name 'my-secret' .EXAMPLE Get-PodeCookieSecret -Global #> -function Get-PodeCookieSecret -{ +function Get-PodeCookieSecret { [CmdletBinding()] [OutputType([string])] param( - [Parameter(Mandatory=$true, ParameterSetName='General')] + [Parameter(Mandatory = $true, ParameterSetName = 'General')] [string] $Name, - [Parameter(ParameterSetName='Global')] + [Parameter(ParameterSetName = 'Global')] [switch] $Global ) diff --git a/src/Public/Core.ps1 b/src/Public/Core.ps1 index a0ea55e0b..86a09c7e4 100644 --- a/src/Public/Core.ps1 +++ b/src/Public/Core.ps1 @@ -62,15 +62,14 @@ Start-PodeServer -Interval 10 { /* logic */ } .EXAMPLE Start-PodeServer -Request $LambdaInput -ServerlessType AwsLambda { /* logic */ } #> -function Start-PodeServer -{ - [CmdletBinding(DefaultParameterSetName='Script')] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0, ParameterSetName='Script')] +function Start-PodeServer { + [CmdletBinding(DefaultParameterSetName = 'Script')] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, @@ -121,7 +120,7 @@ function Start-PodeServer [switch] $Browse, - [Parameter(ParameterSetName='File')] + [Parameter(ParameterSetName = 'File')] [switch] $CurrentPath ) @@ -243,8 +242,7 @@ Closes the Pode server. .EXAMPLE Close-PodeServer #> -function Close-PodeServer -{ +function Close-PodeServer { [CmdletBinding()] param() @@ -261,8 +259,7 @@ Restarts the Pode server. .EXAMPLE Restart-PodeServer #> -function Restart-PodeServer -{ +function Restart-PodeServer { [CmdletBinding()] param() @@ -324,8 +321,7 @@ Start-PodeStaticServer -Address '127.0.0.3' -Port 8000 .EXAMPLE Start-PodeStaticServer -Path '/installers' -DownloadOnly #> -function Start-PodeStaticServer -{ +function Start-PodeStaticServer { [CmdletBinding()] param( [Parameter()] @@ -423,11 +419,10 @@ pode build .EXAMPLE pode start #> -function Pode -{ +function Pode { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [ValidateSet('init', 'test', 'start', 'install', 'build')] [Alias('a')] [string] @@ -445,18 +440,18 @@ function Pode # default config data that's used to populate on init $map = @{ - 'name' = $name; - 'version' = '1.0.0'; - 'description' = ''; - 'main' = './server.ps1'; - 'scripts' = @{ - 'start' = './server.ps1'; - 'install' = 'yarn install --force --ignore-scripts --modules-folder pode_modules'; - "build" = 'psake'; - 'test' = 'invoke-pester ./tests/*.ps1' - }; - 'author' = ''; - 'license' = 'MIT'; + 'name' = $name + 'version' = '1.0.0' + 'description' = '' + 'main' = './server.ps1' + 'scripts' = @{ + 'start' = './server.ps1' + 'install' = 'yarn install --force --ignore-scripts --modules-folder pode_modules' + 'build' = 'psake' + 'test' = 'invoke-pester ./tests/*.ps1' + } + 'author' = '' + 'license' = 'MIT' } # check and load config if already exists @@ -490,8 +485,7 @@ function Pode } } - switch ($Action.ToLowerInvariant()) - { + switch ($Action.ToLowerInvariant()) { 'init' { $v = Read-Host -Prompt "name ($($map.name))" if (![string]::IsNullOrWhiteSpace($v)) { $map.name = $v } @@ -499,12 +493,12 @@ function Pode $v = Read-Host -Prompt "version ($($map.version))" if (![string]::IsNullOrWhiteSpace($v)) { $map.version = $v } - $map.description = Read-Host -Prompt "description" + $map.description = Read-Host -Prompt 'description' $v = Read-Host -Prompt "entry point ($($map.main))" if (![string]::IsNullOrWhiteSpace($v)) { $map.main = $v; $map.scripts.start = $v } - $map.author = Read-Host -Prompt "author" + $map.author = Read-Host -Prompt 'author' $v = Read-Host -Prompt "license ($($map.license))" if (![string]::IsNullOrWhiteSpace($v)) { $map.license = $v } @@ -573,11 +567,10 @@ Stops the Application from appearing on the taskbar. .EXAMPLE Show-PodeGui -Title 'MyApplication' -WindowState 'Maximized' #> -function Show-PodeGui -{ +function Show-PodeGui { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Title, @@ -761,10 +754,9 @@ Add-PodeEndpoint -Address 127.0.0.2 -Hostname dev.pode.com -Port 8443 -Protocol .EXAMPLE Add-PodeEndpoint -Address live.pode.com -Protocol Https -CertificateThumbprint '2A9467F7D3940243D6C07DE61E7FCCE292' #> -function Add-PodeEndpoint -{ - [CmdletBinding(DefaultParameterSetName='Default')] - param ( +function Add-PodeEndpoint { + [CmdletBinding(DefaultParameterSetName = 'Default')] + param( [Parameter()] [string] $Address = 'localhost', @@ -782,45 +774,45 @@ function Add-PodeEndpoint [string] $Protocol, - [Parameter(Mandatory=$true, ParameterSetName='CertFile')] + [Parameter(Mandatory = $true, ParameterSetName = 'CertFile')] [string] $Certificate = $null, - [Parameter(ParameterSetName='CertFile')] + [Parameter(ParameterSetName = 'CertFile')] [string] $CertificatePassword = $null, - [Parameter(ParameterSetName='CertFile')] + [Parameter(ParameterSetName = 'CertFile')] [string] $CertificateKey = $null, - [Parameter(Mandatory=$true, ParameterSetName='CertThumb')] + [Parameter(Mandatory = $true, ParameterSetName = 'CertThumb')] [string] $CertificateThumbprint, - [Parameter(Mandatory=$true, ParameterSetName='CertName')] + [Parameter(Mandatory = $true, ParameterSetName = 'CertName')] [string] $CertificateName, - [Parameter(ParameterSetName='CertName')] - [Parameter(ParameterSetName='CertThumb')] + [Parameter(ParameterSetName = 'CertName')] + [Parameter(ParameterSetName = 'CertThumb')] [System.Security.Cryptography.X509Certificates.StoreName] $CertificateStoreName = 'My', - [Parameter(ParameterSetName='CertName')] - [Parameter(ParameterSetName='CertThumb')] + [Parameter(ParameterSetName = 'CertName')] + [Parameter(ParameterSetName = 'CertThumb')] [System.Security.Cryptography.X509Certificates.StoreLocation] $CertificateStoreLocation = 'CurrentUser', - [Parameter(Mandatory=$true, ParameterSetName='CertRaw')] + [Parameter(Mandatory = $true, ParameterSetName = 'CertRaw')] [X509Certificate] $X509Certificate = $null, - [Parameter(ParameterSetName='CertFile')] - [Parameter(ParameterSetName='CertThumb')] - [Parameter(ParameterSetName='CertName')] - [Parameter(ParameterSetName='CertRaw')] - [Parameter(ParameterSetName='CertSelf')] + [Parameter(ParameterSetName = 'CertFile')] + [Parameter(ParameterSetName = 'CertThumb')] + [Parameter(ParameterSetName = 'CertName')] + [Parameter(ParameterSetName = 'CertRaw')] + [Parameter(ParameterSetName = 'CertSelf')] [ValidateSet('Implicit', 'Explicit')] [string] $TlsMode = 'Implicit', @@ -852,7 +844,7 @@ function Add-PodeEndpoint [switch] $Force, - [Parameter(ParameterSetName='CertSelf')] + [Parameter(ParameterSetName = 'CertSelf')] [switch] $SelfSigned, @@ -874,7 +866,7 @@ function Add-PodeEndpoint # if RedirectTo is supplied, then a Name is mandatory if (![string]::IsNullOrWhiteSpace($RedirectTo) -and [string]::IsNullOrWhiteSpace($Name)) { - throw "A Name is required for the endpoint if the RedirectTo parameter is supplied" + throw 'A Name is required for the endpoint if the RedirectTo parameter is supplied' } # get the type of endpoint @@ -925,53 +917,53 @@ function Add-PodeEndpoint # protocol must be https for client certs, or hosted behind a proxy like iis if (($Protocol -ine 'https') -and !(Test-PodeIsHosted) -and $AllowClientCertificate) { - throw "Client certificates are only supported on HTTPS endpoints" + throw 'Client certificates are only supported on HTTPS endpoints' } # explicit tls is only supported for smtp/tcp if (($type -inotin @('smtp', 'tcp')) -and ($TlsMode -ieq 'explicit')) { - throw "The Explicit TLS mode is only supported on SMTPS and TCPS endpoints" + throw 'The Explicit TLS mode is only supported on SMTPS and TCPS endpoints' } # ack message is only for smtp/tcp if (($type -inotin @('smtp', 'tcp')) -and ![string]::IsNullOrEmpty($Acknowledge)) { - throw "The Acknowledge message is only supported on SMTP and TCP endpoints" + throw 'The Acknowledge message is only supported on SMTP and TCP endpoints' } # crlf message end is only for tcp if (($type -ine 'tcp') -and $CRLFMessageEnd) { - throw "The CRLF message end check is only supported on TCP endpoints" + throw 'The CRLF message end check is only supported on TCP endpoints' } # new endpoint object $obj = @{ - Name = $Name - Description = $Description - Address = $null - RawAddress = $null - Port = $null - IsIPAddress = $true - HostName = $Hostname + Name = $Name + Description = $Description + Address = $null + RawAddress = $null + Port = $null + IsIPAddress = $true + HostName = $Hostname FriendlyName = $Hostname - Url = $null - Ssl = @{ - Enabled = (@('https', 'wss', 'smtps', 'tcps') -icontains $Protocol) + Url = $null + Ssl = @{ + Enabled = (@('https', 'wss', 'smtps', 'tcps') -icontains $Protocol) Protocols = $PodeContext.Server.Sockets.Ssl.Protocols } - Protocol = $Protocol.ToLowerInvariant() - Type = $type.ToLowerInvariant() - Runspace = @{ + Protocol = $Protocol.ToLowerInvariant() + Type = $type.ToLowerInvariant() + Runspace = @{ PoolName = (Get-PodeEndpointRunspacePoolName -Protocol $Protocol) } - Default = $Default.IsPresent - Certificate = @{ - Raw = $X509Certificate - SelfSigned = $SelfSigned + Default = $Default.IsPresent + Certificate = @{ + Raw = $X509Certificate + SelfSigned = $SelfSigned AllowClientCertificate = $AllowClientCertificate - TlsMode = $TlsMode + TlsMode = $TlsMode } - Tcp = @{ - Acknowledge = $Acknowledge + Tcp = @{ + Acknowledge = $Acknowledge CRLFMessageEnd = $CRLFMessageEnd } } @@ -1016,17 +1008,16 @@ function Add-PodeEndpoint # has this endpoint been added before? (for http/https we can just not add it again) $exists = ($PodeContext.Server.Endpoints.Values | Where-Object { ($_.FriendlyName -ieq $obj.FriendlyName) -and ($_.Port -eq $obj.Port) -and ($_.Ssl.Enabled -eq $obj.Ssl.Enabled) -and ($_.Type -ieq $obj.Type) - } | Measure-Object).Count + } | Measure-Object).Count # if we're dealing with a certificate, attempt to import it if (!(Test-PodeIsHosted) -and ($PSCmdlet.ParameterSetName -ilike 'cert*')) { # fail if protocol is not https if (@('https', 'wss', 'smtps', 'tcps') -inotcontains $Protocol) { - throw "Certificate supplied for non-HTTPS/WSS endpoint" + throw 'Certificate supplied for non-HTTPS/WSS endpoint' } - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) - { + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'certfile' { $obj.Certificate.Raw = Get-PodeCertificateByFile -Certificate $Certificate -Password $CertificatePassword -Key $CertificateKey } @@ -1119,8 +1110,7 @@ Get-PodeEndpoint -Protocol Http .EXAMPLE Get-PodeEndpoint -Name Admin, User #> -function Get-PodeEndpoint -{ +function Get-PodeEndpoint { [CmdletBinding()] param( [Parameter()] @@ -1163,23 +1153,23 @@ function Get-PodeEndpoint } $endpoints = @(foreach ($endpoint in $endpoints) { - if ($endpoint.Address.ToString() -ine $Address) { - continue - } + if ($endpoint.Address.ToString() -ine $Address) { + continue + } - $endpoint - }) + $endpoint + }) } # if we have a hostname, filter if (![string]::IsNullOrWhiteSpace($Hostname)) { $endpoints = @(foreach ($endpoint in $endpoints) { - if ($endpoint.Hostname.ToString() -ine $Hostname) { - continue - } + if ($endpoint.Hostname.ToString() -ine $Hostname) { + continue + } - $endpoint - }) + $endpoint + }) } # if we have a port, filter @@ -1193,12 +1183,12 @@ function Get-PodeEndpoint } $endpoints = @(foreach ($endpoint in $endpoints) { - if ($endpoint.Port -ne $Port) { - continue - } + if ($endpoint.Port -ne $Port) { + continue + } - $endpoint - }) + $endpoint + }) } # if we have a protocol, filter @@ -1208,25 +1198,25 @@ function Get-PodeEndpoint } $endpoints = @(foreach ($endpoint in $endpoints) { - if ($endpoint.Protocol -ine $Protocol) { - continue - } + if ($endpoint.Protocol -ine $Protocol) { + continue + } - $endpoint - }) + $endpoint + }) } # further filter by endpoint names if (($null -ne $Name) -and ($Name.Length -gt 0)) { $endpoints = @(foreach ($_name in $Name) { - foreach ($endpoint in $endpoints) { - if ($endpoint.Name -ine $_name) { - continue - } + foreach ($endpoint in $endpoints) { + if ($endpoint.Name -ine $_name) { + continue + } - $endpoint - } - }) + $endpoint + } + }) } # return diff --git a/src/Public/Events.ps1 b/src/Public/Events.ps1 index bca2b8249..fe03becab 100644 --- a/src/Public/Events.ps1 +++ b/src/Public/Events.ps1 @@ -20,20 +20,19 @@ An array of arguments to supply to the ScriptBlock. .EXAMPLE Register-PodeEvent -Type Start -Name 'Event1' -ScriptBlock { } #> -function Register-PodeEvent -{ +function Register-PodeEvent { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')] [string] $Type, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [scriptblock] $ScriptBlock, @@ -52,10 +51,10 @@ function Register-PodeEvent # add event $PodeContext.Server.Events[$Type][$Name] = @{ - Name = $Name - ScriptBlock = $ScriptBlock + Name = $Name + ScriptBlock = $ScriptBlock UsingVariables = $usingVars - Arguments = $ArgumentList + Arguments = $ArgumentList } } @@ -75,16 +74,15 @@ The Name of the event to unregister. .EXAMPLE Unregister-PodeEvent -Type Start -Name 'Event1' #> -function Unregister-PodeEvent -{ +function Unregister-PodeEvent { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')] [string] $Type, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -114,16 +112,15 @@ The Name of the event to test. .EXAMPLE Test-PodeEvent -Type Start -Name 'Event1' #> -function Test-PodeEvent -{ +function Test-PodeEvent { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')] [string] $Type, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -147,16 +144,15 @@ The Name of the event to retrieve. .EXAMPLE Get-PodeEvent -Type Start -Name 'Event1' #> -function Get-PodeEvent -{ +function Get-PodeEvent { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')] [string] $Type, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -177,11 +173,10 @@ The Type of event to clear. .EXAMPLE Clear-PodeEvent -Type Start #> -function Clear-PodeEvent -{ +function Clear-PodeEvent { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')] [string] $Type @@ -206,8 +201,7 @@ Use-PodeEvents .EXAMPLE Use-PodeEvents -Path './my-events' #> -function Use-PodeEvents -{ +function Use-PodeEvents { [CmdletBinding()] param( [Parameter()] diff --git a/src/Public/FileWatchers.ps1 b/src/Public/FileWatchers.ps1 index 6448849db..cce9fbd28 100644 --- a/src/Public/FileWatchers.ps1 +++ b/src/Public/FileWatchers.ps1 @@ -53,9 +53,8 @@ Add-PodeFileWatcher -Path '/temp/logs' -EventName Created -NotifyFilter Creation .EXAMPLE $watcher = Add-PodeFileWatcher -Path '/temp/logs' -Exclude *.txt -ScriptBlock {} -PassThru #> -function Add-PodeFileWatcher -{ - [CmdletBinding(DefaultParameterSetName='Script')] +function Add-PodeFileWatcher { + [CmdletBinding(DefaultParameterSetName = 'Script')] param( [Parameter()] [string] @@ -66,15 +65,15 @@ function Add-PodeFileWatcher [string[]] $EventName = @('Changed', 'Created', 'Deleted', 'Renamed'), - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, - [Parameter(Mandatory=$true, ParameterSetName='Script')] + [Parameter(Mandatory = $true, ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, @@ -165,22 +164,22 @@ function Add-PodeFileWatcher # add the file watcher $PodeContext.Fim.Items[$Name] = @{ - Name = $Name - Events = @($EventName) - Path = $Path - Placeholders = @{ - Path = $rgxPath + Name = $Name + Events = @($EventName) + Path = $Path + Placeholders = @{ + Path = $rgxPath Exist = $hasPlaceholders } - Script = $ScriptBlock - UsingVariables = $usingVars - Arguments = $ArgumentList - NotifyFilters = @($NotifyFilter) + Script = $ScriptBlock + UsingVariables = $usingVars + Arguments = $ArgumentList + NotifyFilters = @($NotifyFilter) IncludeSubdirectories = !$NoSubdirectories.IsPresent - InternalBufferSize = $InternalBufferSize - Exclude = $Exclude - Include = $Include - Paths = $paths + InternalBufferSize = $InternalBufferSize + Exclude = $Exclude + Include = $Include + Paths = $paths } # return? @@ -202,11 +201,10 @@ The Name of the File Watcher. .EXAMPLE if (Test-PodeFileWatcher -Name WatcherName) { } #> -function Test-PodeFileWatcher -{ +function Test-PodeFileWatcher { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -230,8 +228,7 @@ Get-PodeFileWatcher .EXAMPLE Get-PodeFileWatcher -Name Name1, Name2 #> -function Get-PodeFileWatcher -{ +function Get-PodeFileWatcher { [CmdletBinding()] param( [Parameter()] @@ -244,14 +241,14 @@ function Get-PodeFileWatcher # further filter by file watcher names if (($null -ne $Name) -and ($Name.Length -gt 0)) { $watchers = @(foreach ($_name in $Name) { - foreach ($watcher in $watchers) { - if ($watcher.Name -ine $_name) { - continue - } + foreach ($watcher in $watchers) { + if ($watcher.Name -ine $_name) { + continue + } - $watcher - } - }) + $watcher + } + }) } # return @@ -271,11 +268,10 @@ The Name of the File Watcher to be removed. .EXAMPLE Remove-PodeFileWatcher -Name 'Logs' #> -function Remove-PodeFileWatcher -{ +function Remove-PodeFileWatcher { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -293,8 +289,7 @@ Removes all File Watchers. .EXAMPLE Clear-PodeFileWatchers #> -function Clear-PodeFileWatchers -{ +function Clear-PodeFileWatchers { [CmdletBinding()] param() @@ -317,8 +312,7 @@ Use-PodeFileWatchers .EXAMPLE Use-PodeFileWatchers -Path './my-watchers' #> -function Use-PodeFileWatchers -{ +function Use-PodeFileWatchers { [CmdletBinding()] param( [Parameter()] diff --git a/src/Public/Flash.ps1 b/src/Public/Flash.ps1 index 805908861..b646c761e 100644 --- a/src/Public/Flash.ps1 +++ b/src/Public/Flash.ps1 @@ -15,15 +15,14 @@ The message to append. .EXAMPLE Add-PodeFlashMessage -Name 'error' -Message 'There was an error' #> -function Add-PodeFlashMessage -{ +function Add-PodeFlashMessage { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Message ) @@ -56,8 +55,7 @@ Clears all of the flash messages currently stored in the session. .EXAMPLE Clear-PodeFlashMessages #> -function Clear-PodeFlashMessages -{ +function Clear-PodeFlashMessages { [CmdletBinding()] param() @@ -86,12 +84,11 @@ The name of the flash messages to return. .EXAMPLE Get-PodeFlashMessage -Name 'error' #> -function Get-PodeFlashMessage -{ +function Get-PodeFlashMessage { [CmdletBinding()] [OutputType([string[]])] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [string] $Name ) @@ -126,8 +123,7 @@ Returns all of the names for each of the messages currently being stored. This d .EXAMPLE Get-PodeFlashMessageNames #> -function Get-PodeFlashMessageNames -{ +function Get-PodeFlashMessageNames { [CmdletBinding()] [OutputType([string[]])] param() @@ -158,11 +154,10 @@ The name of the flash messages to remove. .EXAMPLE Remove-PodeFlashMessage -Name 'error' #> -function Remove-PodeFlashMessage -{ +function Remove-PodeFlashMessage { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [string] $Name ) @@ -191,12 +186,11 @@ The name of the flash message to check. .EXAMPLE Test-PodeFlashMessage -Name 'error' #> -function Test-PodeFlashMessage -{ +function Test-PodeFlashMessage { [CmdletBinding()] [OutputType([bool])] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [string] $Name ) diff --git a/src/Public/Handlers.ps1 b/src/Public/Handlers.ps1 index f1a228ab4..eb418f2f0 100644 --- a/src/Public/Handlers.ps1 +++ b/src/Public/Handlers.ps1 @@ -29,24 +29,23 @@ Add-PodeHandler -Type Service -Name 'Looper' -ScriptBlock { /* logic */ } .EXAMPLE Add-PodeHandler -Type Smtp -Name 'Main' -ScriptBlock { /* logic */ } -ArgumentList 'arg1', 'arg2' #> -function Add-PodeHandler -{ - [CmdletBinding(DefaultParameterSetName='Script')] - param ( - [Parameter(Mandatory=$true)] +function Add-PodeHandler { + [CmdletBinding(DefaultParameterSetName = 'Script')] + param( + [Parameter(Mandatory = $true)] [ValidateSet('Service', 'Smtp')] [string] $Type, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ParameterSetName='Script')] + [Parameter(Mandatory = $true, ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, @@ -74,10 +73,10 @@ function Add-PodeHandler # add the handler Write-Verbose "Adding Handler: [$($Type)] $($Name)" $PodeContext.Server.Handlers[$Type][$Name] += @(@{ - Logic = $ScriptBlock - UsingVariables = $usingVars - Arguments = $ArgumentList - }) + Logic = $ScriptBlock + UsingVariables = $usingVars + Arguments = $ArgumentList + }) } <# @@ -96,16 +95,15 @@ The name of the Handler to be removed. .EXAMPLE Remove-PodeHandler -Type Smtp -Name 'Main' #> -function Remove-PodeHandler -{ +function Remove-PodeHandler { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [ValidateSet('Service', 'Smtp')] [string] $Type, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -132,10 +130,9 @@ The Type of Handlers to remove. .EXAMPLE Clear-PodeHandlers -Type Smtp #> -function Clear-PodeHandlers -{ +function Clear-PodeHandlers { [CmdletBinding()] - param ( + param( [Parameter()] [ValidateSet('', 'Service', 'Smtp')] [string] @@ -168,8 +165,7 @@ Use-PodeHandlers .EXAMPLE Use-PodeHandlers -Path './my-handlers' #> -function Use-PodeHandlers -{ +function Use-PodeHandlers { [CmdletBinding()] param( [Parameter()] diff --git a/src/Public/Headers.ps1 b/src/Public/Headers.ps1 index 76bbc6e04..223eeb8b9 100644 --- a/src/Public/Headers.ps1 +++ b/src/Public/Headers.ps1 @@ -17,15 +17,14 @@ If supplied, the secret with which to sign the header's value. .EXAMPLE Add-PodeHeader -Name 'X-AuthToken' -Value 'AA-BB-CC-33' #> -function Add-PodeHeader -{ +function Add-PodeHeader { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Value, @@ -64,11 +63,10 @@ If supplied, the secret with which to sign the header values. .EXAMPLE Add-PodeHeaderBulk -Values @{ Name1 = 'Value1'; Name2 = 'Value2' } #> -function Add-PodeHeaderBulk -{ +function Add-PodeHeaderBulk { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [hashtable] $Values, @@ -108,12 +106,11 @@ The name of the header to test. .EXAMPLE Test-PodeHeader -Name 'X-AuthToken' #> -function Test-PodeHeader -{ +function Test-PodeHeader { [CmdletBinding()] [OutputType([bool])] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [string] $Name ) @@ -138,12 +135,11 @@ The secret used to unsign the header's value. .EXAMPLE Get-PodeHeader -Name 'X-AuthToken' #> -function Get-PodeHeader -{ +function Get-PodeHeader { [CmdletBinding()] [OutputType([string])] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [string] $Name, @@ -182,15 +178,14 @@ If supplied, the secret with which to sign the header's value. .EXAMPLE Set-PodeHeader -Name 'X-AuthToken' -Value 'AA-BB-CC-33' #> -function Set-PodeHeader -{ +function Set-PodeHeader { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Value, @@ -229,11 +224,10 @@ If supplied, the secret with which to sign the header values. .EXAMPLE Set-PodeHeaderBulk -Values @{ Name1 = 'Value1'; Name2 = 'Value2' } #> -function Set-PodeHeaderBulk -{ +function Set-PodeHeaderBulk { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [hashtable] $Values, @@ -276,12 +270,11 @@ A secret to use for attempting to unsign the header's value. .EXAMPLE Test-PodeHeaderSigned -Name 'X-Header-Name' -Secret 'hunter2' #> -function Test-PodeHeaderSigned -{ +function Test-PodeHeaderSigned { [CmdletBinding()] [OutputType([bool])] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [string] $Name, diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 20c32a7d3..801002f7b 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -59,40 +59,39 @@ $file_logging = New-PodeLoggingMethod -File -Path ./logs -Name 'requests' .EXAMPLE $custom_logging = New-PodeLoggingMethod -Custom -ScriptBlock { /* logic */ } #> -function New-PodeLoggingMethod -{ - [CmdletBinding(DefaultParameterSetName='Terminal')] +function New-PodeLoggingMethod { + [CmdletBinding(DefaultParameterSetName = 'Terminal')] [OutputType([hashtable])] - param ( - [Parameter(ParameterSetName='Terminal')] + param( + [Parameter(ParameterSetName = 'Terminal')] [switch] $Terminal, - [Parameter(ParameterSetName='File')] + [Parameter(ParameterSetName = 'File')] [switch] $File, - [Parameter(ParameterSetName='File')] + [Parameter(ParameterSetName = 'File')] [string] $Path = './logs', - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $Name, - [Parameter(ParameterSetName='EventViewer')] + [Parameter(ParameterSetName = 'EventViewer')] [switch] $EventViewer, - [Parameter(ParameterSetName='EventViewer')] + [Parameter(ParameterSetName = 'EventViewer')] [string] $EventLogName = 'Application', - [Parameter(ParameterSetName='EventViewer')] + [Parameter(ParameterSetName = 'EventViewer')] [string] $Source = 'Pode', - [Parameter(ParameterSetName='EventViewer')] + [Parameter(ParameterSetName = 'EventViewer')] [int] $EventID = 0, @@ -104,55 +103,55 @@ function New-PodeLoggingMethod [int] $BatchTimeout = 0, - [Parameter(ParameterSetName='File')] + [Parameter(ParameterSetName = 'File')] [ValidateScript({ - if ($_ -lt 0) { - throw "MaxDays must be 0 or greater, but got: $($_)s" - } + if ($_ -lt 0) { + throw "MaxDays must be 0 or greater, but got: $($_)s" + } - return $true - })] + return $true + })] [int] $MaxDays = 0, - [Parameter(ParameterSetName='File')] + [Parameter(ParameterSetName = 'File')] [ValidateScript({ - if ($_ -lt 0) { - throw "MaxSize must be 0 or greater, but got: $($_)s" - } + if ($_ -lt 0) { + throw "MaxSize must be 0 or greater, but got: $($_)s" + } - return $true - })] + return $true + })] [int] $MaxSize = 0, - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [switch] $Custom, - [Parameter(Mandatory=$true, ParameterSetName='Custom')] + [Parameter(Mandatory = $true, ParameterSetName = 'Custom')] [ValidateScript({ - if (Test-PodeIsEmpty $_) { - throw "A non-empty ScriptBlock is required for the Custom logging output method" - } + if (Test-PodeIsEmpty $_) { + throw 'A non-empty ScriptBlock is required for the Custom logging output method' + } - return $true - })] + return $true + })] [scriptblock] $ScriptBlock, - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [object[]] $ArgumentList ) # batch details $batchInfo = @{ - Size = $Batch - Timeout = $BatchTimeout + Size = $Batch + Timeout = $BatchTimeout LastUpdate = $null - Items = @() - RawItems = @() + Items = @() + RawItems = @() } # return info on appropriate logging type @@ -160,8 +159,8 @@ function New-PodeLoggingMethod 'terminal' { return @{ ScriptBlock = (Get-PodeLoggingTerminalMethod) - Batch = $batchInfo - Arguments = @{} + Batch = $batchInfo + Arguments = @{} } } @@ -172,14 +171,14 @@ function New-PodeLoggingMethod return @{ ScriptBlock = (Get-PodeLoggingFileMethod) - Batch = $batchInfo - Arguments = @{ - Name = $Name - Path = $Path - MaxDays = $MaxDays - MaxSize = $MaxSize - FileId = 0 - Date = $null + Batch = $batchInfo + Arguments = @{ + Name = $Name + Path = $Path + MaxDays = $MaxDays + MaxSize = $MaxSize + FileId = 0 + Date = $null NextClearDown = [datetime]::Now.Date } } @@ -188,7 +187,7 @@ function New-PodeLoggingMethod 'eventviewer' { # only windows if (!(Test-PodeIsWindows)) { - throw "Event Viewer logging only supported on Windows" + throw 'Event Viewer logging only supported on Windows' } # create source @@ -198,11 +197,11 @@ function New-PodeLoggingMethod return @{ ScriptBlock = (Get-PodeLoggingEventViewerMethod) - Batch = $batchInfo - Arguments = @{ + Batch = $batchInfo + Arguments = @{ LogName = $EventLogName - Source = $Source - ID = $EventID + Source = $Source + ID = $EventID } } } @@ -211,10 +210,10 @@ function New-PodeLoggingMethod $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState return @{ - ScriptBlock = $ScriptBlock + ScriptBlock = $ScriptBlock UsingVariables = $usingVars - Batch = $batchInfo - Arguments = $ArgumentList + Batch = $batchInfo + Arguments = $ArgumentList } } } @@ -240,11 +239,10 @@ If supplied, the log item returned will be the raw Request item as a hashtable a .EXAMPLE New-PodeLoggingMethod -Terminal | Enable-PodeRequestLogging #> -function Enable-PodeRequestLogging -{ +function Enable-PodeRequestLogging { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Method, @@ -267,7 +265,7 @@ function Enable-PodeRequestLogging # ensure the Method contains a scriptblock if (Test-PodeIsEmpty $Method.ScriptBlock) { - throw "The supplied output Method for Request Logging requires a valid ScriptBlock" + throw 'The supplied output Method for Request Logging requires a valid ScriptBlock' } # username property @@ -277,12 +275,12 @@ function Enable-PodeRequestLogging # add the request logger $PodeContext.Server.Logging.Types[$name] = @{ - Method = $Method + Method = $Method ScriptBlock = (Get-PodeLoggingInbuiltType -Type Requests) - Properties = @{ + Properties = @{ Username = $UsernameProperty } - Arguments = @{ + Arguments = @{ Raw = $Raw } } @@ -298,8 +296,7 @@ Disables Request Logging. .EXAMPLE Disable-PodeRequestLogging #> -function Disable-PodeRequestLogging -{ +function Disable-PodeRequestLogging { [CmdletBinding()] param() @@ -325,11 +322,10 @@ If supplied, the log item returned will be the raw Error item as a hashtable and .EXAMPLE New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging #> -function Enable-PodeErrorLogging -{ +function Enable-PodeErrorLogging { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Method, @@ -352,7 +348,7 @@ function Enable-PodeErrorLogging # ensure the Method contains a scriptblock if (Test-PodeIsEmpty $Method.ScriptBlock) { - throw "The supplied output Method for Error Logging requires a valid ScriptBlock" + throw 'The supplied output Method for Error Logging requires a valid ScriptBlock' } # all errors? @@ -362,10 +358,10 @@ function Enable-PodeErrorLogging # add the error logger $PodeContext.Server.Logging.Types[$name] = @{ - Method = $Method + Method = $Method ScriptBlock = (Get-PodeLoggingInbuiltType -Type Errors) - Arguments = @{ - Raw = $Raw + Arguments = @{ + Raw = $Raw Levels = $Levels } } @@ -381,8 +377,7 @@ Disables Error Logging. .EXAMPLE Disable-PodeErrorLogging #> -function Disable-PodeErrorLogging -{ +function Disable-PodeErrorLogging { [CmdletBinding()] param() @@ -411,26 +406,25 @@ An array of arguments to supply to the Custom Logger's ScriptBlock. .EXAMPLE New-PodeLoggingMethod -Terminal | Add-PodeLogger -Name 'Main' -ScriptBlock { /* logic */ } #> -function Add-PodeLogger -{ +function Add-PodeLogger { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Method, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateScript({ - if (Test-PodeIsEmpty $_) { - throw "A non-empty ScriptBlock is required for the logging method" - } + if (Test-PodeIsEmpty $_) { + throw 'A non-empty ScriptBlock is required for the logging method' + } - return $true - })] + return $true + })] [scriptblock] $ScriptBlock, @@ -454,10 +448,10 @@ function Add-PodeLogger # add logging method to server $PodeContext.Server.Logging.Types[$Name] = @{ - Method = $Method - ScriptBlock = $ScriptBlock + Method = $Method + ScriptBlock = $ScriptBlock UsingVariables = $usingVars - Arguments = $ArgumentList + Arguments = $ArgumentList } } @@ -474,11 +468,10 @@ The Name of the Logging method. .EXAMPLE Remove-PodeLogger -Name 'LogName' #> -function Remove-PodeLogger -{ +function Remove-PodeLogger { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name ) @@ -496,8 +489,7 @@ Clears all Logging methods that have been configured. .EXAMPLE Clear-PodeLoggers #> -function Clear-PodeLoggers -{ +function Clear-PodeLoggers { [CmdletBinding()] param() @@ -529,15 +521,14 @@ try { /* logic */ } catch { $_ | Write-PodeErrorLog } .EXAMPLE [System.Exception]::new('error message') | Write-PodeErrorLog #> -function Write-PodeErrorLog -{ +function Write-PodeErrorLog { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName='Exception')] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Exception')] [System.Exception] $Exception, - [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName='Error')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Error')] [System.Management.Automation.ErrorRecord] $ErrorRecord, @@ -547,7 +538,7 @@ function Write-PodeErrorLog [string] $Level = 'Error', - [Parameter(ParameterSetName='Exception')] + [Parameter(ParameterSetName = 'Exception')] [switch] $CheckInnerException ) @@ -568,16 +559,16 @@ function Write-PodeErrorLog switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'exception' { $item = @{ - Category = $Exception.Source - Message = $Exception.Message + Category = $Exception.Source + Message = $Exception.Message StackTrace = $Exception.StackTrace } } 'error' { $item = @{ - Category = $ErrorRecord.CategoryInfo.ToString() - Message = $ErrorRecord.Exception.Message + Category = $ErrorRecord.CategoryInfo.ToString() + Message = $ErrorRecord.Exception.Message StackTrace = $ErrorRecord.ScriptStackTrace } } @@ -591,9 +582,9 @@ function Write-PodeErrorLog # add the item to be processed $null = $PodeContext.LogsToProcess.Add(@{ - Name = $name - Item = $item - }) + Name = $name + Item = $item + }) # for exceptions, check the inner exception if ($CheckInnerException -and ($null -ne $Exception.InnerException) -and ![string]::IsNullOrWhiteSpace($Exception.InnerException.Message)) { @@ -617,15 +608,14 @@ The Object to write. .EXAMPLE $object | Write-PodeLog -Name 'LogName' #> -function Write-PodeLog -{ +function Write-PodeLog { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [object] $InputObject ) @@ -637,9 +627,9 @@ function Write-PodeLog # add the item to be processed $null = $PodeContext.LogsToProcess.Add(@{ - Name = $Name - Item = $InputObject - }) + Name = $Name + Item = $InputObject + }) } <# @@ -656,11 +646,10 @@ The string Item to mask values. .EXAMPLE $value = Protect-PodeLogItem -Item 'Username=Morty, Password=Hunter2' #> -function Protect-PodeLogItem -{ +function Protect-PodeLogItem { [CmdletBinding()] param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [string] $Item ) @@ -714,8 +703,7 @@ Use-PodeLogging .EXAMPLE Use-PodeLogging -Path './my-logging' #> -function Use-PodeLogging -{ +function Use-PodeLogging { [CmdletBinding()] param( [Parameter()] diff --git a/src/Public/Metrics.ps1 b/src/Public/Metrics.ps1 index 5b5b2acd0..f30787caf 100644 --- a/src/Public/Metrics.ps1 +++ b/src/Public/Metrics.ps1 @@ -14,8 +14,7 @@ $currentUptime = Get-PodeServerUptime .EXAMPLE $totalUptime = Get-PodeServerUptime -Total #> -function Get-PodeServerUptime -{ +function Get-PodeServerUptime { [CmdletBinding()] param( [switch] @@ -40,8 +39,7 @@ Returns the number of times the server has restarted. .EXAMPLE $restarts = Get-PodeServerRestartCount #> -function Get-PodeServerRestartCount -{ +function Get-PodeServerRestartCount { [CmdletBinding()] param() @@ -70,15 +68,14 @@ $statusReqs = Get-PodeServerRequestMetric .EXAMPLE $404Reqs = Get-PodeServerRequestMetric -StatusCode 404 #> -function Get-PodeServerRequestMetric -{ - [CmdletBinding(DefaultParameterSetName='StatusCode')] +function Get-PodeServerRequestMetric { + [CmdletBinding(DefaultParameterSetName = 'StatusCode')] param( - [Parameter(ParameterSetName='StatusCode')] + [Parameter(ParameterSetName = 'StatusCode')] [int] $StatusCode = 0, - [Parameter(ParameterSetName='Total')] + [Parameter(ParameterSetName = 'Total')] [switch] $Total ) @@ -109,8 +106,7 @@ Returns the total number of Signal requests the Server has receieved. .EXAMPLE $totalReqs = Get-PodeServerSignalMetric #> -function Get-PodeServerSignalMetric -{ +function Get-PodeServerSignalMetric { [CmdletBinding()] param() @@ -133,8 +129,7 @@ Get-PodeServerActiveRequestMetric .EXAMPLE Get-PodeServerActiveRequestMetric -CountType Queued #> -function Get-PodeServerActiveRequestMetric -{ +function Get-PodeServerActiveRequestMetric { [CmdletBinding()] param( [Parameter()] @@ -177,8 +172,7 @@ Get-PodeServerActiveSignalMetric .EXAMPLE Get-PodeServerActiveSignalMetric -Type Client -CountType Queued #> -function Get-PodeServerActiveSignalMetric -{ +function Get-PodeServerActiveSignalMetric { [CmdletBinding()] param( [Parameter()] diff --git a/src/Public/Middleware.ps1 b/src/Public/Middleware.ps1 index b7a2b2e0e..9fb4c4809 100644 --- a/src/Public/Middleware.ps1 +++ b/src/Public/Middleware.ps1 @@ -20,21 +20,20 @@ Add-PodeAccessRule -Access Allow -Type IP -Values '127.0.0.1' .EXAMPLE Add-PodeAccessRule -Access Deny -Type IP -Values @('192.168.1.1', '10.10.1.0/24') #> -function Add-PodeAccessRule -{ +function Add-PodeAccessRule { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [ValidateSet('Allow', 'Deny')] [string] $Access, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('IP')] [string] $Type, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string[]] $Values ) @@ -43,8 +42,7 @@ function Add-PodeAccessRule Test-PodeIsServerless -FunctionName 'Add-PodeAccessRule' -ThrowError # call the appropriate access method - switch ($Type.ToLowerInvariant()) - { + switch ($Type.ToLowerInvariant()) { 'ip' { foreach ($ip in $Values) { Add-PodeIPAccess -Access $Access -IP $ip @@ -84,24 +82,23 @@ Add-PodeLimitRule -Type IP -Values @('192.168.1.1', '10.10.1.0/24') -Limit 50 -S .EXAMPLE Add-PodeLimitRule -Type Route -Values '/downloads' -Limit 5 -Seconds 1 #> -function Add-PodeLimitRule -{ +function Add-PodeLimitRule { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [ValidateSet('IP', 'Route', 'Endpoint')] [string] $Type, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string[]] $Values, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Limit, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Seconds, @@ -110,10 +107,8 @@ function Add-PodeLimitRule ) # call the appropriate limit method - foreach ($value in $Values) - { - switch ($Type.ToLowerInvariant()) - { + foreach ($value in $Values) { + switch ($Type.ToLowerInvariant()) { 'ip' { Test-PodeIsServerless -FunctionName 'Add-PodeLimitRule' -ThrowError Add-PodeIPLimit -IP $value -Limit $Limit -Seconds $Seconds -Group:$Group @@ -140,8 +135,7 @@ Creates and returns a new secure token for use with CSRF. .EXAMPLE $token = New-PodeCsrfToken #> -function New-PodeCsrfToken -{ +function New-PodeCsrfToken { [CmdletBinding()] [OutputType([string])] param() @@ -170,8 +164,7 @@ Returns adhoc CSRF CSRF verification Middleware, for use on Routes. $csrf = Get-PodeCsrfMiddleware Add-PodeRoute -Method Get -Path '/cpu' -Middleware $csrf -ScriptBlock { /* logic */ } #> -function Get-PodeCsrfMiddleware -{ +function Get-PodeCsrfMiddleware { [CmdletBinding()] [OutputType([hashtable])] param() @@ -189,7 +182,7 @@ function Get-PodeCsrfMiddleware # verify the token on the request, if invalid, throw a 403 $token = Get-PodeCsrfToken - if (!(Test-PodeCsrfToken -Secret $secret -Token $token)){ + if (!(Test-PodeCsrfToken -Secret $secret -Token $token)) { Set-PodeResponseStatus -Code 403 -Description 'Invalid CSRF Token' return $false } @@ -223,10 +216,9 @@ Initialize-PodeCsrf -IgnoreMethods @('Get', 'Trace') .EXAMPLE Initialize-PodeCsrf -Secret 'some-secret' -UseCookies #> -function Initialize-PodeCsrf -{ +function Initialize-PodeCsrf { [CmdletBinding()] - param ( + param( [Parameter()] [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace')] [string[]] @@ -261,9 +253,9 @@ function Initialize-PodeCsrf # set the options against the server context $PodeContext.Server.Cookies.Csrf = @{ - Name = 'pode.csrf' - UseCookies = $UseCookies - Secret = $Secret + Name = 'pode.csrf' + UseCookies = $UseCookies + Secret = $Secret IgnoredMethods = $IgnoreMethods } } @@ -290,20 +282,19 @@ Enable-PodeCsrfMiddleware -IgnoreMethods @('Get', 'Trace') .EXAMPLE Enable-PodeCsrfMiddleware -Secret 'some-secret' -UseCookies #> -function Enable-PodeCsrfMiddleware -{ +function Enable-PodeCsrfMiddleware { [CmdletBinding()] - param ( + param( [Parameter()] [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace')] [string[]] $IgnoreMethods = @('Get', 'Head', 'Options', 'Trace'), - [Parameter(ParameterSetName='Cookies')] + [Parameter(ParameterSetName = 'Cookies')] [string] $Secret, - [Parameter(ParameterSetName='Cookies')] + [Parameter(ParameterSetName = 'Cookies')] [switch] $UseCookies ) @@ -324,7 +315,7 @@ function Enable-PodeCsrfMiddleware # verify the token on the request, if invalid, throw a 403 $token = Get-PodeCsrfToken - if (!(Test-PodeCsrfToken -Secret $secret -Token $token)){ + if (!(Test-PodeCsrfToken -Secret $secret -Token $token)) { Set-PodeResponseStatus -Code 403 -Description 'Invalid CSRF Token' return $false } @@ -352,16 +343,15 @@ The ScriptBlock that will parse the body content, and return the result. .EXAMPLE Add-PodeBodyParser -ContentType 'application/json' -ScriptBlock { param($body) /* parsing logic */ } #> -function Add-PodeBodyParser -{ +function Add-PodeBodyParser { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidatePattern('^\w+\/[\w\.\+-]+$')] [string] $ContentType, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [scriptblock] $ScriptBlock ) @@ -375,7 +365,7 @@ function Add-PodeBodyParser $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState $PodeContext.Server.BodyParsers[$ContentType] = @{ - ScriptBlock = $ScriptBlock + ScriptBlock = $ScriptBlock UsingVariables = $usingVars } } @@ -393,11 +383,10 @@ The ContentType of the custom body parser. .EXAMPLE Remove-PodeBodyParser -ContentType 'application/json' #> -function Remove-PodeBodyParser -{ +function Remove-PodeBodyParser { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidatePattern('^\w+\/[\w\.\+-]+$')] [string] $ContentType @@ -442,19 +431,18 @@ Add-PodeMiddleware -Name 'BlockAgents' -ScriptBlock { /* logic */ } .EXAMPLE Add-PodeMiddleware -Name 'CheckEmailOnApi' -Route '/api/*' -ScriptBlock { /* logic */ } #> -function Add-PodeMiddleware -{ - [CmdletBinding(DefaultParameterSetName='Script')] +function Add-PodeMiddleware { + [CmdletBinding(DefaultParameterSetName = 'Script')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ParameterSetName='Script')] + [Parameter(Mandatory = $true, ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, - [Parameter(Mandatory=$true, ParameterSetName='Input', ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Input', ValueFromPipeline = $true)] [hashtable] $InputObject, @@ -475,10 +463,10 @@ function Add-PodeMiddleware # if it's a script - call New-PodeMiddleware if ($PSCmdlet.ParameterSetName -ieq 'script') { $InputObject = (New-PodeMiddlewareInternal ` - -ScriptBlock $ScriptBlock ` - -Route $Route ` - -ArgumentList $ArgumentList ` - -PSSession $PSCmdlet.SessionState) + -ScriptBlock $ScriptBlock ` + -Route $Route ` + -ArgumentList $ArgumentList ` + -PSSession $PSCmdlet.SessionState) } else { $Route = ConvertTo-PodeRouteRegex -Path $Route @@ -488,7 +476,7 @@ function Add-PodeMiddleware # ensure we have a script to run if (Test-PodeIsEmpty $InputObject.Logic) { - throw "[Middleware]: No logic supplied in ScriptBlock" + throw '[Middleware]: No logic supplied in ScriptBlock' } # set name, and override route/args @@ -520,12 +508,11 @@ Boolean. ScriptBlock should return $true to continue to the next middleware/rout .EXAMPLE New-PodeMiddleware -ScriptBlock { /* logic */ } -ArgumentList 'Email' | Add-PodeMiddleware -Name 'CheckEmail' #> -function New-PodeMiddleware -{ +function New-PodeMiddleware { [CmdletBinding()] [OutputType([hashtable])] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [scriptblock] $ScriptBlock, @@ -538,11 +525,11 @@ function New-PodeMiddleware $ArgumentList ) - return (New-PodeMiddlewareInternal ` + return New-PodeMiddlewareInternal ` -ScriptBlock $ScriptBlock ` -Route $Route ` -ArgumentList $ArgumentList ` - -PSSession $PSCmdlet.SessionState) + -PSSession $PSCmdlet.SessionState } <# @@ -558,11 +545,10 @@ The Name of the Middleware to be removed. .EXAMPLE Remove-PodeMiddleware -Name 'Sessions' #> -function Remove-PodeMiddleware -{ +function Remove-PodeMiddleware { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -580,8 +566,7 @@ Removes all user defined Middleware. .EXAMPLE Clear-PodeMiddleware #> -function Clear-PodeMiddleware -{ +function Clear-PodeMiddleware { [CmdletBinding()] param() @@ -604,8 +589,7 @@ Use-PodeMiddleware .EXAMPLE Use-PodeMiddleware -Path './my-middleware' #> -function Use-PodeMiddleware -{ +function Use-PodeMiddleware { [CmdletBinding()] param( [Parameter()] diff --git a/src/Public/OpenApi.ps1 b/src/Public/OpenApi.ps1 index d8294dc43..32bc93256 100644 --- a/src/Public/OpenApi.ps1 +++ b/src/Public/OpenApi.ps1 @@ -35,8 +35,7 @@ Enable-PodeOpenApi -Title 'My API' -Version '1.0.0' -RouteFilter '/api/*' -Restr .EXAMPLE Enable-PodeOpenApi -Path '/docs/openapi' -Title 'My API' -Version '1.0.0' #> -function Enable-PodeOpenApi -{ +function Enable-PodeOpenApi { [CmdletBinding()] param( [Parameter()] @@ -44,7 +43,7 @@ function Enable-PodeOpenApi [string] $Path = '/openapi', - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Title, @@ -75,9 +74,9 @@ function Enable-PodeOpenApi $PodeContext.Server.OpenAPI.Path = $Path $meta = @{ - Version = $Version - Description = $Description - RouteFilter = $RouteFilter + Version = $Version + Description = $Description + RouteFilter = $RouteFilter RestrictRoutes = $RestrictRoutes } @@ -127,8 +126,7 @@ If supplied, only routes that are available on the Requests URI will be used to .EXAMPLE $def = Get-PodeOpenApiDefinition -RouteFilter '/api/*' #> -function Get-PodeOpenApiDefinition -{ +function Get-PodeOpenApiDefinition { [CmdletBinding()] param( [Parameter()] @@ -154,18 +152,18 @@ function Get-PodeOpenApiDefinition $Title = Protect-PodeValue -Value $Title -Default $PodeContext.Server.OpenAPI.Title if ([string]::IsNullOrWhiteSpace($Title)) { - throw "No Title supplied for OpenAPI definition" + throw 'No Title supplied for OpenAPI definition' } $Version = Protect-PodeValue -Value $Version -Default $PodeContext.Server.OpenAPI.Version if ([string]::IsNullOrWhiteSpace($Version)) { - throw "No Version supplied for OpenAPI definition" + throw 'No Version supplied for OpenAPI definition' } $Description = Protect-PodeValue -Value $Description -Default $PodeContext.Server.OpenAPI.Description # generate the openapi definition - return (Get-PodeOpenApiDefinitionInternal ` + return Get-PodeOpenApiDefinitionInternal ` -Title $Title ` -Version $Version ` -Description $Description ` @@ -173,7 +171,7 @@ function Get-PodeOpenApiDefinition -Protocol $WebEvent.Endpoint.Protocol ` -Address $WebEvent.Endpoint.Address ` -EndpointName $WebEvent.Endpoint.Name ` - -RestrictRoutes:$RestrictRoutes) + -RestrictRoutes:$RestrictRoutes } <# @@ -216,32 +214,31 @@ Add-PodeRoute -PassThru | Add-PodeOAResponse -StatusCode 200 -ContentSchemas @{ .EXAMPLE Add-PodeRoute -PassThru | Add-PodeOAResponse -StatusCode 200 -Reference 'OKResponse' #> -function Add-PodeOAResponse -{ - [CmdletBinding(DefaultParameterSetName='Schema')] +function Add-PodeOAResponse { + [CmdletBinding(DefaultParameterSetName = 'Schema')] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $StatusCode, - [Parameter(ParameterSetName='Schema')] + [Parameter(ParameterSetName = 'Schema')] [hashtable] $ContentSchemas, - [Parameter(ParameterSetName='Schema')] + [Parameter(ParameterSetName = 'Schema')] [hashtable] $HeaderSchemas, - [Parameter(ParameterSetName='Schema')] + [Parameter(ParameterSetName = 'Schema')] [string] $Description = $null, - [Parameter(Mandatory=$true, ParameterSetName='Reference')] + [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [string] $Reference, @@ -292,8 +289,8 @@ function Add-PodeOAResponse 'schema' { $r.OpenApi.Responses[$code] = @{ description = $Description - content = $content - headers = $headers + content = $content + headers = $headers } } @@ -335,16 +332,15 @@ Add-PodeRoute -PassThru | Remove-PodeOAResponse -StatusCode 200 .EXAMPLE Add-PodeRoute -PassThru | Remove-PodeOAResponse -StatusCode 201 -Default #> -function Remove-PodeOAResponse -{ +function Remove-PodeOAResponse { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $StatusCode, @@ -398,11 +394,10 @@ Add-PodeOAComponentResponse -Name 'OKResponse' -ContentSchemas @{ 'application/j .EXAMPLE Add-PodeOAComponentResponse -Name 'ErrorResponse' -ContentSchemas @{ 'application/json' = 'ErrorSchema' } #> -function Add-PodeOAComponentResponse -{ +function Add-PodeOAComponentResponse { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -414,7 +409,7 @@ function Add-PodeOAComponentResponse [hashtable] $HeaderSchemas, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Description ) @@ -431,8 +426,8 @@ function Add-PodeOAComponentResponse $PodeContext.Server.OpenAPI.components.responses[$Name] = @{ description = $Description - content = $content - headers = $headers + content = $content + headers = $headers } } @@ -458,11 +453,10 @@ If supplied, the route passed in will be returned for further chaining. .EXAMPLE Add-PodeRoute -PassThru | Set-PodeOARequest -RequestBody (New-PodeOARequestBody -Reference 'UserIdBody') #> -function Set-PodeOARequest -{ +function Set-PodeOARequest { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, @@ -522,23 +516,22 @@ New-PodeOARequestBody -ContentSchemas @{ 'application/json' = 'UserIdSchema' } .EXAMPLE New-PodeOARequestBody -Reference 'UserIdBody' #> -function New-PodeOARequestBody -{ - [CmdletBinding(DefaultParameterSetName='Schema')] +function New-PodeOARequestBody { + [CmdletBinding(DefaultParameterSetName = 'Schema')] param( - [Parameter(Mandatory=$true, ParameterSetName='Reference')] + [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [string] $Reference, - [Parameter(Mandatory=$true, ParameterSetName='Schema')] + [Parameter(Mandatory = $true, ParameterSetName = 'Schema')] [hashtable] $ContentSchemas, - [Parameter(ParameterSetName='Schema')] + [Parameter(ParameterSetName = 'Schema')] [string] $Description = $null, - [Parameter(ParameterSetName='Schema')] + [Parameter(ParameterSetName = 'Schema')] [switch] $Required ) @@ -546,9 +539,9 @@ function New-PodeOARequestBody switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'schema' { return @{ - required = $Required.IsPresent + required = $Required.IsPresent description = $Description - content = ($ContentSchemas | ConvertTo-PodeOAContentTypeSchema) + content = ($ContentSchemas | ConvertTo-PodeOAContentTypeSchema) } } @@ -580,15 +573,14 @@ The Schema definition (the schema is created using the Property functions). .EXAMPLE Add-PodeOAComponentSchema -Name 'UserIdSchema' -Schema (New-PodeOAIntProperty -Name 'userId' -Object) #> -function Add-PodeOAComponentSchema -{ +function Add-PodeOAComponentSchema { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Schema ) @@ -621,15 +613,14 @@ Add-PodeOAComponentRequestBody -Name 'UserIdBody' -ContentSchemas @{ 'applicatio .EXAMPLE Add-PodeOAComponentRequestBody -Name 'UserIdBody' -ContentSchemas @{ 'application/json' = 'UserIdSchema' } #> -function Add-PodeOAComponentRequestBody -{ +function Add-PodeOAComponentRequestBody { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $ContentSchemas, @@ -643,9 +634,9 @@ function Add-PodeOAComponentRequestBody ) $PodeContext.Server.OpenAPI.components.requestBodies[$Name] = @{ - required = $Required.IsPresent + required = $Required.IsPresent description = $Description - content = ($ContentSchemas | ConvertTo-PodeOAContentTypeSchema) + content = ($ContentSchemas | ConvertTo-PodeOAContentTypeSchema) } } @@ -665,15 +656,14 @@ The Parameter to use for the component (from ConvertTo-PodeOAParameter) .EXAMPLE New-PodeOAIntProperty -Name 'userId' | ConvertTo-PodeOAParameter -In Query | Add-PodeOAComponentParameter -Name 'UserIdParam' #> -function Add-PodeOAComponentParameter -{ +function Add-PodeOAComponentParameter { [CmdletBinding()] param( [Parameter()] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Parameter ) @@ -731,8 +721,7 @@ If supplied, the integer will be automatically wrapped in an object. .EXAMPLE New-PodeOANumberProperty -Name 'age' -Required #> -function New-PodeOAIntProperty -{ +function New-PodeOAIntProperty { [CmdletBinding()] param( [Parameter()] @@ -782,17 +771,17 @@ function New-PodeOAIntProperty ) $param = @{ - name = $Name - type = 'integer' - array = $Array.IsPresent - object = $Object.IsPresent - required = $Required.IsPresent - deprecated = $Deprecated.IsPresent + name = $Name + type = 'integer' + array = $Array.IsPresent + object = $Object.IsPresent + required = $Required.IsPresent + deprecated = $Deprecated.IsPresent description = $Description - format = $Format.ToLowerInvariant() - default = $Default + format = $Format.ToLowerInvariant() + default = $Default - meta = @{ + meta = @{ enum = $Enum } } @@ -858,8 +847,7 @@ If supplied, the number will be automatically wrapped in an object. .EXAMPLE New-PodeOANumberProperty -Name 'gravity' -Default 9.8 #> -function New-PodeOANumberProperty -{ +function New-PodeOANumberProperty { [CmdletBinding()] param( [Parameter()] @@ -909,17 +897,17 @@ function New-PodeOANumberProperty ) $param = @{ - name = $Name - type = 'number' - array = $Array.IsPresent - object = $Object.IsPresent - required = $Required.IsPresent - deprecated = $Deprecated.IsPresent + name = $Name + type = 'number' + array = $Array.IsPresent + object = $Object.IsPresent + required = $Required.IsPresent + deprecated = $Deprecated.IsPresent description = $Description - format = $Format.ToLowerInvariant() - default = $Default + format = $Format.ToLowerInvariant() + default = $Default - meta = @{ + meta = @{ enum = $Enum } } @@ -991,20 +979,19 @@ New-PodeOAStringProperty -Name 'userType' -Default 'admin' .EXAMPLE New-PodeOAStringProperty -Name 'password' -Format Password #> -function New-PodeOAStringProperty -{ - [CmdletBinding(DefaultParameterSetName='Inbuilt')] +function New-PodeOAStringProperty { + [CmdletBinding(DefaultParameterSetName = 'Inbuilt')] param( [Parameter()] [string] $Name, - [Parameter(ParameterSetName='Inbuilt')] + [Parameter(ParameterSetName = 'Inbuilt')] [ValidateSet('', 'Binary', 'Byte', 'Date', 'Date-Time', 'Password')] [string] $Format, - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [string] $CustomFormat, @@ -1051,18 +1038,18 @@ function New-PodeOAStringProperty } $param = @{ - name = $Name - type = 'string' - array = $Array.IsPresent - object = $Object.IsPresent - required = $Required.IsPresent - deprecated = $Deprecated.IsPresent + name = $Name + type = 'string' + array = $Array.IsPresent + object = $Object.IsPresent + required = $Required.IsPresent + deprecated = $Deprecated.IsPresent description = $Description - format = $_format.ToLowerInvariant() - default = $Default + format = $_format.ToLowerInvariant() + default = $Default - meta = @{ - enum = $Enum + meta = @{ + enum = $Enum pattern = $Pattern } } @@ -1112,8 +1099,7 @@ If supplied, the boolean will be automatically wrapped in an object. .EXAMPLE New-PodeOABoolProperty -Name 'enabled' -Required #> -function New-PodeOABoolProperty -{ +function New-PodeOABoolProperty { [CmdletBinding()] param( [Parameter()] @@ -1146,16 +1132,16 @@ function New-PodeOABoolProperty ) $param = @{ - name = $Name - type = 'boolean' - array = $Array.IsPresent - object = $Object.IsPresent - required = $Required.IsPresent - deprecated = $Deprecated.IsPresent + name = $Name + type = 'boolean' + array = $Array.IsPresent + object = $Object.IsPresent + required = $Required.IsPresent + deprecated = $Deprecated.IsPresent description = $Description - default = $Default + default = $Default - meta = @{ + meta = @{ enum = $Enum } } @@ -1191,15 +1177,14 @@ If supplied, the object will be treated as an array of objects. .EXAMPLE New-PodeOAObjectProperty -Name 'user' -Properties @('') #> -function New-PodeOAObjectProperty -{ +function New-PodeOAObjectProperty { [CmdletBinding()] param( [Parameter()] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [hashtable[]] $Properties, @@ -1218,14 +1203,14 @@ function New-PodeOAObjectProperty ) $param = @{ - name = $Name - type = 'object' - array = $Array.IsPresent - required = $Required.IsPresent - deprecated = $Deprecated.IsPresent + name = $Name + type = 'object' + array = $Array.IsPresent + required = $Required.IsPresent + deprecated = $Deprecated.IsPresent description = $Description - properties = $Properties - default = $Default + properties = $Properties + default = $Default } return $param @@ -1253,15 +1238,14 @@ If supplied, the schema reference will be treated as an array. .EXAMPLE New-PodeOASchemaProperty -Name 'Config' -ComponentSchema "MyConfigSchema" #> -function New-PodeOASchemaProperty -{ +function New-PodeOASchemaProperty { [CmdletBinding()] param( [Parameter()] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Reference, @@ -1273,15 +1257,15 @@ function New-PodeOASchemaProperty $Array ) - if(!(Test-PodeOAComponentSchema -Name $Reference)) { + if (!(Test-PodeOAComponentSchema -Name $Reference)) { throw "The OpenApi component schema doesn't exist: $($Reference)" } $param = @{ - name = $Name - type = 'schema' - schema = $Reference - array = $Array.IsPresent + name = $Name + type = 'schema' + schema = $Reference + array = $Array.IsPresent description = $Description } @@ -1310,21 +1294,20 @@ New-PodeOAIntProperty -Name 'userId' | ConvertTo-PodeOAParameter -In Query .EXAMPLE ConvertTo-PodeOAParameter -Reference 'UserIdParam' #> -function ConvertTo-PodeOAParameter -{ - [CmdletBinding(DefaultParameterSetName='Reference')] +function ConvertTo-PodeOAParameter { + [CmdletBinding(DefaultParameterSetName = 'Reference')] param( - [Parameter(Mandatory=$true, ParameterSetName='Schema')] + [Parameter(Mandatory = $true, ParameterSetName = 'Schema')] [ValidateSet('Cookie', 'Header', 'Path', 'Query')] [string] $In, - [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName='Schema')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Schema')] [ValidateNotNull()] [hashtable] $Property, - [Parameter(Mandatory=$true, ParameterSetName='Reference')] + [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [string] $Reference ) @@ -1342,16 +1325,16 @@ function ConvertTo-PodeOAParameter # non-object/array only if (@('array', 'object') -icontains $Property.type) { - throw "OpenApi request parameter cannot be an array of object" + throw 'OpenApi request parameter cannot be an array of object' } # build the base parameter $prop = @{ - in = $In.ToLowerInvariant() - name = $Property.name + in = $In.ToLowerInvariant() + name = $Property.name description = $Property.description - schema = @{ - type = $Property.type + schema = @{ + type = $Property.type format = $Property.format } } @@ -1409,11 +1392,10 @@ If supplied, the route passed in will be returned for further chaining. .EXAMPLE Add-PodeRoute -PassThru | Set-PodeOARouteInfo -Summary 'A quick summary' -Tags 'Admin' #> -function Set-PodeOARouteInfo -{ +function Set-PodeOARouteInfo { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, @@ -1485,11 +1467,10 @@ Enable-PodeOpenApiViewer -Type Swagger -DarkMode .EXAMPLE Enable-PodeOpenApiViewer -Type ReDoc -Title 'Some Title' -OpenApi 'http://some-url/openapi' #> -function Enable-PodeOpenApiViewer -{ +function Enable-PodeOpenApiViewer { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Swagger', 'ReDoc')] [string] $Type, @@ -1535,9 +1516,9 @@ function Enable-PodeOpenApiViewer # setup meta info $meta = @{ - Type = $Type.ToLowerInvariant() - Title = $Title - OpenApi = $OpenApiUrl + Type = $Type.ToLowerInvariant() + Title = $Title + OpenApi = $OpenApiUrl DarkMode = $DarkMode } @@ -1546,8 +1527,8 @@ function Enable-PodeOpenApiViewer param($meta) $podeRoot = Get-PodeModuleMiscPath Write-PodeFileResponse -Path ([System.IO.Path]::Combine($podeRoot, "default-$($meta.Type).html.pode")) -Data @{ - Title = $meta.Title - OpenApi = $meta.OpenApi + Title = $meta.Title + OpenApi = $meta.OpenApi DarkMode = $meta.DarkMode } } diff --git a/src/Public/Responses.ps1 b/src/Public/Responses.ps1 index 8b05f1886..3af31830c 100644 --- a/src/Public/Responses.ps1 +++ b/src/Public/Responses.ps1 @@ -33,11 +33,10 @@ Set-PodeResponseAttachment -Path './data.txt' -ContentType 'application/json' .EXAMPLE Set-PodeResponseAttachment -Path '/assets/data.txt' -EndpointName 'Example' #> -function Set-PodeResponseAttachment -{ +function Set-PodeResponseAttachment { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Path, @@ -158,15 +157,14 @@ Write-PodeTextResponse -Bytes (Get-Content -Path ./some/image.png -Raw -AsByteSt .EXAMPLE Write-PodeTextResponse -Value 'Untitled Text Response' -StatusCode 418 #> -function Write-PodeTextResponse -{ - [CmdletBinding(DefaultParameterSetName='String')] - param ( - [Parameter(ParameterSetName='String', ValueFromPipeline=$true, Position=0)] +function Write-PodeTextResponse { + [CmdletBinding(DefaultParameterSetName = 'String')] + param( + [Parameter(ParameterSetName = 'String', ValueFromPipeline = $true, Position = 0)] [string] $Value, - [Parameter(ParameterSetName='Bytes')] + [Parameter(ParameterSetName = 'Bytes')] [byte[]] $Bytes, @@ -212,7 +210,7 @@ function Write-PodeTextResponse # set a cache value if ($Cache) { Set-PodeHeader -Name 'Cache-Control' -Value "max-age=$($MaxAge), must-revalidate" - Set-PodeHeader -Name 'Expires' -Value ([datetime]::UtcNow.AddSeconds($MaxAge).ToString("r", [CultureInfo]::InvariantCulture)) + Set-PodeHeader -Name 'Expires' -Value ([datetime]::UtcNow.AddSeconds($MaxAge).ToString('r', [CultureInfo]::InvariantCulture)) } # specify the content-type if supplied (adding utf-8 if missing) @@ -247,44 +245,44 @@ function Write-PodeTextResponse $size = $Bytes.Length $Bytes = @(foreach ($range in $WebEvent.Ranges) { - # ensure range not invalid - if (([int]$range.Start -lt 0) -or ([int]$range.Start -ge $size) -or ([int]$range.End -lt 0)) { - Set-PodeResponseStatus -Code 416 -NoErrorPage - return - } - - # skip start bytes only - if ([string]::IsNullOrWhiteSpace($range.End)) { - $Bytes[$range.Start..($size - 1)] - $lengths += "$($range.Start)-$($size - 1)/$($size)" - } + # ensure range not invalid + if (([int]$range.Start -lt 0) -or ([int]$range.Start -ge $size) -or ([int]$range.End -lt 0)) { + Set-PodeResponseStatus -Code 416 -NoErrorPage + return + } - # end bytes only - elseif ([string]::IsNullOrWhiteSpace($range.Start)) { - if ([int]$range.End -gt $size) { - $range.End = $size + # skip start bytes only + if ([string]::IsNullOrWhiteSpace($range.End)) { + $Bytes[$range.Start..($size - 1)] + $lengths += "$($range.Start)-$($size - 1)/$($size)" } - if ([int]$range.End -gt 0) { - $Bytes[$($size - $range.End)..($size - 1)] - $lengths += "$($size - $range.End)-$($size - 1)/$($size)" + # end bytes only + elseif ([string]::IsNullOrWhiteSpace($range.Start)) { + if ([int]$range.End -gt $size) { + $range.End = $size + } + + if ([int]$range.End -gt 0) { + $Bytes[$($size - $range.End)..($size - 1)] + $lengths += "$($size - $range.End)-$($size - 1)/$($size)" + } + else { + $lengths += "0-0/$($size)" + } } + + # normal range else { - $lengths += "0-0/$($size)" - } - } + if ([int]$range.End -ge $size) { + Set-PodeResponseStatus -Code 416 -NoErrorPage + return + } - # normal range - else { - if ([int]$range.End -ge $size) { - Set-PodeResponseStatus -Code 416 -NoErrorPage - return + $Bytes[$range.Start..$range.End] + $lengths += "$($range.Start)-$($range.End)/$($size)" } - - $Bytes[$range.Start..$range.End] - $lengths += "$($range.Start)-$($range.End)/$($size)" - } - }) + }) Set-PodeHeader -Name 'Content-Range' -Value "bytes $($lengths -join ', ')" if ($StatusCode -eq 200) { @@ -381,11 +379,10 @@ Write-PodeFileResponse -Path 'C:/Views/Index.pode' -Data @{ Counter = 2 } .EXAMPLE Write-PodeFileResponse -Path 'C:/Files/Stuff.txt' -StatusCode 201 #> -function Write-PodeFileResponse -{ +function Write-PodeFileResponse { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNull()] [string] $Path, @@ -422,9 +419,9 @@ function Write-PodeFileResponse # generate dynamic content if (![string]::IsNullOrWhiteSpace($mainExt) -and ( - ($mainExt -ieq 'pode') -or - ($mainExt -ieq $PodeContext.Server.ViewEngine.Extension -and $PodeContext.Server.ViewEngine.IsDynamic) - )) { + ($mainExt -ieq 'pode') -or + ($mainExt -ieq $PodeContext.Server.ViewEngine.Extension -and $PodeContext.Server.ViewEngine.IsDynamic) + )) { $content = Get-PodeFileContentUsingViewEngine -Path $Path -Data $Data # get the sub-file extension, if empty, use original @@ -474,14 +471,13 @@ Write-PodeCsvResponse -Value @{ Name = 'Rick' } .EXAMPLE Write-PodeCsvResponse -Path 'E:/Files/Names.csv' #> -function Write-PodeCsvResponse -{ - [CmdletBinding(DefaultParameterSetName='Value')] - param ( - [Parameter(Mandatory=$true, ParameterSetName='Value', ValueFromPipeline=$true, Position=0)] +function Write-PodeCsvResponse { + [CmdletBinding(DefaultParameterSetName = 'Value')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)] $Value, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $Path, @@ -500,8 +496,8 @@ function Write-PodeCsvResponse 'value' { if ($Value -isnot [string]) { $Value = @(foreach ($v in $Value) { - New-Object psobject -Property $v - }) + New-Object psobject -Property $v + }) if (Test-PodeIsPSCore) { $Value = ($Value | ConvertTo-Csv -Delimiter ',' -IncludeTypeInformation:$false) @@ -547,14 +543,13 @@ Write-PodeHtmlResponse -Value @{ Message = 'Hello, all!' } .EXAMPLE Write-PodeHtmlResponse -Path 'E:/Site/About.html' #> -function Write-PodeHtmlResponse -{ - [CmdletBinding(DefaultParameterSetName='Value')] - param ( - [Parameter(Mandatory=$true, ParameterSetName='Value', ValueFromPipeline=$true, Position=0)] +function Write-PodeHtmlResponse { + [CmdletBinding(DefaultParameterSetName = 'Value')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)] $Value, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $Path, @@ -610,14 +605,13 @@ Write-PodeMarkdownResponse -Value '# Hello, world!' -AsHtml .EXAMPLE Write-PodeMarkdownResponse -Path 'E:/Site/About.md' #> -function Write-PodeMarkdownResponse -{ - [CmdletBinding(DefaultParameterSetName='Value')] - param ( - [Parameter(Mandatory=$true, ParameterSetName='Value', ValueFromPipeline=$true, Position=0)] +function Write-PodeMarkdownResponse { + [CmdletBinding(DefaultParameterSetName = 'Value')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)] $Value, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $Path, @@ -681,14 +675,13 @@ Write-PodeJsonResponse -Value @{ Name = 'Rick' } -StatusCode 201 .EXAMPLE Write-PodeJsonResponse -Path 'E:/Files/Names.json' #> -function Write-PodeJsonResponse -{ - [CmdletBinding(DefaultParameterSetName='Value')] - param ( - [Parameter(Mandatory=$true, ParameterSetName='Value', ValueFromPipeline=$true, Position=0)] +function Write-PodeJsonResponse { + [CmdletBinding(DefaultParameterSetName = 'Value')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)] $Value, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $Path, @@ -752,14 +745,13 @@ Write-PodeXmlResponse -Value @{ Name = 'Rick' } -StatusCode 201 .EXAMPLE Write-PodeXmlResponse -Path 'E:/Files/Names.xml' #> -function Write-PodeXmlResponse -{ - [CmdletBinding(DefaultParameterSetName='Value')] - param ( - [Parameter(Mandatory=$true, ParameterSetName='Value', ValueFromPipeline=$true, Position=0)] +function Write-PodeXmlResponse { + [CmdletBinding(DefaultParameterSetName = 'Value')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)] $Value, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $Path, @@ -778,8 +770,8 @@ function Write-PodeXmlResponse 'value' { if ($Value -isnot [string]) { $Value = @(foreach ($v in $Value) { - New-Object psobject -Property $v - }) + New-Object psobject -Property $v + }) $Value = ($Value | ConvertTo-Xml -Depth 10 -As String -NoTypeInformation) } @@ -824,11 +816,10 @@ Write-PodeViewResponse -Path 'accounts/profile_page' -Data @{ Username = 'Morty' .EXAMPLE Write-PodeViewResponse -Path 'login' -FlashMessages #> -function Write-PodeViewResponse -{ +function Write-PodeViewResponse { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Path, @@ -935,11 +926,10 @@ Set-PodeResponseStatus -Code 500 -Exception $_.Exception .EXAMPLE Set-PodeResponseStatus -Code 500 -Exception $_.Exception -ContentType 'application/json' #> -function Set-PodeResponseStatus -{ +function Set-PodeResponseStatus { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Code, @@ -1018,28 +1008,27 @@ Move-PodeResponseUrl -Protocol HTTPS .EXAMPLE Move-PodeResponseUrl -Port 9000 -Moved #> -function Move-PodeResponseUrl -{ - [CmdletBinding(DefaultParameterSetName='Url')] +function Move-PodeResponseUrl { + [CmdletBinding(DefaultParameterSetName = 'Url')] param( - [Parameter(Mandatory=$true, ParameterSetName='Url')] + [Parameter(Mandatory = $true, ParameterSetName = 'Url')] [string] $Url, - [Parameter(ParameterSetName='Endpoint')] + [Parameter(ParameterSetName = 'Endpoint')] [string] $EndpointName, - [Parameter(ParameterSetName='Components')] + [Parameter(ParameterSetName = 'Components')] [int] $Port = 0, - [Parameter(ParameterSetName='Components')] + [Parameter(ParameterSetName = 'Components')] [ValidateSet('', 'Http', 'Https')] [string] $Protocol, - [Parameter(ParameterSetName='Components')] + [Parameter(ParameterSetName = 'Components')] [string] $Address, @@ -1112,11 +1101,10 @@ The message to write .EXAMPLE Write-PodeTcpClient -Message '250 OK' #> -function Write-PodeTcpClient -{ +function Write-PodeTcpClient { [CmdletBinding()] param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [string] $Message ) @@ -1146,20 +1134,19 @@ $data = Read-PodeTcpClient .EXAMPLE $data = Read-PodeTcpClient -CRLFMessageEnd #> -function Read-PodeTcpClient -{ - [CmdletBinding(DefaultParameterSetName='default')] +function Read-PodeTcpClient { + [CmdletBinding(DefaultParameterSetName = 'default')] [OutputType([string])] param( [Parameter()] [int] $Timeout = 0, - [Parameter(ParameterSetName='CheckBytes')] + [Parameter(ParameterSetName = 'CheckBytes')] [byte[]] $CheckBytes = $null, - [Parameter(ParameterSetName='CRLF')] + [Parameter(ParameterSetName = 'CRLF')] [switch] $CRLFMessageEnd ) @@ -1182,8 +1169,7 @@ Close an open TCP client connection .EXAMPLE Close-PodeTcpClient #> -function Close-PodeTcpClient -{ +function Close-PodeTcpClient { [CmdletBinding()] param() @@ -1216,11 +1202,10 @@ Save-PodeRequestFile -Key 'avatar' -Path 'F:/Images' .EXAMPLE Save-PodeRequestFile -Key 'avatar' -Path 'F:/Images' -FileName 'icon.png' #> -function Save-PodeRequestFile -{ +function Save-PodeRequestFile { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key, @@ -1245,10 +1230,10 @@ function Save-PodeRequestFile $files = @($WebEvent.Data[$Key]) if (($null -ne $FileName) -and ($FileName.Length -gt 0)) { $files = @(foreach ($file in $files) { - if ($FileName -icontains $file) { - $file - } - }) + if ($FileName -icontains $file) { + $file + } + }) } # ensure the file data exists @@ -1290,11 +1275,10 @@ Test-PodeRequestFile -Key 'avatar' .EXAMPLE Test-PodeRequestFile -Key 'avatar' -FileName 'icon.png' #> -function Test-PodeRequestFile -{ +function Test-PodeRequestFile { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key, @@ -1347,10 +1331,9 @@ Set-PodeViewEngine -Type Markdown .EXAMPLE Set-PodeViewEngine -Type PSHTML -Extension PS1 -ScriptBlock { param($path, $data) /* logic */ } #> -function Set-PodeViewEngine -{ +function Set-PodeViewEngine { [CmdletBinding()] - param ( + param( [Parameter()] [string] $Type, @@ -1406,12 +1389,11 @@ If supplied, a custom views folder will be used. .EXAMPLE Use-PodePartialView -Path 'shared/footer' #> -function Use-PodePartialView -{ +function Use-PodePartialView { [CmdletBinding()] [OutputType([string])] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Path, @@ -1482,11 +1464,10 @@ Send-PodeSignal -Value @{ Message = 'Hello, world!' } .EXAMPLE Send-PodeSignal -Value @{ Data = @(123, 100, 101) } -Path '/response-charts' #> -function Send-PodeSignal -{ +function Send-PodeSignal { [CmdletBinding()] param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] $Value, [Parameter()] @@ -1512,7 +1493,7 @@ function Send-PodeSignal # error if not configured if (!$PodeContext.Server.Signals.Enabled) { - throw "WebSockets have not been configured to send signal messages" + throw 'WebSockets have not been configured to send signal messages' } # do nothing if no value @@ -1570,15 +1551,14 @@ The literal, or relative, path to the directory that contains views. .EXAMPLE Add-PodeViewFolder -Name 'assets' -Source './assets' #> -function Add-PodeViewFolder -{ +function Add-PodeViewFolder { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true)] + param( + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Source ) diff --git a/src/Public/Routes.ps1 b/src/Public/Routes.ps1 index d06199ade..ae6d1d1e5 100644 --- a/src/Public/Routes.ps1 +++ b/src/Public/Routes.ps1 @@ -89,16 +89,15 @@ Add-PodeRoute -Method Get -Path '/' -ScriptBlock { /* logic */ } -ArgumentList ' .EXAMPLE Add-PodeRoute -Method Get -Path '/' -Role 'Developer', 'QA' -ScriptBlock { /* logic */ } #> -function Add-PodeRoute -{ - [CmdletBinding(DefaultParameterSetName='Script')] +function Add-PodeRoute { + [CmdletBinding(DefaultParameterSetName = 'Script')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')] [string[]] $Method, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -106,7 +105,7 @@ function Add-PodeRoute [object[]] $Middleware, - [Parameter(ParameterSetName='Script')] + [Parameter(ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, @@ -127,7 +126,7 @@ function Add-PodeRoute [string] $ErrorContentType, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, @@ -250,7 +249,7 @@ function Add-PodeRoute # split route on '?' for query $Path = Split-PodeRouteQuery -Path $Path if ([string]::IsNullOrWhiteSpace($Path)) { - throw "No Path supplied for Route" + throw 'No Path supplied for Route' } # ensure the route has appropriate slashes @@ -289,7 +288,7 @@ function Add-PodeRoute # if an access name was supplied, setup access as middleware first to it's after auth middleware if (![string]::IsNullOrWhiteSpace($Access)) { if ([string]::IsNullOrWhiteSpace($Authentication)) { - throw "Access requires Authentication to be supplied on Routes" + throw 'Access requires Authentication to be supplied on Routes' } if (!(Test-PodeAccessExists -Name $Access)) { @@ -310,10 +309,10 @@ function Add-PodeRoute } $options = @{ - Name = $Authentication - Login = $Login + Name = $Authentication + Login = $Login Logout = $Logout - Anon = $AllowAnon + Anon = $AllowAnon } $Middleware = (@(Get-PodeAuthMiddlewareScript | New-PodeMiddleware -ArgumentList $options) + $Middleware) @@ -334,20 +333,20 @@ function Add-PodeRoute foreach ($_method in $Method) { # ensure the route doesn't already exist for each endpoint $endpoints = @(foreach ($_endpoint in $endpoints) { - $found = Test-PodeRouteInternal -Method $_method -Path $Path -Protocol $_endpoint.Protocol -Address $_endpoint.Address -ThrowError:($IfExists -ieq 'Error') + $found = Test-PodeRouteInternal -Method $_method -Path $Path -Protocol $_endpoint.Protocol -Address $_endpoint.Address -ThrowError:($IfExists -ieq 'Error') - if ($found) { - if ($IfExists -ieq 'Overwrite') { - Remove-PodeRoute -Method $_method -Path $origPath -EndpointName $_endpoint.Name - } + if ($found) { + if ($IfExists -ieq 'Overwrite') { + Remove-PodeRoute -Method $_method -Path $origPath -EndpointName $_endpoint.Name + } - if ($IfExists -ieq 'Skip') { - continue + if ($IfExists -ieq 'Skip') { + continue + } } - } - $_endpoint - }) + $_endpoint + }) if (($null -eq $endpoints) -or ($endpoints.Length -eq 0)) { continue @@ -356,49 +355,49 @@ function Add-PodeRoute # add the route(s) Write-Verbose "Adding Route: [$($_method)] $($Path)" $methodRoutes = @(foreach ($_endpoint in $endpoints) { - @{ - Logic = $ScriptBlock - UsingVariables = $usingVars - Middleware = $Middleware - Authentication = $Authentication - Access = $Access - AccessMeta = @{ - Role = $Role - Group = $Group - Scope = $Scope - User = $User - Custom = $CustomAccess - } - Endpoint = @{ - Protocol = $_endpoint.Protocol - Address = $_endpoint.Address.Trim() - Name = $_endpoint.Name - } - ContentType = $ContentType - TransferEncoding = $TransferEncoding - ErrorType = $ErrorContentType - Arguments = $ArgumentList - Method = $_method - Path = $Path - OpenApi = @{ - Path = $OpenApiPath - Responses = @{ - '200' = @{ description = 'OK' } - 'default' = @{ description = 'Internal server error' } + @{ + Logic = $ScriptBlock + UsingVariables = $usingVars + Middleware = $Middleware + Authentication = $Authentication + Access = $Access + AccessMeta = @{ + Role = $Role + Group = $Group + Scope = $Scope + User = $User + Custom = $CustomAccess } - Parameters = $null - RequestBody = $null - Authentication = @() - } - IsStatic = $false - Metrics = @{ - Requests = @{ - Total = 0 - StatusCodes = @{} + Endpoint = @{ + Protocol = $_endpoint.Protocol + Address = $_endpoint.Address.Trim() + Name = $_endpoint.Name + } + ContentType = $ContentType + TransferEncoding = $TransferEncoding + ErrorType = $ErrorContentType + Arguments = $ArgumentList + Method = $_method + Path = $Path + OpenApi = @{ + Path = $OpenApiPath + Responses = @{ + '200' = @{ description = 'OK' } + 'default' = @{ description = 'Internal server error' } + } + Parameters = $null + RequestBody = $null + Authentication = @() + } + IsStatic = $false + Metrics = @{ + Requests = @{ + Total = 0 + StatusCodes = @{} + } } } - } - }) + }) if (![string]::IsNullOrWhiteSpace($Authentication)) { Set-PodeOAAuth -Route $methodRoutes -Name $Authentication @@ -486,15 +485,14 @@ Add-PodeStaticRoute -Path '/assets' -Source './assets' -Defaults @('index.html') .EXAMPLE Add-PodeStaticRoute -Path '/installers' -Source './exes' -DownloadOnly #> -function Add-PodeStaticRoute -{ +function Add-PodeStaticRoute { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Source, @@ -669,20 +667,20 @@ function Add-PodeStaticRoute # ensure the route doesn't already exist for each endpoint $endpoints = @(foreach ($_endpoint in $endpoints) { - $found = Test-PodeRouteInternal -Method $Method -Path $Path -Protocol $_endpoint.Protocol -Address $_endpoint.Address -ThrowError:($IfExists -ieq 'Error') + $found = Test-PodeRouteInternal -Method $Method -Path $Path -Protocol $_endpoint.Protocol -Address $_endpoint.Address -ThrowError:($IfExists -ieq 'Error') - if ($found) { - if ($IfExists -ieq 'Overwrite') { - Remove-PodeStaticRoute -Path $origPath -EndpointName $_endpoint.Name - } + if ($found) { + if ($IfExists -ieq 'Overwrite') { + Remove-PodeStaticRoute -Path $origPath -EndpointName $_endpoint.Name + } - if ($IfExists -ieq 'Skip') { - continue + if ($IfExists -ieq 'Skip') { + continue + } } - } - $_endpoint - }) + $_endpoint + }) if (($null -eq $endpoints) -or ($endpoints.Length -eq 0)) { return @@ -708,7 +706,7 @@ function Add-PodeStaticRoute # if an access name was supplied, setup access as middleware first to it's after auth middleware if (![string]::IsNullOrWhiteSpace($Access)) { if ([string]::IsNullOrWhiteSpace($Authentication)) { - throw "Access requires Authentication to be supplied on Static Routes" + throw 'Access requires Authentication to be supplied on Static Routes' } if (!(Test-PodeAccessExists -Name $Access)) { @@ -745,49 +743,49 @@ function Add-PodeStaticRoute # add the route(s) Write-Verbose "Adding Route: [$($Method)] $($Path)" $newRoutes = @(foreach ($_endpoint in $endpoints) { - @{ - Source = $Source - Path = $Path - Method = $Method - Defaults = $Defaults - Middleware = $Middleware - Authentication = $Authentication - Access = $Access - AccessMeta = @{ - Role = $Role - Group = $Group - Scope = $Scope - User = $User - Custom = $CustomAccess - } - Endpoint = @{ - Protocol = $_endpoint.Protocol - Address = $_endpoint.Address.Trim() - Name = $_endpoint.Name - } - ContentType = $ContentType - TransferEncoding = $TransferEncoding - ErrorType = $ErrorContentType - Download = $DownloadOnly - OpenApi = @{ - Path = $OpenApiPath - Responses = @{ - '200' = @{ description = 'OK' } - 'default' = @{ description = 'Internal server error' } + @{ + Source = $Source + Path = $Path + Method = $Method + Defaults = $Defaults + Middleware = $Middleware + Authentication = $Authentication + Access = $Access + AccessMeta = @{ + Role = $Role + Group = $Group + Scope = $Scope + User = $User + Custom = $CustomAccess } - Parameters = @() - RequestBody = @{} - Authentication = @() - } - IsStatic = $true - Metrics = @{ - Requests = @{ - Total = 0 - StatusCodes = @{} + Endpoint = @{ + Protocol = $_endpoint.Protocol + Address = $_endpoint.Address.Trim() + Name = $_endpoint.Name + } + ContentType = $ContentType + TransferEncoding = $TransferEncoding + ErrorType = $ErrorContentType + Download = $DownloadOnly + OpenApi = @{ + Path = $OpenApiPath + Responses = @{ + '200' = @{ description = 'OK' } + 'default' = @{ description = 'Internal server error' } + } + Parameters = @() + RequestBody = @{} + Authentication = @() + } + IsStatic = $true + Metrics = @{ + Requests = @{ + Total = 0 + StatusCodes = @{} + } } } - } - }) + }) if (![string]::IsNullOrWhiteSpace($Authentication)) { Set-PodeOAAuth -Route $newRoutes -Name $Authentication @@ -832,15 +830,14 @@ Add-PodeSignalRoute -Path '/message' -ScriptBlock { /* logic */ } .EXAMPLE Add-PodeSignalRoute -Path '/message' -ScriptBlock { /* logic */ } -ArgumentList 'arg1', 'arg2' #> -function Add-PodeSignalRoute -{ - [CmdletBinding(DefaultParameterSetName='Script')] +function Add-PodeSignalRoute { + [CmdletBinding(DefaultParameterSetName = 'Script')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, - [Parameter(ParameterSetName='Script')] + [Parameter(ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, @@ -848,7 +845,7 @@ function Add-PodeSignalRoute [string[]] $EndpointName, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, @@ -899,20 +896,20 @@ function Add-PodeSignalRoute # ensure the route doesn't already exist for each endpoint $endpoints = @(foreach ($_endpoint in $endpoints) { - $found = Test-PodeRouteInternal -Method $Method -Path $Path -Protocol $_endpoint.Protocol -Address $_endpoint.Address -ThrowError:($IfExists -ieq 'Error') + $found = Test-PodeRouteInternal -Method $Method -Path $Path -Protocol $_endpoint.Protocol -Address $_endpoint.Address -ThrowError:($IfExists -ieq 'Error') - if ($found) { - if ($IfExists -ieq 'Overwrite') { - Remove-PodeSignalRoute -Path $origPath -EndpointName $_endpoint.Name - } + if ($found) { + if ($IfExists -ieq 'Overwrite') { + Remove-PodeSignalRoute -Path $origPath -EndpointName $_endpoint.Name + } - if ($IfExists -ieq 'Skip') { - continue + if ($IfExists -ieq 'Skip') { + continue + } } - } - $_endpoint - }) + $_endpoint + }) if (($null -eq $endpoints) -or ($endpoints.Length -eq 0)) { return @@ -934,25 +931,25 @@ function Add-PodeSignalRoute # add the route(s) Write-Verbose "Adding Route: [$($Method)] $($Path)" $newRoutes = @(foreach ($_endpoint in $endpoints) { - @{ - Logic = $ScriptBlock - UsingVariables = $usingVars - Endpoint = @{ - Protocol = $_endpoint.Protocol - Address = $_endpoint.Address.Trim() - Name = $_endpoint.Name - } - Arguments = $ArgumentList - Method = $Method - Path = $Path - IsStatic = $false - Metrics = @{ - Requests = @{ - Total = 0 + @{ + Logic = $ScriptBlock + UsingVariables = $usingVars + Endpoint = @{ + Protocol = $_endpoint.Protocol + Address = $_endpoint.Address.Trim() + Name = $_endpoint.Name + } + Arguments = $ArgumentList + Method = $Method + Path = $Path + IsStatic = $false + Metrics = @{ + Requests = @{ + Total = 0 + } } } - } - }) + }) $PodeContext.Server.Routes[$Method][$Path] += @($newRoutes) } @@ -1012,15 +1009,14 @@ If supplied, the Routes will allow anonymous access for non-authenticated users. .EXAMPLE Add-PodeRouteGroup -Path '/api' -Routes { Add-PodeRoute -Path '/route1' -Etc } #> -function Add-PodeRouteGroup -{ +function Add-PodeRouteGroup { [CmdletBinding()] param( [Parameter()] [string] $Path, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $Routes, @@ -1080,7 +1076,7 @@ function Add-PodeRouteGroup ) if (Test-PodeIsEmpty $Routes) { - throw "No scriptblock for -Routes passed" + throw 'No scriptblock for -Routes passed' } if ($Path -eq '/') { @@ -1154,21 +1150,21 @@ function Add-PodeRouteGroup } $RouteGroup = @{ - Path = $Path - Middleware = $Middleware - EndpointName = $EndpointName - ContentType = $ContentType + Path = $Path + Middleware = $Middleware + EndpointName = $EndpointName + ContentType = $ContentType TransferEncoding = $TransferEncoding ErrorContentType = $ErrorContentType - Authentication = $Authentication - Access = $Access - AllowAnon = $AllowAnon - IfExists = $IfExists - AccessMeta = @{ - Role = $Role - Group = $Group - Scope = $Scope - User = $User + Authentication = $Authentication + Access = $Access + AllowAnon = $AllowAnon + IfExists = $IfExists + AccessMeta = @{ + Role = $Role + Group = $Group + Scope = $Scope + User = $User Custom = $CustomAccess } } @@ -1242,8 +1238,7 @@ One or more optional Users that will be authorised to access this Route, when us .EXAMPLE Add-PodeStaticRouteGroup -Path '/static' -Routes { Add-PodeStaticRoute -Path '/images' -Etc } #> -function Add-PodeStaticRouteGroup -{ +function Add-PodeStaticRouteGroup { [CmdletBinding()] param( [Parameter()] @@ -1254,7 +1249,7 @@ function Add-PodeStaticRouteGroup [string] $Source, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $Routes, @@ -1321,7 +1316,7 @@ function Add-PodeStaticRouteGroup ) if (Test-PodeIsEmpty $Routes) { - throw "No scriptblock for -Routes passed" + throw 'No scriptblock for -Routes passed' } if ($Path -eq '/') { @@ -1407,24 +1402,24 @@ function Add-PodeStaticRouteGroup } $RouteGroup = @{ - Path = $Path - Source = $Source - Middleware = $Middleware - EndpointName = $EndpointName - ContentType = $ContentType + Path = $Path + Source = $Source + Middleware = $Middleware + EndpointName = $EndpointName + ContentType = $ContentType TransferEncoding = $TransferEncoding - Defaults = $Defaults + Defaults = $Defaults ErrorContentType = $ErrorContentType - Authentication = $Authentication - Access = $Access - AllowAnon = $AllowAnon - DownloadOnly = $DownloadOnly - IfExists = $IfExists - AccessMeta = @{ - Role = $Role - Group = $Group - Scope = $Scope - User = $User + Authentication = $Authentication + Access = $Access + AllowAnon = $AllowAnon + DownloadOnly = $DownloadOnly + IfExists = $IfExists + AccessMeta = @{ + Role = $Role + Group = $Group + Scope = $Scope + User = $User Custom = $CustomAccess } } @@ -1457,15 +1452,14 @@ Specifies what action to take when a Signal Route already exists. (Default: Defa .EXAMPLE Add-PodeSignalRouteGroup -Path '/signals' -Routes { Add-PodeSignalRoute -Path '/signal1' -Etc } #> -function Add-PodeSignalRouteGroup -{ +function Add-PodeSignalRouteGroup { [CmdletBinding()] param( [Parameter()] [string] $Path, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $Routes, @@ -1480,7 +1474,7 @@ function Add-PodeSignalRouteGroup ) if (Test-PodeIsEmpty $Routes) { - throw "No scriptblock for -Routes passed" + throw 'No scriptblock for -Routes passed' } if ($Path -eq '/') { @@ -1506,9 +1500,9 @@ function Add-PodeSignalRouteGroup } $RouteGroup = @{ - Path = $Path + Path = $Path EndpointName = $EndpointName - IfExists = $IfExists + IfExists = $IfExists } # add routes @@ -1538,16 +1532,15 @@ Remove-PodeRoute -Method Get -Route '/about' .EXAMPLE Remove-PodeRoute -Method Post -Route '/users/:userId' -EndpointName User #> -function Remove-PodeRoute -{ +function Remove-PodeRoute { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')] [string] $Method, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -1573,8 +1566,8 @@ function Remove-PodeRoute # remove the route's logic $PodeContext.Server.Routes[$Method][$Path] = @($PodeContext.Server.Routes[$Method][$Path] | Where-Object { - $_.Endpoint.Name -ine $EndpointName - }) + $_.Endpoint.Name -ine $EndpointName + }) # if the route has no more logic, just remove it if ((Get-PodeCount $PodeContext.Server.Routes[$Method][$Path]) -eq 0) { @@ -1598,11 +1591,10 @@ The EndpointName of an Endpoint(s) bound to the static Route to be removed. .EXAMPLE Remove-PodeStaticRoute -Path '/assets' #> -function Remove-PodeStaticRoute -{ +function Remove-PodeStaticRoute { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -1623,8 +1615,8 @@ function Remove-PodeStaticRoute # remove the route's logic $PodeContext.Server.Routes[$Method][$Path] = @($PodeContext.Server.Routes[$Method][$Path] | Where-Object { - $_.Endpoint.Name -ine $EndpointName - }) + $_.Endpoint.Name -ine $EndpointName + }) # if the route has no more logic, just remove it if ((Get-PodeCount $PodeContext.Server.Routes[$Method][$Path]) -eq 0) { @@ -1648,11 +1640,10 @@ The EndpointName of an Endpoint(s) bound to the Signal Route to be removed. .EXAMPLE Remove-PodeSignalRoute -Route '/message' #> -function Remove-PodeSignalRoute -{ +function Remove-PodeSignalRoute { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -1673,8 +1664,8 @@ function Remove-PodeSignalRoute # remove the route's logic $PodeContext.Server.Routes[$Method][$Path] = @($PodeContext.Server.Routes[$Method][$Path] | Where-Object { - $_.Endpoint.Name -ine $EndpointName - }) + $_.Endpoint.Name -ine $EndpointName + }) # if the route has no more logic, just remove it if ((Get-PodeCount $PodeContext.Server.Routes[$Method][$Path]) -eq 0) { @@ -1698,8 +1689,7 @@ Clear-PodeRoutes .EXAMPLE Clear-PodeRoutes -Method Get #> -function Clear-PodeRoutes -{ +function Clear-PodeRoutes { [CmdletBinding()] param( [Parameter()] @@ -1728,8 +1718,7 @@ Removes all added static Routes. .EXAMPLE Clear-PodeStaticRoutes #> -function Clear-PodeStaticRoutes -{ +function Clear-PodeStaticRoutes { [CmdletBinding()] param() @@ -1746,8 +1735,7 @@ Removes all added Signal Routes. .EXAMPLE Clear-PodeSignalRoutes #> -function Clear-PodeSignalRoutes -{ +function Clear-PodeSignalRoutes { [CmdletBinding()] param() @@ -1815,11 +1803,10 @@ ConvertTo-PodeRoute -Module Pester -Path '/api' .EXAMPLE ConvertTo-PodeRoute -Commands @('Invoke-Pester') -Module Pester #> -function ConvertTo-PodeRoute -{ +function ConvertTo-PodeRoute { [CmdletBinding()] param( - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [string[]] $Commands, @@ -1879,7 +1866,7 @@ function ConvertTo-PodeRoute if (![string]::IsNullOrWhiteSpace($Module)) { Import-PodeModule -Name $Module - Write-Verbose "Getting exported commands from module" + Write-Verbose 'Getting exported commands from module' $ModuleCommands = (Get-Module -Name $Module | Sort-Object -Descending | Select-Object -First 1).ExportedCommands.Keys # if commands were supplied validate them - otherwise use all exported ones @@ -1937,18 +1924,18 @@ function ConvertTo-PodeRoute # create the route $params = @{ - Method = $_method - Path = $_path - Middleware = $Middleware - Authentication = $Authentication - Access = $Access - Role = $Role - Group = $Group - Scope = $Scope - User = $User - AllowAnon = $AllowAnon - ArgumentList = $cmd - PassThru = $true + Method = $_method + Path = $_path + Middleware = $Middleware + Authentication = $Authentication + Access = $Access + Role = $Role + Group = $Group + Scope = $Scope + User = $User + AllowAnon = $AllowAnon + ArgumentList = $cmd + PassThru = $true } $route = Add-PodeRoute @params -ScriptBlock { @@ -1986,8 +1973,8 @@ function ConvertTo-PodeRoute } $props = @(foreach ($key in $params.Keys) { - $params[$key] | ConvertTo-PodeOAPropertyFromCmdletParameter - }) + $params[$key] | ConvertTo-PodeOAPropertyFromCmdletParameter + }) if ($_method -ieq 'get') { $route | Set-PodeOARequest -Parameters @(foreach ($prop in $props) { $prop | ConvertTo-PodeOAParameter -In Query }) @@ -2063,24 +2050,23 @@ Add-PodePage -Name Index -View 'index' .EXAMPLE Add-PodePage -Name About -FilePath '.\views\about.pode' -Data @{ Date = [DateTime]::UtcNow } #> -function Add-PodePage -{ - [CmdletBinding(DefaultParameterSetName='ScriptBlock')] +function Add-PodePage { + [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Name, - [Parameter(Mandatory=$true, ParameterSetName='ScriptBlock')] + [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')] [scriptblock] $ScriptBlock, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, - [Parameter(Mandatory=$true, ParameterSetName='View')] + [Parameter(Mandatory = $true, ParameterSetName = 'View')] [string] $View, @@ -2124,7 +2110,7 @@ function Add-PodePage [switch] $AllowAnon, - [Parameter(ParameterSetName='View')] + [Parameter(ParameterSetName = 'View')] [switch] $FlashMessages ) @@ -2142,10 +2128,9 @@ function Add-PodePage $Path = $Path.TrimEnd('/') # define the appropriate logic - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) - { + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'scriptblock' { - if (Test-PodeIsEmpty $ScriptBlock){ + if (Test-PodeIsEmpty $ScriptBlock) { throw 'A non-empty ScriptBlock is required to created a Page Route' } @@ -2191,18 +2176,18 @@ function Add-PodePage # create the route $params = @{ - Method = 'Get' - Path = $_path - Middleware = $Middleware - Authentication = $Authentication - Access = $Access - Role = $Role - Group = $Group - Scope = $Scope - User = $User - AllowAnon = $AllowAnon - ArgumentList = $arg - ScriptBlock = $logic + Method = 'Get' + Path = $_path + Middleware = $Middleware + Authentication = $Authentication + Access = $Access + Role = $Role + Group = $Group + Scope = $Scope + User = $User + AllowAnon = $AllowAnon + ArgumentList = $arg + ScriptBlock = $logic } Add-PodeRoute @params @@ -2230,8 +2215,7 @@ Get-PodeRoute -Method Get -Path '/about' .EXAMPLE Get-PodeRoute -Method Post -Path '/users/:userId' -EndpointName User #> -function Get-PodeRoute -{ +function Get-PodeRoute { [CmdletBinding()] param( [Parameter()] @@ -2257,12 +2241,12 @@ function Get-PodeRoute # if we have a method, filter if (![string]::IsNullOrWhiteSpace($Method)) { $routes = @(foreach ($route in $routes) { - if ($route.Method -ine $Method) { - continue - } + if ($route.Method -ine $Method) { + continue + } - $route - }) + $route + }) } # if we have a path, filter @@ -2272,25 +2256,25 @@ function Get-PodeRoute $Path = Resolve-PodePlaceholders -Path $Path $routes = @(foreach ($route in $routes) { - if ($route.Path -ine $Path) { - continue - } + if ($route.Path -ine $Path) { + continue + } - $route - }) + $route + }) } # further filter by endpoint names if (($null -ne $EndpointName) -and ($EndpointName.Length -gt 0)) { $routes = @(foreach ($name in $EndpointName) { - foreach ($route in $routes) { - if ($route.Endpoint.Name -ine $name) { - continue - } + foreach ($route in $routes) { + if ($route.Endpoint.Name -ine $name) { + continue + } - $route - } - }) + $route + } + }) } # return @@ -2316,8 +2300,7 @@ Get-PodeStaticRoute -Path '/assets' .EXAMPLE Get-PodeStaticRoute -Path '/assets' -EndpointName User #> -function Get-PodeStaticRoute -{ +function Get-PodeStaticRoute { [CmdletBinding()] param( [Parameter()] @@ -2339,25 +2322,25 @@ function Get-PodeStaticRoute if (![string]::IsNullOrWhiteSpace($Path)) { $Path = Update-PodeRouteSlashes -Path $Path -Static $routes = @(foreach ($route in $routes) { - if ($route.Path -ine $Path) { - continue - } + if ($route.Path -ine $Path) { + continue + } - $route - }) + $route + }) } # further filter by endpoint names if (($null -ne $EndpointName) -and ($EndpointName.Length -gt 0)) { $routes = @(foreach ($name in $EndpointName) { - foreach ($route in $routes) { - if ($route.Endpoint.Name -ine $name) { - continue - } + foreach ($route in $routes) { + if ($route.Endpoint.Name -ine $name) { + continue + } - $route - } - }) + $route + } + }) } # return @@ -2380,8 +2363,7 @@ The name of an endpoint to filter signal routes. .EXAMPLE Get-PodeSignalRoute -Path '/message' #> -function Get-PodeSignalRoute -{ +function Get-PodeSignalRoute { [CmdletBinding()] param( [Parameter()] @@ -2403,25 +2385,25 @@ function Get-PodeSignalRoute if (![string]::IsNullOrWhiteSpace($Path)) { $Path = Update-PodeRouteSlashes -Path $Path $routes = @(foreach ($route in $routes) { - if ($route.Path -ine $Path) { - continue - } + if ($route.Path -ine $Path) { + continue + } - $route - }) + $route + }) } # further filter by endpoint names if (($null -ne $EndpointName) -and ($EndpointName.Length -gt 0)) { $routes = @(foreach ($name in $EndpointName) { - foreach ($route in $routes) { - if ($route.Endpoint.Name -ine $name) { - continue - } + foreach ($route in $routes) { + if ($route.Endpoint.Name -ine $name) { + continue + } - $route - } - }) + $route + } + }) } # return @@ -2447,8 +2429,7 @@ Use-PodeRoutes .EXAMPLE Use-PodeRoutes -Path './my-routes' -IfExists Skip #> -function Use-PodeRoutes -{ +function Use-PodeRoutes { [CmdletBinding()] param( [Parameter()] @@ -2482,8 +2463,7 @@ Specifies what action to take when a Route already exists. (Default: Default) .EXAMPLE Set-PodeRouteIfExistsPreference -Value Overwrite #> -function Set-PodeRouteIfExistsPreference -{ +function Set-PodeRouteIfExistsPreference { [CmdletBinding()] param( [Parameter()] @@ -2523,16 +2503,15 @@ Test-PodeRoute -Method Post -Path '/example' -CheckWildcard .EXAMPLE Test-PodeRoute -Method Get -Path '/example/:exampleId' -CheckWildcard #> -function Test-PodeRoute -{ +function Test-PodeRoute { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')] [string] $Method, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -2547,7 +2526,7 @@ function Test-PodeRoute # split route on '?' for query $Path = Split-PodeRouteQuery -Path $Path if ([string]::IsNullOrWhiteSpace($Path)) { - throw "No Path supplied for testing Route" + throw 'No Path supplied for testing Route' } # ensure the route has appropriate slashes @@ -2582,11 +2561,10 @@ The EndpointName of an Endpoint the Static Route is bound against. .EXAMPLE Test-PodeStaticRoute -Path '/assets' #> -function Test-PodeStaticRoute -{ +function Test-PodeStaticRoute { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -2601,7 +2579,7 @@ function Test-PodeStaticRoute # split route on '?' for query $Path = Split-PodeRouteQuery -Path $Path if ([string]::IsNullOrWhiteSpace($Path)) { - throw "No Path supplied for testing Static Route" + throw 'No Path supplied for testing Static Route' } # ensure the route has appropriate slashes @@ -2631,11 +2609,10 @@ The EndpointName of an Endpoint the Signal Route is bound against. .EXAMPLE Test-PodeSignalRoute -Path '/message' #> -function Test-PodeSignalRoute -{ +function Test-PodeSignalRoute { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, diff --git a/src/Public/Schedules.ps1 b/src/Public/Schedules.ps1 index 3c7cfa1cc..e88b577c4 100644 --- a/src/Public/Schedules.ps1 +++ b/src/Public/Schedules.ps1 @@ -44,19 +44,18 @@ Add-PodeSchedule -Name 'StartAfter2days' -Cron '@hourly' -StartTime [DateTime]:: .EXAMPLE Add-PodeSchedule -Name 'Args' -Cron '@minutely' -ScriptBlock { /* logic */ } -ArgumentList @{ Arg1 = 'value' } #> -function Add-PodeSchedule -{ - [CmdletBinding(DefaultParameterSetName='Script')] +function Add-PodeSchedule { + [CmdletBinding(DefaultParameterSetName = 'Script')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string[]] $Cron, - [Parameter(Mandatory=$true, ParameterSetName='Script')] + [Parameter(Mandatory = $true, ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, @@ -72,7 +71,7 @@ function Add-PodeSchedule [DateTime] $EndTime, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, @@ -120,20 +119,20 @@ function Add-PodeSchedule $PodeContext.Schedules.Enabled = $true $PodeContext.Schedules.Items[$Name] = @{ - Name = $Name - StartTime = $StartTime - EndTime = $EndTime - Crons = $parsedCrons - CronsRaw = @($Cron) - Limit = $Limit - Count = 0 + Name = $Name + StartTime = $StartTime + EndTime = $EndTime + Crons = $parsedCrons + CronsRaw = @($Cron) + Limit = $Limit + Count = 0 NextTriggerTime = $nextTrigger LastTriggerTime = $null - Script = $ScriptBlock - UsingVariables = $usingVars - Arguments = (Protect-PodeValue -Value $ArgumentList -Default @{}) - OnStart = $OnStart - Completed = ($null -eq $nextTrigger) + Script = $ScriptBlock + UsingVariables = $usingVars + Arguments = (Protect-PodeValue -Value $ArgumentList -Default @{}) + OnStart = $OnStart + Completed = ($null -eq $nextTrigger) } } @@ -150,11 +149,10 @@ The Maximum number of schedules to run. .EXAMPLE Set-PodeScheduleConcurrency -Maximum 25 #> -function Set-PodeScheduleConcurrency -{ +function Set-PodeScheduleConcurrency { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Maximum ) @@ -197,11 +195,10 @@ A hashtable of arguments to supply to the Schedule's ScriptBlock. .EXAMPLE Invoke-PodeSchedule -Name 'schedule-name' #> -function Invoke-PodeSchedule -{ +function Invoke-PodeSchedule { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name, @@ -232,11 +229,10 @@ The Name of the Schedule to be removed. .EXAMPLE Remove-PodeSchedule -Name 'RenewToken' #> -function Remove-PodeSchedule -{ +function Remove-PodeSchedule { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name ) @@ -254,8 +250,7 @@ Removes all Schedules. .EXAMPLE Clear-PodeSchedules #> -function Clear-PodeSchedules -{ +function Clear-PodeSchedules { [CmdletBinding()] param() @@ -287,11 +282,10 @@ Edit-PodeSchedule -Name 'Hello' -Cron '@minutely' .EXAMPLE Edit-PodeSchedule -Name 'Hello' -Cron @('@hourly', '0 0 * * TUE') #> -function Edit-PodeSchedule -{ +function Edit-PodeSchedule { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name, @@ -360,8 +354,7 @@ Get-PodeSchedule -Name Name1, Name2 .EXAMPLE Get-PodeSchedule -Name Name1, Name2 -StartTime [datetime]::new(2020, 3, 1) -EndTime [datetime]::new(2020, 3, 31) #> -function Get-PodeSchedule -{ +function Get-PodeSchedule { [CmdletBinding()] param( [Parameter()] @@ -380,58 +373,58 @@ function Get-PodeSchedule # further filter by schedule names if (($null -ne $Name) -and ($Name.Length -gt 0)) { $schedules = @(foreach ($_name in $Name) { - foreach ($schedule in $schedules) { - if ($schedule.Name -ine $_name) { - continue - } + foreach ($schedule in $schedules) { + if ($schedule.Name -ine $_name) { + continue + } - $schedule - } - }) + $schedule + } + }) } # filter by some start time if ($null -ne $StartTime) { $schedules = @(foreach ($schedule in $schedules) { - if (($null -ne $schedule.StartTime) -and ($StartTime -lt $schedule.StartTime)) { - continue - } + if (($null -ne $schedule.StartTime) -and ($StartTime -lt $schedule.StartTime)) { + continue + } - $_end = $EndTime - if ($null -eq $_end) { - $_end = $schedule.EndTime - } + $_end = $EndTime + if ($null -eq $_end) { + $_end = $schedule.EndTime + } - if (($null -ne $schedule.EndTime) -and + if (($null -ne $schedule.EndTime) -and (($StartTime -gt $schedule.EndTime) -or ((Get-PodeScheduleNextTrigger -Name $schedule.Name -DateTime $StartTime) -gt $_end))) { - continue - } + continue + } - $schedule - }) + $schedule + }) } # filter by some end time if ($null -ne $EndTime) { $schedules = @(foreach ($schedule in $schedules) { - if (($null -ne $schedule.EndTime) -and ($EndTime -gt $schedule.EndTime)) { - continue - } + if (($null -ne $schedule.EndTime) -and ($EndTime -gt $schedule.EndTime)) { + continue + } - $_start = $StartTime - if ($null -eq $_start) { - $_start = $schedule.StartTime - } + $_start = $StartTime + if ($null -eq $_start) { + $_start = $schedule.StartTime + } - if (($null -ne $schedule.StartTime) -and + if (($null -ne $schedule.StartTime) -and (($EndTime -lt $schedule.StartTime) -or ((Get-PodeScheduleNextTrigger -Name $schedule.Name -DateTime $_start) -gt $EndTime))) { - continue - } + continue + } - $schedule - }) + $schedule + }) } # return @@ -451,11 +444,10 @@ The Name of the Schedule. .EXAMPLE if (Test-PodeSchedule -Name ScheduleName) { } #> -function Test-PodeSchedule -{ +function Test-PodeSchedule { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -482,11 +474,10 @@ Get-PodeScheduleNextTrigger -Name Schedule1 .EXAMPLE Get-PodeScheduleNextTrigger -Name Schedule1 -DateTime [datetime]::new(2020, 3, 10) #> -function Get-PodeScheduleNextTrigger -{ +function Get-PodeScheduleNextTrigger { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name, @@ -534,8 +525,7 @@ Use-PodeSchedules .EXAMPLE Use-PodeSchedules -Path './my-schedules' #> -function Use-PodeSchedules -{ +function Use-PodeSchedules { [CmdletBinding()] param( [Parameter()] diff --git a/src/Public/Secrets.ps1 b/src/Public/Secrets.ps1 index bcfe71cd7..ca7eb47c5 100644 --- a/src/Public/Secrets.ps1 +++ b/src/Public/Secrets.ps1 @@ -56,11 +56,10 @@ Register-PodeSecretVault -Name 'VaultName' -ModuleName 'Az.KeyVault' -VaultParam .EXAMPLE Register-PodeSecretVault -Name 'VaultName' -VaultParameters @{ Address = 'http://127.0.0.1:8200' } -ScriptBlock { ... } #> -function Register-PodeSecretVault -{ +function Register-PodeSecretVault { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -91,35 +90,35 @@ function Register-PodeSecretVault [scriptblock] $InitScriptBlock, - [Parameter(ParameterSetName='SecretManagement')] + [Parameter(ParameterSetName = 'SecretManagement')] [string] $VaultName, - [Parameter(Mandatory=$true, ParameterSetName='SecretManagement')] + [Parameter(Mandatory = $true, ParameterSetName = 'SecretManagement')] [Alias('Module')] [string] $ModuleName, - [Parameter(Mandatory=$true, ParameterSetName='Custom')] + [Parameter(Mandatory = $true, ParameterSetName = 'Custom')] [scriptblock] $ScriptBlock, # Read a secret - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [Alias('Unlock')] [scriptblock] $UnlockScriptBlock, - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [Alias('Remove')] [scriptblock] $RemoveScriptBlock, - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [Alias('Set')] [scriptblock] $SetScriptBlock, - [Parameter(ParameterSetName='Custom')] + [Parameter(ParameterSetName = 'Custom')] [Alias('Unregister')] [scriptblock] $UnregisterScriptBlock @@ -141,18 +140,18 @@ function Register-PodeSecretVault } $vault = @{ - Name = $Name - Type = $PSCmdlet.ParameterSetName.ToLowerInvariant() - Parameters = $VaultParameters + Name = $Name + Type = $PSCmdlet.ParameterSetName.ToLowerInvariant() + Parameters = $VaultParameters AutoImported = $false - Unlock = @{ - Secret = $UnlockSecureSecret - Expiry = $null + Unlock = @{ + Secret = $UnlockSecureSecret + Expiry = $null Interval = $UnlockInterval - Enabled = (!(Test-PodeIsEmpty $UnlockSecureSecret)) + Enabled = (!(Test-PodeIsEmpty $UnlockSecureSecret)) } - Cache = @{ - Ttl = $CacheTtl + Cache = @{ + Ttl = $CacheTtl Enabled = ($CacheTtl -gt 0) } } @@ -205,11 +204,10 @@ The Name of the Secret Vault in Pode to unregister. .EXAMPLE Unregister-PodeSecretVault -Name 'VaultName' #> -function Unregister-PodeSecretVault -{ +function Unregister-PodeSecretVault { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -250,10 +248,9 @@ The Name of the Secret Vault in Pode to be unlocked. .EXAMPLE Unlock-PodeSecretVault -Name 'VaultName' #> -function Unlock-PodeSecretVault -{ +function Unlock-PodeSecretVault { param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -311,11 +308,10 @@ $vault = Get-PodeSecretVault -Name 'VaultName' .EXAMPLE $vaults = Get-PodeSecretVault -Name 'VaultName1', 'VaultName2' #> -function Get-PodeSecretVault -{ +function Get-PodeSecretVault { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string[]] $Name ) @@ -325,14 +321,14 @@ function Get-PodeSecretVault # further filter by vault names if (($null -ne $Name) -and ($Name.Length -gt 0)) { $vaults = @(foreach ($_name in $Name) { - foreach ($vault in $vaults) { - if ($vault.Name -ine $_name) { - continue - } + foreach ($vault in $vaults) { + if ($vault.Name -ine $_name) { + continue + } - $vault - } - }) + $vault + } + }) } # return @@ -352,11 +348,10 @@ The Name of the Secret Vault to test. .EXAMPLE if (Test-PodeSecretVault -Name 'VaultName') { ... } #> -function Test-PodeSecretVault -{ +function Test-PodeSecretVault { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -399,15 +394,14 @@ Mount-PodeSecret -Name 'SecretName' -Vault 'VaultName' -Key 'path/to/secret' -Ex .EXAMPLE Mount-PodeSecret -Name 'SecretName' -Vault 'VaultName' -Key 'key_of_secret' -CacheTtl 5 #> -function Mount-PodeSecret -{ +function Mount-PodeSecret { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Vault, @@ -419,7 +413,7 @@ function Mount-PodeSecret [string] $ExpandProperty, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key, @@ -460,16 +454,16 @@ function Mount-PodeSecret } $PodeContext.Server.Secrets.Keys[$Name] = @{ - Key = $Key + Key = $Key Properties = @{ - Fields = $props - Expand = (![string]::IsNullOrWhiteSpace($ExpandProperty)) + Fields = $props + Expand = (![string]::IsNullOrWhiteSpace($ExpandProperty)) Enabled = (!(Test-PodeIsEmpty $props)) } - Vault = $Vault - Arguments = $ArgumentList - Cache = @{ - Ttl = $CacheTtl + Vault = $Vault + Arguments = $ArgumentList + Cache = @{ + Ttl = $CacheTtl Enabled = ($CacheTtl -gt 0) } } @@ -494,11 +488,10 @@ Dismount-PodeSecret -Name 'SecretName' .EXAMPLE Dismount-PodeSecret -Name 'SecretName' -Remove #> -function Dismount-PodeSecret -{ +function Dismount-PodeSecret { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -541,11 +534,10 @@ $value = Get-PodeSecret -Name 'SecretName' .EXAMPLE $value = $secret:SecretName #> -function Get-PodeSecret -{ +function Get-PodeSecret { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -577,7 +569,7 @@ function Get-PodeSecret # filter the value by any properties if ($secret.Properties.Enabled) { if ($secret.Properties.Expand) { - $value = Select-Object -InputObject $value -ExpandProperty $secret.Properties.Fields + $value = Select-Object -InputObject $value -ExpandProperty $secret.Properties.Fields } else { $value = Select-Object -InputObject $value -Property $secret.Properties.Fields @@ -607,11 +599,10 @@ The friendly Name of a Secret. .EXAMPLE if (Test-PodeSecret -Name 'SecretName') { ... } #> -function Test-PodeSecret -{ +function Test-PodeSecret { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -645,16 +636,15 @@ Update-PodeSecret -Name 'SecretName' -InputObject 'value' .EXAMPLE $secret:SecretName = 'value' #> -function Update-PodeSecret -{ +function Update-PodeSecret { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, #> byte[], string, securestring, pscredential, hashtable - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [object] $InputObject, @@ -718,15 +708,14 @@ An optional array of Arguments to be supplied to a custom Secret Vault's scriptb .EXAMPLE Remove-PodeSecret -Key 'path/to/secret' -Vault 'VaultName' #> -function Remove-PodeSecret -{ +function Remove-PodeSecret { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Vault, @@ -780,15 +769,14 @@ $value = Read-PodeSecret -Key 'path/to/secret' -Vault 'VaultName' .EXAMPLE $value = Read-PodeSecret -Key 'key_of_secret' -Vault 'VaultName' -Property prop1, prop2 #> -function Read-PodeSecret -{ +function Read-PodeSecret { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Vault, @@ -823,7 +811,7 @@ function Read-PodeSecret # filter the value by any properties if (![string]::IsNullOrWhiteSpace($ExpandProperty)) { - $value = Select-Object -InputObject $value -ExpandProperty $ExpandProperty + $value = Select-Object -InputObject $value -ExpandProperty $ExpandProperty } elseif (![string]::IsNullOrEmpty($Property)) { $value = Select-Object -InputObject $value -Property $Property @@ -862,20 +850,19 @@ Set-PodeSecret -Key 'path/to/secret' -Vault 'VaultName' -InputObject 'value' .EXAMPLE Set-PodeSecret -Key 'key_of_secret' -Vault 'VaultName' -InputObject @{ key = value } #> -function Set-PodeSecret -{ +function Set-PodeSecret { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Key, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Vault, #> byte[], string, securestring, pscredential, hashtable - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [object] $InputObject, diff --git a/src/Public/Security.ps1 b/src/Public/Security.ps1 index e99e2010a..5d5190014 100644 --- a/src/Public/Security.ps1 +++ b/src/Public/Security.ps1 @@ -20,11 +20,10 @@ Set-PodeSecurity -Type Simple .EXAMPLE Set-PodeSecurity -Type Strict -UseHsts #> -function Set-PodeSecurity -{ +function Set-PodeSecurity { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Simple', 'Strict')] [string] $Type, @@ -90,8 +89,7 @@ Removes definitions for all security headers. .EXAMPLE Remove-PodeSecurity #> -function Remove-PodeSecurity -{ +function Remove-PodeSecurity { [CmdletBinding()] param() @@ -115,11 +113,10 @@ The Value of the security header. .EXAMPLE Add-PodeSecurityHeader -Name 'X-Header-Name' -Value 'SomeValue' #> -function Add-PodeSecurityHeader -{ +function Add-PodeSecurityHeader { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -146,11 +143,10 @@ The Name of the security header. .EXAMPLE Remove-PodeSecurityHeader -Name 'X-Header-Name' #> -function Remove-PodeSecurityHeader -{ +function Remove-PodeSecurityHeader { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -168,8 +164,7 @@ Hide the Server HTTP Header from Responses .EXAMPLE Hide-PodeSecurityServer #> -function Hide-PodeSecurityServer -{ +function Hide-PodeSecurityServer { [CmdletBinding()] param() @@ -186,8 +181,7 @@ Show the Server HTTP Header on Responses .EXAMPLE Show-PodeSecurityServer #> -function Show-PodeSecurityServer -{ +function Show-PodeSecurityServer { [CmdletBinding()] param() @@ -207,11 +201,10 @@ The Type to use. .EXAMPLE Set-PodeSecurityFrameOptions -Type SameOrigin #> -function Set-PodeSecurityFrameOptions -{ +function Set-PodeSecurityFrameOptions { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('Deny', 'SameOrigin')] [string] $Type @@ -230,8 +223,7 @@ Removes definition for the X-Frame-Options header. .EXAMPLE Remove-PodeSecurityFrameOptions #> -function Remove-PodeSecurityFrameOptions -{ +function Remove-PodeSecurityFrameOptions { [CmdletBinding()] param() @@ -299,8 +291,7 @@ If supplied, the X-XSS-Protection header will be set to blocking mode. (Default: .EXAMPLE Set-PodeSecurityContentSecurityPolicy -Default 'self' #> -function Set-PodeSecurityContentSecurityPolicy -{ +function Set-PodeSecurityContentSecurityPolicy { [CmdletBinding()] param( [Parameter()] @@ -408,10 +399,10 @@ function Set-PodeSecurityContentSecurityPolicy # this is done to explicitly disable XSS auditors in modern browsers # as having it enabled has now been found to cause more vulnerabilities if ($XssBlock) { - Add-PodeSecurityHeader -Name 'X-XSS-Protection' -Value "1; mode=block" + Add-PodeSecurityHeader -Name 'X-XSS-Protection' -Value '1; mode=block' } else { - Add-PodeSecurityHeader -Name 'X-XSS-Protection' -Value "0" + Add-PodeSecurityHeader -Name 'X-XSS-Protection' -Value '0' } } @@ -473,8 +464,7 @@ If supplied, the header will have the upgrade-insecure-requests value added. .EXAMPLE Add-PodeSecurityContentSecurityPolicy -Default '*.twitter.com' -Image 'data' #> -function Add-PodeSecurityContentSecurityPolicy -{ +function Add-PodeSecurityContentSecurityPolicy { [CmdletBinding()] param( [Parameter()] @@ -587,8 +577,7 @@ Removes definition for the Content-Security-Policy and X-XSS-Protection headers. .EXAMPLE Remove-PodeSecurityContentSecurityPolicy #> -function Remove-PodeSecurityContentSecurityPolicy -{ +function Remove-PodeSecurityContentSecurityPolicy { [CmdletBinding()] param() @@ -696,8 +685,7 @@ The values to use for the XrSpatialTracking portion of the header. .EXAMPLE Set-PodeSecurityPermissionsPolicy -LayoutAnimations 'none' -UnoptimisedImages 'none' -OversizedImages 'none' -SyncXhr 'none' -UnsizedMedia 'none' #> -function Set-PodeSecurityPermissionsPolicy -{ +function Set-PodeSecurityPermissionsPolicy { [CmdletBinding()] param( [Parameter()] @@ -962,8 +950,7 @@ The values to add for the XrSpatialTracking portion of the header. .EXAMPLE Add-PodeSecurityPermissionsPolicy -AmbientLightSensor 'none' #> -function Add-PodeSecurityPermissionsPolicy -{ +function Add-PodeSecurityPermissionsPolicy { [CmdletBinding()] param( [Parameter()] @@ -1138,8 +1125,7 @@ Removes definitions for the Permissions-Policy header. .EXAMPLE Remove-PodeSecurityPermissionsPolicy #> -function Remove-PodeSecurityPermissionsPolicy -{ +function Remove-PodeSecurityPermissionsPolicy { [CmdletBinding()] param() @@ -1159,11 +1145,10 @@ The Type to use. .EXAMPLE Set-PodeSecurityReferrerPolicy -Type No-Referrer #> -function Set-PodeSecurityReferrerPolicy -{ +function Set-PodeSecurityReferrerPolicy { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateSet('No-Referrer', 'No-Referrer-When-Downgrade', 'Same-Origin', 'Origin', 'Strict-Origin', 'Origin-When-Cross-Origin', 'Strict-Origin-When-Cross-Origin', 'Unsafe-Url')] [string] @@ -1183,8 +1168,7 @@ Removes definitions for the Referrer-Policy header. .EXAMPLE Remove-PodeSecurityReferrerPolicy #> -function Remove-PodeSecurityReferrerPolicy -{ +function Remove-PodeSecurityReferrerPolicy { [CmdletBinding()] param() @@ -1201,8 +1185,7 @@ Set a value for the X-Content-Type-Options header to "nosniff". .EXAMPLE Set-PodeSecurityContentTypeOptions #> -function Set-PodeSecurityContentTypeOptions -{ +function Set-PodeSecurityContentTypeOptions { [CmdletBinding()] param() @@ -1219,8 +1202,7 @@ Removes definitions for the X-Content-Type-Options header. .EXAMPLE Remove-PodeSecurityContentTypeOptions #> -function Remove-PodeSecurityContentTypeOptions -{ +function Remove-PodeSecurityContentTypeOptions { [CmdletBinding()] param() @@ -1243,8 +1225,7 @@ If supplied, the header will have includeSubDomains. .EXAMPLE Set-PodeSecurityStrictTransportSecurity -Duration 86400 -IncludeSubDomains #> -function Set-PodeSecurityStrictTransportSecurity -{ +function Set-PodeSecurityStrictTransportSecurity { [CmdletBinding()] param( [Parameter()] @@ -1260,9 +1241,9 @@ function Set-PodeSecurityStrictTransportSecurity } $value = "max-age=$($Duration)" - + if ($IncludeSubDomains) { - $value += "; includeSubDomains" + $value += '; includeSubDomains' } Add-PodeSecurityHeader -Name 'Strict-Transport-Security' -Value $value @@ -1278,8 +1259,7 @@ Removes definitions for the Strict-Transport-Security header. .EXAMPLE Remove-PodeSecurityStrictTransportSecurity #> -function Remove-PodeSecurityStrictTransportSecurity -{ +function Remove-PodeSecurityStrictTransportSecurity { [CmdletBinding()] param() @@ -1305,8 +1285,7 @@ Specifies a value for Cross-Origin-Resource-Policy. .EXAMPLE Set-PodeSecurityCrossOrigin -Embed Require-Corp -Open Same-Origin -Resource Same-Origin #> -function Set-PodeSecurityCrossOrigin -{ +function Set-PodeSecurityCrossOrigin { [CmdletBinding()] param( [Parameter()] @@ -1340,8 +1319,7 @@ Removes definitions for the Cross-Origin headers: Cross-Origin-Embedder-Policy, .EXAMPLE Remove-PodeSecurityCrossOrigin #> -function Remove-PodeSecurityCrossOrigin -{ +function Remove-PodeSecurityCrossOrigin { [CmdletBinding()] param() @@ -1378,8 +1356,7 @@ If supplied, a global Options Route will be created. .EXAMPLE Set-PodeSecurityAccessControl -Origin '*' -Methods '*' -Headers '*' -Duration 7200 #> -function Set-PodeSecurityAccessControl -{ +function Set-PodeSecurityAccessControl { [CmdletBinding()] param( [Parameter()] @@ -1459,8 +1436,7 @@ Removes definitions for the Access-Control headers: Access-Control-Allow-Origin, .EXAMPLE Remove-PodeSecurityAccessControl #> -function Remove-PodeSecurityAccessControl -{ +function Remove-PodeSecurityAccessControl { [CmdletBinding()] param() diff --git a/src/Public/Sessions.ps1 b/src/Public/Sessions.ps1 index a96496a02..b8335452b 100644 --- a/src/Public/Sessions.ps1 +++ b/src/Public/Sessions.ps1 @@ -45,10 +45,9 @@ Enable-PodeSessionMiddleware -Duration 120 -Extend -Generator { return [System.I .EXAMPLE Enable-PodeSessionMiddleware -Secret 'schwifty' -Duration 120 -UseHeaders -Strict #> -function Enable-PodeSessionMiddleware -{ - [CmdletBinding(DefaultParameterSetName='Cookies')] - param ( +function Enable-PodeSessionMiddleware { + [CmdletBinding(DefaultParameterSetName = 'Cookies')] + param( [Parameter()] [string] $Secret, @@ -60,12 +59,12 @@ function Enable-PodeSessionMiddleware [Parameter()] [ValidateScript({ - if ($_ -lt 0) { - throw "Duration must be 0 or greater, but got: $($_)s" - } + if ($_ -lt 0) { + throw "Duration must be 0 or greater, but got: $($_)s" + } - return $true - })] + return $true + })] [int] $Duration = 0, @@ -80,18 +79,18 @@ function Enable-PodeSessionMiddleware [switch] $Extend, - [Parameter(ParameterSetName='Cookies')] + [Parameter(ParameterSetName = 'Cookies')] [switch] $HttpOnly, - [Parameter(ParameterSetName='Cookies')] + [Parameter(ParameterSetName = 'Cookies')] [switch] $Secure, [switch] $Strict, - [Parameter(ParameterSetName='Headers')] + [Parameter(ParameterSetName = 'Headers')] [switch] $UseHeaders ) @@ -114,7 +113,7 @@ function Enable-PodeSessionMiddleware # verify the secret, set to guid if not supplied, or error if none and we have a storage if ([string]::IsNullOrEmpty($Secret)) { if (!(Test-PodeIsEmpty $Storage)) { - throw "A Secret is required when using custom session storage" + throw 'A Secret is required when using custom session storage' } $Secret = New-PodeGuid -Secure @@ -128,16 +127,16 @@ function Enable-PodeSessionMiddleware # set options against server context $PodeContext.Server.Sessions = @{ - Name = $Name - Secret = $Secret + Name = $Name + Secret = $Secret GenerateId = (Protect-PodeValue -Value $Generator -Default { return (New-PodeGuid) }) - Store = $Storage - Info = @{ - Duration = $Duration - Extend = $Extend.IsPresent - Secure = $Secure.IsPresent - Strict = $Strict.IsPresent - HttpOnly = $HttpOnly.IsPresent + Store = $Storage + Info = @{ + Duration = $Duration + Extend = $Extend.IsPresent + Secure = $Secure.IsPresent + Strict = $Strict.IsPresent + HttpOnly = $HttpOnly.IsPresent UseHeaders = $UseHeaders.IsPresent } } @@ -157,8 +156,7 @@ Remove the current Session, logging it out. This will remove the session from St .EXAMPLE Remove-PodeSession #> -function Remove-PodeSession -{ +function Remove-PodeSession { [CmdletBinding()] param() @@ -189,8 +187,7 @@ If supplied, the data will be saved even if nothing has changed. .EXAMPLE Save-PodeSession -Force #> -function Save-PodeSession -{ +function Save-PodeSession { [CmdletBinding()] param( [switch] @@ -233,8 +230,7 @@ If supplied, the sessionId will be returned regardless of authentication. .EXAMPLE $sessionId = Get-PodeSessionId #> -function Get-PodeSessionId -{ +function Get-PodeSessionId { [CmdletBinding()] param( [switch] @@ -282,8 +278,7 @@ Resets the current Session's expiry date, to be from the current time plus the d .EXAMPLE Reset-PodeSessionExpiry #> -function Reset-PodeSessionExpiry -{ +function Reset-PodeSessionExpiry { [CmdletBinding()] param() @@ -314,8 +309,7 @@ Returns the defined Session duration that all Session are created using. .EXAMPLE $duration = Get-PodeSessionDuration #> -function Get-PodeSessionDuration -{ +function Get-PodeSessionDuration { [CmdletBinding()] param() @@ -332,8 +326,7 @@ Returns the datetime on which the current Session's will expire. .EXAMPLE $expiry = Get-PodeSessionExpiry #> -function Get-PodeSessionExpiry -{ +function Get-PodeSessionExpiry { [CmdletBinding()] param() diff --git a/src/Public/State.ps1 b/src/Public/State.ps1 index ec2ca2a11..34d8519bb 100644 --- a/src/Public/State.ps1 +++ b/src/Public/State.ps1 @@ -20,16 +20,15 @@ Set-PodeState -Name 'Data' -Value @{ 'Name' = 'Rick Sanchez' } .EXAMPLE Set-PodeState -Name 'Users' -Value @('user1', 'user2') -Scope General, Users #> -function Set-PodeState -{ +function Set-PodeState { [CmdletBinding()] [OutputType([object])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [object] $Value, @@ -39,7 +38,7 @@ function Set-PodeState ) if ($null -eq $PodeContext.Server.State) { - throw "Pode has not been initialised" + throw 'Pode has not been initialised' } if ($null -eq $Scope) { @@ -70,11 +69,10 @@ If supplied, the state's value and scope will be returned as a hashtable. .EXAMPLE Get-PodeState -Name 'Data' #> -function Get-PodeState -{ +function Get-PodeState { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -83,7 +81,7 @@ function Get-PodeState ) if ($null -eq $PodeContext.Server.State) { - throw "Pode has not been initialised" + throw 'Pode has not been initialised' } if ($WithScope) { @@ -113,8 +111,7 @@ $names = Get-PodeStateNames -Scope '' .EXAMPLE $names = Get-PodeStateNames -Pattern '^\w+[0-9]{0,2}$' #> -function Get-PodeStateNames -{ +function Get-PodeStateNames { [CmdletBinding()] param( [Parameter()] @@ -127,7 +124,7 @@ function Get-PodeStateNames ) if ($null -eq $PodeContext.Server.State) { - throw "Pode has not been initialised" + throw 'Pode has not been initialised' } if ($null -eq $Scope) { @@ -138,19 +135,19 @@ function Get-PodeStateNames $keys = $tempState.Keys if ($Scope.Length -gt 0) { - $keys = @(foreach($key in $keys) { - if ($tempState[$key].Scope -iin $Scope) { - $key - } - }) + $keys = @(foreach ($key in $keys) { + if ($tempState[$key].Scope -iin $Scope) { + $key + } + }) } if (![string]::IsNullOrWhiteSpace($Pattern)) { - $keys = @(foreach($key in $keys) { - if ($key -imatch $Pattern) { - $key - } - }) + $keys = @(foreach ($key in $keys) { + if ($key -imatch $Pattern) { + $key + } + }) } return $keys @@ -169,18 +166,17 @@ The name of the state object. .EXAMPLE Remove-PodeState -Name 'Data' #> -function Remove-PodeState -{ +function Remove-PodeState { [CmdletBinding()] [OutputType([object])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) if ($null -eq $PodeContext.Server.State) { - throw "Pode has not been initialised" + throw 'Pode has not been initialised' } $value = $PodeContext.Server.State[$Name].Value @@ -222,11 +218,10 @@ Save-PodeState -Path './state.json' -Exclude Name1, Name2 .EXAMPLE Save-PodeState -Path './state.json' -Scope Users #> -function Save-PodeState -{ +function Save-PodeState { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -252,7 +247,7 @@ function Save-PodeState # error if attempting to use outside of the pode server if ($null -eq $PodeContext.Server.State) { - throw "Pode has not been initialised" + throw 'Pode has not been initialised' } # get the full path to save the state @@ -330,11 +325,10 @@ Saved JSON maximum depth. Will be passed to ConvertFrom-JSON's -Depth parameter .EXAMPLE Restore-PodeState -Path './state.json' #> -function Restore-PodeState -{ +function Restore-PodeState { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path, @@ -347,7 +341,7 @@ function Restore-PodeState # error if attempting to use outside of the pode server if ($null -eq $PodeContext.Server.State) { - throw "Pode has not been initialised" + throw 'Pode has not been initialised' } # get the full path to the state @@ -411,18 +405,17 @@ The name of the state object. .EXAMPLE Test-PodeState -Name 'Data' #> -function Test-PodeState -{ +function Test-PodeState { [CmdletBinding()] [OutputType([bool])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) if ($null -eq $PodeContext.Server.State) { - throw "Pode has not been initialised" + throw 'Pode has not been initialised' } return $PodeContext.Server.State.ContainsKey($Name) diff --git a/src/Public/Tasks.ps1 b/src/Public/Tasks.ps1 index ad0a6d331..d4f47573c 100644 --- a/src/Public/Tasks.ps1 +++ b/src/Public/Tasks.ps1 @@ -23,19 +23,18 @@ Add-PodeTask -Name 'Example1' -ScriptBlock { Invoke-SomeLogic } .EXAMPLE Add-PodeTask -Name 'Example1' -ScriptBlock { return Get-SomeObject } #> -function Add-PodeTask -{ - [CmdletBinding(DefaultParameterSetName='Script')] +function Add-PodeTask { + [CmdletBinding(DefaultParameterSetName = 'Script')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ParameterSetName='Script')] + [Parameter(Mandatory = $true, ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, @@ -59,10 +58,10 @@ function Add-PodeTask # add the task $PodeContext.Tasks.Enabled = $true $PodeContext.Tasks.Items[$Name] = @{ - Name = $Name - Script = $ScriptBlock + Name = $Name + Script = $ScriptBlock UsingVariables = $usingVars - Arguments = (Protect-PodeValue -Value $ArgumentList -Default @{}) + Arguments = (Protect-PodeValue -Value $ArgumentList -Default @{}) } } @@ -79,11 +78,10 @@ The Maximum number of Tasks to run. .EXAMPLE Set-PodeTaskConcurrency -Maximum 10 #> -function Set-PodeTaskConcurrency -{ +function Set-PodeTaskConcurrency { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Maximum ) @@ -138,11 +136,10 @@ $task = Invoke-PodeTask -Name 'Example1' .EXAMPLE Invoke-PodeTask -Name 'Example1' | Wait-PodeTask -Timeout 3 #> -function Invoke-PodeTask -{ +function Invoke-PodeTask { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name, @@ -188,11 +185,10 @@ The Name of Task to be removed. .EXAMPLE Remove-PodeTask -Name 'Example1' #> -function Remove-PodeTask -{ +function Remove-PodeTask { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name ) @@ -210,8 +206,7 @@ Removes all Tasks. .EXAMPLE Clear-PodeTasks #> -function Clear-PodeTasks -{ +function Clear-PodeTasks { [CmdletBinding()] param() @@ -237,11 +232,10 @@ Any new Arguments for the Task. .EXAMPLE Edit-PodeTask -Name 'Example1' -ScriptBlock { Invoke-SomeNewLogic } #> -function Edit-PodeTask -{ +function Edit-PodeTask { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name, @@ -290,8 +284,7 @@ Get-PodeTask .EXAMPLE Get-PodeTask -Name Example1, Example2 #> -function Get-PodeTask -{ +function Get-PodeTask { [CmdletBinding()] param( [Parameter()] @@ -304,14 +297,14 @@ function Get-PodeTask # further filter by task names if (($null -ne $Name) -and ($Name.Length -gt 0)) { $tasks = @(foreach ($_name in $Name) { - foreach ($task in $tasks) { - if ($task.Name -ine $_name) { - continue - } + foreach ($task in $tasks) { + if ($task.Name -ine $_name) { + continue + } - $task - } - }) + $task + } + }) } # return @@ -334,8 +327,7 @@ Use-PodeTasks .EXAMPLE Use-PodeTasks -Path './my-tasks' #> -function Use-PodeTasks -{ +function Use-PodeTasks { [CmdletBinding()] param( [Parameter()] @@ -359,11 +351,10 @@ The Task to be closed. .EXAMPLE Invoke-PodeTask -Name 'Example1' | Close-PodeTask #> -function Close-PodeTask -{ +function Close-PodeTask { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Task ) @@ -384,11 +375,10 @@ The Task to be check. .EXAMPLE Invoke-PodeTask -Name 'Example1' | Test-PodeTaskCompleted #> -function Test-PodeTaskCompleted -{ +function Test-PodeTaskCompleted { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $Task ) @@ -415,12 +405,11 @@ $context = Wait-PodeTask -Task $listener.GetContextAsync() .EXAMPLE $result = Invoke-PodeTask -Name 'Example1' | Wait-PodeTask #> -function Wait-PodeTask -{ +function Wait-PodeTask { [CmdletBinding()] [OutputType([object])] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] $Task, [Parameter()] @@ -436,5 +425,5 @@ function Wait-PodeTask return (Wait-PodeTaskInternal -Task $Task -Timeout $Timeout) } - throw "Task type is invalid, expected either [System.Threading.Tasks.Task] or [hashtable]" + throw 'Task type is invalid, expected either [System.Threading.Tasks.Task] or [hashtable]' } \ No newline at end of file diff --git a/src/Public/Threading.ps1 b/src/Public/Threading.ps1 index 041649e9f..a8249d15e 100644 --- a/src/Public/Threading.ps1 +++ b/src/Public/Threading.ps1 @@ -35,20 +35,19 @@ Lock-PodeObject -Name 'LockName' -Timeout 5000 -ScriptBlock { /* logic */ } .EXAMPLE $result = (Lock-PodeObject -Return -Object $SomeArray -ScriptBlock { /* logic */ }) #> -function Lock-PodeObject -{ - [CmdletBinding(DefaultParameterSetName='Object')] +function Lock-PodeObject { + [CmdletBinding(DefaultParameterSetName = 'Object')] [OutputType([object])] param( - [Parameter(ValueFromPipeline=$true, ParameterSetName='Object')] + [Parameter(ValueFromPipeline = $true, ParameterSetName = 'Object')] [object] $Object, - [Parameter(Mandatory=$true, ParameterSetName='Name')] + [Parameter(Mandatory = $true, ParameterSetName = 'Name')] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, @@ -102,11 +101,10 @@ The Name of the Lockable object. .EXAMPLE New-PodeLockable -Name 'Lock1' #> -function New-PodeLockable -{ +function New-PodeLockable { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -131,11 +129,10 @@ The Name of the Lockable object to remove. .EXAMPLE Remove-PodeLockable -Name 'Lock1' #> -function Remove-PodeLockable -{ +function Remove-PodeLockable { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -158,11 +155,10 @@ The Name of the Lockable object. .EXAMPLE Get-PodeLockable -Name 'Lock1' | Lock-PodeObject -ScriptBlock {} #> -function Get-PodeLockable -{ +function Get-PodeLockable { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -183,11 +179,10 @@ The Name of the Lockable object. .EXAMPLE Test-PodeLockable -Name 'Lock1' #> -function Test-PodeLockable -{ +function Test-PodeLockable { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -220,15 +215,14 @@ Enter-PodeLockable -Object $SomeArray .EXAMPLE Enter-PodeLockable -Name 'LockName' -Timeout 5000 #> -function Enter-PodeLockable -{ - [CmdletBinding(DefaultParameterSetName='Object')] +function Enter-PodeLockable { + [CmdletBinding(DefaultParameterSetName = 'Object')] param( - [Parameter(ValueFromPipeline=$true, ParameterSetName='Object')] + [Parameter(ValueFromPipeline = $true, ParameterSetName = 'Object')] [object] $Object, - [Parameter(Mandatory=$true, ParameterSetName='Name')] + [Parameter(Mandatory = $true, ParameterSetName = 'Name')] [string] $Name, @@ -269,7 +263,7 @@ function Enter-PodeLockable $locked = $false [System.Threading.Monitor]::TryEnter($Object.SyncRoot, $Timeout, [ref]$locked) if (!$locked) { - throw "Failed to acquire lock on object" + throw 'Failed to acquire lock on object' } } @@ -292,15 +286,14 @@ Exit-PodeLockable -Object $SomeArray .EXAMPLE Exit-PodeLockable -Name 'LockName' #> -function Exit-PodeLockable -{ - [CmdletBinding(DefaultParameterSetName='Object')] +function Exit-PodeLockable { + [CmdletBinding(DefaultParameterSetName = 'Object')] param( - [Parameter(ValueFromPipeline=$true, ParameterSetName='Object')] + [Parameter(ValueFromPipeline = $true, ParameterSetName = 'Object')] [object] $Object, - [Parameter(Mandatory=$true, ParameterSetName='Name')] + [Parameter(Mandatory = $true, ParameterSetName = 'Name')] [string] $Name ) @@ -341,8 +334,7 @@ Remove all Lockables. .EXAMPLE Clear-PodeLockables #> -function Clear-PodeLockables -{ +function Clear-PodeLockables { [CmdletBinding()] param() @@ -380,11 +372,10 @@ New-PodeMutex -Name 'LocalMutex' -Scope Local .EXAMPLE New-PodeMutex -Name 'GlobalMutex' -Scope Global #> -function New-PodeMutex -{ +function New-PodeMutex { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -430,11 +421,10 @@ The Name of the Mutex. .EXAMPLE Test-PodeMutex -Name 'LocalMutex' #> -function Test-PodeMutex -{ +function Test-PodeMutex { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -455,11 +445,10 @@ The Name of the Mutex. .EXAMPLE $mutex = Get-PodeMutex -Name 'SelfMutex' #> -function Get-PodeMutex -{ +function Get-PodeMutex { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -480,11 +469,10 @@ The Name of the Mutex. .EXAMPLE Remove-PodeMutex -Name 'GlobalMutex' #> -function Remove-PodeMutex -{ +function Remove-PodeMutex { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -520,15 +508,14 @@ Use-PodeMutex -Name 'SelfMutex' -Timeout 5000 -ScriptBlock {} .EXAMPLE $result = Use-PodeMutex -Name 'LocalMutex' -Return -ScriptBlock {} #> -function Use-PodeMutex -{ +function Use-PodeMutex { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, @@ -573,11 +560,10 @@ If supplied, a number of milliseconds to timeout after if a hold cannot be acqui .EXAMPLE Enter-PodeMutex -Name 'SelfMutex' -Timeout 5000 #> -function Enter-PodeMutex -{ +function Enter-PodeMutex { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -609,11 +595,10 @@ The Name of the Mutex. .EXAMPLE Exit-PodeMutex -Name 'SelfMutex' #> -function Exit-PodeMutex -{ +function Exit-PodeMutex { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -636,8 +621,7 @@ Removes all Mutexes. .EXAMPLE Clear-PodeMutexes #> -function Clear-PodeMutexes -{ +function Clear-PodeMutexes { [CmdletBinding()] param() @@ -678,11 +662,10 @@ New-PodeSemaphore -Name 'LocalSemaphore' -Scope Local .EXAMPLE New-PodeSemaphore -Name 'GlobalSemaphore' -Count 3 -Scope Global #> -function New-PodeSemaphore -{ +function New-PodeSemaphore { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -736,11 +719,10 @@ The Name of the Semaphore. .EXAMPLE Test-PodeSemaphore -Name 'LocalSemaphore' #> -function Test-PodeSemaphore -{ +function Test-PodeSemaphore { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -761,11 +743,10 @@ The Name of the Semaphore. .EXAMPLE $semaphore = Get-PodeSemaphore -Name 'SelfSemaphore' #> -function Get-PodeSemaphore -{ +function Get-PodeSemaphore { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -786,11 +767,10 @@ The Name of the Semaphore. .EXAMPLE Remove-PodeSemaphore -Name 'GlobalSemaphore' #> -function Remove-PodeSemaphore -{ +function Remove-PodeSemaphore { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -826,15 +806,14 @@ Use-PodeSemaphore -Name 'SelfSemaphore' -Timeout 5000 -ScriptBlock {} .EXAMPLE $result = Use-PodeSemaphore -Name 'LocalSemaphore' -Return -ScriptBlock {} #> -function Use-PodeSemaphore -{ +function Use-PodeSemaphore { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, @@ -879,11 +858,10 @@ If supplied, a number of milliseconds to timeout after if a hold cannot be acqui .EXAMPLE Enter-PodeSemaphore -Name 'SelfSemaphore' -Timeout 5000 #> -function Enter-PodeSemaphore -{ +function Enter-PodeSemaphore { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -918,11 +896,10 @@ The number of releases to release in one go. (Default: 1) .EXAMPLE Exit-PodeSemaphore -Name 'SelfSemaphore' #> -function Exit-PodeSemaphore -{ +function Exit-PodeSemaphore { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, @@ -953,8 +930,7 @@ Removes all Semaphores. .EXAMPLE Clear-PodeSemaphores #> -function Clear-PodeSemaphores -{ +function Clear-PodeSemaphores { [CmdletBinding()] param() diff --git a/src/Public/Timers.ps1 b/src/Public/Timers.ps1 index cda89acb1..84ef29dd7 100644 --- a/src/Public/Timers.ps1 +++ b/src/Public/Timers.ps1 @@ -41,19 +41,18 @@ Add-PodeTimer -Name 'RunAfter60secs' -Interval 10 -Skip 6 -ScriptBlock { /* logi .EXAMPLE Add-PodeTimer -Name 'Args' -Interval 2 -ScriptBlock { /* logic */ } -ArgumentList 'arg1', 'arg2' #> -function Add-PodeTimer -{ - [CmdletBinding(DefaultParameterSetName='Script')] - param ( - [Parameter(Mandatory=$true)] +function Add-PodeTimer { + [CmdletBinding(DefaultParameterSetName = 'Script')] + param( + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Interval, - [Parameter(Mandatory=$true, ParameterSetName='Script')] + [Parameter(Mandatory = $true, ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, @@ -65,7 +64,7 @@ function Add-PodeTimer [int] $Skip = 0, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, @@ -117,18 +116,18 @@ function Add-PodeTimer # add the timer $PodeContext.Timers.Enabled = $true $PodeContext.Timers.Items[$Name] = @{ - Name = $Name - Interval = $Interval - Limit = $Limit - Count = 0 - Skip = $Skip + Name = $Name + Interval = $Interval + Limit = $Limit + Count = 0 + Skip = $Skip NextTriggerTime = $NextTriggerTime LastTriggerTime = $null - Script = $ScriptBlock - UsingVariables = $usingVars - Arguments = $ArgumentList - OnStart = $OnStart - Completed = $false + Script = $ScriptBlock + UsingVariables = $usingVars + Arguments = $ArgumentList + OnStart = $OnStart + Completed = $false } } @@ -149,11 +148,10 @@ An array of arguments to supply to the Timer's ScriptBlock. .EXAMPLE Invoke-PodeTimer -Name 'timer-name' #> -function Invoke-PodeTimer -{ +function Invoke-PodeTimer { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name, @@ -184,11 +182,10 @@ The Name of Timer to be removed. .EXAMPLE Remove-PodeTimer -Name 'SaveState' #> -function Remove-PodeTimer -{ +function Remove-PodeTimer { [CmdletBinding()] - param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name ) @@ -206,8 +203,7 @@ Removes all Timers. .EXAMPLE Clear-PodeTimers #> -function Clear-PodeTimers -{ +function Clear-PodeTimers { [CmdletBinding()] param() @@ -236,11 +232,10 @@ Any new Arguments for the Timer. .EXAMPLE Edit-PodeTimer -Name 'Hello' -Interval 10 #> -function Edit-PodeTimer -{ +function Edit-PodeTimer { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $Name, @@ -298,8 +293,7 @@ Get-PodeTimer .EXAMPLE Get-PodeTimer -Name Name1, Name2 #> -function Get-PodeTimer -{ +function Get-PodeTimer { [CmdletBinding()] param( [Parameter()] @@ -312,14 +306,14 @@ function Get-PodeTimer # further filter by timer names if (($null -ne $Name) -and ($Name.Length -gt 0)) { $timers = @(foreach ($_name in $Name) { - foreach ($timer in $timers) { - if ($timer.Name -ine $_name) { - continue - } + foreach ($timer in $timers) { + if ($timer.Name -ine $_name) { + continue + } - $timer - } - }) + $timer + } + }) } # return @@ -339,11 +333,10 @@ The Name of the Timer. .EXAMPLE if (Test-PodeTimer -Name TimerName) { } #> -function Test-PodeTimer -{ +function Test-PodeTimer { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -367,8 +360,7 @@ Use-PodeTimers .EXAMPLE Use-PodeTimers -Path './my-timers' #> -function Use-PodeTimers -{ +function Use-PodeTimers { [CmdletBinding()] param( [Parameter()] diff --git a/src/Public/Utilities.ps1 b/src/Public/Utilities.ps1 index 800feafad..8074c3379 100644 --- a/src/Public/Utilities.ps1 +++ b/src/Public/Utilities.ps1 @@ -17,8 +17,7 @@ If an error is thrown, check the reason - if it's network related ignore the err .EXAMPLE Close-PodeDisposable -Disposable $stream -Close #> -function Close-PodeDisposable -{ +function Close-PodeDisposable { [CmdletBinding()] param( [Parameter()] @@ -64,8 +63,7 @@ Returns the literal path of the server. .EXAMPLE $path = Get-PodeServerPath #> -function Get-PodeServerPath -{ +function Get-PodeServerPath { [CmdletBinding()] [OutputType([string])] param() @@ -89,15 +87,14 @@ The ScriptBlock to time. .EXAMPLE Start-PodeStopwatch -Name 'ReadFile' -ScriptBlock { $content = Get-Content './file.txt' } #> -function Start-PodeStopwatch -{ +function Start-PodeStopwatch { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [scriptblock] $ScriptBlock ) @@ -132,16 +129,15 @@ The ScriptBlock to invoke. It will be supplied the Stream. .EXAMPLE $content = (Use-PodeStream -Stream $stream -ScriptBlock { return $args[0].ReadToEnd() }) #> -function Use-PodeStream -{ +function Use-PodeStream { [CmdletBinding()] [OutputType([object])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [System.IDisposable] $Stream, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) @@ -171,11 +167,10 @@ The path, literal or relative to the server, to some script. .EXAMPLE Use-PodeScript -Path './scripts/tools.ps1' #> -function Use-PodeScript -{ +function Use-PodeScript { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Path ) @@ -217,8 +212,7 @@ Returns the loaded configuration of the server. .EXAMPLE $s = Get-PodeConfig #> -function Get-PodeConfig -{ +function Get-PodeConfig { [CmdletBinding()] [OutputType([hashtable])] param() @@ -242,11 +236,10 @@ An array of arguments to supply to the Endware's ScriptBlock. .EXAMPLE Add-PodeEndware -ScriptBlock { /* logic */ } #> -function Add-PodeEndware -{ +function Add-PodeEndware { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [scriptblock] $ScriptBlock, @@ -260,9 +253,9 @@ function Add-PodeEndware # add the scriptblock to array of endware that needs to be run $PodeContext.Server.Endware += @{ - Logic = $ScriptBlock + Logic = $ScriptBlock UsingVariables = $usingVars - Arguments = $ArgumentList + Arguments = $ArgumentList } } @@ -282,8 +275,7 @@ Use-PodeEndware .EXAMPLE Use-PodeEndware -Path './endware' #> -function Use-PodeEndware -{ +function Use-PodeEndware { [CmdletBinding()] param( [Parameter()] @@ -313,15 +305,14 @@ Import-PodeModule -Name IISManager .EXAMPLE Import-PodeModule -Path './modules/utilities.psm1' #> -function Import-PodeModule -{ - [CmdletBinding(DefaultParameterSetName='Name')] +function Import-PodeModule { + [CmdletBinding(DefaultParameterSetName = 'Name')] param( - [Parameter(Mandatory=$true, ParameterSetName='Name')] + [Parameter(Mandatory = $true, ParameterSetName = 'Name')] [string] $Name, - [Parameter(Mandatory=$true, ParameterSetName='Path')] + [Parameter(Mandatory = $true, ParameterSetName = 'Path')] [string] $Path ) @@ -383,11 +374,10 @@ The name of a Snapin to import. .EXAMPLE Import-PodeSnapin -Name 'WDeploySnapin3.0' #> -function Import-PodeSnapin -{ +function Import-PodeSnapin { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) @@ -417,8 +407,7 @@ A default value to return should the main value be null/empty. .EXAMPLE $Name = Protect-PodeValue -Value $Name -Default 'Rick' #> -function Protect-PodeValue -{ +function Protect-PodeValue { [CmdletBinding()] [OutputType([object])] param( @@ -451,12 +440,11 @@ The value to use if evaluated to False. .EXAMPLE $Port = Resolve-PodeValue -Check $AllowSsl -TrueValue 443 -FalseValue -80 #> -function Resolve-PodeValue -{ +function Resolve-PodeValue { [CmdletBinding()] [OutputType([object])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [bool] $Check, @@ -505,12 +493,11 @@ Invoke-PodeScriptBlock -ScriptBlock { Write-Host 'Hello!' } .EXAMPLE Invoke-PodeScriptBlock -Arguments 'Morty' -ScriptBlock { /* logic */ } #> -function Invoke-PodeScriptBlock -{ +function Invoke-PodeScriptBlock { [CmdletBinding()] [OutputType([object])] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, @@ -573,8 +560,7 @@ The value to test. .EXAMPLE if (Test-PodeIsEmpty @{}) { /* logic */ } #> -function Test-PodeIsEmpty -{ +function Test-PodeIsEmpty { [CmdletBinding()] [OutputType([bool])] param( @@ -619,8 +605,7 @@ Tests if the the current session is running in PowerShell Core. .EXAMPLE if (Test-PodeIsPSCore) { /* logic */ } #> -function Test-PodeIsPSCore -{ +function Test-PodeIsPSCore { [CmdletBinding()] [OutputType([bool])] param() @@ -638,8 +623,7 @@ Tests if the current OS is Unix. .EXAMPLE if (Test-PodeIsUnix) { /* logic */ } #> -function Test-PodeIsUnix -{ +function Test-PodeIsUnix { [CmdletBinding()] [OutputType([bool])] param() @@ -657,8 +641,7 @@ Tests if the current OS is Windows. .EXAMPLE if (Test-PodeIsWindows) { /* logic */ } #> -function Test-PodeIsWindows -{ +function Test-PodeIsWindows { [CmdletBinding()] [OutputType([bool])] param() @@ -677,8 +660,7 @@ Tests if the current OS is MacOS. .EXAMPLE if (Test-PodeIsMacOS) { /* logic */ } #> -function Test-PodeIsMacOS -{ +function Test-PodeIsMacOS { [CmdletBinding()] [OutputType([bool])] param() @@ -696,8 +678,7 @@ Tests if the scope you're in is currently within a Pode runspace. .EXAMPLE If (Test-PodeInRunspace) { ... } #> -function Test-PodeInRunspace -{ +function Test-PodeInRunspace { [CmdletBinding()] param() @@ -721,11 +702,10 @@ The object to output. .EXAMPLE @{ Name = 'Rick' } | Out-PodeHost #> -function Out-PodeHost -{ +function Out-PodeHost { [CmdletBinding()] param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [object] $InputObject ) @@ -755,11 +735,10 @@ Whether or not to write a new line. .EXAMPLE 'Some output' | Write-PodeHost -ForegroundColor Cyan #> -function Write-PodeHost -{ +function Write-PodeHost { [CmdletBinding()] param( - [Parameter(Position=0, ValueFromPipeline=$true)] + [Parameter(Position = 0, ValueFromPipeline = $true)] [object] $Object, @@ -793,8 +772,7 @@ Returns whether or not the server is running via IIS. .EXAMPLE if (Test-PodeIsIIS) { } #> -function Test-PodeIsIIS -{ +function Test-PodeIsIIS { [CmdletBinding()] param() @@ -811,8 +789,7 @@ Returns the IIS application path, or null if not using IIS. .EXAMPLE $path = Get-PodeIISApplicationPath #> -function Get-PodeIISApplicationPath -{ +function Get-PodeIISApplicationPath { [CmdletBinding()] param() @@ -833,8 +810,7 @@ Returns whether or not the server is running via Heroku. .EXAMPLE if (Test-PodeIsHeroku) { } #> -function Test-PodeIsHeroku -{ +function Test-PodeIsHeroku { [CmdletBinding()] param() @@ -851,8 +827,7 @@ Returns whether or not the server is being hosted behind another application, su .EXAMPLE if (Test-PodeIsHosted) { } #> -function Test-PodeIsHosted -{ +function Test-PodeIsHosted { [CmdletBinding()] param() @@ -875,15 +850,14 @@ The Value of the variable to be set .EXAMPLE Out-PodeVariable -Name ExampleVar -Value @{ Name = 'Bob' } #> -function Out-PodeVariable -{ +function Out-PodeVariable { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline = $true)] [object] $Value ) @@ -949,8 +923,7 @@ New-PodeCron -Every Hour -Day Monday # every hour .EXAMPLE New-PodeCron -Every Quarter # every 1st jan, apr, jul, oct, at 00:00 #> -function New-PodeCron -{ +function New-PodeCron { [CmdletBinding()] param( [Parameter()] @@ -990,50 +963,50 @@ function New-PodeCron # cant have None and Interval if (($Every -ieq 'none') -and ($Interval -gt 0)) { - throw "Cannot supply an interval when -Every is set to None" + throw 'Cannot supply an interval when -Every is set to None' } # base cron $cron = @{ Minute = '*' - Hour = '*' - Date = '*' - Month = '*' - Day = '*' + Hour = '*' + Date = '*' + Month = '*' + Day = '*' } # convert month/day to numbers if ($Month.Length -gt 0) { $MonthInts = @(foreach ($item in $Month) { (@{ - January = 1 - February = 2 - March = 3 - April = 4 - May = 5 - June = 6 - July = 7 - August = 8 - September = 9 - October = 10 - November = 11 - December = 12 - })[$item] - }) + January = 1 + February = 2 + March = 3 + April = 4 + May = 5 + June = 6 + July = 7 + August = 8 + September = 9 + October = 10 + November = 11 + December = 12 + })[$item] + }) } if ($Day.Length -gt 0) { $DayInts = @(foreach ($item in $Day) { (@{ - Sunday = 0 - Monday = 1 - Tuesday = 2 - Wednesday = 3 - Thursday = 4 - Friday = 5 - Saturday = 6 - })[$item] - }) + Sunday = 0 + Monday = 1 + Tuesday = 2 + Wednesday = 3 + Thursday = 4 + Friday = 5 + Saturday = 6 + })[$item] + }) } # set "every" defaults @@ -1090,7 +1063,7 @@ function New-PodeCron $cron.Month = '1,4,7,10' if ($Interval -gt 0) { - throw "Cannot supply interval value for every quarter" + throw 'Cannot supply interval value for every quarter' } } @@ -1101,7 +1074,7 @@ function New-PodeCron $cron.Month = '1' if ($Interval -gt 0) { - throw "Cannot supply interval value for every year" + throw 'Cannot supply interval value for every year' } } } diff --git a/src/Public/Verbs.ps1 b/src/Public/Verbs.ps1 index 750740f8a..8ef239b81 100644 --- a/src/Public/Verbs.ps1 +++ b/src/Public/Verbs.ps1 @@ -38,20 +38,19 @@ Add-PodeVerb -Verb 'Quit' -Close .EXAMPLE Add-PodeVerb -Verb 'StartTls' -UpgradeToSsl #> -function Add-PodeVerb -{ - [CmdletBinding(DefaultParameterSetName='Script')] +function Add-PodeVerb { + [CmdletBinding(DefaultParameterSetName = 'Script')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Verb, - [Parameter(ParameterSetName='Script')] + [Parameter(ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, @@ -101,22 +100,22 @@ function Add-PodeVerb # add the verb(s) Write-Verbose "Adding Verb: $($Verb)" $PodeContext.Server.Verbs[$Verb] += @(foreach ($_endpoint in $endpoints) { - @{ - Logic = $ScriptBlock - UsingVariables = $usingVars - Endpoint = @{ - Protocol = $_endpoint.Protocol - Address = $_endpoint.Address.Trim() - Name = $_endpoint.Name - } - Arguments = $ArgumentList - Verb = $Verb - Connection = @{ - UpgradeToSsl = $UpgradeToSsl - Close = $Close + @{ + Logic = $ScriptBlock + UsingVariables = $usingVars + Endpoint = @{ + Protocol = $_endpoint.Protocol + Address = $_endpoint.Address.Trim() + Name = $_endpoint.Name + } + Arguments = $ArgumentList + Verb = $Verb + Connection = @{ + UpgradeToSsl = $UpgradeToSsl + Close = $Close + } } - } - }) + }) } <# @@ -138,11 +137,10 @@ Remove-PodeVerb -Verb 'Hello' .EXAMPLE Remove-PodeVerb -Verb 'Hello :username' -EndpointName User #> -function Remove-PodeVerb -{ +function Remove-PodeVerb { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Verb, @@ -161,8 +159,8 @@ function Remove-PodeVerb # remove the verb's logic $PodeContext.Server.Verbs[$Verb] = @($PodeContext.Server.Verbs[$Verb] | Where-Object { - $_.Endpoint.Name -ine $EndpointName - }) + $_.Endpoint.Name -ine $EndpointName + }) # if the verb has no more logic, just remove it if ((Get-PodeCount $PodeContext.Server.Verbs[$Verb]) -eq 0) { @@ -180,8 +178,7 @@ Removes all added Verbs. .EXAMPLE Clear-PodeVerbs #> -function Clear-PodeVerbs -{ +function Clear-PodeVerbs { [CmdletBinding()] param() @@ -207,8 +204,7 @@ Get-PodeVerb -Verb 'Hello' .EXAMPLE Get-PodeVerb -Verb 'Hello :username' -EndpointName User #> -function Get-PodeVerb -{ +function Get-PodeVerb { [CmdletBinding()] param( [Parameter()] @@ -237,14 +233,14 @@ function Get-PodeVerb # further filter by endpoint names if (($null -ne $EndpointName) -and ($EndpointName.Length -gt 0)) { $verbs = @(foreach ($name in $EndpointName) { - foreach ($v in $verbs) { - if ($v.Endpoint.Name -ine $name) { - continue - } + foreach ($v in $verbs) { + if ($v.Endpoint.Name -ine $name) { + continue + } - $v - } - }) + $v + } + }) } # return @@ -267,8 +263,7 @@ Use-PodeVerbs .EXAMPLE Use-PodeVerbs -Path './my-verbs' #> -function Use-PodeVerbs -{ +function Use-PodeVerbs { [CmdletBinding()] param( [Parameter()] diff --git a/src/Public/WebSockets.ps1 b/src/Public/WebSockets.ps1 index c7f7078e6..5d2cbd21d 100644 --- a/src/Public/WebSockets.ps1 +++ b/src/Public/WebSockets.ps1 @@ -13,11 +13,10 @@ The Maximum number of threads available to process WebSocket connection messages .EXAMPLE Set-PodeWebSocketConcurrency -Maximum 5 #> -function Set-PodeWebSocketConcurrency -{ +function Set-PodeWebSocketConcurrency { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [int] $Maximum ) @@ -84,23 +83,22 @@ Connect-PodeWebSocket -Name 'Example' -Url 'ws://example.com/some/socket' -FileP .EXAMPLE Connect-PodeWebSocket -Name 'Example' -Url 'ws://example.com/some/socket' -ScriptBlock { ... } -ContentType 'text/xml' #> -function Connect-PodeWebSocket -{ - [CmdletBinding(DefaultParameterSetName='Script')] +function Connect-PodeWebSocket { + [CmdletBinding(DefaultParameterSetName = 'Script')] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Url, - [Parameter(ParameterSetName='Script')] + [Parameter(ParameterSetName = 'Script')] [scriptblock] $ScriptBlock, - [Parameter(Mandatory=$true, ParameterSetName='File')] + [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string] $FilePath, @@ -138,11 +136,11 @@ function Connect-PodeWebSocket } $PodeContext.Server.WebSockets.Connections[$Name] = @{ - Name = $Name - Url = $Url - Logic = $ScriptBlock + Name = $Name + Url = $Url + Logic = $ScriptBlock UsingVariables = $usingVars - Arguments = $ArgumentList + Arguments = $ArgumentList } } @@ -159,8 +157,7 @@ The Name of the WebSocket connection (optional if in the scope where $WsEvent is .EXAMPLE Disconnect-PodeWebSocket -Name 'Example' #> -function Disconnect-PodeWebSocket -{ +function Disconnect-PodeWebSocket { [CmdletBinding()] param( [Parameter()] @@ -173,7 +170,7 @@ function Disconnect-PodeWebSocket } if ([string]::IsNullOrWhiteSpace($Name)) { - throw "No Name for a WebSocket to disconnect from supplied" + throw 'No Name for a WebSocket to disconnect from supplied' } if (Test-PodeWebSocket -Name $Name) { @@ -194,8 +191,7 @@ The Name of the WebSocket connection (optional if in the scope where $WsEvent is .EXAMPLE Remove-PodeWebSocket -Name 'Example' #> -function Remove-PodeWebSocket -{ +function Remove-PodeWebSocket { [CmdletBinding()] param( [Parameter()] @@ -208,7 +204,7 @@ function Remove-PodeWebSocket } if ([string]::IsNullOrWhiteSpace($Name)) { - throw "No Name for a WebSocket to remove supplied" + throw 'No Name for a WebSocket to remove supplied' } $PodeContext.Server.WebSockets.Receiver.RemoveWebSocket($Name) @@ -237,8 +233,7 @@ An optional message Type. (default: Text) .EXAMPLE Send-PodeWebSocket -Name 'Example' -Message @{ message = 'Hello, there' } #> -function Send-PodeWebSocket -{ +function Send-PodeWebSocket { [CmdletBinding()] param( [Parameter()] @@ -265,7 +260,7 @@ function Send-PodeWebSocket # do we have a name? if ([string]::IsNullOrWhiteSpace($Name)) { - throw "No Name for a WebSocket to send message to supplied" + throw 'No Name for a WebSocket to send message to supplied' } # do the socket exist? @@ -302,8 +297,7 @@ Reset-PodeWebSocket -Name 'Example' .EXAMPLE Reset-PodeWebSocket -Name 'Example' -Url 'ws://example.com/some/socket' #> -function Reset-PodeWebSocket -{ +function Reset-PodeWebSocket { [CmdletBinding()] param( [Parameter()] @@ -321,7 +315,7 @@ function Reset-PodeWebSocket } if ([string]::IsNullOrWhiteSpace($Name)) { - throw "No Name for a WebSocket to reset supplied" + throw 'No Name for a WebSocket to reset supplied' } if (Test-PodeWebSocket -Name $Name) { @@ -342,11 +336,10 @@ The Name of the WebSocket connection. .EXAMPLE Test-PodeWebSocket -Name 'Example' #> -function Test-PodeWebSocket -{ +function Test-PodeWebSocket { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $Name ) From b8dd623dabf6f4fb018d9fa16b22210c15b00cc7 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Tue, 17 Oct 2023 22:20:57 +0100 Subject: [PATCH 53/57] #1170: bump images in dockerfiles --- alpine.dockerfile | 2 +- arm32.dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alpine.dockerfile b/alpine.dockerfile index dafff22aa..3880711d0 100644 --- a/alpine.dockerfile +++ b/alpine.dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/powershell:7.3-alpine-3.15 +FROM mcr.microsoft.com/powershell:7.3-alpine-3.17 LABEL maintainer="Matthew Kelly (Badgerati)" RUN mkdir -p /usr/local/share/powershell/Modules/Pode COPY ./pkg/ /usr/local/share/powershell/Modules/Pode \ No newline at end of file diff --git a/arm32.dockerfile b/arm32.dockerfile index 1574f6e31..d3804fa55 100644 --- a/arm32.dockerfile +++ b/arm32.dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/powershell:preview-7.3-arm32v7-ubuntu-18.04 +FROM mcr.microsoft.com/powershell:7.3-ubuntu-22.04-arm32 LABEL maintainer="Matthew Kelly (Badgerati)" RUN mkdir -p /usr/local/share/powershell/Modules/Pode COPY ./pkg/ /usr/local/share/powershell/Modules/Pode \ No newline at end of file From c4439d5359c0f1049605b097f48c6d8cc98e41ca Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Tue, 17 Oct 2023 22:42:54 +0100 Subject: [PATCH 54/57] #1171: bump mkdocs and theme versions --- pode.build.ps1 | 59 +++++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/pode.build.ps1 b/pode.build.ps1 index eeb8a7f89..a0366fd10 100644 --- a/pode.build.ps1 +++ b/pode.build.ps1 @@ -8,43 +8,38 @@ param( #> $Versions = @{ - Pester = '4.8.0' - MkDocs = '1.4.2' + Pester = '4.8.0' + MkDocs = '1.5.3' PSCoveralls = '1.0.0' - SevenZip = '18.5.0.20180730' - DotNet = '7.0.1' - Checksum = '0.2.0' - MkDocsTheme = '9.0.2' - PlatyPS = '0.14.2' + SevenZip = '18.5.0.20180730' + DotNet = '7.0.1' + Checksum = '0.2.0' + MkDocsTheme = '9.4.6' + PlatyPS = '0.14.2' } <# # Helper Functions #> -function Test-PodeBuildIsWindows -{ +function Test-PodeBuildIsWindows { $v = $PSVersionTable return ($v.Platform -ilike '*win*' -or ($null -eq $v.Platform -and $v.PSEdition -ieq 'desktop')) } -function Test-PodeBuildIsGitHub -{ +function Test-PodeBuildIsGitHub { return (![string]::IsNullOrWhiteSpace($env:GITHUB_REF)) } -function Test-PodeBuildCanCodeCoverage -{ +function Test-PodeBuildCanCodeCoverage { return (@('1', 'true') -icontains $env:PODE_RUN_CODE_COVERAGE) } -function Get-PodeBuildService -{ +function Get-PodeBuildService { return 'github-actions' } -function Test-PodeBuildCommand($cmd) -{ +function Test-PodeBuildCommand($cmd) { $path = $null if (Test-PodeBuildIsWindows) { @@ -57,13 +52,11 @@ function Test-PodeBuildCommand($cmd) return (![string]::IsNullOrWhiteSpace($path)) } -function Get-PodeBuildBranch -{ +function Get-PodeBuildBranch { return ($env:GITHUB_REF -ireplace 'refs\/heads\/', '') } -function Invoke-PodeBuildInstall($name, $version) -{ +function Invoke-PodeBuildInstall($name, $version) { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 if (Test-PodeBuildIsWindows) { @@ -84,8 +77,7 @@ function Invoke-PodeBuildInstall($name, $version) } } -function Install-PodeBuildModule($name) -{ +function Install-PodeBuildModule($name) { if ($null -ne ((Get-Module -ListAvailable $name) | Where-Object { $_.Version -ieq $Versions[$name] })) { return } @@ -95,8 +87,7 @@ function Install-PodeBuildModule($name) Install-Module -Name "$($name)" -Scope CurrentUser -RequiredVersion "$($Versions[$name])" -Force -SkipPublisherCheck } -function Invoke-PodeBuildDotnetBuild($target) -{ +function Invoke-PodeBuildDotnetBuild($target) { dotnet build --configuration Release --self-contained --framework $target if (!$?) { throw "dotnet build failed for $($target)" @@ -354,17 +345,17 @@ task DocsHelpBuild DocsDeps, { $updated = $false $content = (Get-Content -Path $_.FullName | ForEach-Object { - $line = $_ + $line = $_ - while ($line -imatch '\[`(?[a-z]+\-pode[a-z]+)`\](?([^(]|$))') { - $updated = $true - $name = $Matches['name'] - $char = $Matches['char'] - $line = ($line -ireplace "\[``$($name)``\]([^(]|$)", "[``$($name)``]($('../' * $depth)Functions/$($map[$name])/$($name))$($char)") - } + while ($line -imatch '\[`(?[a-z]+\-pode[a-z]+)`\](?([^(]|$))') { + $updated = $true + $name = $Matches['name'] + $char = $Matches['char'] + $line = ($line -ireplace "\[``$($name)``\]([^(]|$)", "[``$($name)``]($('../' * $depth)Functions/$($map[$name])/$($name))$($char)") + } - $line - }) + $line + }) if ($updated) { $content | Out-File -FilePath $_.FullName -Force -Encoding ascii From dc15078764edbb3cd6346692cc61901f9307dfe4 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Tue, 24 Oct 2023 21:14:06 +0100 Subject: [PATCH 55/57] add v2.9.0 release notes --- docs/release-notes.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 3603552dc..132cbc412 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,5 +1,46 @@ # Release Notes +## v2.9.0 + +Date: TBC + +```plain +### Features +* #992: Introduces new Authorisation middleware support + +### Enhancements +* #588: Adds support for merging Authentication methods into a new Authentication method +* #1082, #1107: Adds a new "Running" event type, which will be triggered once all Runspaces have started +* #1101: Adds a new `-SslProtocol` parameter to `Add-PodeEndpoint`, to allow setting SSL Protocols per endpoint +* #1106: Adds two new Security functions to control the hiding/showing of the Server header in responses +* #1142: The `Test-PodeJwt` function is now public (thanks @alan-null!) +* #1163: Adds a new "Session" authentication method, useful if you need multiple authentication methods and the user can choose one + +### Bugs +* #1030: Fixes as issue with some Authentication methods when `-AsCredential` was supplied on the scheme +* #1081: Don't attempt to parse the query string if there is no query string supplied +* #1083: Fixes a time-zone issue when verifying exp and nbf properties (thanks @avin3sh!) +* #1087: Fixes an SMTP body parsing issue when multiple headers are in the request +* #1093: Allow greater JSON depths to be used when saving/restoring State (thanks @plk!) +* #1125: Fixes an issue where Verbs weren't being cleared down appropriately on server restart +* #1130: When request logging is enabled, and an authenticated user is available, the username will now be used and not "-" +* #1137: Fixes the loading of AutoImport configuration - it was being ignored! + +### Documentation +* #1078: Adds release dates to the releases notes page +* #1099: Adds a reference to the "Protected Users" group in the AD Authentication page +* #1115, #1116: Fixes an incorrect port in an Example script (thanks @ArieHein!) +* #1117, #1118, #1119: Fixes markdown syntax in pages (thanks @ArieHein!) +* #1123: Adds reference link about using the SecretManagement module in automation scenarios +* #1133: Fixes broken links to functions (thanks @Chris--A!) +* #1141: Updates the IIS page to reference the use of Maximum Worker Processes, and Sessions being stored in-memory + +### Packaging +* #1169: Adds a `.vscode` workspace settings file, with PowerShell code formatting settings +* #1170: Bumps the Alpine version to 3.17, and Ubuntu to 22.04 in Dockerfiles +* #1171: Bumps the versions of MkDocs and the Material theme +``` + ## v2.8.0 Date: 2nd February 2023 From d1a70d46e83f5c04cd4b8f6b7d266abc94079773 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Tue, 24 Oct 2023 21:25:43 +0100 Subject: [PATCH 56/57] minor tweaks to release notes --- docs/release-notes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 132cbc412..6c9894ecf 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -17,9 +17,9 @@ Date: TBC * #1163: Adds a new "Session" authentication method, useful if you need multiple authentication methods and the user can choose one ### Bugs -* #1030: Fixes as issue with some Authentication methods when `-AsCredential` was supplied on the scheme +* #1030: Fixes an issue with some Authentication methods when `-AsCredential` was supplied on the scheme * #1081: Don't attempt to parse the query string if there is no query string supplied -* #1083: Fixes a time-zone issue when verifying exp and nbf properties (thanks @avin3sh!) +* #1083: Fixes a time-zone issue when verifying JWT "exp" and "nbf" properties (thanks @avin3sh!) * #1087: Fixes an SMTP body parsing issue when multiple headers are in the request * #1093: Allow greater JSON depths to be used when saving/restoring State (thanks @plk!) * #1125: Fixes an issue where Verbs weren't being cleared down appropriately on server restart @@ -29,7 +29,7 @@ Date: TBC ### Documentation * #1078: Adds release dates to the releases notes page * #1099: Adds a reference to the "Protected Users" group in the AD Authentication page -* #1115, #1116: Fixes an incorrect port in an Example script (thanks @ArieHein!) +* #1115, #1116: Fixes incorrect ports in Example scripts (thanks @ArieHein!) * #1117, #1118, #1119: Fixes markdown syntax in pages (thanks @ArieHein!) * #1123: Adds reference link about using the SecretManagement module in automation scenarios * #1133: Fixes broken links to functions (thanks @Chris--A!) From 7459f7357e0be76cbfb9e83cb21b9e257446c01c Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Mon, 30 Oct 2023 22:48:04 +0000 Subject: [PATCH 57/57] add 2.9.0 release date --- docs/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 6c9894ecf..eec9e4468 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,7 +2,7 @@ ## v2.9.0 -Date: TBC +Date: 30th October 2023 ```plain ### Features