Enrich professor profiles and harden client policy
This commit is contained in:
@@ -34,10 +34,12 @@ photo, and manager fields are ignored. Student enrichment targets the read-only
|
||||
student information page and reads only the matching student number, structured
|
||||
name, email, career, and postal address. The career becomes an AD title in the
|
||||
form `Estudiante de ...`; faculty/department remains unset because the verified
|
||||
page does not expose it. Professors retain the menu display-name fallback until
|
||||
a richer role-specific page is verified. Missing or changed presentation HTML
|
||||
never blocks authentication or password synchronization after the lightweight
|
||||
NTLM root has accepted the credential.
|
||||
page does not expose it. Professors use the portal menu name as their safe base
|
||||
profile and then attempt the same personal and location modules as administrative
|
||||
staff. A missing route, non-success response, timeout, or absent element ID merely
|
||||
skips that optional field. Missing or changed presentation HTML never blocks
|
||||
authentication or password synchronization after the lightweight NTLM root has
|
||||
accepted the credential.
|
||||
|
||||
Operational documentation:
|
||||
|
||||
|
||||
+16
-7
@@ -28,9 +28,11 @@ administrative incident overview for `AD`, the student information page for
|
||||
`AL`, or the portal menu for `DO`. After the incident page confirms an `AD`
|
||||
employee number, two additional GETs in the same in-memory session read the
|
||||
structured name from `datos/personales.aspx` and the address from
|
||||
`datos/ubicacion.aspx`. A supplemental failure preserves fields already
|
||||
collected, and a profile timeout does not invalidate an already authenticated
|
||||
credential. NTLM may still require its normal
|
||||
`datos/ubicacion.aspx`. Docentes keep the menu name as a base and attempt those
|
||||
same two shared staff modules without requiring them to exist. A supplemental
|
||||
404, changed/missing element ID, other failure, or timeout preserves fields
|
||||
already collected, and a profile timeout does not invalidate an already
|
||||
authenticated credential. NTLM may still require its normal
|
||||
challenge/response round trips on the connection. Transient portal cookies are
|
||||
kept only in an in-memory per-request container and are never persisted or
|
||||
returned to the client.
|
||||
@@ -60,9 +62,12 @@ updates the applicable `displayName`, `givenName`, `sn`, `mail`, `title`,
|
||||
`postalCode` attributes. Administrative and student numbers must match the six
|
||||
numeric digits of the requested identity before any role-specific metadata is
|
||||
trusted. Administrative personal and location pages are accepted only after
|
||||
that incident-page match. Student faculty/department is deliberately left unset because the
|
||||
verified page does not expose it. Missing metadata does not clear existing AD
|
||||
values and never changes the password outcome.
|
||||
that incident-page match. A docente's supplemental fields remain tied to the
|
||||
fresh NTLM-authenticated portal session and are optional; the menu display name
|
||||
remains usable if neither shared page is available. Student faculty/department
|
||||
is deliberately left unset because the verified page does not expose it.
|
||||
Missing metadata does not clear existing AD values and never changes the
|
||||
password outcome.
|
||||
|
||||
Human-readable SGU values are decoded with BOM/header/meta detection, strict
|
||||
UTF-8 validation, and a Windows-1252 fallback for the legacy portal. Names and
|
||||
@@ -91,7 +96,11 @@ synthetic Windows account to imitate that title or ordering.
|
||||
The managed hierarchy is rooted at `OU=Usuarios-SGU`: `Docentes`, `Alumnos`,
|
||||
and `Administrativos` are direct child OUs beneath it. The domain GPO
|
||||
`SGU - User session restrictions` is linked to this root and enables the
|
||||
per-user `DisableLockWorkstation` policy for the complete hierarchy.
|
||||
per-user `DisableLockWorkstation` policy and disables screen savers for the
|
||||
complete hierarchy. The computer GPO `SGU - Windows client experience` is linked
|
||||
to `OU=Laboratorio`; it suppresses first-logon/privacy/diagnostic prompts,
|
||||
disables location, and enforces always-on display, sleep, and hibernation
|
||||
settings for managed clients.
|
||||
|
||||
Per-user synchronization is serialized inside the broker to prevent concurrent
|
||||
create/reset races. Production deployments should run the broker as a gMSA with
|
||||
|
||||
@@ -50,8 +50,9 @@ Eso es comportamiento esperado, no una caída del servicio.
|
||||
- El broker permite hasta **20 segundos** para el desafío NTLM ligero de
|
||||
`/psulsa/`. El enriquecimiento usa el límite total independiente
|
||||
`ProfileTimeoutSeconds` —**90 segundos** en la configuración del laboratorio—
|
||||
y conserva los campos que alcance a obtener si una página administrativa se
|
||||
retrasa o falla. El Credential Provider mantiene su propio límite de **90
|
||||
y conserva los campos que alcance a obtener si una página de personal se
|
||||
retrasa, no existe o cambia sus IDs. Esto incluye los módulos opcionales de
|
||||
nombre y ubicación para docentes. El Credential Provider mantiene su propio límite de **90
|
||||
segundos**: si SGU excede ese presupuesto, Windows continúa por el fallback
|
||||
normal de AD o credenciales de dominio en caché.
|
||||
- El instalador configura recuperación del servicio con reinicios a los 5, 15
|
||||
|
||||
@@ -82,17 +82,29 @@ siguiente arranque síncrono y el fondo se aplica al siguiente inicio de sesión
|
||||
del usuario SGU.
|
||||
|
||||
En el controlador de dominio, aplicar una vez la directiva de usuario al árbol
|
||||
completo `Usuarios-SGU`:
|
||||
completo `Usuarios-SGU` y la directiva de equipo a `Laboratorio`:
|
||||
|
||||
```powershell
|
||||
.\Set-SguDomainUserPolicies.ps1
|
||||
.\Set-SguDomainComputerPolicies.ps1
|
||||
```
|
||||
|
||||
El script crea y vincula de forma idempotente el GPO
|
||||
`SGU - User session restrictions` y configura `DisableLockWorkstation=1` para
|
||||
Administrativos, Alumnos y Docentes. La directiva elimina la acción manual de
|
||||
bloqueo; no sustituye otras directivas independientes de protector de pantalla,
|
||||
inactividad o desconexión de RDP.
|
||||
Administrativos, Alumnos y Docentes; también deshabilita el protector de
|
||||
pantalla. El segundo crea `SGU - Windows client experience` y, antes del primer
|
||||
inicio de sesión de dominio, suprime la experiencia de privacidad, las
|
||||
notificaciones y controles de telemetría, la animación **Hi / Preparing
|
||||
Windows**, y el acceso a ubicación. Además fija en **Nunca** el apagado de
|
||||
pantalla, suspensión, hibernación y suspensión híbrida, conectado a corriente o
|
||||
batería. El guard de enrolamiento vuelve a aplicar `powercfg /hibernate off` y
|
||||
los tiempos en cero al inicio y diariamente.
|
||||
|
||||
`HideEULAPage` no forma parte de esta GPO: es una opción de archivo Unattend para
|
||||
la fase OOBE y Microsoft la reserva para pruebas de OEM/System Builder. La GPO
|
||||
usa las alternativas soportadas `DisablePrivacyExperience=1` y
|
||||
`EnableFirstLogonAnimation=0`, que son las que afectan a cada cuenta de dominio
|
||||
nueva.
|
||||
|
||||
## 3. Verificación y reparación
|
||||
|
||||
@@ -134,5 +146,10 @@ Referencias de Microsoft:
|
||||
- <https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-admx-credentialproviders#defaultcredentialprovider>
|
||||
- <https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-windowslogon#enumeratelocalusersondomainjoinedcomputers>
|
||||
- <https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-admx-ctrlaltdel#disablelockcomputer>
|
||||
- <https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-privacy#disableprivacyexperience>
|
||||
- <https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-windowslogon#enablefirstlogonanimation>
|
||||
- <https://learn.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#182-location>
|
||||
- <https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/powercfg-command-line-options>
|
||||
- <https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/automate-oobe>
|
||||
- <https://learn.microsoft.com/en-us/windows/win32/api/credentialprovider/ne-credentialprovider-credential_provider_field_type>
|
||||
- <https://learn.microsoft.com/en-us/windows/win32/api/credentialprovider/nf-credentialprovider-icredentialprovidercredential2-getusersid>
|
||||
|
||||
+8
-4
@@ -4,8 +4,8 @@ Validated lab inventory:
|
||||
|
||||
- Domain controller/broker: Windows Server 2025 Standard,
|
||||
`WIN-1AIQMMA1EPR.lci.lasalle.mx`, `192.168.50.10`.
|
||||
- Client: Windows 10 Pro 22H2,
|
||||
`DESKTOP-U1I3BNN.lci.lasalle.mx`, `192.168.50.20`.
|
||||
- Client: no active Windows client VM. The next enrollment target is a clean
|
||||
Windows 11 Enterprise VM placed in `OU=Laboratorio`.
|
||||
- Domain: `lci.lasalle.mx` / `LCI`.
|
||||
- Private switch: `Laboratorio AD`.
|
||||
|
||||
@@ -133,14 +133,18 @@ Use Lithnet's `Invoke-CredUI` test utility when available, or sign out/restart
|
||||
the VM and select **Acceso institucional SGU**. Keep the built-in Windows
|
||||
password provider available through **Other user**.
|
||||
|
||||
On the domain controller, apply the SGU user policy once:
|
||||
On the domain controller, apply the SGU user and computer policies once:
|
||||
|
||||
```powershell
|
||||
.\Set-SguDomainUserPolicies.ps1
|
||||
.\Set-SguDomainComputerPolicies.ps1
|
||||
```
|
||||
|
||||
It links `SGU - User session restrictions` to `OU=Usuarios-SGU` and prevents
|
||||
managed users from manually locking the workstation.
|
||||
managed users from manually locking the workstation or starting a screen saver.
|
||||
It also links `SGU - Windows client experience` to `OU=Laboratorio` to suppress
|
||||
first-logon privacy/diagnostic/location screens and keep display, sleep, and
|
||||
hibernation disabled.
|
||||
|
||||
Before testing through Hyper-V Enhanced Session/RDP, enable the dedicated lab
|
||||
group and Windows PowerShell Remoting:
|
||||
|
||||
+5
-1
@@ -43,12 +43,16 @@
|
||||
city/municipality, state, and postal code from known element IDs.
|
||||
- Student CURP, birth date, sex, blood type, marital status, telephone, mobile,
|
||||
guardian, medical, financial, and academic-history values are ignored.
|
||||
- Professor enrichment keeps the menu display name as its base and optionally
|
||||
reads only the same name and postal-address element IDs used by staff pages.
|
||||
A missing professor route or element never makes authentication fail.
|
||||
- Incident details, calendars, photographs, manager names, and manager positions
|
||||
are deliberately ignored.
|
||||
- The employee or student number must match the authenticated `AD` or `AL` key
|
||||
before role-specific metadata is synchronized. The two supplemental
|
||||
administrative pages are never requested unless the incident page supplied
|
||||
the matching employee number.
|
||||
the matching employee number. Professor supplemental data comes from the
|
||||
same fresh, request-scoped NTLM session as its menu fallback.
|
||||
- If SGU changes its HTML, authentication and exact-password synchronization
|
||||
continue without enrichment; existing AD metadata is not erased.
|
||||
- Slow profile pages cannot change an accepted credential into a rejection. The
|
||||
|
||||
@@ -63,6 +63,8 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass `
|
||||
|
||||
El script realiza de forma idempotente lo siguiente:
|
||||
|
||||
- fija pantalla, suspensión e hibernación en **Nunca** para corriente y batería,
|
||||
y ejecuta `powercfg /hibernate off`;
|
||||
- habilita RDP y conserva Network Level Authentication;
|
||||
- habilita las reglas RDP solo para el perfil Domain;
|
||||
- autoriza el grupo SGU dedicado;
|
||||
@@ -121,6 +123,12 @@ contraseña incorrecta. Comprobar que la cuenta pertenece a
|
||||
`SG-Laboratorio-Usuarios-RDP` y que ese grupo aparece en **Remote Desktop Users**.
|
||||
Las directivas de grupo de dominio prevalecen sobre la política local.
|
||||
|
||||
En el controlador de dominio, `Set-SguDomainComputerPolicies.ps1` crea y vincula
|
||||
la GPO `SGU - Windows client experience` a `OU=Laboratorio`. Esa GPO refuerza la
|
||||
configuración siempre activa y evita las experiencias iniciales de privacidad,
|
||||
telemetría, ubicación y **Hi / Preparing Windows** antes de que un usuario SGU
|
||||
entre por primera vez.
|
||||
|
||||
Microsoft documenta este derecho en:
|
||||
<https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-userrights#allowlogonthroughremotedesktop>
|
||||
y PowerShell Remoting en:
|
||||
|
||||
@@ -20,6 +20,23 @@ $remoteDesktopUsersSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-
|
||||
$remoteDesktopUsersGroup = ($remoteDesktopUsersSid.Translate([Security.Principal.NTAccount]).Value -split '\\', 2)[1]
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Enable RDP and grant $RemoteDesktopPrincipal access")) {
|
||||
foreach ($powerChange in @(
|
||||
@('monitor-timeout-ac', '0'),
|
||||
@('monitor-timeout-dc', '0'),
|
||||
@('standby-timeout-ac', '0'),
|
||||
@('standby-timeout-dc', '0'),
|
||||
@('hibernate-timeout-ac', '0'),
|
||||
@('hibernate-timeout-dc', '0'))) {
|
||||
& powercfg.exe /change $powerChange[0] $powerChange[1]
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "powercfg /change $($powerChange[0]) failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
}
|
||||
& powercfg.exe /hibernate off
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "powercfg /hibernate off failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
Set-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' `
|
||||
-Name fDenyTSConnections -Type DWord -Value 0
|
||||
Set-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' `
|
||||
@@ -87,4 +104,5 @@ $rdpMembers = @(Get-LocalGroupMember -Group $remoteDesktopUsersGroup -ErrorActio
|
||||
TermService = (Get-Service TermService).Status
|
||||
WinRM = (Get-Service WinRM).Status
|
||||
FirewallProfile = 'Domain'
|
||||
AlwaysOnPowerPolicyApplied = $true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[string]$TargetOuDn = 'OU=Laboratorio,DC=lci,DC=lasalle,DC=mx',
|
||||
[string]$GpoName = 'SGU - Windows client experience',
|
||||
[string]$DomainController = $env:COMPUTERNAME
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw 'Run this script from an elevated Windows PowerShell session on a domain controller or management host.'
|
||||
}
|
||||
|
||||
Import-Module ActiveDirectory -ErrorAction Stop
|
||||
Import-Module GroupPolicy -ErrorAction Stop
|
||||
|
||||
$targetOu = Get-ADOrganizationalUnit `
|
||||
-Identity $TargetOuDn `
|
||||
-Server $DomainController `
|
||||
-ErrorAction Stop
|
||||
$domainDn = ($targetOu.DistinguishedName -split ',DC=', 2)[1]
|
||||
if (-not $domainDn) {
|
||||
throw 'TargetOuDn does not contain a domain distinguished name.'
|
||||
}
|
||||
$domainName = ($domainDn -replace ',DC=', '.')
|
||||
|
||||
$gpo = Get-GPO -Name $GpoName -Domain $domainName -Server $DomainController -ErrorAction SilentlyContinue
|
||||
if (-not $gpo -and $PSCmdlet.ShouldProcess($GpoName, 'Create the SGU Windows client policy GPO')) {
|
||||
$gpo = New-GPO -Name $GpoName -Domain $domainName -Server $DomainController
|
||||
}
|
||||
if (-not $gpo) {
|
||||
throw "The GPO '$GpoName' does not exist and was not created."
|
||||
}
|
||||
|
||||
$existingLink = @(Get-GPInheritance -Target $TargetOuDn -Domain $domainName -Server $DomainController).GpoLinks |
|
||||
Where-Object DisplayName -eq $GpoName |
|
||||
Select-Object -First 1
|
||||
$existingLinkEnabled = $existingLink -and (
|
||||
$existingLink.Enabled -eq $true -or
|
||||
[string]$existingLink.Enabled -eq 'Yes')
|
||||
if (-not $existingLink) {
|
||||
if ($PSCmdlet.ShouldProcess($TargetOuDn, "Link and enable '$GpoName'")) {
|
||||
New-GPLink `
|
||||
-Name $GpoName `
|
||||
-Target $TargetOuDn `
|
||||
-Domain $domainName `
|
||||
-Server $DomainController `
|
||||
-LinkEnabled Yes | Out-Null
|
||||
}
|
||||
}
|
||||
elseif (-not $existingLinkEnabled -and
|
||||
$PSCmdlet.ShouldProcess($TargetOuDn, "Enable the '$GpoName' link")) {
|
||||
Set-GPLink `
|
||||
-Name $GpoName `
|
||||
-Target $TargetOuDn `
|
||||
-Domain $domainName `
|
||||
-Server $DomainController `
|
||||
-LinkEnabled Yes | Out-Null
|
||||
}
|
||||
|
||||
$dataCollectionKey = 'HKLM\Software\Policies\Microsoft\Windows\DataCollection'
|
||||
$powerPolicyRoot = 'HKLM\Software\Policies\Microsoft\Power\PowerSettings'
|
||||
$policies = @(
|
||||
@{ Key = $dataCollectionKey; Name = 'AllowTelemetry'; Value = 0 },
|
||||
@{ Key = $dataCollectionKey; Name = 'DisableTelemetryOptInSettingsUx'; Value = 1 },
|
||||
@{ Key = $dataCollectionKey; Name = 'DisableTelemetryOptInChangeNotification'; Value = 1 },
|
||||
@{ Key = $dataCollectionKey; Name = 'DisableDiagnosticDataViewer'; Value = 1 },
|
||||
@{ Key = 'HKLM\Software\Policies\Microsoft\Windows\OOBE'; Name = 'DisablePrivacyExperience'; Value = 1 },
|
||||
@{ Key = 'HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System'; Name = 'EnableFirstLogonAnimation'; Value = 0 },
|
||||
@{ Key = 'HKLM\Software\Policies\Microsoft\Windows\LocationAndSensors'; Name = 'DisableLocation'; Value = 1 },
|
||||
@{ Key = 'HKLM\Software\Policies\Microsoft\Windows\AppPrivacy'; Name = 'LetAppsAccessLocation'; Value = 2 }
|
||||
)
|
||||
|
||||
$powerSettingIds = @(
|
||||
'3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e', # Turn off display after
|
||||
'29f6c1db-86da-48c5-9fdb-f2b67b1f44da', # Sleep after
|
||||
'9d7815a6-7ee4-497e-8888-515a05f02364', # Hibernate after
|
||||
'94ac6d29-73ce-41a6-809f-6363ba21b47e' # Allow hybrid sleep
|
||||
)
|
||||
foreach ($settingId in $powerSettingIds) {
|
||||
$settingKey = "$powerPolicyRoot\$settingId"
|
||||
$policies += @{ Key = $settingKey; Name = 'ACSettingIndex'; Value = 0 }
|
||||
$policies += @{ Key = $settingKey; Name = 'DCSettingIndex'; Value = 0 }
|
||||
}
|
||||
|
||||
foreach ($policy in $policies) {
|
||||
if ($PSCmdlet.ShouldProcess($GpoName, "Set $($policy.Key)\$($policy.Name)=$($policy.Value)")) {
|
||||
Set-GPRegistryValue `
|
||||
-Name $GpoName `
|
||||
-Domain $domainName `
|
||||
-Server $DomainController `
|
||||
-Key $policy.Key `
|
||||
-ValueName $policy.Name `
|
||||
-Type DWord `
|
||||
-Value $policy.Value | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
$configuredPolicies = [ordered]@{}
|
||||
foreach ($policy in $policies) {
|
||||
$configured = Get-GPRegistryValue `
|
||||
-Name $GpoName `
|
||||
-Domain $domainName `
|
||||
-Server $DomainController `
|
||||
-Key $policy.Key `
|
||||
-ValueName $policy.Name
|
||||
$configuredPolicies[$policy.Name + '@' + $policy.Key] = [int]$configured.Value
|
||||
}
|
||||
$link = @(Get-GPInheritance -Target $TargetOuDn -Domain $domainName -Server $DomainController).GpoLinks |
|
||||
Where-Object DisplayName -eq $GpoName |
|
||||
Select-Object -First 1
|
||||
$linkEnabled = $link -and (
|
||||
$link.Enabled -eq $true -or
|
||||
[string]$link.Enabled -eq 'Yes')
|
||||
|
||||
[pscustomobject]@{
|
||||
GpoName = $GpoName
|
||||
GpoId = $gpo.Id
|
||||
TargetOu = $TargetOuDn
|
||||
LinkEnabled = [bool]$linkEnabled
|
||||
PolicyCount = $configuredPolicies.Count
|
||||
Policies = [pscustomobject]$configuredPolicies
|
||||
}
|
||||
@@ -8,6 +8,7 @@ param(
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$policyKey = 'HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\System'
|
||||
$policyValueName = 'DisableLockWorkstation'
|
||||
$desktopPolicyKey = 'HKCU\Software\Policies\Microsoft\Windows\Control Panel\Desktop'
|
||||
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||
@@ -71,6 +72,15 @@ if ($PSCmdlet.ShouldProcess($GpoName, 'Prevent SGU users from manually locking w
|
||||
-ValueName $policyValueName `
|
||||
-Type DWord `
|
||||
-Value 1 | Out-Null
|
||||
|
||||
Set-GPRegistryValue `
|
||||
-Name $GpoName `
|
||||
-Domain $domainName `
|
||||
-Server $DomainController `
|
||||
-Key $desktopPolicyKey `
|
||||
-ValueName 'ScreenSaveActive' `
|
||||
-Type String `
|
||||
-Value '0' | Out-Null
|
||||
}
|
||||
|
||||
$configuredValue = Get-GPRegistryValue `
|
||||
@@ -79,6 +89,12 @@ $configuredValue = Get-GPRegistryValue `
|
||||
-Server $DomainController `
|
||||
-Key $policyKey `
|
||||
-ValueName $policyValueName
|
||||
$screenSaverValue = Get-GPRegistryValue `
|
||||
-Name $GpoName `
|
||||
-Domain $domainName `
|
||||
-Server $DomainController `
|
||||
-Key $desktopPolicyKey `
|
||||
-ValueName 'ScreenSaveActive'
|
||||
$link = @(Get-GPInheritance -Target $TargetOuDn -Domain $domainName -Server $DomainController).GpoLinks |
|
||||
Where-Object DisplayName -eq $GpoName |
|
||||
Select-Object -First 1
|
||||
@@ -92,4 +108,5 @@ $linkEnabled = $link -and (
|
||||
TargetOu = $TargetOuDn
|
||||
LinkEnabled = [bool]$linkEnabled
|
||||
DisableLockWorkstation = [int]$configuredValue.Value
|
||||
ScreenSaverDisabled = [string]$screenSaverValue.Value -eq '0'
|
||||
}
|
||||
|
||||
@@ -325,15 +325,19 @@ public sealed class NtlmCredentialValidator : INtlmCredentialValidator
|
||||
response,
|
||||
identity,
|
||||
timeout.Token).ConfigureAwait(false);
|
||||
if (identity.Role == InstitutionalRole.Administrative &&
|
||||
string.Equals(
|
||||
profile?.EmployeeNumber,
|
||||
identity.NumericId,
|
||||
StringComparison.Ordinal))
|
||||
bool mayEnrichStaffProfile =
|
||||
(identity.Role == InstitutionalRole.Administrative &&
|
||||
string.Equals(
|
||||
profile?.EmployeeNumber,
|
||||
identity.NumericId,
|
||||
StringComparison.Ordinal)) ||
|
||||
(identity.Role == InstitutionalRole.Professor && profile is not null);
|
||||
if (mayEnrichStaffProfile)
|
||||
{
|
||||
profile = await TryEnrichAdministrativeProfileAsync(
|
||||
profile = await TryEnrichStaffProfileAsync(
|
||||
client,
|
||||
profile!,
|
||||
identity.Role,
|
||||
allowedHosts,
|
||||
timeout.Token,
|
||||
cancellationToken,
|
||||
@@ -394,15 +398,16 @@ public sealed class NtlmCredentialValidator : INtlmCredentialValidator
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<InstitutionalProfile> TryEnrichAdministrativeProfileAsync(
|
||||
private async Task<InstitutionalProfile> TryEnrichStaffProfileAsync(
|
||||
HttpClient client,
|
||||
InstitutionalProfile verifiedProfile,
|
||||
InstitutionalProfile baseProfile,
|
||||
InstitutionalRole role,
|
||||
HashSet<string> allowedHosts,
|
||||
CancellationToken timeoutToken,
|
||||
CancellationToken requestCancellationToken,
|
||||
Stopwatch elapsed)
|
||||
{
|
||||
InstitutionalProfile profile = verifiedProfile;
|
||||
InstitutionalProfile profile = baseProfile;
|
||||
(string Path, Func<string, InstitutionalProfile?> Parser)[] pages =
|
||||
[
|
||||
(options.AdministrativePersonalProfilePath, SguProfileParser.ParseAdministrativePersonal),
|
||||
@@ -423,7 +428,8 @@ public sealed class NtlmCredentialValidator : INtlmCredentialValidator
|
||||
catch (OperationCanceledException) when (!requestCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"SGU administrative profile enrichment reached its total timeout after {ElapsedMilliseconds} ms; preserving fields already collected.",
|
||||
"SGU optional staff profile enrichment for role {Role} reached its total timeout after {ElapsedMilliseconds} ms; preserving fields already collected.",
|
||||
role,
|
||||
elapsed.ElapsedMilliseconds);
|
||||
break;
|
||||
}
|
||||
@@ -431,7 +437,8 @@ public sealed class NtlmCredentialValidator : INtlmCredentialValidator
|
||||
{
|
||||
logger.LogWarning(
|
||||
exception,
|
||||
"An optional SGU administrative profile page failed after {ElapsedMilliseconds} ms; preserving fields already collected.",
|
||||
"An optional SGU staff profile page for role {Role} failed after {ElapsedMilliseconds} ms; preserving fields already collected.",
|
||||
role,
|
||||
elapsed.ElapsedMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,12 @@ public sealed class NtlmCredentialValidatorTests
|
||||
"123456",
|
||||
InstitutionalRole.Student);
|
||||
|
||||
private static readonly UserIdentity Professor = new(
|
||||
"DO123456",
|
||||
"DO",
|
||||
"123456",
|
||||
InstitutionalRole.Professor);
|
||||
|
||||
[Fact]
|
||||
public async Task RedirectAndSuccessWithoutChallengeNeverAuthorizes()
|
||||
{
|
||||
@@ -192,6 +198,94 @@ public sealed class NtlmCredentialValidatorTests
|
||||
Assert.Equal(3, handler.RequestPaths.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProfessorProfileIsEnrichedFromSharedStaffPages()
|
||||
{
|
||||
SequenceHandler handler = new(
|
||||
Challenge(),
|
||||
Response(HttpStatusCode.OK),
|
||||
Response(
|
||||
HttpStatusCode.OK,
|
||||
"""
|
||||
<span id="ctl00_lblNombreUsuario">MARÍA DEL CARMEN</span>
|
||||
"""),
|
||||
Response(
|
||||
HttpStatusCode.OK,
|
||||
"""
|
||||
<input id="ctl00_contenedor_txtNombre" value="MARÍA DEL CARMEN" />
|
||||
<input id="ctl00_contenedor_txtApaterno" value="DE LA FUENTE" />
|
||||
<input id="ctl00_contenedor_txtAmaterno" value="O'CONNOR" />
|
||||
"""),
|
||||
Response(
|
||||
HttpStatusCode.OK,
|
||||
"""
|
||||
<input id="ctl00_contenedor_txtCalle" value="CALLE DEL SOL" />
|
||||
<input id="ctl00_contenedor_txtNoExt" value="15" />
|
||||
<input id="ctl00_contenedor_txtCP" value="01000" />
|
||||
<select id="ctl00_contenedor_ddlEstado">
|
||||
<option selected="selected">CIUDAD DE MÉXICO</option>
|
||||
</select>
|
||||
<select id="ctl00_contenedor_ddlLocalidad">
|
||||
<option selected="selected">ÁLVARO OBREGÓN</option>
|
||||
</select>
|
||||
<select id="ctl00_contenedor_ddlColonia">
|
||||
<option selected="selected">FLORIDA</option>
|
||||
</select>
|
||||
"""));
|
||||
NtlmCredentialValidator validator = CreateValidator(handler);
|
||||
|
||||
NtlmValidationResult result = await validator.ValidateAsync(
|
||||
Professor,
|
||||
"test-password",
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(NtlmValidationStatus.Valid, result.Status);
|
||||
Assert.NotNull(result.Profile);
|
||||
Assert.Equal("María del Carmen de la Fuente O'Connor", result.Profile.DisplayName);
|
||||
Assert.Equal("María del Carmen", result.Profile.GivenName);
|
||||
Assert.Equal("de la Fuente O'Connor", result.Profile.Surname);
|
||||
Assert.Equal("Calle del Sol 15\r\nFlorida", result.Profile.StreetAddress);
|
||||
Assert.Equal("Álvaro Obregón", result.Profile.City);
|
||||
Assert.Equal("Ciudad de México", result.Profile.State);
|
||||
Assert.Equal("01000", result.Profile.PostalCode);
|
||||
Assert.Equal(
|
||||
[
|
||||
"/psulsa/",
|
||||
"/psulsa/",
|
||||
"/psulsa/menu.aspx",
|
||||
"/psulsa/gadmon/capitalhumano/datos/personales.aspx",
|
||||
"/psulsa/gadmon/capitalhumano/datos/ubicacion.aspx"
|
||||
],
|
||||
handler.RequestPaths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MissingProfessorStaffPageOrElementIdsPreservesMenuProfile()
|
||||
{
|
||||
SequenceHandler handler = new(
|
||||
Challenge(),
|
||||
Response(HttpStatusCode.OK),
|
||||
Response(
|
||||
HttpStatusCode.OK,
|
||||
"""
|
||||
<span id="ctl00_lblNombreUsuario">MIGUEL DE CERVANTES</span>
|
||||
"""),
|
||||
Response(HttpStatusCode.OK, "<html><body>Unrecognized layout</body></html>"),
|
||||
Response(HttpStatusCode.NotFound));
|
||||
NtlmCredentialValidator validator = CreateValidator(handler);
|
||||
|
||||
NtlmValidationResult result = await validator.ValidateAsync(
|
||||
Professor,
|
||||
"test-password",
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(NtlmValidationStatus.Valid, result.Status);
|
||||
Assert.NotNull(result.Profile);
|
||||
Assert.Equal("Miguel de Cervantes", result.Profile.DisplayName);
|
||||
Assert.Null(result.Profile.StreetAddress);
|
||||
Assert.Equal(5, handler.RequestPaths.Count);
|
||||
}
|
||||
|
||||
private static NtlmCredentialValidator CreateValidator(SequenceHandler handler)
|
||||
{
|
||||
BrokerOptions options = new()
|
||||
|
||||
Reference in New Issue
Block a user