Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d78a1f515 | ||
|
|
8290e347f5 | ||
|
|
4478753457 | ||
|
|
80ab99cc8a |
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 |
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -18,6 +18,9 @@ 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.
|
||||||
- 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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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 } }
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,17 +215,26 @@ 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
|
||||||
$phrase = if ($article) { "$article $Room" } else { $Room }
|
if ($Gender) {
|
||||||
return "$located $phrase."
|
$phrase = if ($article) { "$article $Room" } else { $Room }
|
||||||
|
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
|
||||||
$phrase = if ($article) { "$article $OuName" } else { $OuName }
|
if ($Gender) {
|
||||||
return "$located $phrase."
|
$phrase = if ($article) { "$article $OuName" } else { $OuName }
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_phrase=$(with_article "$location")
|
room_article=$(article_for "$location")
|
||||||
|
if [[ -n $gender ]]; then
|
||||||
|
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
|
||||||
|
|||||||
@@ -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,21 @@
|
|||||||
|
$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'
|
||||||
|
$synchronizer = Get-Content -LiteralPath $synchronizerPath -Raw
|
||||||
|
$deploy = Get-Content -LiteralPath $deployPath -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'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user