Compare commits

...
5 Commits
23 changed files with 470 additions and 38 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 80 KiB

+9
View File
@@ -62,6 +62,15 @@ plus six digits. It searches `BaseDn` by `sAMAccountName`, creates the user when
absent, moves it to the mapped OU when required, sets `userPrincipalName`, and absent, moves it to the mapped OU when required, sets `userPrincipalName`, and
passes the submitted password directly to ADSI `SetPassword`. passes the submitted password directly to ADSI `SetPassword`.
Before an account becomes usable, the broker applies explicit deny ACEs for the
Active Directory `Change Password` extended right to SELF and Everyone. Users
beneath `OU=Usuarios-SGU` therefore cannot replace the synchronized password
from Windows, Ctrl+Alt+Delete, LDAP or another client. The broker's
administrative `SetPassword` operation uses the separate `Reset Password` right
and remains able to synchronize the current institutional credential after each
successful SGU authentication. Repeated synchronizations detect the existing
ACEs and do not duplicate them.
When the authenticated HTML exposes recognized stable IDs, the broker also When the authenticated HTML exposes recognized stable IDs, the broker also
updates the applicable `displayName`, `givenName`, `sn`, `mail`, `title`, updates the applicable `displayName`, `givenName`, `sn`, `mail`, `title`,
`department`, `employeeType`, `employeeID`, `streetAddress`, `l`, `st`, and `department`, `employeeType`, `employeeID`, `streetAddress`, `l`, `st`, and
+26
View File
@@ -0,0 +1,26 @@
# SGU Credential Provider 0.6.5
Esta versión actualiza los fondos de bienvenida de Windows y Linux sin agregar
dependencias ni trabajo de composición durante el inicio de sesión.
## Identidad visual
- El fondo base azul incorpora de forma permanente el logotipo blanco oficial
de Universidad La Salle México.
- El generador utiliza directamente ese JPEG: no abre, redimensiona ni compone
un segundo archivo de logotipo cada vez que inicia una sesión.
- Se conservan las dimensiones institucionales de `1600x1000` y el área central
disponible para el saludo personalizado.
## Texto de ubicación
- La sala aparece en una primera línea de mayor tamaño.
- La dependencia aparece debajo con una tipografía menor.
- La redacción neutral usa `Acceso a...`; cuando Active Directory contiene el
sexo enriquecido, usa `Estás ubicado...` o `Estás ubicada...`.
- La misma lógica está incluida en los enrolamientos de Windows y Linux.
- Si faltan el sexo o los metadatos de ubicación, permanecen los fallbacks
neutrales documentados y el inicio de sesión no se bloquea.
Los perfiles de usuario locales y el rollback de roaming introducido en 0.6.4
no cambian en esta versión.
+16
View File
@@ -0,0 +1,16 @@
# SGU Credential Provider 0.6.6
Esta versión ajusta la composición del fondo institucional introducido en
0.6.5.
- El logotipo blanco oficial de Universidad La Salle México queda integrado en
la esquina inferior derecha.
- Su tamaño se reduce ligeramente y se conserva un margen interior respecto de
ambos bordes para acercarlo visualmente al centro.
- El fondo original de `1600x1000`, el área central del saludo y la composición
dinámica de nombre y ubicación permanecen sin cambios.
- El generador sigue sin cargar ni componer un logotipo durante el inicio de
sesión.
No se modifican la autenticación, el enrolamiento, las políticas del dominio ni
el comportamiento de perfiles locales establecido en 0.6.4.
+17
View File
@@ -0,0 +1,17 @@
# SGU Credential Provider 0.6.7
Esta versión separa la imagen de la pantalla de bloqueo de la base utilizada
para generar el escritorio personalizado.
- `darkblue-lockscreen.jpg` contiene únicamente el fondo azul institucional,
sin logotipo ni datos personales.
- `darkblue.jpg` continúa como base del escritorio de cada sesión y conserva el
logotipo oficial integrado.
- El logo de escritorio se reduce nuevamente y se desplaza ligeramente hacia
arriba, sin interferir con el saludo central.
- El instalador, la GPO clásica, `PersonalizationCSP` y los paquetes de servidor
y cliente utilizan explícitamente el archivo correspondiente a cada función.
- Linux continúa usando sólo la base de escritorio personalizada.
No se modifican la autenticación, los perfiles locales ni las reglas de
enrolamiento.
+17
View File
@@ -0,0 +1,17 @@
# SGU Credential Provider 0.6.8
Esta versión declara al Auth Broker como la única autoridad de contraseñas para
todas las cuentas administradas bajo `OU=Usuarios-SGU`.
- Cada creación, movimiento o actualización de una cuenta agrega de forma
idempotente las denegaciones SELF y Everyone para el derecho extendido de
Active Directory `Change Password`.
- El usuario no puede cambiar la contraseña desde Windows, Ctrl+Alt+Delete,
LDAP ni herramientas equivalentes.
- El broker conserva el derecho administrativo separado `Reset Password` y
continúa sincronizando la contraseña institucional exacta mediante
`SetPassword` después de una autenticación SGU válida.
- El despliegue del broker recorre todas las cuentas existentes en
`Usuarios-SGU` y corrige aquellas que todavía permiten cambios directos.
No se modifica la contraseña institucional ni se almacena una copia adicional.
+16
View File
@@ -0,0 +1,16 @@
# SGU Credential Provider 0.6.9
Esta versión completa la autoridad de contraseñas del Auth Broker en la
interfaz de Windows.
- La GPO `SGU - User session restrictions` habilita la directiva **Remove
Change Password** para todas las cuentas bajo `OU=Usuarios-SGU`.
- La opción **Cambiar una contraseña** deja de aparecer en la pantalla de
seguridad de Ctrl+Alt+Supr.
- La protección real continúa en Active Directory mediante las denegaciones
del derecho extendido `Change Password`; la GPO únicamente evita mostrar una
acción que esas cuentas no pueden completar.
El Auth Broker conserva el derecho administrativo separado `Reset Password`
para sincronizar la contraseña institucional después de una autenticación SGU
válida.
+7
View File
@@ -18,6 +18,13 @@ the same source list. RDP uses a separate allowlist. See
`Marshal.ZeroFreeGlobalAllocUnicode`; managed references are released as soon `Marshal.ZeroFreeGlobalAllocUnicode`; managed references are released as soon
as each request completes. as each request completes.
- The broker uses the exact received value for both NTLM and AD `SetPassword`. - The broker uses the exact received value for both NTLM and AD `SetPassword`.
- Managed `Usuarios-SGU` accounts deny the SELF and Everyone `Change Password`
extended right. Only an administrator or the broker through the separate
`Reset Password` right can replace the AD password.
- The `SGU - User session restrictions` GPO also hides the **Change a
password** command from the Windows Ctrl+Alt+Delete security screen for
managed users. This is a user-interface complement to the directory ACL,
not a substitute for it.
- There is no HMAC password, pepper, local password cache, Supabase password, or - There is no HMAC password, pepper, local password cache, Supabase password, or
other derived credential in this Windows path. other derived credential in this Windows path.
- Neither application logs request bodies or passwords. Deployment configuration - Neither application logs request bodies or passwords. Deployment configuration
+20 -8
View File
@@ -1,9 +1,15 @@
# Fondo de bienvenida personalizado # Fondo de bienvenida personalizado
El enrolamiento instala un fondo base azul, las familias `Indivisa Text Sans` y El enrolamiento instala dos fondos base azules. El escritorio personalizado usa
`Indivisa Text Serif`, y un generador local. La GPO de equipos `assets/branding/darkblue.jpg`, que contiene el logotipo blanco oficial de
Universidad La Salle México, pequeño y elevado en el sector inferior derecho.
La pantalla de bloqueo usa `assets/branding/darkblue-lockscreen.jpg`, que sólo
contiene el fondo azul y nunca muestra el logo. También instala las familias
`Indivisa Text Sans` y `Indivisa Text Serif`, y un generador local. El logotipo
está horneado únicamente en la base de escritorio: el generador no carga,
redimensiona ni compone otro logo durante el inicio de sesión. La GPO de equipos
`SGU - Windows client experience` ejecuta el generador al abrir cada sesión y `SGU - Windows client experience` ejecuta el generador al abrir cada sesión y
mantiene el fondo base en la pantalla de bloqueo. mantiene la base limpia independiente en la pantalla de bloqueo.
Windows no conoce todavía la identidad que se autenticará mientras muestra la Windows no conoce todavía la identidad que se autenticará mientras muestra la
pantalla previa al inicio de sesión. Por ello, esa pantalla utiliza el fondo base pantalla previa al inicio de sesión. Por ello, esa pantalla utiliza el fondo base
@@ -28,12 +34,18 @@ restaura el comando genérico de inicio.
El saludo usa `Bienvenido/ubicado` para `Male` y `Bienvenida/ubicada` para El saludo usa `Bienvenido/ubicado` para `Male` y `Bienvenida/ubicada` para
`Female`. Cuando el enriquecimiento no produjo este dato, utiliza la redacción `Female`. Cuando el enriquecimiento no produjo este dato, utiliza la redacción
neutral `Te damos la bienvenida` y `Ubicación:`. El texto secundario sigue estas neutral `Te damos la bienvenida` y `Acceso`. La ubicación se divide en dos
reglas: líneas: la sala ocupa la primera y la dependencia aparece debajo con un tamaño
menor. Sigue estas reglas:
1. Con `location` y OU: `Acceso al Aula Flexible del Centro de Experiencia Digital.` 1. Sin sexo: `Acceso a la Sala de Aplicaciones`, seguido por
2. Con sólo uno de los datos: muestra únicamente el dato disponible. `del Laboratorio de Cómputo de Ingeniería.`
3. Sin ambos: el texto adaptado `Bienvenido/Bienvenida al Laboratorio...`; sin 2. Masculino: `Estás ubicado en el Aula Flexible`, seguido por
`del Centro de Experiencia Digital.`
3. Femenino: `Estás ubicada en la Sala de Redes`, seguido por
`del Laboratorio de Cómputo de Ingeniería.`
4. Con sólo uno de los datos: muestra únicamente el dato disponible.
5. Sin ambos: el texto adaptado `Bienvenido/Bienvenida al Laboratorio...`; sin
sexo disponible, la forma neutral `Acceso al Laboratorio de Cómputo de Ingeniería.` sexo disponible, la forma neutral `Acceso al Laboratorio de Cómputo de Ingeniería.`
La ausencia de AD, de un atributo o de una tipografía nunca bloquea la sesión. La ausencia de AD, de un atributo o de una tipografía nunca bloquea la sesión.
+15 -1
View File
@@ -201,6 +201,19 @@ if ($RemoteDesktopGroupDn) {
} }
} }
$managedUsersPasswordChangeCorrected = 0
if ($PSCmdlet.ShouldProcess($usersOuDn, 'Deny direct password changes for every managed SGU user')) {
$managedUsers = @(Get-ADUser -Filter * -SearchBase $usersOuDn -SearchScope Subtree `
-Properties CannotChangePassword -Server $LdapHost -ErrorAction Stop)
foreach ($managedUser in $managedUsers) {
if (-not $managedUser.CannotChangePassword) {
Set-ADAccountControl -Identity $managedUser.DistinguishedName `
-CannotChangePassword $true -Server $LdapHost -Confirm:$false
$managedUsersPasswordChangeCorrected++
}
}
}
foreach ($file in @('SGU.AuthBroker.exe', 'SGU.AuthBroker.dll', 'appsettings.json')) { foreach ($file in @('SGU.AuthBroker.exe', 'SGU.AuthBroker.dll', 'appsettings.json')) {
if (-not (Test-Path -LiteralPath (Join-Path $PublishPath $file))) { if (-not (Test-Path -LiteralPath (Join-Path $PublishPath $file))) {
throw "PublishPath is missing $file." throw "PublishPath is missing $file."
@@ -356,4 +369,5 @@ if ($PSCmdlet.ShouldProcess($installPath, 'Install the SGU Authentication Broker
} }
Get-Service -Name $serviceName | Select-Object Name, Status, StartType, Get-Service -Name $serviceName | Select-Object Name, Status, StartType,
@{ Name = 'EventLog'; Expression = { $brokerEventLogName } } @{ Name = 'EventLog'; Expression = { $brokerEventLogName } },
@{ Name = 'ExistingUsersPasswordChangeDenied'; Expression = { $managedUsersPasswordChangeCorrected } }
+5
View File
@@ -38,6 +38,7 @@ $interactiveLogonPolicyPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\P
$accountPictureSourcePath = Join-Path $PublishPath 'branding\user.png' $accountPictureSourcePath = Join-Path $PublishPath 'branding\user.png'
$accountPictureDirectory = Join-Path $env:ProgramData 'Microsoft\User Account Pictures' $accountPictureDirectory = Join-Path $env:ProgramData 'Microsoft\User Account Pictures'
$welcomeWallpaperSourcePath = Join-Path $PublishPath 'branding\darkblue.jpg' $welcomeWallpaperSourcePath = Join-Path $PublishPath 'branding\darkblue.jpg'
$welcomeLockScreenSourcePath = Join-Path $PublishPath 'branding\darkblue-lockscreen.jpg'
$welcomeWallpaperScriptSourcePath = Join-Path $PublishPath 'branding\Set-SguWelcomeWallpaper.ps1' $welcomeWallpaperScriptSourcePath = Join-Path $PublishPath 'branding\Set-SguWelcomeWallpaper.ps1'
$welcomeFontsSourcePath = Join-Path $PublishPath 'branding\fonts' $welcomeFontsSourcePath = Join-Path $PublishPath 'branding\fonts'
$welcomeWallpaperDirectory = Join-Path $env:ProgramData 'SGU\Branding' $welcomeWallpaperDirectory = Join-Path $env:ProgramData 'SGU\Branding'
@@ -128,6 +129,7 @@ function Install-DefaultAccountPicture {
function Install-WelcomeWallpaperAssets { function Install-WelcomeWallpaperAssets {
if (-not (Test-Path -LiteralPath $welcomeWallpaperSourcePath -PathType Leaf) -or if (-not (Test-Path -LiteralPath $welcomeWallpaperSourcePath -PathType Leaf) -or
-not (Test-Path -LiteralPath $welcomeLockScreenSourcePath -PathType Leaf) -or
-not (Test-Path -LiteralPath $welcomeWallpaperScriptSourcePath -PathType Leaf)) { -not (Test-Path -LiteralPath $welcomeWallpaperScriptSourcePath -PathType Leaf)) {
return $false return $false
} }
@@ -135,6 +137,8 @@ function Install-WelcomeWallpaperAssets {
New-Item -ItemType Directory -Path $welcomeWallpaperDirectory -Force | Out-Null New-Item -ItemType Directory -Path $welcomeWallpaperDirectory -Force | Out-Null
Copy-Item -LiteralPath $welcomeWallpaperSourcePath ` Copy-Item -LiteralPath $welcomeWallpaperSourcePath `
-Destination (Join-Path $welcomeWallpaperDirectory 'darkblue.jpg') -Force -Destination (Join-Path $welcomeWallpaperDirectory 'darkblue.jpg') -Force
Copy-Item -LiteralPath $welcomeLockScreenSourcePath `
-Destination (Join-Path $welcomeWallpaperDirectory 'darkblue-lockscreen.jpg') -Force
Copy-Item -LiteralPath $welcomeWallpaperScriptSourcePath ` Copy-Item -LiteralPath $welcomeWallpaperScriptSourcePath `
-Destination (Join-Path $welcomeWallpaperDirectory 'Set-SguWelcomeWallpaper.ps1') -Force -Destination (Join-Path $welcomeWallpaperDirectory 'Set-SguWelcomeWallpaper.ps1') -Force
if (Test-Path -LiteralPath $welcomeFontsSourcePath -PathType Container) { if (Test-Path -LiteralPath $welcomeFontsSourcePath -PathType Container) {
@@ -358,5 +362,6 @@ catch {
SystemPasswordProviderPreserved = $true SystemPasswordProviderPreserved = $true
WelcomeWallpaperAssetsInstalled = WelcomeWallpaperAssetsInstalled =
(Test-Path -LiteralPath (Join-Path $welcomeWallpaperDirectory 'darkblue.jpg') -PathType Leaf) -and (Test-Path -LiteralPath (Join-Path $welcomeWallpaperDirectory 'darkblue.jpg') -PathType Leaf) -and
(Test-Path -LiteralPath (Join-Path $welcomeWallpaperDirectory 'darkblue-lockscreen.jpg') -PathType Leaf) -and
(Test-Path -LiteralPath (Join-Path $welcomeWallpaperDirectory 'Set-SguWelcomeWallpaper.ps1') -PathType Leaf) (Test-Path -LiteralPath (Join-Path $welcomeWallpaperDirectory 'Set-SguWelcomeWallpaper.ps1') -PathType Leaf)
} }
+4
View File
@@ -143,6 +143,8 @@ Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\lasalle-ma
-Destination (Join-Path $clientRoot 'payload\credential-provider\branding\user.png') -Destination (Join-Path $clientRoot 'payload\credential-provider\branding\user.png')
Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\darkblue.jpg') ` Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\darkblue.jpg') `
-Destination (Join-Path $clientRoot 'payload\credential-provider\branding\darkblue.jpg') -Destination (Join-Path $clientRoot 'payload\credential-provider\branding\darkblue.jpg')
Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\darkblue-lockscreen.jpg') `
-Destination (Join-Path $clientRoot 'payload\credential-provider\branding\darkblue-lockscreen.jpg')
Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Set-SguWelcomeWallpaper.ps1') ` Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Set-SguWelcomeWallpaper.ps1') `
-Destination (Join-Path $clientRoot 'payload\credential-provider\branding\Set-SguWelcomeWallpaper.ps1') -Destination (Join-Path $clientRoot 'payload\credential-provider\branding\Set-SguWelcomeWallpaper.ps1')
foreach ($fontName in $welcomeFontNames) { foreach ($fontName in $welcomeFontNames) {
@@ -238,6 +240,8 @@ Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Set-SguWelcomeWallpaper.ps1'
-Destination (Join-Path $serverContentTarget 'welcome-wallpaper\Set-SguWelcomeWallpaper.ps1') -Destination (Join-Path $serverContentTarget 'welcome-wallpaper\Set-SguWelcomeWallpaper.ps1')
Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\darkblue.jpg') ` Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\darkblue.jpg') `
-Destination (Join-Path $serverContentTarget 'welcome-wallpaper\darkblue.jpg') -Destination (Join-Path $serverContentTarget 'welcome-wallpaper\darkblue.jpg')
Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\darkblue-lockscreen.jpg') `
-Destination (Join-Path $serverContentTarget 'welcome-wallpaper\darkblue-lockscreen.jpg')
foreach ($fontName in $welcomeFontNames) { foreach ($fontName in $welcomeFontNames) {
Copy-RequiredFile -Source (Join-Path $repositoryRoot "assets\branding\fonts\$fontName") ` Copy-RequiredFile -Source (Join-Path $repositoryRoot "assets\branding\fonts\$fontName") `
-Destination (Join-Path $serverContentTarget "welcome-wallpaper\fonts\$fontName") -Destination (Join-Path $serverContentTarget "welcome-wallpaper\fonts\$fontName")
+6 -5
View File
@@ -5,7 +5,8 @@ param(
[string]$DomainController = $env:COMPUTERNAME, [string]$DomainController = $env:COMPUTERNAME,
[string]$EventCollectorFqdn, [string]$EventCollectorFqdn,
[string]$WelcomeWallpaperScriptPath = 'C:\ProgramData\SGU\Branding\Set-SguWelcomeWallpaper.ps1', [string]$WelcomeWallpaperScriptPath = 'C:\ProgramData\SGU\Branding\Set-SguWelcomeWallpaper.ps1',
[string]$WelcomeWallpaperBasePath = 'C:\ProgramData\SGU\Branding\darkblue.jpg' [string]$WelcomeWallpaperBasePath = 'C:\ProgramData\SGU\Branding\darkblue.jpg',
[string]$WelcomeLockScreenBasePath = 'C:\ProgramData\SGU\Branding\darkblue-lockscreen.jpg'
) )
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
@@ -113,14 +114,14 @@ $policies = @(
# The machine GPO remains the authority for every interactive session. The # The machine GPO remains the authority for every interactive session. The
# local payload lets the first desktop render without depending on SMB. # local payload lets the first desktop render without depending on SMB.
@{ Key = $runPolicyKey; Name = 'SGUWelcomeWallpaper'; Type = 'String'; Value = $welcomeWallpaperCommand }, @{ Key = $runPolicyKey; Name = 'SGUWelcomeWallpaper'; Type = 'String'; Value = $welcomeWallpaperCommand },
@{ Key = $personalizationPolicyKey; Name = 'LockScreenImage'; Type = 'String'; Value = $WelcomeWallpaperBasePath }, @{ Key = $personalizationPolicyKey; Name = 'LockScreenImage'; Type = 'String'; Value = $WelcomeLockScreenBasePath },
@{ Key = $personalizationPolicyKey; Name = 'NoChangingLockScreen'; Type = 'DWord'; Value = 1 }, @{ Key = $personalizationPolicyKey; Name = 'NoChangingLockScreen'; Type = 'DWord'; Value = 1 },
# Windows 11 Pro can ignore the legacy lock-screen policy even though it is # Windows 11 Pro can ignore the legacy lock-screen policy even though it is
# present in the registry. PersonalizationCSP provides the same local image # present in the registry. PersonalizationCSP provides the same local image
# to Pro while remaining harmless on Enterprise and LTSC editions. # to Pro while remaining harmless on Enterprise and LTSC editions.
@{ Key = $personalizationCspKey; Name = 'LockScreenImagePath'; Type = 'String'; Value = $WelcomeWallpaperBasePath }, @{ Key = $personalizationCspKey; Name = 'LockScreenImagePath'; Type = 'String'; Value = $WelcomeLockScreenBasePath },
@{ Key = $personalizationCspKey; Name = 'LockScreenImageUrl'; Type = 'String'; Value = $WelcomeWallpaperBasePath }, @{ Key = $personalizationCspKey; Name = 'LockScreenImageUrl'; Type = 'String'; Value = $WelcomeLockScreenBasePath },
@{ Key = $personalizationCspKey; Name = 'LockScreenImageStatus'; Type = 'DWord'; Value = 1 }, @{ Key = $personalizationCspKey; Name = 'LockScreenImageStatus'; Type = 'DWord'; Value = 1 },
@{ Key = $cloudContentPolicyKey; Name = 'DisableWindowsSpotlightFeatures'; Type = 'DWord'; Value = 1 }, @{ Key = $cloudContentPolicyKey; Name = 'DisableWindowsSpotlightFeatures'; Type = 'DWord'; Value = 1 },
@{ Key = $cloudContentPolicyKey; Name = 'DisableWindowsSpotlightOnLockScreen'; Type = 'DWord'; Value = 1 } @{ Key = $cloudContentPolicyKey; Name = 'DisableWindowsSpotlightOnLockScreen'; Type = 'DWord'; Value = 1 }
@@ -176,6 +177,6 @@ $linkEnabled = $link -and (
PolicyCount = $configuredPolicies.Count PolicyCount = $configuredPolicies.Count
EventCollector = $EventCollectorFqdn EventCollector = $EventCollectorFqdn
WelcomeWallpaperCommand = $welcomeWallpaperCommand WelcomeWallpaperCommand = $welcomeWallpaperCommand
LockScreenImage = $WelcomeWallpaperBasePath LockScreenImage = $WelcomeLockScreenBasePath
Policies = [pscustomobject]$configuredPolicies Policies = [pscustomobject]$configuredPolicies
} }
+19
View File
@@ -10,6 +10,7 @@ param(
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$policyKey = 'HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\System' $policyKey = 'HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\System'
$policyValueName = 'DisableLockWorkstation' $policyValueName = 'DisableLockWorkstation'
$disableChangePasswordValueName = 'DisableChangePassword'
$desktopPolicyKey = 'HKCU\Software\Policies\Microsoft\Windows\Control Panel\Desktop' $desktopPolicyKey = 'HKCU\Software\Policies\Microsoft\Windows\Control Panel\Desktop'
$themeKey = 'HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize' $themeKey = 'HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize'
@@ -76,6 +77,17 @@ if ($PSCmdlet.ShouldProcess($GpoName, 'Prevent SGU users from manually locking w
-Type DWord ` -Type DWord `
-Value 1 | Out-Null -Value 1 | Out-Null
# The directory ACL remains the security boundary. This user policy also
# removes the unusable Change a password action from Ctrl+Alt+Delete.
Set-GPRegistryValue `
-Name $GpoName `
-Domain $domainName `
-Server $DomainController `
-Key $policyKey `
-ValueName $disableChangePasswordValueName `
-Type DWord `
-Value 1 | Out-Null
Set-GPRegistryValue ` Set-GPRegistryValue `
-Name $GpoName ` -Name $GpoName `
-Domain $domainName ` -Domain $domainName `
@@ -135,6 +147,12 @@ $configuredValue = Get-GPRegistryValue `
-Server $DomainController ` -Server $DomainController `
-Key $policyKey ` -Key $policyKey `
-ValueName $policyValueName -ValueName $policyValueName
$disableChangePasswordValue = Get-GPRegistryValue `
-Name $GpoName `
-Domain $domainName `
-Server $DomainController `
-Key $policyKey `
-ValueName $disableChangePasswordValueName
$screenSaverValue = Get-GPRegistryValue ` $screenSaverValue = Get-GPRegistryValue `
-Name $GpoName ` -Name $GpoName `
-Domain $domainName ` -Domain $domainName `
@@ -175,6 +193,7 @@ if ($WallpaperPath) {
TargetOu = $TargetOuDn TargetOu = $TargetOuDn
LinkEnabled = [bool]$linkEnabled LinkEnabled = [bool]$linkEnabled
DisableLockWorkstation = [int]$configuredValue.Value DisableLockWorkstation = [int]$configuredValue.Value
DisableChangePassword = [int]$disableChangePasswordValue.Value
ScreenSaverDisabled = [string]$screenSaverValue.Value -eq '0' ScreenSaverDisabled = [string]$screenSaverValue.Value -eq '0'
DarkMode = ([int]$appsThemeValue.Value -eq 0) -and ([int]$systemThemeValue.Value -eq 0) DarkMode = ([int]$appsThemeValue.Value -eq 0) -and ([int]$systemThemeValue.Value -eq 0)
Wallpaper = $configuredWallpaper Wallpaper = $configuredWallpaper
+33 -5
View File
@@ -189,7 +189,7 @@ function Get-WelcomeLocationText {
$located = switch ($Gender) { $located = switch ($Gender) {
'Male' { 'Est{0}s ubicado en' -f [char]0x00E1 } 'Male' { 'Est{0}s ubicado en' -f [char]0x00E1 }
'Female' { 'Est{0}s ubicada en' -f [char]0x00E1 } 'Female' { 'Est{0}s ubicada en' -f [char]0x00E1 }
default { 'Ubicaci{0}n:' -f [char]0x00F3 } default { $null }
} }
$engineeringLab = switch ($Gender) { $engineeringLab = switch ($Gender) {
'Male' { 'Bienvenido al Laboratorio de C{0}mputo de Ingenier{1}a.' -f [char]0x00F3,[char]0x00ED } 'Male' { 'Bienvenido al Laboratorio de C{0}mputo de Ingenier{1}a.' -f [char]0x00F3,[char]0x00ED }
@@ -202,7 +202,10 @@ function Get-WelcomeLocationText {
if ($Room -and $OuName) { if ($Room -and $OuName) {
$roomArticle = Get-SpanishArticle -Value $Room $roomArticle = Get-SpanishArticle -Value $Room
$ouArticle = Get-SpanishArticle -Value $OuName $ouArticle = Get-SpanishArticle -Value $OuName
$roomPhrase = if ($roomArticle -eq 'el') { $roomPhrase = if ($Gender) {
if ($roomArticle) { "$roomArticle $Room" } else { $Room }
}
elseif ($roomArticle -eq 'el') {
"al $Room" "al $Room"
} }
elseif ($roomArticle) { elseif ($roomArticle) {
@@ -212,18 +215,27 @@ function Get-WelcomeLocationText {
"a $Room" "a $Room"
} }
$ouPhrase = if ($ouArticle -eq 'el') { "del $OuName" } elseif ($ouArticle) { "de $ouArticle $OuName" } else { "de $OuName" } $ouPhrase = if ($ouArticle -eq 'el') { "del $OuName" } elseif ($ouArticle) { "de $ouArticle $OuName" } else { "de $OuName" }
return "Acceso $roomPhrase $ouPhrase." $primary = if ($Gender) { "$located $roomPhrase" } else { "Acceso $roomPhrase" }
return "$primary`n$ouPhrase."
} }
if ($Room) { if ($Room) {
$article = Get-SpanishArticle -Value $Room $article = Get-SpanishArticle -Value $Room
if ($Gender) {
$phrase = if ($article) { "$article $Room" } else { $Room } $phrase = if ($article) { "$article $Room" } else { $Room }
return "$located $phrase." return "$located $phrase."
} }
$destination = if ($article -eq 'el') { "al $Room" } elseif ($article) { "a $article $Room" } else { "a $Room" }
return "Acceso $destination."
}
if ($OuName) { if ($OuName) {
$article = Get-SpanishArticle -Value $OuName $article = Get-SpanishArticle -Value $OuName
if ($Gender) {
$phrase = if ($article) { "$article $OuName" } else { $OuName } $phrase = if ($article) { "$article $OuName" } else { $OuName }
return "$located $phrase." return "$located $phrase."
} }
$destination = if ($article -eq 'el') { "al $OuName" } elseif ($article) { "a $article $OuName" } else { "a $OuName" }
return "Acceso $destination."
}
return $engineeringLab return $engineeringLab
} }
@@ -350,6 +362,9 @@ if (-not $PSBoundParameters.ContainsKey('OrganizationalUnit')) {
} }
} }
$locationText = Get-WelcomeLocationText -Room $Location -OuName $OrganizationalUnit -Gender $Gender $locationText = Get-WelcomeLocationText -Room $Location -OuName $OrganizationalUnit -Gender $Gender
$locationLines = @($locationText -split '\r?\n', 2)
$locationPrimaryText = $locationLines[0]
$locationSecondaryText = if ($locationLines.Count -gt 1) { $locationLines[1] } else { $null }
if (-not $CanvasWidth -or -not $CanvasHeight) { if (-not $CanvasWidth -or -not $CanvasHeight) {
try { try {
@@ -436,6 +451,8 @@ try {
-PreferredStyle ([Drawing.FontStyle]::Bold -bor [Drawing.FontStyle]::Italic) -PreferredStyle ([Drawing.FontStyle]::Bold -bor [Drawing.FontStyle]::Italic)
$locationFont = New-WelcomeFont -Family $sansFamily -Size ([single](27 * $scale)) ` $locationFont = New-WelcomeFont -Family $sansFamily -Size ([single](27 * $scale)) `
-PreferredStyle ([Drawing.FontStyle]::Regular) -PreferredStyle ([Drawing.FontStyle]::Regular)
$locationSecondaryFont = New-WelcomeFont -Family $sansFamily -Size ([single](20 * $scale)) `
-PreferredStyle ([Drawing.FontStyle]::Regular)
$format = [Drawing.StringFormat]::new() $format = [Drawing.StringFormat]::new()
$format.Alignment = [Drawing.StringAlignment]::Center $format.Alignment = [Drawing.StringAlignment]::Center
$format.LineAlignment = [Drawing.StringAlignment]::Center $format.LineAlignment = [Drawing.StringAlignment]::Center
@@ -447,11 +464,20 @@ try {
Draw-CenteredText -Graphics $graphics -Text $DisplayName -Font $nameFont ` Draw-CenteredText -Graphics $graphics -Text $DisplayName -Font $nameFont `
-Brush $whiteBrush -Bounds ([Drawing.RectangleF]::new($panelX + 30*$scale, $panelY + 64*$scale, $panelWidth - 60*$scale, 105*$scale)) -Format $format -Brush $whiteBrush -Bounds ([Drawing.RectangleF]::new($panelX + 30*$scale, $panelY + 64*$scale, $panelWidth - 60*$scale, 105*$scale)) -Format $format
$graphics.DrawLine($linePen, $panelX + 150*$scale, $panelY + 180*$scale, $panelX + $panelWidth - 150*$scale, $panelY + 180*$scale) $graphics.DrawLine($linePen, $panelX + 150*$scale, $panelY + 180*$scale, $panelX + $panelWidth - 150*$scale, $panelY + 180*$scale)
Draw-CenteredText -Graphics $graphics -Text $locationText -Font $locationFont ` if ($locationSecondaryText) {
-Brush $accentBrush -Bounds ([Drawing.RectangleF]::new($panelX + 60*$scale, $panelY + 190*$scale, $panelWidth - 120*$scale, 94*$scale)) -Format $format Draw-CenteredText -Graphics $graphics -Text $locationPrimaryText -Font $locationFont `
-Brush $accentBrush -Bounds ([Drawing.RectangleF]::new($panelX + 60*$scale, $panelY + 190*$scale, $panelWidth - 120*$scale, 46*$scale)) -Format $format
Draw-CenteredText -Graphics $graphics -Text $locationSecondaryText -Font $locationSecondaryFont `
-Brush $accentBrush -Bounds ([Drawing.RectangleF]::new($panelX + 60*$scale, $panelY + 235*$scale, $panelWidth - 120*$scale, 38*$scale)) -Format $format
}
else {
Draw-CenteredText -Graphics $graphics -Text $locationPrimaryText -Font $locationFont `
-Brush $accentBrush -Bounds ([Drawing.RectangleF]::new($panelX + 60*$scale, $panelY + 195*$scale, $panelWidth - 120*$scale, 78*$scale)) -Format $format
}
} }
finally { finally {
$format.Dispose() $format.Dispose()
$locationSecondaryFont.Dispose()
$locationFont.Dispose() $locationFont.Dispose()
$nameFont.Dispose() $nameFont.Dispose()
$welcomeFont.Dispose() $welcomeFont.Dispose()
@@ -519,6 +545,8 @@ Write-WelcomeLog -Message ("OK computer={0}; gender={1}; location={2}; ou={3}; o
Gender = $Gender Gender = $Gender
WelcomeHeading = $welcomeHeading WelcomeHeading = $welcomeHeading
LocationText = $locationText LocationText = $locationText
LocationPrimaryText = $locationPrimaryText
LocationSecondaryText = $locationSecondaryText
OutputPath = $OutputPath OutputPath = $OutputPath
Applied = -not $SkipApply Applied = -not $SkipApply
} }
+54 -12
View File
@@ -129,8 +129,8 @@ fi
article_for() { article_for() {
local value=${1,,} local value=${1,,}
case "$value" in case "$value" in
sala*|aula*|facultad*|unidad*|biblioteca*|oficina*|coordinación*) printf la ;; sala*|facultad*|unidad*|biblioteca*|oficina*|coordinación*) printf la ;;
laboratorio*|centro*|edificio*|campus*|taller*|auditorio*) printf el ;; aula*|laboratorio*|centro*|edificio*|campus*|taller*|auditorio*) printf el ;;
*) printf '' ;; *) printf '' ;;
esac esac
} }
@@ -159,29 +159,60 @@ case "$gender" in
;; ;;
*) *)
welcome_text='Te damos la bienvenida,' welcome_text='Te damos la bienvenida,'
located_text='Ubicación:' located_text=''
engineering_lab_text='Acceso al Laboratorio de Cómputo de Ingeniería.' engineering_lab_text='Acceso al Laboratorio de Cómputo de Ingeniería.'
;; ;;
esac esac
location_primary=''
location_secondary=''
if [[ -n $location && -n $organizational_unit ]]; then if [[ -n $location && -n $organizational_unit ]]; then
room_article=$(article_for "$location")
if [[ -n $gender ]]; then
room_phrase=$(with_article "$location") room_phrase=$(with_article "$location")
location_primary="${located_text} ${room_phrase}"
elif [[ $room_article == el ]]; then
location_primary="Acceso al ${location}"
elif [[ -n $room_article ]]; then
location_primary="Acceso a ${room_article} ${location}"
else
location_primary="Acceso a ${location}"
fi
ou_article=$(article_for "$organizational_unit") ou_article=$(article_for "$organizational_unit")
if [[ $ou_article == el ]]; then if [[ $ou_article == el ]]; then
ou_phrase="del ${organizational_unit}" location_secondary="del ${organizational_unit}."
elif [[ -n $ou_article ]]; then elif [[ -n $ou_article ]]; then
ou_phrase="de ${ou_article} ${organizational_unit}" location_secondary="de ${ou_article} ${organizational_unit}."
else else
ou_phrase="de ${organizational_unit}" location_secondary="de ${organizational_unit}."
fi fi
location_text="${located_text} ${room_phrase} ${ou_phrase}."
elif [[ -n $location ]]; then elif [[ -n $location ]]; then
location_text="${located_text} $(with_article "$location")." room_article=$(article_for "$location")
if [[ -n $gender ]]; then
location_primary="${located_text} $(with_article "$location")."
elif [[ $room_article == el ]]; then
location_primary="Acceso al ${location}."
elif [[ -n $room_article ]]; then
location_primary="Acceso a ${room_article} ${location}."
else
location_primary="Acceso a ${location}."
fi
elif [[ -n $organizational_unit ]]; then elif [[ -n $organizational_unit ]]; then
location_text="${located_text} $(with_article "$organizational_unit")." ou_article=$(article_for "$organizational_unit")
if [[ -n $gender ]]; then
location_primary="${located_text} $(with_article "$organizational_unit")."
elif [[ $ou_article == el ]]; then
location_primary="Acceso al ${organizational_unit}."
elif [[ -n $ou_article ]]; then
location_primary="Acceso a ${ou_article} ${organizational_unit}."
else
location_primary="Acceso a ${organizational_unit}."
fi
else else
location_text=$engineering_lab_text location_primary=$engineering_lab_text
fi fi
location_text=$location_primary
[[ -n $location_secondary ]] && location_text+=$'\n'"$location_secondary"
width=1600 width=1600
height=1000 height=1000
@@ -202,6 +233,7 @@ scale=$(( height * 100 / 1000 ))
welcome_size=$(( 34 * scale / 100 )) welcome_size=$(( 34 * scale / 100 ))
name_size=$(( 70 * scale / 100 )) name_size=$(( 70 * scale / 100 ))
location_size=$(( 27 * scale / 100 )) location_size=$(( 27 * scale / 100 ))
location_secondary_size=$(( 20 * scale / 100 ))
panel_width=$(( width * 76 / 100 )) panel_width=$(( width * 76 / 100 ))
panel_height=$(( 310 * scale / 100 )) panel_height=$(( 310 * scale / 100 ))
panel_x1=$(( (width - panel_width) / 2 )) panel_x1=$(( (width - panel_width) / 2 ))
@@ -232,6 +264,17 @@ if [[ $serif_font == DejaVu-Serif ]] && command -v fc-list >/dev/null 2>&1; then
fi fi
fi fi
location_annotations=(
-font "$sans_font" -weight 400 -style Normal -pointsize "$location_size"
-fill '#D3E2FF' -stroke none -annotate "+0+$(( 76 * scale / 100 ))" "$location_primary"
)
if [[ -n $location_secondary ]]; then
location_annotations+=(
-font "$sans_font" -weight 400 -style Normal -pointsize "$location_secondary_size"
-fill '#D3E2FF' -stroke none -annotate "+0+$(( 116 * scale / 100 ))" "$location_secondary"
)
fi
if ! "${image_command[@]}" "$BASE_IMAGE" \ if ! "${image_command[@]}" "$BASE_IMAGE" \
-resize "${width}x${height}^" -gravity center -extent "${width}x${height}" \ -resize "${width}x${height}^" -gravity center -extent "${width}x${height}" \
-fill 'rgba(0,13,58,0.30)' -draw "rectangle ${panel_x1},${panel_y1} ${panel_x2},${panel_y2}" \ -fill 'rgba(0,13,58,0.30)' -draw "rectangle ${panel_x1},${panel_y1} ${panel_x2},${panel_y2}" \
@@ -241,8 +284,7 @@ if ! "${image_command[@]}" "$BASE_IMAGE" \
-annotate "+0-$(( 92 * scale / 100 ))" "$welcome_text" \ -annotate "+0-$(( 92 * scale / 100 ))" "$welcome_text" \
-font "$serif_font" -weight 700 -style Italic -pointsize "$name_size" \ -font "$serif_font" -weight 700 -style Italic -pointsize "$name_size" \
-fill white -annotate "+0-$(( 22 * scale / 100 ))" "$display_name" \ -fill white -annotate "+0-$(( 22 * scale / 100 ))" "$display_name" \
-font "$sans_font" -weight 400 -style Normal -pointsize "$location_size" \ "${location_annotations[@]}" \
-fill '#D3E2FF' -annotate "+0+$(( 88 * scale / 100 ))" "$location_text" \
-quality 94 "$output_path" 2>>"$log_path"; then -quality 94 "$output_path" 2>>"$log_path"; then
fail_softly 'ImageMagick could not render the welcome wallpaper.' fail_softly 'ImageMagick could not render the welcome wallpaper.'
fi fi
+1
View File
@@ -22,4 +22,5 @@ internal static class BrokerEventIds
internal static readonly EventId DirectoryOptionalMetadataFailure = new(1301, nameof(DirectoryOptionalMetadataFailure)); internal static readonly EventId DirectoryOptionalMetadataFailure = new(1301, nameof(DirectoryOptionalMetadataFailure));
internal static readonly EventId DirectoryGroupMembershipFailure = new(1302, nameof(DirectoryGroupMembershipFailure)); internal static readonly EventId DirectoryGroupMembershipFailure = new(1302, nameof(DirectoryGroupMembershipFailure));
internal static readonly EventId DirectoryRoleGroupMembershipAdded = new(1303, nameof(DirectoryRoleGroupMembershipAdded)); internal static readonly EventId DirectoryRoleGroupMembershipAdded = new(1303, nameof(DirectoryRoleGroupMembershipAdded));
internal static readonly EventId DirectoryPasswordChangeDenied = new(1304, nameof(DirectoryPasswordChangeDenied));
} }
@@ -1,5 +1,7 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.DirectoryServices; using System.DirectoryServices;
using System.Security.AccessControl;
using System.Security.Principal;
using SGU.AuthBroker.Core.Directory; using SGU.AuthBroker.Core.Directory;
using SGU.AuthBroker.Core.Identity; using SGU.AuthBroker.Core.Identity;
using SGU.AuthBroker.Core.Profiles; using SGU.AuthBroker.Core.Profiles;
@@ -15,6 +17,12 @@ public sealed class ActiveDirectorySynchronizer(
private const int InfoAttributeMaximumLength = 1024; private const int InfoAttributeMaximumLength = 1024;
private const int AccountDisabled = 0x0002; private const int AccountDisabled = 0x0002;
private const int NormalAccount = 0x0200; private const int NormalAccount = 0x0200;
private static readonly Guid ChangePasswordExtendedRight =
new("AB721A53-1E2F-11D0-9819-00AA0040529B");
private static readonly SecurityIdentifier EveryoneSid =
new(WellKnownSidType.WorldSid, null);
private static readonly SecurityIdentifier SelfSid =
new(WellKnownSidType.SelfSid, null);
private static readonly AuthenticationTypes BindFlags = private static readonly AuthenticationTypes BindFlags =
AuthenticationTypes.Secure | AuthenticationTypes.Signing | AuthenticationTypes.Sealing; AuthenticationTypes.Secure | AuthenticationTypes.Signing | AuthenticationTypes.Sealing;
@@ -117,6 +125,12 @@ public sealed class ActiveDirectorySynchronizer(
// account without its required classification. // account without its required classification.
EnsureRoleGroupMembership(user, identity); EnsureRoleGroupMembership(user, identity);
// SGU remains the password authority. Deny the user's Change
// Password extended right before making the account usable. This
// does not deny the broker's administrative Reset Password right,
// which ADSI SetPassword uses for each successful authentication.
EnsureCannotChangePassword(user, identity.UserName);
// The exact institutional password received by the broker is passed to AD. // The exact institutional password received by the broker is passed to AD.
// It is not derived, transformed, written to disk, or included in logs. // It is not derived, transformed, written to disk, or included in logs.
user.Invoke("SetPassword", [password]); user.Invoke("SetPassword", [password]);
@@ -143,6 +157,52 @@ public sealed class ActiveDirectorySynchronizer(
} }
} }
private void EnsureCannotChangePassword(DirectoryEntry user, string institutionalUser)
{
user.Options!.SecurityMasks = SecurityMasks.Dacl;
ActiveDirectorySecurity security = user.ObjectSecurity;
if (!EnsureCannotChangePassword(security))
{
return;
}
user.ObjectSecurity = security;
user.CommitChanges();
logger.LogInformation(
BrokerEventIds.DirectoryPasswordChangeDenied,
"Denied direct password changes for managed Active Directory user {InstitutionalUser}; SGU Auth Broker remains the password authority.",
institutionalUser);
}
internal static bool EnsureCannotChangePassword(ActiveDirectorySecurity security)
{
bool changed = false;
foreach (SecurityIdentifier identity in new[] { EveryoneSid, SelfSid })
{
bool exists = security
.GetAccessRules(includeExplicit: true, includeInherited: false, typeof(SecurityIdentifier))
.OfType<ActiveDirectoryAccessRule>()
.Any(rule =>
rule.AccessControlType == AccessControlType.Deny &&
rule.IdentityReference.Equals(identity) &&
rule.ObjectType == ChangePasswordExtendedRight &&
(rule.ActiveDirectoryRights & ActiveDirectoryRights.ExtendedRight) != 0);
if (exists)
{
continue;
}
security.AddAccessRule(new ActiveDirectoryAccessRule(
identity,
ActiveDirectoryRights.ExtendedRight,
AccessControlType.Deny,
ChangePasswordExtendedRight));
changed = true;
}
return changed;
}
private static void TryApplyProfile( private static void TryApplyProfile(
DirectoryEntry user, DirectoryEntry user,
UserIdentity identity, UserIdentity identity,
@@ -0,0 +1,28 @@
$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
$synchronizerPath = Join-Path $repositoryRoot 'src\SGU.AuthBroker\Services\ActiveDirectorySynchronizer.cs'
$deployPath = Join-Path $repositoryRoot 'scripts\Deploy-AuthBroker.ps1'
$userPolicyPath = Join-Path $repositoryRoot 'scripts\Set-SguDomainUserPolicies.ps1'
$synchronizer = Get-Content -LiteralPath $synchronizerPath -Raw
$deploy = Get-Content -LiteralPath $deployPath -Raw
$userPolicy = Get-Content -LiteralPath $userPolicyPath -Raw
Describe 'SGU Auth Broker password authority' {
It 'denies the Change Password extended right to SELF and Everyone before SetPassword' {
$synchronizer | Should Match 'AB721A53-1E2F-11D0-9819-00AA0040529B'
$synchronizer | Should Match 'WellKnownSidType\.WorldSid'
$synchronizer | Should Match 'WellKnownSidType\.SelfSid'
$synchronizer | Should Match 'AccessControlType\.Deny'
$synchronizer.IndexOf('EnsureCannotChangePassword(user', [StringComparison]::Ordinal) |
Should BeLessThan $synchronizer.IndexOf('user.Invoke("SetPassword"', [StringComparison]::Ordinal)
}
It 'repairs every existing account below Usuarios-SGU during broker deployment' {
$deploy | Should Match 'Get-ADUser.*-SearchBase \$usersOuDn.*-SearchScope Subtree'
$deploy | Should Match '(?s)Set-ADAccountControl.*-CannotChangePassword \$true'
}
It 'removes Change Password from the Windows security screen for managed users' {
$userPolicy | Should Match "disableChangePasswordValueName = 'DisableChangePassword'"
$userPolicy | Should Match '(?s)-ValueName \$disableChangePasswordValueName.*-Type DWord.*-Value 1'
}
}
+11
View File
@@ -83,4 +83,15 @@ Describe 'SGU Windows client enrollment scripts' {
(Get-Content -LiteralPath $computerPolicyScriptPath -Raw) | (Get-Content -LiteralPath $computerPolicyScriptPath -Raw) |
Should Match "Name = 'HideFastUserSwitching'; Type = 'DWord'; Value = 1" Should Match "Name = 'HideFastUserSwitching'; Type = 'DWord'; Value = 1"
} }
It 'separates the clean lock screen base from the branded desktop base' {
$installerSource = Get-Content -LiteralPath $credentialProviderInstallerPath -Raw
$policySource = Get-Content -LiteralPath $computerPolicyScriptPath -Raw
$packageSource = Get-Content -LiteralPath $packageScriptPath -Raw
$installerSource | Should Match 'darkblue-lockscreen\.jpg'
$packageSource | Should Match 'darkblue-lockscreen\.jpg'
$policySource | Should Match "LockScreenImage.*WelcomeLockScreenBasePath"
$policySource | Should Match "LockScreenImagePath.*WelcomeLockScreenBasePath"
$policySource | Should Match "LockScreenImageUrl.*WelcomeLockScreenBasePath"
}
} }
@@ -1,3 +1,6 @@
using System.DirectoryServices;
using System.Security.AccessControl;
using System.Security.Principal;
using SGU.AuthBroker.Core.Profiles; using SGU.AuthBroker.Core.Profiles;
using SGU.AuthBroker.Services; using SGU.AuthBroker.Services;
using Xunit; using Xunit;
@@ -31,4 +34,29 @@ public sealed class ActiveDirectorySynchronizerTests
Assert.Null(updated); Assert.Null(updated);
} }
[Fact]
public void CannotChangePasswordRulesAreCompleteAndIdempotent()
{
ActiveDirectorySecurity security = new();
Assert.True(ActiveDirectorySynchronizer.EnsureCannotChangePassword(security));
Assert.False(ActiveDirectorySynchronizer.EnsureCannotChangePassword(security));
Guid changePasswordRight = new("AB721A53-1E2F-11D0-9819-00AA0040529B");
ActiveDirectoryAccessRule[] rules = security
.GetAccessRules(includeExplicit: true, includeInherited: false, typeof(SecurityIdentifier))
.OfType<ActiveDirectoryAccessRule>()
.Where(rule =>
rule.AccessControlType == AccessControlType.Deny &&
rule.ObjectType == changePasswordRight &&
(rule.ActiveDirectoryRights & ActiveDirectoryRights.ExtendedRight) != 0)
.ToArray();
Assert.Equal(2, rules.Length);
Assert.Contains(rules, rule => rule.IdentityReference.Equals(
new SecurityIdentifier(WellKnownSidType.WorldSid, null)));
Assert.Contains(rules, rule => rule.IdentityReference.Equals(
new SecurityIdentifier(WellKnownSidType.SelfSid, null)));
}
} }
+73 -2
View File
@@ -89,7 +89,29 @@ Describe 'Welcome wallpaper with AD metadata' {
$result = Invoke-WelcomeFixture -DirectoryGender $null ` $result = Invoke-WelcomeFixture -DirectoryGender $null `
-DirectoryLocation 'Aula Flexible' ` -DirectoryLocation 'Aula Flexible' `
-DirectoryOu 'Centro de Experiencia Digital' -DirectoryOu 'Centro de Experiencia Digital'
$result.LocationText | Should Be 'Acceso al Aula Flexible del Centro de Experiencia Digital.' $result.LocationText | Should Be "Acceso al Aula Flexible`ndel Centro de Experiencia Digital."
$result.LocationPrimaryText | Should Be 'Acceso al Aula Flexible'
$result.LocationSecondaryText | Should Be 'del Centro de Experiencia Digital.'
}
It 'uses the requested neutral, masculine and feminine location wording' {
$neutral = Invoke-WelcomeFixture -DirectoryGender $null `
-DirectoryLocation 'Sala de Aplicaciones' `
-DirectoryOu 'Laboratorio de Cómputo de Ingeniería'
$neutral.LocationPrimaryText | Should Be 'Acceso a la Sala de Aplicaciones'
$neutral.LocationSecondaryText | Should Be 'del Laboratorio de Cómputo de Ingeniería.'
$male = Invoke-WelcomeFixture -DirectoryGender 'Male' `
-DirectoryLocation 'Aula Flexible' `
-DirectoryOu 'Centro de Experiencia Digital'
$male.LocationPrimaryText | Should Be 'Estás ubicado en el Aula Flexible'
$male.LocationSecondaryText | Should Be 'del Centro de Experiencia Digital.'
$female = Invoke-WelcomeFixture -DirectoryGender 'Female' `
-DirectoryLocation 'Sala de Redes' `
-DirectoryOu 'Laboratorio de Cómputo de Ingeniería'
$female.LocationPrimaryText | Should Be 'Estás ubicada en la Sala de Redes'
$female.LocationSecondaryText | Should Be 'del Laboratorio de Cómputo de Ingeniería.'
} }
It 'keeps machine location metadata when the GPO command has no location arguments' { It 'keeps machine location metadata when the GPO command has no location arguments' {
@@ -98,6 +120,55 @@ Describe 'Welcome wallpaper with AD metadata' {
-DirectoryOu 'Laboratorio' ` -DirectoryOu 'Laboratorio' `
-MachineLocation 'Aula Flexible' ` -MachineLocation 'Aula Flexible' `
-MachineOu 'Centro de Experiencia Digital' -MachineOu 'Centro de Experiencia Digital'
$result.LocationText | Should Be 'Acceso al Aula Flexible del Centro de Experiencia Digital.' $result.LocationText | Should Be "Acceso al Aula Flexible`ndel Centro de Experiencia Digital."
}
It 'ships the logo at the lower right of the base image instead of compositing it at runtime' {
$source | Should Not Match 'lasalle-logo-blanco|DrawImage.*logo|composite.*logo'
$bitmap = [Drawing.Bitmap]::FromFile((Join-Path $repositoryRoot 'assets\branding\darkblue.jpg'))
try {
$upperLeftWhitePixels = 0
for ($x = 60; $x -lt 390; $x += 2) {
for ($y = 45; $y -lt 175; $y += 2) {
$pixel = $bitmap.GetPixel($x, $y)
if ($pixel.R -gt 220 -and $pixel.G -gt 220 -and $pixel.B -gt 220) {
$upperLeftWhitePixels++
}
}
}
$lowerRightWhitePixels = 0
for ($x = 1260; $x -lt 1510; $x += 2) {
for ($y = 740; $y -lt 860; $y += 2) {
$pixel = $bitmap.GetPixel($x, $y)
if ($pixel.R -gt 220 -and $pixel.G -gt 220 -and $pixel.B -gt 220) {
$lowerRightWhitePixels++
}
}
}
($upperLeftWhitePixels -lt 25) | Should Be $true
($lowerRightWhitePixels -gt 250) | Should Be $true
}
finally {
$bitmap.Dispose()
}
}
It 'keeps the lock screen base free of the desktop logo' {
$bitmap = [Drawing.Bitmap]::FromFile((Join-Path $repositoryRoot 'assets\branding\darkblue-lockscreen.jpg'))
try {
$whitePixels = 0
for ($x = 0; $x -lt $bitmap.Width; $x += 4) {
for ($y = 0; $y -lt $bitmap.Height; $y += 4) {
$pixel = $bitmap.GetPixel($x, $y)
if ($pixel.R -gt 220 -and $pixel.G -gt 220 -and $pixel.B -gt 220) {
$whitePixels++
}
}
}
($whitePixels -lt 10) | Should Be $true
}
finally {
$bitmap.Dispose()
}
} }
} }