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 a Grouper Stem or use the -search switch to get all Grouper Stem(s) that match stem pattern From API docs -- find by approx name, pass the name in. stem name is required .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 search Switch to do a search. Use with the caution, results from grouper API are not very reliable .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, [switch]$search, [string]$subjectId ) Begin{} Process { $uri = "$uri/stems" $body = @{ WsRestFindStemsRequest = @{ wsStemQueryFilter = @{stemName = $stemName} } } if ($search){$body['WsRestFindStemsRequest']['wsStemQueryFilter']['stemQueryFilterType'] = 'FIND_BY_STEM_NAME_APPROXIMATE'} else{$body['WsRestFindStemsRequest']['wsStemQueryFilter']['stemQueryFilterType'] = 'FIND_BY_STEM_NAME'} 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 Get-GrouperStemByParent function Get-GrouperStemByParent { <# .SYNOPSIS Get Grouper child Stem(s) of a parent stem .DESCRIPTION Get Grouper child Stem(s) of a parent 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 parentStemName stemName of Parent .PARAMETER noRecursion By default the function will recursivly search for all sub-stems, use this switch to only get stems one level below the parent 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)] [string]$parentStemName, [switch]$noRecursion, [string]$subjectId ) Begin{} Process { $uri = "$uri/stems" $body = @{ WsRestFindStemsRequest = @{ wsStemQueryFilter = @{parentStemName = $parentStemName;stemQueryFilterType = 'FIND_BY_PARENT_STEM_NAME'} } } if($noRecursion){$body['WsRestFindStemsRequest']['wsStemQueryFilter']["parentStemNameScope"] = 'ONE_LEVEL'} else{$body['WsRestFindStemsRequest']['wsStemQueryFilter']["parentStemNameScope"] = 'ALL_IN_SUBTREE'} if ($subjectId) { $body['WsRestFindStemsRequest']['actAsSubjectLookup'] = @{subjectId = $subjectId}; } $body = $body | ConvertTo-Json -Depth 5 Write-Verbose -Message $body $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 Get-GrouperStemByUUID function Get-GrouperStemByUUID { <# .SYNOPSIS Get a Grouper Stem by its UUID .DESCRIPTION Get a Grouper Stem by its UUID .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 uuid UUID of the stem to retrieve .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]$uuid, [string]$subjectId ) Begin{} Process { $uri = "$uri/stems" $body = @{ WsRestFindStemsRequest = @{ wsStemQueryFilter = @{stemUuid = $uuid;stemQueryFilterType = 'FIND_BY_STEM_UUID'} } } 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 $body['WsRestAssignGrouperPrivilegesLiteRequest']['subjectSourceId'] = "g:gsa" } 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 #Write-Debug $body $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 # MIIVSwYJKoZIhvcNAQcCoIIVPDCCFTgCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUoQxuToC/B4V1mEmTklL4/6vP # beOgghA8MIIEmTCCA4GgAwIBAgIPFojwOSVeY45pFDkH5jMLMA0GCSqGSIb3DQEB # 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+cPwOoRXrToW9GOUEB1d0MYwggWsMIIElKADAgEC # AhByTV4gE9XCkUBV83xUiVRxMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVT # MQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFyYm9yMRIwEAYDVQQKEwlJbnRl # cm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMSUwIwYDVQQDExxJbkNvbW1vbiBSU0Eg # Q29kZSBTaWduaW5nIENBMB4XDTE3MTIxNDAwMDAwMFoXDTIwMTIxMzIzNTk1OVow # gcsxCzAJBgNVBAYTAlVTMQ4wDAYDVQQRDAU1NTQ1NTESMBAGA1UECAwJTWlubmVz # b3RhMRQwEgYDVQQHDAtNaW5uZWFwb2xpczEYMBYGA1UECQwPMTAwIFVuaW9uIFN0 # IFNFMSAwHgYDVQQKDBdVbml2ZXJzaXR5IG9mIE1pbm5lc290YTEkMCIGA1UECwwb # Q29tcHV0ZXIgYW5kIERldmljZSBTdXBwb3J0MSAwHgYDVQQDDBdVbml2ZXJzaXR5 # IG9mIE1pbm5lc290YTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMJO # pCxPbvrVr9CVJCCUp+aVn2tPYOnKhS7nAl6JjY/TWI/LfiXz52t4Auix79zkAFAx # zqAuR/vDd3mu6lRmCIsD0R81zhqhv7VWolObOYRt7wYX0V/Ftf9oOP4aYHl1M68W # J/9nOUjhhstDFNWSclEhRHbI1xxFex1yuKphRlfUScbYi2R9alJXQoJcXI+88NEJ # 1HRuqAMacEwnladOe/1l25yEfGtID7Hvsv53sOfggVBkwL68OitgayUFbHSCFZ/6 # vgW3GcYfT6XmX07bIupYJL8tG1FrZebEqXe+fob8OEpdxwGlmbaFjeyz5mw221UT # tgK7+HaIeAoaHeMCecUCAwEAAaOCAdgwggHUMB8GA1UdIwQYMBaAFK41Ixf//wY9 # nFDgjCRlMx5wEIiiMB0GA1UdDgQWBBReCxIRJVVL0/J+bcTiUjQHM6KEgDAOBgNV # HQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDAzAR # BglghkgBhvhCAQEEBAMCBBAwZgYDVR0gBF8wXTBbBgwrBgEEAa4jAQQDAgEwSzBJ # BggrBgEFBQcCARY9aHR0cHM6Ly93d3cuaW5jb21tb24ub3JnL2NlcnQvcmVwb3Np # dG9yeS9jcHNfY29kZV9zaWduaW5nLnBkZjBJBgNVHR8EQjBAMD6gPKA6hjhodHRw # Oi8vY3JsLmluY29tbW9uLXJzYS5vcmcvSW5Db21tb25SU0FDb2RlU2lnbmluZ0NB # LmNybDB+BggrBgEFBQcBAQRyMHAwRAYIKwYBBQUHMAKGOGh0dHA6Ly9jcnQuaW5j # b21tb24tcnNhLm9yZy9JbkNvbW1vblJTQUNvZGVTaWduaW5nQ0EuY3J0MCgGCCsG # AQUFBzABhhxodHRwOi8vb2NzcC5pbmNvbW1vbi1yc2Eub3JnMBkGA1UdEQQSMBCB # Dm9pdG1wdEB1bW4uZWR1MA0GCSqGSIb3DQEBCwUAA4IBAQBDUZXrDCpgWmdIOvJb # bWBDLWj4s/g2jaTpVwbNYB+umoIQdWj+ookSXBVWrBi1FRZ3HRtcW8OdNy06wVHO # TxAjFQ1ReyignxwNTnhVfbzlsr3VYG3t6Wg8tbI+AgAztg9IJ8XBomaBYRGYgZE7 # 0rI+Etxl2RL6BRycSS8NpL3S588tIB/2/2/8m5cANXyl8OJaHFKQzOqk8DO0oioJ # EOVu/9vNBGLvASOnPK6YiZqjNbWS3KbMLmrwdNE+dmPmWZs97TDFYBR5/aRRZbWK # 97N2xHpOyOqVPLpyEm4skM8aoSFCmgYvIcvC4KGOVJxo1+Yx7CLyDc5rPiV+SLct # NrC9MIIF6zCCA9OgAwIBAgIQZeHi49XeUEWF8yYkgAXi1DANBgkqhkiG9w0BAQ0F # ADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcT # C0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAs # BgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcN # MTQwOTE5MDAwMDAwWhcNMjQwOTE4MjM1OTU5WjB8MQswCQYDVQQGEwJVUzELMAkG # A1UECBMCTUkxEjAQBgNVBAcTCUFubiBBcmJvcjESMBAGA1UEChMJSW50ZXJuZXQy # MREwDwYDVQQLEwhJbkNvbW1vbjElMCMGA1UEAxMcSW5Db21tb24gUlNBIENvZGUg # U2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMCgL4se # ertqdaz4PtyjujkiyvOjduS/fTAn5rrTmDJWI1wGhpcNgOjtooE16wv2Xn6pPmhz # /Z3UZ3nOqupotxnbHHY6WYddXpnHobK4qYRzDMyrh0YcasfvOSW+p93aLDVwNh0i # LiA73eMcDj80n+V9/lWAWwZ8gleEVfM4+/IMNqm5XrLFgUcjfRKBoMABKD4D+TiX # o60C8gJo/dUBq/XVUU1Q0xciRuVzGOA65Dd3UciefVKKT4DcJrnATMr8UfoQCRF6 # VypzxOAhKmzCVL0cPoP4W6ks8frbeM/ZiZpto/8Npz9+TFYj1gm+4aUdiwfFv+Pf # WKrvpK+CywX4CgkCAwEAAaOCAVowggFWMB8GA1UdIwQYMBaAFFN5v1qqK0rPVIDh # 2JvAnfKyA2bLMB0GA1UdDgQWBBSuNSMX//8GPZxQ4IwkZTMecBCIojAOBgNVHQ8B # Af8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNVHSUEDDAKBggrBgEFBQcD # AzARBgNVHSAECjAIMAYGBFUdIAAwUAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2Ny # bC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25BdXRob3Jp # dHkuY3JsMHYGCCsGAQUFBwEBBGowaDA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51 # c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJTQUFkZFRydXN0Q0EuY3J0MCUGCCsGAQUF # BzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDQUAA4IC # AQBGLLZ/ak4lZr2caqaq0J69D65ONfzwOCfBx50EyYI024bhE/fBlo0wRBPSNe15 # 91dck6YSV22reZfBJmTfyVzLwzaibZMjoduqMAJr6rjAhdaSokFsrgw5ZcUfTBAq # esReMJx9THLOFnizq0D8vguZFhOYIP+yunPRtVTcC5Jf6aPTkT5Y8SinhYT4Pfk4 # tycxyMVuy3cpY333HForjRUedfwSRwGSKlA8Ny7K3WFs4IOMdOrYDLzhH9JyE3pa # RU8albzLSYZzn2W6XV2UOaNU7KcX0xFTkALKdOR1DQl8oc55VS69CWjZDO3nYJOf # c5nU20hnTKvGbbrulcq4rzpTEj1pmsuTI78E87jaK28Ab9Ay/u3MmQaezWGaLvg6 # BndZRWTdI1OSLECoJt/tNKZ5yeu3K3RcH8//G6tzIU4ijlhG9OBU9zmVafo872go # R1i0PIGwjkYApWmatR92qiOyXkZFhBBKek7+FgFbK/4uy6F1O9oDm/AgMzxasCOB # MXHa8adCODl2xAh5Q6lOLEyJ6sJTMKH5sXjuLveNfeqiKiUJfvEspJdOlZLajLsf # OCMN2UCx9PCfC2iflg1MnHODo2OtSOxRsQg5G0kH956V3kRZtCAZ/Bolvk0Q5Oid # lyRS1hLVWZoW6BZQS6FJah1AirtEDoVP/gBDqp2PfI9s0TGCBHkwggR1AgEBMIGQ # MHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFyYm9y # MRIwEAYDVQQKEwlJbnRlcm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMSUwIwYDVQQD # ExxJbkNvbW1vbiBSU0EgQ29kZSBTaWduaW5nIENBAhByTV4gE9XCkUBV83xUiVRx # MAkGBSsOAwIaBQCgeDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3 # DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEV # MCMGCSqGSIb3DQEJBDEWBBTsag4gPnTNhhmr1ypWjBmbJuookDANBgkqhkiG9w0B # AQEFAASCAQAsheNJDvD/FNKQPtflQ6OAIjt7lkXrx9XhgrVYfUyoc+hkaOcRzMbu # XlLyQ06iMtEjzQlTm1fRQmodx7lz3FTjs5fQVZyPjE5pBFMU7UTLN8SCQjiuFm0F # WSdFqIXjYzo+xiSlE63rqS2U6/zSRJ6/JWOTi9hneX72INVFpO33LuPAtX9y5SLY # uHKwLdE0Funq5EYs9eh7G8E/owVFhhkGXTp2Kbg3YCgH7+gNoQZztL2mhf3DK9nG # oeG4z/XhKUraJrKPzamNGWb6gbwcmQkhWBL3Pi4dNKL2utMtbA69aTvGkf2T1GWW # Gn4soC5xSVXL4LK7+A1A4wVObGudYuG/oYICQzCCAj8GCSqGSIb3DQEJBjGCAjAw # ggIsAgEBMIGpMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcT # DlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsx # ITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVRO # LVVTRVJGaXJzdC1PYmplY3QCDxaI8DklXmOOaRQ5B+YzCzAJBgUrDgMCGgUAoF0w # GAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkwNDEx # MTYwNjIzWjAjBgkqhkiG9w0BCQQxFgQUazjby2tRB7N8mzvCtfCeRVwq2DQwDQYJ # KoZIhvcNAQEBBQAEggEAL8wTO56ikUnLOYfmq478ZxLKArS73To9WKdJ6ohObcXF # yH8tkZ4OGhLnyzs7kPVQ7Gk46zbeSvBNWV631iFNejkHc6yg4sEC2s7VUhexvzLX # tvkbWWjaOylKAtgetyGOTD8p4/ql7Kx4hXSjadu7YOfk/yNpgR7WT3uu6b4yho6h # pMftN5iRNj1yASzWFSEaZUGxZ1Px71bmSEtwBH0saoYdNzVzQ8BryVlIxeFvoR3m # 8jvvHN6xWrKfTWLV07fnEnCc/M2o4hn0Hq5rEGdzEJ/ABRFUZ52D7vHT/MR8Fetg # gYSp05avL2EkuCZ7AUZrGbtgYs4nrG0TiBsvisHiog== # SIG # End signature block |