From f4003212d753092e3ddf4c3cf07f3d83e5152fea Mon Sep 17 00:00:00 2001 From: Anthony Somerset Date: Mon, 4 Sep 2023 15:33:23 +0200 Subject: [PATCH 1/2] updating Netskope Data Connector to migrate from v1 API to v2 API --- .../Netskope/AzureFunctionNetskope/run.ps1 | 72 ++++++++++--------- .../Data Connectors/Netskope/host.json | 2 +- .../Netskope/requirements.psd1 | 2 +- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/Solutions/Netskope/Data Connectors/Netskope/AzureFunctionNetskope/run.ps1 b/Solutions/Netskope/Data Connectors/Netskope/AzureFunctionNetskope/run.ps1 index ce4f8b3c41e..d9751c059ab 100644 --- a/Solutions/Netskope/Data Connectors/Netskope/AzureFunctionNetskope/run.ps1 +++ b/Solutions/Netskope/Data Connectors/Netskope/AzureFunctionNetskope/run.ps1 @@ -25,18 +25,18 @@ if ($Timer.IsPastDue) { $logAnalyticsUri = $env:logAnalyticsUri # Function to call the Netskope API for different Event Types -function CallNetskope($logtype) { +function CallNetskope($LogType) { # Function to contruct the Netskope Uri for alerts, event types, and to accomodate for pagination function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ - if("$logtype" -eq "alert") { - $url = "$uri/api/v1/alerts?token=$ApiKey&limit=$Page&starttime=$StartTime&endtime=$EndTime" + if("$LogType" -eq "alert") { + $url = "$uri/api/v2/events/data/alert?limit=$Page&starttime=$StartTime&endtime=$EndTime" } else { - $url = "$uri/api/v1/events?token=$ApiKey&limit=$Page&type=$LogType&starttime=$StartTime&endtime=$EndTime" + $url = "$uri/api/v2/events/data/${LogType}?limit=$Page&starttime=$StartTime&endtime=$EndTime" } if ($skip -ne 0) { - $url = "$url&skip=$Skip" + $url = "$url&offset=$Skip" Write-Host "Retrieving next page of $LogType events skipping the previous $Skip records" return $url } @@ -46,7 +46,7 @@ function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ } # Function for retrieving alerts and events from Netskope's APIs - function GetNetSkopeAPILogs($logtype) { + function GetNetSkopeAPILogs($LogType) { $timeInterval = [int]($env:timeInterval) * 60 $pageLimit = 10000 @@ -57,20 +57,20 @@ function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ $apikey = $env:apikey $uri = $env:uri $tableName = "Netskope" - $LastRecordObject = GetStartTime -CheckpointFile $checkPointFile -LogType $logtype -TimeInterval $timeInterval # function to create starttime + $LastRecordObject = GetStartTime -CheckpointFile $checkPointFile -LogType $LogType -TimeInterval $timeInterval # function to create starttime $LastRecordData = $LastRecordObject.Split("|"); $startTime = [Int]($LastRecordData[0]) $skip = $LastRecordData.Length -gt 1 ? [Int]($LastRecordData[1]) : $skip $endTime = [Int]($startTime + $timeInterval) - Write-Host "For Logtype $($logtype) starttime is $($startTime) and endtime is $($endTime)." + Write-Host "For Logtype $($LogType) starttime is $($startTime) and endtime is $($endTime)." #$netskopestartInterval = (Get-Date 01.01.1970)+([System.TimeSpan]::fromseconds($startTime)) #netskopeendInterval = (Get-Date 01.01.1970)+([System.TimeSpan]::fromseconds($endTime)) #$netskopetimediff = ($netskopeendInterval - $netskopestartInterval) #if($netskopetimediff.TotalSeconds -gt 300) #{ - # Write-Host "Time difference is > 10 minutes for Logtype :- $($logtype).Hence Resetting the endtime to add 10 minutes difference between starttime - $($startTime) and endtime - $($endTime) " + # Write-Host "Time difference is > 10 minutes for Logtype :- $($LogType).Hence Resetting the endtime to add 10 minutes difference between starttime - $($startTime) and endtime - $($endTime) " # $endTime = [Int](Get-Date -Date ($netskopestartInterval.AddSeconds(600)) -UFormat %s) - # Write-Host "For Logtype $($logtype) new modified endtime is $($endTime)" + # Write-Host "For Logtype $($LogType) new modified endtime is $($endTime)" #} #$alleventobjs = @() $count = 0 @@ -81,8 +81,8 @@ function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ if ($endTime -gt ((Get-Date -Date ((Get-Date).DateTime) -UFormat %s))) { break } - $response = GetLogs -Uri $uri -ApiKey $apikey -StartTime $startTime -EndTime $endTime -LogType $logtype -Page $pageLimit -Skip $skip - $netskopeevents = $response.data + $response = GetLogs -Uri $uri -ApiKey $apikey -StartTime $startTime -EndTime $endTime -LogType $LogType -Page $pageLimit -Skip $skip + $netskopeevents = $response.result if($null -ne $netskopeevents) { @@ -93,7 +93,6 @@ function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ $netskopeevents | Add-Member -MemberType NoteProperty transactionid -Value "" $netskopeevents | Add-Member -MemberType NoteProperty browser_sessionid -Value "" $netskopeevents | Add-Member -MemberType NoteProperty requestid -Value "" - $netskopeevents | ForEach-Object{ if($_.dlp_incident_id -ne $NULL){ $_.dlp_incidentid = [string]$_.dlp_incident_id @@ -121,13 +120,13 @@ function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ #$dataLength = $netskopeevents.Length #$alleventobjs += $netskopeevents $allEventsLength = $netskopeevents.Length - $responseCode = ProcessData -allEventsLength $allEventsLength -alleventobjs $netskopeevents -checkPointFile $checkPointFile -logtype $logtype -endTime $endTime + $responseCode = ProcessData -allEventsLength $allEventsLength -alleventobjs $netskopeevents -checkPointFile $checkPointFile -LogType $LogType -endTime $endTime # If the API response length for the given log type is equal to the page limit, it indicates there are subsquent pages, continue while loop, and increment the skip value by the records already recieved for the subquent API requests if($allEventsLength -eq $pageLimit){ $skip = $skip + $pageLimit } else { - # If the API response length for the given logtype is less than the page limit, it indicates there are no subsquent pages, break the while loop and move to the next logtype + # If the API response length for the given LogType is less than the page limit, it indicates there are no subsquent pages, break the while loop and move to the next LogType $skip = 0 $count = 1 @@ -137,29 +136,29 @@ function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ if($responseCode -ne 200) { Write-Error "ERROR: Log Analytics POST, Status Code: $responseCode, unsuccessful." $skip = $skip - $pageLimit -lt 0 ? 0 : $skip - $pageLimit - UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $logtype -LastSuccessfulTime $startTime -skip $skip + UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $LogType -LastSuccessfulTime $startTime -skip $skip }elseif($count -eq 0) { - UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $logtype -LastSuccessfulTime $startTime -skip $skip + UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $LogType -LastSuccessfulTime $startTime -skip $skip }else { - UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $logtype -LastSuccessfulTime $endTime -skip $skip + UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $LogType -LastSuccessfulTime $endTime -skip $skip $startTime = $startTime + $timeInterval $count = 0 - Write-Host "For Logtype $($logtype) modified starttime is $($startTime)." + Write-Host "For Logtype $($LogType) modified starttime is $($startTime)." } $functionCurrentTimeEpoch = (Get-Date -Date ((Get-Date).DateTime) -UFormat %s) $TimeDifferenceEpoch = $functionCurrentTimeEpoch - $functionStartTimeEpoch if ($TimeDifferenceEpoch -ge 420) { - Write-Host "Exiting from do while loop for logType : $($logtype) to avoid function timeout." - #UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $logtype -LastSuccessfulTime $startTime -skip $skip + Write-Host "Exiting from do while loop for logType : $($LogType) to avoid function timeout." + #UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $LogType -LastSuccessfulTime $startTime -skip $skip break } } catch { - UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $logtype -LastSuccessfulTime $startTime -skip $skip - Write-Host "Exiting from do while loop for logType : $($logtype) because of error message as : " + $($Error[0].Exception.Message) + UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $LogType -LastSuccessfulTime $startTime -skip $skip + Write-Host "Exiting from do while loop for logType : $($LogType) because of error message as : " + $($Error[0].Exception.Message) break } @@ -167,20 +166,20 @@ function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ #if($count -eq 1) #{ - # UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $logtype -LastSuccessfulTime $endTime -skip $skip + # UpdateCheckpointTime -CheckpointFile $checkPointFile -LogType $LogType -LastSuccessfulTime $endTime -skip $skip #} } # Function for processing the Netskope's API response - function ProcessData($allEventsLength, $alleventobjs, $checkPointFile, $logtype, $endTime, $skip) { - Write-Host "Process Data function:- EventsLength - $($allEventsLength), Logtype - $($logtype) and Endtime - $($endTime)" + function ProcessData($allEventsLength, $alleventobjs, $checkPointFile, $LogType, $endTime, $skip) { + Write-Host "Process Data function:- EventsLength - $($allEventsLength), Logtype - $($LogType) and Endtime - $($endTime)" $customerId = $env:workspaceId - $sharedKey = $env:workspacekey + $sharedKey = $env:workspaceKey $responseCode = 200 if ($allEventsLength -ne 0) { $jsonPayload = $alleventobjs | ConvertTo-Json -Depth 3 $mbytes = ([System.Text.Encoding]::UTF8.GetBytes($jsonPayload)).Count / 1024 / 1024 - Write-Host "Total mbytes :- $($mbytes) for type :- $($logtype)" + Write-Host "Total mbytes :- $($mbytes) for type :- $($LogType)" # Check the payload size, if under 30MB post to Log Analytics. if (($mbytes -le 30)) { $responseCode = Post-LogAnalyticsData -customerId $customerId -sharedKey $sharedKey -body ([System.Text.Encoding]::UTF8.GetBytes($jsonPayload)) -logType $tableName @@ -196,7 +195,7 @@ function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ else { $startInterval = (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($startTime)) $endInterval = (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($endTime)) - Write-Host "INFO: No new '$logtype' records created between $startInterval and $endInterval" + Write-Host "INFO: No new '$LogType' records created between $startInterval and $endInterval" } return $responseCode } @@ -232,10 +231,13 @@ function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ } function GetLogs ($Uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip) { - $url = GetUrl -Uri $Uri -ApiKey $ApiKey -StartTime $StartTime -EndTime $EndTime -logtype $LogType -Page $Page -Skip $Skip - $obfurl = $url -replace "token=[a-z0-9]+\&", "token=&" - Write-Host "Retrieving '$LogType' events from $obfurl" - $response = Invoke-RestMethod -Uri $url + $url = GetUrl -Uri $Uri -ApiKey $ApiKey -StartTime $StartTime -EndTime $EndTime -LogType $LogType -Page $Page -Skip $Skip + Write-Host "Retrieving '$LogType' events from $url" + #we have to set header on rest method for v2 - Netskope-Api-Token + $headers = @{ + "Netskope-Api-Token"="$ApiKey" + } + $response = Invoke-RestMethod -Uri $url -Headers $headers if ($response.status -eq "error") { $errorCode = $response.errorCode $errors = $response.errors @@ -246,7 +248,7 @@ function GetUrl ($uri, $ApiKey, $StartTime, $EndTime, $LogType, $Page, $Skip){ } } - # Function to retrieve the checkpoint start time of the last successful API call for a given logtype. Checkpoint file will be created if none exists + # Function to retrieve the checkpoint start time of the last successful API call for a given LogType. Checkpoint file will be created if none exists function GetStartTime($CheckpointFile, $LogType, $TimeInterval) { $loggingOptions = $env:logTypes @@ -382,7 +384,7 @@ function SplitDataAndProcess($customerId, $sharedKey, $payload, $logType) { Write-Host "Error, error message: $($Error[0].Exception.Message)" } } - GetNetSkopeAPILogs -logtype $logtype + GetNetSkopeAPILogs -LogType $LogType } diff --git a/Solutions/Netskope/Data Connectors/Netskope/host.json b/Solutions/Netskope/Data Connectors/Netskope/host.json index d77b1014827..51b6c77c907 100644 --- a/Solutions/Netskope/Data Connectors/Netskope/host.json +++ b/Solutions/Netskope/Data Connectors/Netskope/host.json @@ -18,6 +18,6 @@ }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[3.*, 4.0.0)" + "version": "[4.0.0, 5.0.0)" } } \ No newline at end of file diff --git a/Solutions/Netskope/Data Connectors/Netskope/requirements.psd1 b/Solutions/Netskope/Data Connectors/Netskope/requirements.psd1 index 341251dabf4..ae730570b11 100644 --- a/Solutions/Netskope/Data Connectors/Netskope/requirements.psd1 +++ b/Solutions/Netskope/Data Connectors/Netskope/requirements.psd1 @@ -3,5 +3,5 @@ # @{ # For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'. - 'Az' = '4.*' + 'Az' = '9.*' } \ No newline at end of file From 2cec5808c7d3fab2efcecf5b57b17c1872a6681e Mon Sep 17 00:00:00 2001 From: v-atulyadav <104008048+v-atulyadav@users.noreply.github.com> Date: Tue, 5 Sep 2023 12:07:46 +0530 Subject: [PATCH 2/2] Update AzureFunctionNetskope.zip --- .../Netskope/AzureFunctionNetskope.zip | Bin 7373 -> 7368 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Solutions/Netskope/Data Connectors/Netskope/AzureFunctionNetskope.zip b/Solutions/Netskope/Data Connectors/Netskope/AzureFunctionNetskope.zip index c7a5e5632df4166d8a076caafa71853a9256ed20..67b0ed5b53fc2209253cc5c4e8975b49b2fd3dc7 100644 GIT binary patch delta 6379 zcmZu$V{9b=v#o90HgCPPZQEPhz2(+h+cvj0wp-h`?$);P?U$Fl2!Xf8!6 z=NphFqGyZ5f%t;trx)tuQm+*}trN~#2~^FfRbH}eN?A?{r{2X4TiiH3Gveda|(tN3xMnz1p0y_Kr3CJm+%i>}M`osHO-Bu3Pcz~38kpE%6eQL-&pClpwy zKv(3$^ShyCeKo0a`eEd)2CL`ywfVk;19>vomm;ciSsdCmd|LE#j(Fa5F0sYiipax# zF3V)M44ThVkCCw)oB;S;S|Gy{zb+QEa~M+-l+K7~s~lb_d(;V+UJ3QCB{-t(pfGS* zbK&QRzS%R(33xx)vB=wQ2CaV1nc|SuYM$6c2%>r#cRX&h0377;UAoHbE+H2Mp zp3o$jwo8KksGWN>Q-^-$I6jR;`!fa#04iM@Zt&E1oOLFAjyYY%vML}2H_TUS>~PS4 z=I9-#2YybTyCYhi%J#5CLZF~^+0+bhkgb=vuZS|a5Er`oOEM7AyQE?lBCv601pzqG z+!%fw&gmw>@9BAi0fpmZu;4oBlCbADCzFkjvEMluii;#DhOX9iYXa512c05JXk1o( zlAf?nl5OjYrQKyjzQ~A!4M*5uS#{{3Up<`%G}&mYanGvf^=gX5>Z0j|%fl z4qM(g#0U2`NJdIPMrD@@pth}3V7UV4!q&=ndh1n*f!-VjEvawSJRg(BTf&nS`J-4+ zaop(0t^6x=$}f~}biOWu8h5#C;4M&H>wD;68;GeD*8NKeACkqIfF1Ph6Q^YdqZne2`)GmNnhBO^mZHa z1}n)VT<@7afb5fbeRe0q4h+h=B6dDeLQG}=I5E(lXR)?Kz1O7dtk;EzIS>yFj-t4u z(oac2e5+a$K(a@P5HK4=4-YL_d^G-rJ@due3a)FHGk=Q#aEy{sNk)^FfY29{8QGK< z4?_OC|0N_WJ2b_s1$7pH`uj9_=Q++NZ^?ORxx{1$k_5L5li-1r`< z@S`^@qKLep;_))xz3*xWv0lAl?r2JSCC@M;L4}Ci`*?`JnJsF8J|VAyZ!F|AveA=} zjd0SId)gBWVi#{|vSDC61Nx*JXs_Y@;~G&yww0%d)fAHrLaMwTW^gD32eh>E zsURX9BE&BozAE{Fr4O>E7Fu-C%sC`YvQPTWMjIu2ZI{AAcjDfAPk?6lZ5II(Un^8e zXbC|z54e+ZM{7{TR1_<%+!?bFVi=UL2G4zYOaGfsn&-MpE1*X0V zLJ3R15WN$%$M?^u^+u2#pVoyIBEF#0>5adkVHs|EskoC1Lkwr#3PVkmeK@N))Lp~5 zr=t77Wu<<&%vO_a5!Tvmcf_p#)o|5C7=qR-2)VG=S``~sO(R2R zrZM_MO3oFiIOhCd1I_F7tmY*@<@b-Y;GSJFaPlSa*80Vn!1{%Ic@M~d6qyQXH`s|n z;m;9C6Zm`i9BC8pbUBF7!&Na%lm+tM!BOw`&*0vI7{ zgU%WeFr>$%Y3jbodKo#P&o6^nOAIHy>kscnADyrfTvzPUs7bWy_g?!df3g%XL_@(; zTx8j7r&%fvdu4*FQk`gl%R4{F`HS@JNEdE@i-XQy;|>!JiLv0MW9Giqp~5(rrXmHk z#Xzjiu6^U@=OtJkmyUofzCj2kt2A&9A&aQX)gh0GWu>v9D~sW>zdOWwSY!>x1e8+a zCy?%L&)%(~Uh$5cil<_vkb^$d!!l$D@y`iW$@<6(x}-X!edX(b#h{j?m#dIV9rcJHVM%({)E3(5AMAEoVaMMCKf2KShO2~XL-=a3& zDrd`*>ycZ#?KNKX=ORfxjz*Uxa46A$TnVj7Z97f9MY|nXFVjqHzQd(7`d~2>^T!Mi zZ6#O|zk+cMPSH-$;HO5~9_#V~qz(3w=Bo+g&q| zk#AH;iewFpt~3c2CUmsM{4phT6*2VpCi@Fz*WV``((ke8Be0!%KQOfhdAUzhcmp~o z8Uv>|QcpE4cAqrhWW<=8L6#e{MF5Sp6hZY#J0k(#ttAKdx+VC{q%*R(}b37;} zcJ?q3QNp%hEMybb0!E}c50(aAM@`k*50$E-bBhkl;egvDk#OzT%!X9Qddh9{kS0;U zzKI^SY|UuIV`uJIgR6EZm3ky7gFGuU}5Do z;avtEga@m%&KF8JcMgPFD*Uoo3=#->A(4-GmNlA5ta4i>&rfkoDe;#(u(|03v1s+#3TEUG?@$f7SVTGUc~>wN#f# zoC18SA(8kYA)3#{w+5s{o<#*bxhwymdu$z(9l8d&2Pt?<9kt)ceChXrO|Gqq_LJ4>kR$kgLzgiw3d${pvAJxY&_&D94g zC7I-tt950byDMcJdArM?dU^=xPu?t=N!9}qN%mtX%zi0nm93UvpgXC1Es1c|QLFJk zgzIr@^!y;>Ch0obNR$sbs^bVnq}u4_{|!>;+lfY-PSk6aF;8N5#{ATw3I{enK@d0c zDPr@YGQ47lnz+DiB(oqB9UKTXbnAlAzT@ovigg$^Ee$A;>sCP&FA_W2Wlr?C+u))t z-c)?U>9Y0R{2RgJ;YX0JS>J9y&)1smzNp+KJMtZ#Tao`YeVK;%rtfqHpHHf*lojME zaqeg?v3mK~Sz>`RrUp@7>I-BK1FO6)#o;JFh#ec89_9}#=cgOFfHwDZ^qVo{({7Gh zTHUgpKC^ena(Mszxe3{lZP=f(1IGqRhC6YjBYXL&r^#v@j5cnqm(tDz7J2BG*m1%& zr0Zh*GPk7Qw!)Le6R|93_;b=c7^0gov8HgxiSdQiaZlfbvg~UounFiB`_H8I+L$ZB z8ob;FGP^x;(n*R=o|MCOc#f7#&B${a1Qh-bQ3O2rt7+`l1ji%4Q;;2?0jF-of1@Cq zr7ze(80>I1(?R>NgFl^fIaE!54ojXl4=o0r2h}JdcAV!Ko!k$(Bu@JAYcw1xCx@iV z4O_0BM?D`C z85~v)xdPKc6q}u_R^n3ry>pP6a!qFU-ge*20P*5pD}j;n-i&sT)5DHk_Iq^>eLatx z_M2FzcU4wkpiAr~k5xsdgz7lK_4nTovJ<%4?6|j-np0q1nn3ky-=J2mU?6?mzy$7v zVCtbpzta*4`~6j+D9*BjURXM9s&|)-pApk-#Rl=ZyycfJC7c6(Tsn%<2c>@i>6Ctp zzes>I9?Hvr-BGD1YkGvQD@Vx z$D}6(i?5_c5CYeWG7XFX9M@IMLUX5`R;{D$;(c_HKhOfl-%Gx5@{e+#;+jyocD+X2 zU2av>;3N;)&pwFCmrwm+P;{OZ&4%vRrCWr8XLLwm1A5}T-&8>O9e@olwE&V;hyy#i9}uX`e5mmBCCuW4mS z+o5mhHxnO^@MC^U>Z6KxM;hFPF%alg9;nI>QFqFT2Hlf${%c|!%>&`C(^>HZ zuz#tkwKZDUNI{6)G6E{4jBeIl;o(T0Dhxf+RBp*|5SK46`WZhjqCh*bvT>Kp{J@|j zG@W`sc`12mt1EdTdFLbM6Fx++(Ik(qpvOz~`pD|KkCr_4l%)%MNOypxx_b}JElf4* zw<1Lt%cegmSLJq-68Oe#{NBR1TS#fbGhk@TRp>z5fJtpJ>Q>rsYwHDoY*|^uKdBG) z-NA0aEN7X&+2diF#dE`!aC#5T!TSl)oP}^^73NF)HHj`O5B+OsS5js`Xg(`5@F}Gn zZy_uUZ)6G?HS3=G)1e`tzRXj<_Pe(79)yI_E}kq+glaIeX3PsSg;?qDg8I$54gjL@*L^tj{InaIJUy~3OFkylD^Z^W6!HZbL1IVy~e{Rk~YVO9dS15PJ0R%!Vs3O zC>s%dw4^0G$&yS`<{eg9kjhqfil((6E}K)exm0Q}&inRuCR z_*0U=(kftl?(ISX&#?m+Ly`-;_{$g@h=7aCg;l%8FCD3bC^L=^#l|O2l zBPh&aq&VpyT}C%SkDUMQex8@&ggClDp4dOzK{D`{(v+WyqPuMVXOH7vt;4Zt{ynRA zhcVP5=9CU2C>n1vim6`7TRK|)qi~7(QW{>{xIkS{V^kZ{ zKrW!c7z#ZWRf@i}Be3T#3(7pQc$`C%l_QXcY=Gt~GgnXl11b10ECe25=NmkmYIUC8 zmGy}Z#YP&8_j zBor1H7#JK_Iq=^-S{J-==lx`Y-W{|{}P+}v5dyE!=;sw&DuvZM7NenwF9nCccC zTRKH!L-p7=<<0MN!!#v(B&RGleQlO(c9M1|c$0F*Xa0TAOe9$^y!(S*jp(WEqJ?q| zNJ4@)&W2(6q2Ok;eVY}ZC6zF%GGil(K!qynA?OB-GE7|6sR#>a*+Y85t@h9(oaYR+ z(YGrZRW}|Z{2+c$QAS|Rq9Xr@LQ~V7l{IobGa{AOV_3 zoLb_yN7id0UI7nJ*!wOSjc%H}M-Qwb-R0)Kep%w-K)~1j(}$ij_H-gSjDQ))A1u<8 ziKxIbn+9sd`$*VlR~p@VcZ-V~SZ_-2QY2*A?gTYSeJ?GcQW!-rQnbu0EmA zk^1SeCPjc9l4O8Cd{m$FoMX3W_b4qpif~`DJ)zvNWY(nH6H(evn=)f(t$LIJ>6Bct(>%{Qes)Se&IA(uO|} zKWu+5%2`(QSYQ5h5Qjy^vT3Q{vXUHRQ9O-l1Vc79);C_PZw@x)^iejS)3nrZ_tLnG zgM^&Wga(X0eIkav+8K{d_KqieM~8A>vzU?Bi!>|&E3%#EBn*;-k#a_>Jsu}PIn9bW z**@MQy;)i2Y`DH2r)hQ-lr)@$Nw$dmi;RDd_QPzx9%Uh0SM%TP(j;G}>3Ts~=>>W0 zU2S^V6)mn}dhO53dD16kM)F0HkRYK&NrE&Y^a}RFNEDQT4`@;Xv-2!vluV0kZX<|z z!9EHa#xN}+aY}v}9~}}zIw*ay%c14!*Z;>7V^nebu?@zK*1q@g0$rX_xt9$GiME=!+u9Ccfy%*&TvUs&D z@YeN=rWRJ3xp8`xg@So790ZpZFfd373uiQ1Bs2o7;wh1v^z}M@@h33R*5yM8iG`$+uZi@|SZUcW&-I~G-^dq3_ zQ_45DgQ`F#H#ucbpXi;AV5XYX!}UuTz<1GDBTIp7VQH^}SmHWd<=C1~ID$an3#tOl z3_{T648)xBp?Po-r<}M39>9Ok(;@+aE{b@Kbhq>PS9${<#$`~HNM!iDlSc9rh%9ad zJ{^OZ#9^0l-j}40r@fBaNr!m!PtxJobu@GEf`t;GZwD7#2XnH1wXrTX@ne}?(sWDW z@GObvak<48dd3hNJi{?toSdC%gelA?6qJ)*q3ovgeTm$pB%5lUD3gnTavX)?k{2)|TQI>W zG_hF`F=K^-g6&K>Dod&78h5MCB|~0O&WZ|C!C3P|;O1XR@hU@oVFHRNk}zP51K?NK z9;+*e!m5ju-%G(tTm^qf$~SRZzU^_{(I1W-g`5?VI`gW2x*)L>D%EDm7f6#(ukQD7+YCU zivS9rzl7RH-?a_gFP_o}zUx}nBknDm(BRB#00hsd43dAbK!Qubi^0I0WZ2aeVzZBP zTKNtk=f@9VqR2c>7bRufzAE+(J(I)!E}w8h83jH|`>)uF&Oj8IfQ&WHX?YDBH$=p% zNr)vPo+%-uWACLQz?yO5|DKzq{&o~eg7v-wV)co4`kH&8V~?{9jPez0+5j zdHmv47B}@(Xoz<}=jRZ{$iIANhgnIFRfrWO`MWp`;|S)&5%GQtk_9E5&X-;T z0?kKUAruidXw+k^5ajtgMj3m=CCVa5S@5X4I4`nm@ChG#Ck6d;0oK|Ef8PbbO6&yT z%oBfZSBtRYg=+K8HwE||u82Bxc)GveUour)wfQkR?QcX%<82dmJ3NPZQdfngP=SZy zs1>Mzp@#bz_*Dw@5d19lv+%Rveg=Mq`aJ|eTYD`GZOEU2qM^PILDANJ3q>39XP{V9 z|A(O2v>+C$%@sZaU1>%hf@WobEc7afpNxOjZJPyK>EgLA1GufaL6ebP+f>VQeBPMd zVl!bDPZ6>Qt*RZ8Q5K=|1ZFz~cJaX1tAlQ=7cpQ#05Z>NqW2E`#>BNqtMmL9&(*CF@Z&H?9tIfMz>j}e z4S;`aan3+QQjsiUYdn<-@ed`}GsHj1vRouf;an&vmS99p0bRl?e%77e@H;YbMuG%9 z+UTaLRVZX(DY25+5f{uBhNC((_(1q!W?{i~TtE9Vb*YkVz>(e--7?gnI ziG2M%Ih1BiCh?$w1E{#fnKaL?sN#Pkmel} z2uq96K5h;MZDExNfOm=l*wEQIIXOBR)@n(|N8`yFfgq_|FoGb5ww+ibiVH8&M!z63~C$qTD0B z1=msSTFLxRLHBP!Psh*8ki*N$n6@V#S|&S^Ddu&bM2}ALwC6Wn;*EOhashNMXCx|< zUn-lU;p3oJI`yVNw=e6prI+q!SY)BJzM=<)`J2sF$+sxK^FHS3L|(XwGSevIIgm(! z8d_|@eB)IH4qKJ62Q;!p*=B!hRvuHF@2PEgwP&pCn>E`H=NgdJLCt@_wmzGg=QIo! zj29azKXm4R1)l8zgOMFn_Fk7CM{fpi{hjwA&5`B)ff(jc0|%8Y3g1e^Y&&g^Sh0=x z#**~rChm{O4(K^&4shBvh{~nSya1SVyf08!^3Yll54~YLe_uk+gj7xS<|?$ z3A8O^V0BT5^+flJ!eZzV+kr(Hs3Fr|(cc;-v17 z*)syZE}MZP8U0$-H}x>P%2|t(HqD|hHLsG7zZZ>=)#P=sKI}jpWmP#3}=e2V1YmD@1$Xdojz}e!_&#@|MB5`d5Hsi=A!++ zKjLz~PBsRc-^f4q0#-!{i%~?bC`v}+OJDf$H;qbC%|x#dt}4={7aM2sUm!O?wnZAz zg1i_Uywp8UmMiMpu#nJdeefYE%yd@|8euH}N0rooMSyDvi79_DUL_QUe0b*>gl-Qa zIS+M(InYIM*LhoaS-?Tp96W{h5P2CjA`2CJ%*0_iO}+AN9>wRj!8 z><|yU5qeR8z(k`g0e=5nP?|aqxFuiP+)#4^zXt_2R(&-jLgFxw!sSHNFkcMj=VZp~ zjX5o_!%SH{Of!FrmM=J97}n*vfsnS*C$p- z_sCxp+m4frs^`e$cKdk>da&RJz342|r@Lr$ypB(%TXCz{mmjpei;rT@Ts*Iz zwod1v*R{O|t23_YCb&*mM>W?ZaLqg64w-$F_u;hJH~lsFh2L*_Dq=|EK91(D3;&&C zSyQ;~d{NT(AWw(%T9$E$5}+Ao!$6Py102)qP6LqqHL`jr+qXvSYNTrhtA;!|beb>Y z{~pBUQA&S%eJ~lkjQYJ}sAGF%%XQnIWMiH#?xEm!K5}2RrPNzV3?9sT^E@j`4>o`$ zV@#de9B~vm~!&n;v; zCO|<0TYU(7aK_Ac5UpP9BOe4!-uc|ZVun+T=m&pH>x*illX0*K$8sz61;;djh17bU zE?Hs=tAPHI?7SD89RKF#B=HV2L~1V;ZO?J$I36w1gI(Q87jrzdvZB|&0}t{=5i!{( zOjG32_JFmiyKEqu$V7_bNTQ5t zn=OBmh@=^~34bo)LfCtb4&R)j3Y-TOeF-NpKLkFJE)5*TbtJdtU0t+jIyK>O9V?>t?Pfj zUArLLE}q1x;pB1uQn-5Abh)F)pPI01SH5W~rovR-5{ka$&rEU)h?)`_rfVh7CWe2lA1XlEJMxi)5TERDve#+b7hVPTI-7b^jm7vF zi1eZ6Qh*>(a91$(1Ev8DPn=d{b*CjPpxCvlC$G8p?{Yy0waBW5*Q0pc72rF4?Aj*( zE_cW2Z`{|ukS?Bky5pW7G@t6#SDlW;<}{ei>sIo`7>2b1G`gG@OpoMwlsl% zDUHF)6^}fY=2XD%)E++cpP@kp)@@pZmm}GCw`e;a{c)(=JsUtphgc&_Y#EK03Lo7g zT%8%sTeXP2z9S4++~Ae-(x8E3*`>kw;~9tw`WG@19N@hSMRTOk`pOOZ9IfZjA5+b% z1@5KleE2^{-8njUt2|!qDSv-Q4))$G6H{Bf_v*2I`$7HcA%6XQeyyruJhA)|fp}v* zrDvq~*Nmh-e8x-w)<825Yv7G4kIx{3qpuEd-kP`Dt3+)vH#w6l!WkUf_Sx3%En((s2yi3#V_%cZ z-j%(Co^Olg7FiwZ648H$!R_$F#_cNcro@&wV^Il5X^jQ$d-=q)trFO(C*Hw;^)gk4 z!v2}I_d^(};H-)+(FZJmiWUHwhLzh!od)He=dWX$;1SWmbA}rx8PO}5hMy4Qj%Jl1 z!SGv?F%361HeY}6;tp_Coq;ImWq6of zSB9ul=m&ahO1C9;FZ1SF4z(58YS@)etRQmiEL1%kgBn7fq^P2-XOhyQtEZ_`w*x#_ zmJRX4;U2@({ZX+q&&!)${hbzeo!#-qi;j9Vj4(~rhQSQ^S;YJ-k5eGntYm)h7f=Y_ z6=T2cV1=A;$;W?`Q7qChwh-e=F~i6q2Bj$qG#yj*wh;kg3}Tp2d6%$7j;A8NHRmDd zRb*;x*h{l=&J(t)0`l4V*?RBI+4Y(C_IdvdL7o{f&m_`>CKAl zx_VN6h#A2kiK8YFe4B2r_m9+X8K(el`A7aH9`Qb+t<8VI#&_P}8*i|oAI<8{^wIT` zfnsOdG)X$^zh$U`;pB7xaSvtCE)Q&J6Jn0l2c$K4%hCyRJSOYB3!0*zL?dsCrl*7B zZEGZ_+-hUcNVxt^&rd|hJ@H;*iwDM0z-0andZW#;|Co2MetJk?jlPX2)M=C=ng8`%cCEXLu1nZdVI@$qZ? z^-qkupj7mj)CxOQcB2ph5hgbzYVJ&+Cg5|PYsY^{OGZ_vkh&wfxLT%YT0h^aP-?W7 z^;@3|;ug;W)Ji&aU7O8Fl)rIGzaa~xhXj>e;_I6t-QI&5Yp!&`Q|9u$O`G4&&xJ=H z&OnAQRl$o-$2Ji8ND20qio(KHooH=OC;Z(VV!cWP8jP_z#^eY0HthFRxwfDU9(tLw zyWM}>yIs3ft-D;?+el|9KaXqg9`=(fg4BbWrolPPWm?Q@c@x_24gpQ*v=nNtwlk6I zaM28=D2VDEU6llav_)9W0b+ua0PY4HKt2IalIW?b0Q7Gm!VePF|K}pwrAd%88r{Rd zWBy1V1{D;8gI(UnC_!~T42HYA!})y3*lm9YBdXpTbPv!acxx`}PvY>>%x^V2TRF%w zxTO4W9qax<)d*PP+XIz!Xe10yEp0QyKaH|}qoluPH^$$yY!BeYb13-b=f+E~nvYjX zcLE)Xg8&XF=bPxT&dK-B(FIOAGEign3QS>Vj>q!r1lxZj zef?GKuq*kkr1e!CT%-UA8-vGXmR}=fL29|^9MWs;h$QzYg06J%d9yX~ia9y&K{e48 zb^7U@UmavrzYyLMh5I>`3^=!gIoIgZx*Z!~{ndEm2RZ);)-@(Ou{h)@eXf+0^th2oSvpX&QY zAGaGNTz!E~@lYaPO|U8T&U{pZGu|5~l;-LI%v<73AdV`_L_TWx?SOdO?3e7^*y=|J zG(!p|$bC-35-d>b=8IkOW9dXW^c7(sjRWr5u z{yyHCEi<%B0+y#w6b!K(w<0hZgiB|yxm&3lbJe>*@eBZITlyYPVA{n6{M8~`1nE~~ zvUjjEB(-;zcK#1gO9KQH004gg2mph0aaBOxofP~4008*`000R9003xjb963hb8l{4 zQOiyPAr#%4_zx3ynug$GH9H$?Vxk-E&J|@&k%YO712nD8{JYRjod+ApdE9$)I9^{B z>98GIZyd3*VE0_GMGtQysgUrsso`SCv=HT;D8-4`x$jWtTte!UuWYPP ztq%>dmwn(T!Ur~}x{-s0R;p9T0h_Kc9$9Uf5+06>TK7exZ%Se^Q zqzIGavv;Ff=iN5&r{5jeg1_`X;`wUk{m(r9%&!N2yC@#H;Nqqabjr~Evtbw(0tpqE zeN^dETZ_z-fEq1-CaXXo*~|qd7idduTa124@)#%N?vY5QH{Emqi)gZ3IdRjbLl6P^OCY%rgw8{@JEU2O~nh4bBNmg6wt%8aenR9=;$8%bZ( z@-Y4f7)~4v2Yq6R8Dxy@GCA1L9=$Mz9ySi|-{XKb6E4j>>8Nt4D0@mV*G6*ZT$=Y8 zYwX10QG6VSOz~*y#?Gycp7SYn^eU0QHHM--zJ$bIxM(-tAYSB?7>A!