UMN-Grouper.psm1
#region License # Copyright 2017 University of Minnesota, Office of Information Technology # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with Foobar. If not, see <http://www.gnu.org/licenses/>. #endregion #region New-GrouperHeader function New-GrouperHeader { <# .SYNOPSIS Create Header to be consumed by all other functions .DESCRIPTION Create Header to be consumed by all other functions .PARAMETER psCreds PScredential composed of your username/password to Server .NOTES Author: Travis Sobeck LASTEDIT: 6/20/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [System.Management.Automation.PSCredential]$psCreds ) $auth = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($psCreds.UserName+':'+$psCreds.GetNetworkCredential().Password)) return (@{"Authorization" = "Basic $auth"}) } #endregion #region Get-GrouperGroup function Get-GrouperGroup { <# .SYNOPSIS Get Grouper Group(s) .DESCRIPTION Get Grouper Group(s) .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER groupName Use this if you know the exact name .PARAMETER stemName Use this to get a list of groups in a specific stem. Use Get-GrouperStem to find stem .PARAMETER subjectId Set this to a username to search as that user if you have access to .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory,ParameterSetName='groupName')] [string]$groupName, [Parameter(Mandatory,ParameterSetName='stemName')] [string]$stemName, [string]$subjectId ) Begin{} Process { if ($groupName) { Write-Warning "This Option has not been coded yet" } else { $uri = "$uri/groups" $groupName = "$stemName`:" $body = @{ WsRestFindGroupsRequest = @{ wsQueryFilter = @{groupName = $groupName;queryFilterType = 'FIND_BY_GROUP_NAME_APPROXIMATE'} } } if ($subjectId) { $body['WsRestFindGroupsRequest']['actAsSubjectLookup'] = @{subjectId = $subjectId}; } $body = $body | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType return ($response.Content | ConvertFrom-Json).WsFindGroupsResults.groupResults } } End{} } #endregion #region Get-GrouperGroupMembers function Get-GrouperGroupMembers { <# .SYNOPSIS Get List of Members in a Group .DESCRIPTION Get List of Members in a Group .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER groupName This represents the identifier for the group, it should look like 'stemname:group' Example: stem1:substem:supergroup .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$groupName ) Begin{} Process { $uri = "$uri/groups" $body = @{ WsRestGetMembersRequest = @{ subjectAttributeNames = @("description","name") wsGroupLookups = @(@{groupName = $groupName}) } } | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType return ($response.Content | ConvertFrom-Json).WsGetMembersResults.results.wsSubjects } End{} } #endregion #region Get-GrouperPrivileges function Get-GrouperPrivileges { <# .SYNOPSIS Get Grouper Privileges .DESCRIPTION Get Grouper Privileges .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER stemName stemName .PARAMETER subjectId Filter result for a specific user .PARAMETER actAsSubjectId User security context to restrict search to. ie search as this user .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory,ParameterSetName='stem')] [string]$stemName, [Parameter(Mandatory,ParameterSetName='group')] [string]$groupName, [string]$actAsSubjectId, [string]$subjectId ) Begin{} Process { $uri = "$uri/grouperPrivileges" $body = @{ WsRestGetGrouperPrivilegesLiteRequest = @{} } if ($subjectId) { $body['WsRestGetGrouperPrivilegesLiteRequest']['actAsSubjectId'] = $subjectId } if ($actAsSubjectId) { $body['WsRestGetGrouperPrivilegesLiteRequest']['actAsSubjectId'] = $actAsSubjectId } if ($groupName) { $body['WsRestGetGrouperPrivilegesLiteRequest']['groupName'] = $groupName } if ($stemName) { $body['WsRestGetGrouperPrivilegesLiteRequest']['stemName'] = $stemName } $body = $body | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType return ($response.Content | ConvertFrom-Json).WsGetGrouperPrivilegesLiteResult.privilegeResults if (($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults.count -gt 0) { ($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults } else { Write-Warning "NO results found" } } End{} } #endregion #region Get-GrouperStem function Get-GrouperStem { <# .SYNOPSIS Get Grouper Stem(s) .DESCRIPTION Get Grouper Stem(s) .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER stemName stemName .PARAMETER subjectId Set this to a username to search as that user if you have access to .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$stemName, [string]$subjectId ) Begin{} Process { $uri = "$uri/stems" $body = @{ WsRestFindStemsRequest = @{ wsStemQueryFilter = @{stemName = $stemName;stemQueryFilterType = 'FIND_BY_STEM_NAME_APPROXIMATE'} } } if ($subjectId) { $body['WsRestFindStemsRequest']['actAsSubjectLookup'] = @{subjectId = $subjectId}; } $body = $body | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType if (($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults.count -gt 0) { ($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults } else { Write-Warning "NO results found" } } End{} } #endregion #region New-GrouperGroup function New-GrouperGroup { <# .SYNOPSIS Create new Group in Grouper .DESCRIPTION Create new Group in Grouper .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER groupName This represents the identifier for the group, it should look like 'stemname:group' Example: stem1:substem:supergroup .PARAMETER description The description represents the the Name in the form users in the UI will see the group .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$groupName, [Parameter(Mandatory)] [string]$description ) Begin{} Process { $uri = "$uri/groups" $body = @{ WsRestGroupSaveRequest = @{ wsGroupToSaves = @(@{wsGroup = @{description = $description;displayExtension = $description;extension = $description;name = $groupName};wsGroupLookup = @{groupName = $groupName}}) } } | ConvertTo-Json -Depth 5 ($response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType) return ($response.Content | ConvertFrom-Json).WsGroupSaveResults.results.wsGroup } End{} } #endregion #region New-GrouperGroupMember function New-GrouperGroupMember { <# .SYNOPSIS Add a user to a Group .DESCRIPTION Add a user to a Group .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER groupName This represents the identifier for the group, it should look like 'stemname:group' Example: stem1:substem:supergroup .PARAMETER subjectId .PARAMETER subjectSourceId Source location of subjectId, ie ldap .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$groupName, [Parameter(Mandatory)] [string]$subjectId, [string]$subjectSourceId ) Begin{} Process { $uri = "$uri/groups" $subjectLookups = @(@{subjectId = $subjectId}) if ($subjectSourceId){$subjectLookups[0]['subjectSourceId'] = $subjectSourceId} $body = @{ WsRestAddMemberRequest = @{ subjectLookups = $subjectLookups wsGroupLookup = @{groupName = $groupName} } } | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType return @(($response.Content | ConvertFrom-Json).WsAddMemberResults.results.wsSubject,($response.Content | ConvertFrom-Json).WsAddMemberResults.wsGroupAssigned) } End{} } #endregion #region New-GrouperPrivileges function New-GrouperPrivileges { <# .SYNOPSIS Set Grouper Privileges .DESCRIPTION Set Grouper Privileges) .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER stemName stemName .PARAMETER subjectId User to apply Privilege to .PARAMETER actAsSubjectId User security context to use to apply change .PARAMETER privilegeName Name of privilege to apply, see Get-GrouperPrivileges for examples .PARAMETER subjectIdIsAGroup Use this switch (set to true) if the subjectID is actually a GroupName. The default assumption is that the subjectID is a users ID .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory,ParameterSetName='stem')] [string]$stemName, [Parameter(Mandatory,ParameterSetName='group')] [string]$groupName, [string]$actAsSubjectId, [Parameter(Mandatory)] [string]$subjectId, [switch]$subjectIdIsAGroup = $false, [Parameter(Mandatory)] [string]$privilegeName ) Begin{} Process { $uri = "$uri/grouperPrivileges" $body = @{ WsRestAssignGrouperPrivilegesLiteRequest = @{ allowed = 'T' privilegeName = $privilegeName } } if ($subjectIdIsAGroup){$body['WsRestAssignGrouperPrivilegesLiteRequest']['subjectIdentifier'] = $subjectId} else {$body['WsRestAssignGrouperPrivilegesLiteRequest']['subjectId'] = $subjectId} if ($actAsSubjectId) { $body['WsRestAssignGrouperPrivilegesLiteRequest']['actAsSubjectId'] = $actAsSubjectId } if ($groupName) { $body['WsRestAssignGrouperPrivilegesLiteRequest']['groupName'] = $groupName $body['WsRestAssignGrouperPrivilegesLiteRequest']['privilegeType'] = 'access' } if ($stemName) { $body['WsRestAssignGrouperPrivilegesLiteRequest']['stemName'] = $stemName $body['WsRestAssignGrouperPrivilegesLiteRequest']['privilegeType'] = 'naming' } $body = $body | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType return ($response.Content | ConvertFrom-Json).WsGetGrouperPrivilegesLiteResult.privilegeResults if (($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults.count -gt 0) { ($response.Content | ConvertFrom-Json).WsFindStemsResults.stemResults } else { Write-Warning "NO results found" } } End{} } #endregion #region New-GrouperStem function New-GrouperStem { <# .SYNOPSIS Create new Stem in Grouper .DESCRIPTION Create new Stem in Grouper .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER stemName This represents the identifier for the stem, it should look like 'stemParentA:stemParentB:stemname' Example: stem1:substem:newstem .PARAMETER description The description represents the the Name in the form users in the UI will see the group .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$stemName, [Parameter(Mandatory)] [string]$description ) Begin{} Process { $uri = "$uri/stems" $body = @{ WsRestStemSaveRequest = @{ wsStemToSaves = @(@{wsStem = @{description = $description;displayExtension = $description;name = $stemName};wsStemLookup = @{stemName = $stemName}}) } } | ConvertTo-Json -Depth 5 ($response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType) return ($response.Content | ConvertFrom-Json).WsStemSaveResults.results.wsStem } End{} } #endregion #region Remove-GrouperGroup function Remove-GrouperGroup { <# .SYNOPSIS Remove a Grouper Group .DESCRIPTION Remove a Grouper Group .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER groupName The groupName, use Get-GrouperGroup to the get the "name" field .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string[]]$groupName ) Begin{} Process { $uri = "$uri/groups" <# This didn't seem to work :() foreach ($gn in $groupName) { $gnArray = $gnArray + @{groupName = $gn} } $body = @{ WsRestGroupDeleteRequest = @{ wsGroupLookups = $gnArray } } | ConvertTo-Json -Depth 5 #> foreach ($gn in $groupName) { $body = @{ WsRestGroupDeleteRequest = @{ wsGroupLookups = @(@{groupName = $gn}) } } | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType $deletedGroups = ($response.Content | ConvertFrom-Json).WsGroupDeleteResults.results.wsGroup $deletedGroups } #return ($response.Content | ConvertFrom-Json).WsGroupDeleteResults.results.resultMetadata.resultCode } End{} } #endregion #region Remove-GrouperStem function Remove-GrouperStem { <# .SYNOPSIS Remove a Grouper Stem .DESCRIPTION Remove a Grouper Stem .PARAMETER uri Full path to Server plus path to API Example "https://<FQDN>/grouper-ws/servicesRest/json/v2_2_100" .PARAMETER header Use New-Header to get this .PARAMETER contentType Set Content Type, currently 'text/x-json;charset=UTF-8' .PARAMETER stemName Use Get-GrouperStem to find name .PARAMETER removeGroups Grouper will not remove a Stem with other Stems or Groups in it. Set this to remove all the groups first .NOTES Author: Travis Sobeck LASTEDIT: 7/30/2018 .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [System.Collections.Hashtable]$header, [string]$contentType = 'text/x-json;charset=UTF-8', [Parameter(Mandatory)] [string]$stemName, [switch]$removeGroups ) Begin{} Process { if ($removeGroups) { # Get all the groups $groupNames = (Get-GrouperGroup -uri $uri -header $header -stemName $stemName).name # Remove the groups if ($groupNames) { $null = Remove-GrouperGroup -uri $uri -header $header -groupName $groupNames Start-Sleep -Seconds 3 } } $uri = "$uri/stems" $body = @{ WsRestStemDeleteRequest = @{ wsStemLookups = @(@{stemName = $stemName}) } } | ConvertTo-Json -Depth 5 $response = Invoke-WebRequest -Uri $uri -Headers $header -Method Post -Body $body -UseBasicParsing -ContentType $contentType $removedStems = ($response.Content | ConvertFrom-Json).WsStemDeleteResults.results.wsStem return $removedStems #($response.Content | ConvertFrom-Json).WsStemDeleteResults.results.resultMetadata.resultCode } End{} } #endregion # SIG # Begin signature block # MIIaxgYJKoZIhvcNAQcCoIIatzCCGrMCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUpKbovXHwzhjwjR32cO8dseVZ # dmWgghW3MIIEmTCCA4GgAwIBAgIPFojwOSVeY45pFDkH5jMLMA0GCSqGSIb3DQEB # BQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQg # TGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNV # BAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJG # aXJzdC1PYmplY3QwHhcNMTUxMjMxMDAwMDAwWhcNMTkwNzA5MTg0MDM2WjCBhDEL # MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE # BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKjAoBgNVBAMT # IUNPTU9ETyBTSEEtMSBUaW1lIFN0YW1waW5nIFNpZ25lcjCCASIwDQYJKoZIhvcN # AQEBBQADggEPADCCAQoCggEBAOnpPd/XNwjJHjiyUlNCbSLxscQGBGue/YJ0UEN9 # xqC7H075AnEmse9D2IOMSPznD5d6muuc3qajDjscRBh1jnilF2n+SRik4rtcTv6O # KlR6UPDV9syR55l51955lNeWM/4Og74iv2MWLKPdKBuvPavql9LxvwQQ5z1IRf0f # aGXBf1mZacAiMQxibqdcZQEhsGPEIhgn7ub80gA9Ry6ouIZWXQTcExclbhzfRA8V # zbfbpVd2Qm8AaIKZ0uPB3vCLlFdM7AiQIiHOIiuYDELmQpOUmJPv/QbZP7xbm1Q8 # ILHuatZHesWrgOkwmt7xpD9VTQoJNIp1KdJprZcPUL/4ygkCAwEAAaOB9DCB8TAf # BgNVHSMEGDAWgBTa7WR0FJwUPKvdmam9WyhNizzJ2DAdBgNVHQ4EFgQUjmstM2v0 # M6eTsxOapeAK9xI1aogwDgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB/wQCMAAwFgYD # VR0lAQH/BAwwCgYIKwYBBQUHAwgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny # bC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDA1BggrBgEF # BQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20w # DQYJKoZIhvcNAQEFBQADggEBALozJEBAjHzbWJ+zYJiy9cAx/usfblD2CuDk5oGt # Joei3/2z2vRz8wD7KRuJGxU+22tSkyvErDmB1zxnV5o5NuAoCJrjOU+biQl/e8Vh # f1mJMiUKaq4aPvCiJ6i2w7iH9xYESEE9XNjsn00gMQTZZaHtzWkHUxY93TYCCojr # QOUGMAu4Fkvc77xVCf/GPhIudrPczkLv+XZX4bcKBUCYWJpdcRaTcYxlgepv84n3 # +3OttOe/2Y5vqgtPJfO44dXddZhogfiqwNGAwsTEOYnB9smebNd0+dmX+E/CmgrN # Xo/4GengpZ/E8JIh5i15Jcki+cPwOoRXrToW9GOUEB1d0MYwggV3MIIEX6ADAgEC # AhAT6ihwW/Ts7Qw2YwmAYUM2MA0GCSqGSIb3DQEBDAUAMG8xCzAJBgNVBAYTAlNF # MRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJu # YWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJv # b3QwHhcNMDAwNTMwMTA0ODM4WhcNMjAwNTMwMTA0ODM4WjCBiDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4w # HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVz # dCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzO # iZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwW # IJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YU # VD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1da # t//O+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+ # UzeQc0PzMsNT79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/z # JSZrM233bkf6c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLa # qUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxb # gtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9A # qURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ # eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwID # AQABo4H0MIHxMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8DveAky1QaMB0GA1Ud # DgQWBBRTeb9aqitKz1SA4dibwJ3ysgNmyzAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T # AQH/BAUwAwEB/zARBgNVHSAECjAIMAYGBFUdIAAwRAYDVR0fBD0wOzA5oDegNYYz # aHR0cDovL2NybC51c2VydHJ1c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3Qu # Y3JsMDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNl # cnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAQEAk2X2N4OVD17Dghwf1nfnPIrA # qgnw6Qsm8eDCanWhx3nJuVJgyCkSDvCtA9YJxHbf5aaBladG2oJXqZWSxbaPAyJs # M3fBezIXbgfOWhRBOgUkG/YUBjuoJSQOu8wqdd25cEE/fNBjNiEHH0b/YKSR4We8 # 3h9+GRTJY2eR6mcHa7SPi8BuQ33DoYBssh68U4V93JChpLwt70ZyVzUFv7tGu25t # N5m2/yOSkcZuQPiPKVbqX9VfFFOs8E9h6vcizKdWC+K4NB8m2XsZBWg/ujzUOAai # 0+aPDuO0cW1AQsWEtECVK/RloEh59h2BY5adT3Xg+HzkjqnR8q2Ks4zHIc3C7zCC # BawwggSUoAMCAQICEHJNXiAT1cKRQFXzfFSJVHEwDQYJKoZIhvcNAQELBQAwfDEL # MAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1JMRIwEAYDVQQHEwlBbm4gQXJib3IxEjAQ # BgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5Db21tb24xJTAjBgNVBAMTHElu # Q29tbW9uIFJTQSBDb2RlIFNpZ25pbmcgQ0EwHhcNMTcxMjE0MDAwMDAwWhcNMjAx # MjEzMjM1OTU5WjCByzELMAkGA1UEBhMCVVMxDjAMBgNVBBEMBTU1NDU1MRIwEAYD # VQQIDAlNaW5uZXNvdGExFDASBgNVBAcMC01pbm5lYXBvbGlzMRgwFgYDVQQJDA8x # MDAgVW5pb24gU3QgU0UxIDAeBgNVBAoMF1VuaXZlcnNpdHkgb2YgTWlubmVzb3Rh # MSQwIgYDVQQLDBtDb21wdXRlciBhbmQgRGV2aWNlIFN1cHBvcnQxIDAeBgNVBAMM # F1VuaXZlcnNpdHkgb2YgTWlubmVzb3RhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A # MIIBCgKCAQEAwk6kLE9u+tWv0JUkIJSn5pWfa09g6cqFLucCXomNj9NYj8t+JfPn # a3gC6LHv3OQAUDHOoC5H+8N3ea7qVGYIiwPRHzXOGqG/tVaiU5s5hG3vBhfRX8W1 # /2g4/hpgeXUzrxYn/2c5SOGGy0MU1ZJyUSFEdsjXHEV7HXK4qmFGV9RJxtiLZH1q # UldCglxcj7zw0QnUdG6oAxpwTCeVp057/WXbnIR8a0gPse+y/new5+CBUGTAvrw6 # K2BrJQVsdIIVn/q+BbcZxh9PpeZfTtsi6lgkvy0bUWtl5sSpd75+hvw4Sl3HAaWZ # toWN7LPmbDbbVRO2Arv4doh4Chod4wJ5xQIDAQABo4IB2DCCAdQwHwYDVR0jBBgw # FoAUrjUjF///Bj2cUOCMJGUzHnAQiKIwHQYDVR0OBBYEFF4LEhElVUvT8n5txOJS # NAczooSAMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoG # CCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBmBgNVHSAEXzBdMFsGDCsGAQQB # riMBBAMCATBLMEkGCCsGAQUFBwIBFj1odHRwczovL3d3dy5pbmNvbW1vbi5vcmcv # Y2VydC9yZXBvc2l0b3J5L2Nwc19jb2RlX3NpZ25pbmcucGRmMEkGA1UdHwRCMEAw # PqA8oDqGOGh0dHA6Ly9jcmwuaW5jb21tb24tcnNhLm9yZy9JbkNvbW1vblJTQUNv # ZGVTaWduaW5nQ0EuY3JsMH4GCCsGAQUFBwEBBHIwcDBEBggrBgEFBQcwAoY4aHR0 # cDovL2NydC5pbmNvbW1vbi1yc2Eub3JnL0luQ29tbW9uUlNBQ29kZVNpZ25pbmdD # QS5jcnQwKAYIKwYBBQUHMAGGHGh0dHA6Ly9vY3NwLmluY29tbW9uLXJzYS5vcmcw # GQYDVR0RBBIwEIEOb2l0bXB0QHVtbi5lZHUwDQYJKoZIhvcNAQELBQADggEBAENR # lesMKmBaZ0g68lttYEMtaPiz+DaNpOlXBs1gH66aghB1aP6iiRJcFVasGLUVFncd # G1xbw503LTrBUc5PECMVDVF7KKCfHA1OeFV9vOWyvdVgbe3paDy1sj4CADO2D0gn # xcGiZoFhEZiBkTvSsj4S3GXZEvoFHJxJLw2kvdLnzy0gH/b/b/yblwA1fKXw4loc # UpDM6qTwM7SiKgkQ5W7/280EYu8BI6c8rpiJmqM1tZLcpswuavB00T52Y+ZZmz3t # MMVgFHn9pFFltYr3s3bEek7I6pU8unISbiyQzxqhIUKaBi8hy8LgoY5UnGjX5jHs # IvINzms+JX5Ity02sL0wggXrMIID06ADAgECAhBl4eLj1d5QRYXzJiSABeLUMA0G # CSqGSIb3DQEBDQUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNl # eTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1Qg # TmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1 # dGhvcml0eTAeFw0xNDA5MTkwMDAwMDBaFw0yNDA5MTgyMzU5NTlaMHwxCzAJBgNV # BAYTAlVTMQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFyYm9yMRIwEAYDVQQK # EwlJbnRlcm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMSUwIwYDVQQDExxJbkNvbW1v # biBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB # CgKCAQEAwKAvix56u2p1rPg+3KO6OSLK86N25L99MCfmutOYMlYjXAaGlw2A6O2i # gTXrC/Zefqk+aHP9ndRnec6q6mi3GdscdjpZh11emcehsriphHMMzKuHRhxqx+85 # Jb6n3dosNXA2HSIuIDvd4xwOPzSf5X3+VYBbBnyCV4RV8zj78gw2qblessWBRyN9 # EoGgwAEoPgP5OJejrQLyAmj91QGr9dVRTVDTFyJG5XMY4DrkN3dRyJ59UopPgNwm # ucBMyvxR+hAJEXpXKnPE4CEqbMJUvRw+g/hbqSzx+tt4z9mJmm2j/w2nP35MViPW # Cb7hpR2LB8W/499Yqu+kr4LLBfgKCQIDAQABo4IBWjCCAVYwHwYDVR0jBBgwFoAU # U3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFK41Ixf//wY9nFDgjCRlMx5w # EIiiMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1UdJQQM # MAoGCCsGAQUFBwMDMBEGA1UdIAQKMAgwBgYEVR0gADBQBgNVHR8ESTBHMEWgQ6BB # hj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQ2VydGlmaWNh # dGlvbkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEEajBoMD8GCCsGAQUFBzAChjNo # dHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQWRkVHJ1c3RDQS5j # cnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZI # hvcNAQENBQADggIBAEYstn9qTiVmvZxqpqrQnr0Prk41/PA4J8HHnQTJgjTbhuET # 98GWjTBEE9I17Xn3V1yTphJXbat5l8EmZN/JXMvDNqJtkyOh26owAmvquMCF1pKi # QWyuDDllxR9MECp6xF4wnH1Mcs4WeLOrQPy+C5kWE5gg/7K6c9G1VNwLkl/po9OR # PljxKKeFhPg9+Ti3JzHIxW7LdyljffccWiuNFR51/BJHAZIqUDw3LsrdYWzgg4x0 # 6tgMvOEf0nITelpFTxqVvMtJhnOfZbpdXZQ5o1TspxfTEVOQAsp05HUNCXyhznlV # Lr0JaNkM7edgk59zmdTbSGdMq8Ztuu6VyrivOlMSPWmay5MjvwTzuNorbwBv0DL+ # 7cyZBp7NYZou+DoGd1lFZN0jU5IsQKgm3+00pnnJ67crdFwfz/8bq3MhTiKOWEb0 # 4FT3OZVp+jzvaChHWLQ8gbCORgClaZq1H3aqI7JeRkWEEEp6Tv4WAVsr/i7LoXU7 # 2gOb8CAzPFqwI4Excdrxp0I4OXbECHlDqU4sTInqwlMwofmxeO4u94196qIqJQl+ # 8Sykl06VktqMux84Iw3ZQLH08J8LaJ+WDUycc4OjY61I7FGxCDkbSQf3npXeRFm0 # IBn8GiW+TRDk6J2XJFLWEtVZmhboFlBLoUlqHUCKu0QOhU/+AEOqnY98j2zRMYIE # eTCCBHUCAQEwgZAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1JMRIwEAYDVQQH # EwlBbm4gQXJib3IxEjAQBgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5Db21t # b24xJTAjBgNVBAMTHEluQ29tbW9uIFJTQSBDb2RlIFNpZ25pbmcgQ0ECEHJNXiAT # 1cKRQFXzfFSJVHEwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKEC # gAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwG # CisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFGEqzk+fTWr30z9aYVPLSgG/jlhE # MA0GCSqGSIb3DQEBAQUABIIBAGqDpoDxCVrR7+5rdxMhU+ABk1pF15xaYMfFfu+X # KNZ8zqC0mwPJvky2iwJbtTDBs68EXFdstE0pjcRAGsG030NXUD4ZVGZnhsbi47+e # M81q6yMaOaAr5NpyF009vwHYQrUtITfGypOfzmL+/392gnuiL4DZQnpH+KAaimJu # 1Mp6yx5rQ4mqqTe2nQgHl+jEOhoBGijyXYZ7mTP1k8l+oFNXmB0G/zT9k2fiS9XT # j+mX2+RzSKfM4Ok5U+LLIvv3Ty1m1czvYkhdFl3VDzxuUWdb+C99TJkQ+zM1Jpu4 # cLNPEbMOP3tm8/KGEjiqVVSoiikIe3u5+bbJ+78tcIc9V1ShggJDMIICPwYJKoZI # hvcNAQkGMYICMDCCAiwCAQEwgakwgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJV # VDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJV # U1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0w # GwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdAIPFojwOSVeY45pFDkH5jMLMAkG # BSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ # BTEPFw0xODA4MTQxOTU0MDVaMCMGCSqGSIb3DQEJBDEWBBRgih2jr4bOkJhzZ65p # H7JBH6G4JTANBgkqhkiG9w0BAQEFAASCAQDiFydcCLwvBhVdAXGFRi2sC2dBJYDy # DnRc94Kr2QszwzYL7iwWNH7S+UpkmxzOYDJylVfGhWpiNQHrgVvhK37RVeL5TK6N # FKstLfK+nN7F1EvFGn86nxbs8c/aaX6EgOWFD7KHDxwokbqCWH1ZGWyPU/J5ZjiD # WDI7SvR7/1bBbly9szUVUmCMBFZd8FD1W/bEtw3PGs9EbMe+bChYxTWQWZZgom7m # dYX5DKZX780zNhE5g3Aply8Vy/GqvbuawVE0wwPTdMuYrq/taN8CBCPxjNtyK45I # fWozsk6VY++LxO0wDx+zP/QvjH10KiHICq8RbBqBpWf2RHMZTSbnJhr0 # SIG # End signature block |