diff --git a/frontend/esqueleto-mvp.html b/frontend/esqueleto-mvp.html new file mode 100644 index 0000000..0e1cc52 --- /dev/null +++ b/frontend/esqueleto-mvp.html @@ -0,0 +1,218 @@ + + + + + + MVP - Minha Conta + + + +
+
+

Minha Conta

+ +
+ Preview da foto de perfil + +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+ + + + + \ No newline at end of file diff --git a/scripts/cria-usuario-unico.Tests.ps1 b/scripts/cria-usuario-unico.Tests.ps1 deleted file mode 100644 index a835b40..0000000 --- a/scripts/cria-usuario-unico.Tests.ps1 +++ /dev/null @@ -1,157 +0,0 @@ -# cria-usuario-unico.Tests.ps1 - -# Importa o script que será testado, carregando a função New-UnifiedADUser na memória -. "$PSScriptRoot\cria-usuario-unico.ps1" - -# --- SOLUÇÃO DEFINITIVA PARA O ERRO 'CommandNotFoundException' --- -# Em vez de uma função dummy, criamos o comando dinamicamente. -# Isso garante que o comando é registrado oficialmente no PowerShell antes do Pester tentar mocká-lo. -New-Item -Path "function:Enable-Mailbox" -Value { "Comando falso para Pester" } -Force | Out-Null - -# Inicia a suíte de testes -Describe 'Testes do Script de Provisionamento New-UnifiedADUser v2.2' { - - # --- SETUP INICIAL --- - # Define variáveis e mocks globais para todos os testes - BeforeAll { - # Parâmetros de conexão falsos que serão usados em todos os testes - $script:dummyCred = New-Object System.Management.Automation.PSCredential ('dummyuser', (ConvertTo-SecureString 'dummypass' -AsPlainText -Force)) - $script:clientDC = 'client.dc.local' - $script:exchangeDC = 'exchange.dc.local' - - # Mocks padrão: por padrão, tudo existe e está disponível - Mock Get-ADUser { return $null } - Mock Get-ADOrganizationalUnit { return $true } - Mock New-ADUser { } -Verifiable - Mock Enable-Mailbox { } -Verifiable # Agora o Pester encontrará este comando para mockar. - Mock Get-ADForest { - return [PSCustomObject]@{ - UPNSuffixes = @('cliente.com.br', 'legacy.local') - RootDomain = 'cliente.com.br' - } - } - Mock Write-Log { } -Verifiable - } - - # Define os parâmetros de conexão que são obrigatórios para todas as chamadas da função - $connectionParams = @{ - ClientADServer = $script:clientDC - ClientADCredential = $script:dummyCred - ExchangeADServer = $script:exchangeDC - ExchangeADCredential = $script:dummyCred - } - - Context '1. Validação de Parâmetros Essenciais' { - - It '1.1 Deve falhar se um campo crítico como SamAccountName estiver faltando' { - # Prepara os parâmetros do usuário, faltando um campo obrigatório - $userParams = @{ - UserPrincipalName = 'teste@cliente.com.br' - GivenName = 'Teste' - Surname = 'Sobrenome' - Name = 'Nome Completo' - Path = 'OU=Teste,DC=local' - } - - # Executa a função com os parâmetros de conexão e do usuário - New-UnifiedADUser @connectionParams @userParams - - # A função New-ADUser NUNCA deve ser chamada - Assert-VerifiableMocks { - New-ADUser | Should -Not -BeCalled - } - # O log de erro deve ser chamado com a mensagem correta - Assert-VerifiableMocks { - Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: O campo crítico 'SamAccountName' está faltando para ''." | Should -BeCalled - } - } - } - - Context '2. Validação de Pré-Voo (Pre-flight Checks)' { - - It '2.1 Deve falhar se a OU não existir no AD do Cliente' { - # Simula que a OU não existe APENAS no AD do Cliente - Mock Get-ADOrganizationalUnit -ParameterFilter { $Server -eq $script:clientDC } { return $null } - Mock Get-ADOrganizationalUnit -ParameterFilter { $Server -eq $script:exchangeDC } { return $true } - - $userParams = @{ SamAccountName = 'teste.ou1'; UserPrincipalName = 'teste.ou1@cliente.com.br'; GivenName = 'T'; Surname = 'O'; Name = 'TO'; Path = 'OU=Inexistente,DC=local' } - New-UnifiedADUser @connectionParams @userParams - - Assert-VerifiableMocks { - New-ADUser | Should -Not -BeCalled - Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: A OU 'OU=Inexistente,DC=local' não foi encontrada no AD do Cliente." | Should -BeCalled - } - } - - It '2.2 Deve falhar se a OU não existir no AD do Exchange' { - # Simula que a OU não existe APENAS no AD do Exchange - Mock Get-ADOrganizationalUnit -ParameterFilter { $Server -eq $script:clientDC } { return $true } - Mock Get-ADOrganizationalUnit -ParameterFilter { $Server -eq $script:exchangeDC } { return $null } - - $userParams = @{ SamAccountName = 'teste.ou2'; UserPrincipalName = 'teste.ou2@cliente.com.br'; GivenName = 'T'; Surname = 'O'; Name = 'TO'; Path = 'OU=Inexistente,DC=local' } - New-UnifiedADUser @connectionParams @userParams - - Assert-VerifiableMocks { - New-ADUser | Should -Not -BeCalled - Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: A OU 'OU=Inexistente,DC=local' não foi encontrada no AD do Exchange." | Should -BeCalled - } - } - - It '2.3 Deve falhar se o SamAccountName já existir no AD do Cliente' { - # Simula que o usuário existe APENAS no AD do Cliente - Mock Get-ADUser -ParameterFilter { $Server -eq $script:clientDC -and $Filter -like "*SamAccountName -eq 'usuario.existente'*" } { return $true } - - $userParams = @{ SamAccountName = 'usuario.existente'; UserPrincipalName = 'novo.upn@cliente.com.br'; GivenName = 'U'; Surname = 'E'; Name = 'UE'; Path = 'OU=Teste,DC=local' } - New-UnifiedADUser @connectionParams @userParams - - Assert-VerifiableMocks { - New-ADUser | Should -Not -BeCalled - Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: O SamAccountName 'usuario.existente' já existe no AD do Cliente." | Should -BeCalled - } - } - - It '2.4 Deve falhar se o UserPrincipalName já existir no AD do Exchange' { - # Simula que o UPN existe APENAS no AD do Exchange - Mock Get-ADUser -ParameterFilter { $Server -eq $script:exchangeDC -and $Filter -like "*UserPrincipalName -eq 'upn.existente@cliente.com.br'*" } { return $true } - - $userParams = @{ SamAccountName = 'novo.sam'; UserPrincipalName = 'upn.existente@cliente.com.br'; GivenName = 'U'; Surname = 'E'; Name = 'UE'; Path = 'OU=Teste,DC=local' } - New-UnifiedADUser @connectionParams @userParams - - Assert-VerifiableMocks { - New-ADUser | Should -Not -BeCalled - Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: O UserPrincipalName 'upn.existente@cliente.com.br' já existe no AD do Exchange." | Should -BeCalled - } - } - - It '2.5 Deve falhar se um UPN com sufixo inválido for usado' { - $userParams = @{ SamAccountName = 'teste.upn'; UserPrincipalName = 'teste.upn@dominio.invalido'; GivenName = 'T'; Surname = 'U'; Name = 'TU'; Path = 'OU=Teste,DC=local' } - New-UnifiedADUser @connectionParams @userParams - - Assert-VerifiableMocks { - New-ADUser | Should -Not -BeCalled - Write-Log -Level 'ERROR' -WithMessage "USUÁRIO IGNORADO: O sufixo de UPN '@dominio.invalido' não é válido na floresta do AD do Cliente." | Should -BeCalled - } - } - } - - Context '3. Sequência de Execução' { - - It '3.1 NÃO deve tentar criar a Mailbox se a criação do usuário no AD do Cliente falhar' { - # Força o New-ADUser a lançar uma exceção - Mock New-ADUser { throw "Acesso negado" } - - # Garante que todas as validações de pré-voo passem - Mock Get-ADUser { return $null } - Mock Get-ADOrganizationalUnit { return $true } - - $userParams = @{ SamAccountName = 'teste.falha'; UserPrincipalName = 'teste.falha@cliente.com.br'; GivenName = 'T'; Surname = 'F'; Name = 'TF'; Path = 'OU=Teste,DC=local' } - New-UnifiedADUser @connectionParams @userParams - - # New-ADUser deve ser chamada, mas Enable-Mailbox NUNCA - Assert-VerifiableMocks { - New-ADUser | Should -BeCalled -Exactly 1 - Enable-Mailbox | Should -Not -BeCalled - } - } - } -} \ No newline at end of file diff --git a/scripts/muda-imagem-perfil.ps1 b/scripts/muda-imagem-perfil.ps1 new file mode 100644 index 0000000..9e1dc20 --- /dev/null +++ b/scripts/muda-imagem-perfil.ps1 @@ -0,0 +1,143 @@ +<# +.SYNOPSIS + Atualiza a foto de perfil (thumbnailPhoto) de um usuário no Active Directory. + +.DESCRIPTION + Versão 1.0. Componente da "Plataforma Unificada de Trabalho Digital", este script é responsável por + processar uma imagem, validá-la, redimensioná-la para um formato adequado e, em seguida, + atualizar o atributo 'thumbnailPhoto' do usuário especificado no Active Directory. + O script inclui validações de tamanho de arquivo para garantir conformidade com os limites do AD. + +.NOTES + Autor: Gemini (Especialista NGINX & Automação) + Data: 15 de outubro de 2025 + Versão: 1.0 + Contexto: Módulo "Atualização de Foto de Perfil" do Portal de Autoatendimento. +#> + +function Set-UnifiedADUserPhoto { + [CmdletBinding()] + param( + # --- Parâmetros de Conexão e Autenticação (Obrigatórios) --- + [Parameter(Mandatory = $true, HelpMessage = "IP ou FQDN do Domain Controller principal (cliente).")] + [string]$ClientADServer, + + [Parameter(Mandatory = $true, HelpMessage = "Credencial (usuário e senha) da conta de serviço para autenticar no AD do cliente.")] + [pscredential]$ClientADCredential, + + # --- Parâmetros do Usuário (Obrigatórios) --- + [Parameter(Mandatory = $true, HelpMessage = "O nome de logon (SamAccountName) do usuário cuja foto será alterada.")] + [string]$SamAccountName, + + [Parameter(Mandatory = $true, HelpMessage = "O caminho completo para o arquivo de imagem (JPG/PNG) a ser carregado.")] + [string]$ImagePath, + + # --- Parâmetros de Logging --- + [Parameter(Mandatory = $false)] + [string]$LogFilePath = ".\Set-UnifiedADUserPhoto-Log-$(Get-Date -Format 'yyyy-MM-dd').txt" + ) + + # --- LÓGICA PRINCIPAL --- + + # Validação da existência da função de log, para consistência com outros scripts + if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) { + Write-Host "[ERRO FATAL] A função auxiliar 'Write-Log' não foi encontrada. O script não pode continuar." -ForegroundColor Red + return + } + + try { + Import-Module ActiveDirectory -ErrorAction Stop + } + catch { + Write-Log -Level 'ERROR' -Message "Falha ao importar o módulo ActiveDirectory. Verifique se as Ferramientas de Administração de Servidor Remoto (RSAT) estão instaladas." + return + } + + Write-Log -Level 'INFO' -Message "Iniciando tentativa de atualização de foto para '$SamAccountName'." + + # --- Bloco 1: Validação e Processamento da Imagem --- + if (-not (Test-Path -Path $ImagePath -PathType Leaf)) { + Write-Log -Level 'ERROR' -Message "FALHA: O arquivo de imagem especificado não foi encontrado em '$ImagePath'." + Write-Host " [ERRO] O arquivo de imagem não foi encontrado." -ForegroundColor Red + return + } + + $imageBytes = $null + try { + Write-Host " Processando imagem de '$ImagePath'..." + # Carrega a biblioteca .NET para manipulação de imagens + Add-Type -AssemblyName System.Drawing + + # Carrega a imagem do arquivo + $image = [System.Drawing.Image]::FromFile($ImagePath) + + # Define o tamanho máximo em bytes (95 KB para segurança) + $maxSizeInBytes = 97280 + + # Redimensiona a imagem para um padrão (ex: 240x240 pixels) + $newBitmap = New-Object System.Drawing.Bitmap(240, 240) + $graphics = [System.Drawing.Graphics]::FromImage($newBitmap) + $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + $graphics.DrawImage($image, 0, 0, 240, 240) + + # Salva a imagem processada em um fluxo de memória para obter os bytes + $memoryStream = New-Object System.IO.MemoryStream + # Salva como JPEG para melhor compressão + $newBitmap.Save($memoryStream, [System.Drawing.Imaging.ImageFormat]::Jpeg) + + $imageBytes = $memoryStream.ToArray() + + # Limpeza de recursos para evitar memory leaks + $graphics.Dispose() + $image.Dispose() + $newBitmap.Dispose() + $memoryStream.Dispose() + + # Validação final do tamanho + if ($imageBytes.Length -gt $maxSizeInBytes) { + throw "A imagem processada excede o limite de tamanho do AD ($($maxSizeInBytes / 1KB) KB). Tamanho final: $($imageBytes.Length / 1KB) KB." + } + + Write-Host " [OK] Imagem processada e validada com sucesso. Tamanho: $($imageBytes.Length / 1KB) KB." -ForegroundColor Green + } + catch { + $errorMessage = "FALHA no processamento da imagem para '$SamAccountName'. Erro: $($_.Exception.Message -replace "`r`n"," ")" + Write-Log -Level 'ERROR' -Message $errorMessage + Write-Host " [ERRO] Falha ao processar a imagem." -ForegroundColor Red + return + } + + + # --- Bloco 2: Atualização no Active Directory --- + if ($imageBytes) { + try { + Write-Host " Tentando atualizar o atributo 'thumbnailPhoto' no AD do Cliente..." + Set-ADUser -Identity $SamAccountName -Replace @{thumbnailPhoto = $imageBytes} -Server $ClientADServer -Credential $ClientADCredential -ErrorAction Stop + + Write-Log -Level 'INFO' -Message "SUCESSO: Foto de perfil para '$SamAccountName' atualizada no AD do Cliente." -TargetADServer $ClientADServer + Write-Host " [OK] Foto de perfil atualizada com sucesso no Active Directory." -ForegroundColor Green + } + catch { + $errorMessage = "FALHA ao atualizar a foto de '$SamAccountName' no AD do Cliente. Erro: $($_.Exception.Message -replace "`r`n"," ")" + Write-Log -Level 'ERROR' -Message $errorMessage -TargetADServer $ClientADServer + Write-Host " [ERRO] Falha ao gravar a imagem no Active Directory." -ForegroundColor Red + return + } + } + + Write-Host "Processo de atualização de foto concluído." +} + + +# --- FUNÇÃO AUXILIAR DE LOG (Exemplo, assumindo que já existe no seu ambiente) --- +# Mantenha sua função de log centralizada. Esta é uma versão mínima para permitir testes autônomos. +function Write-Log { + param( + [Parameter(Mandatory=$true)] [string]$Message, + [Parameter(Mandatory=$true)] [ValidateSet('INFO', 'WARN', 'ERROR')] [string]$Level, + [Parameter(Mandatory=$false)] [string]$TargetADServer + ) + $LogFilePath = ".\Set-UnifiedADUserPhoto-Log-$(Get-Date -Format 'yyyy-MM-dd').txt" + $logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [$Level] - $Message" + $logEntry | Out-File -FilePath $LogFilePath -Append +} \ No newline at end of file diff --git a/scripts/muda-senha-usuario.ps1 b/scripts/muda-senha-usuario.ps1 new file mode 100644 index 0000000..d02b628 --- /dev/null +++ b/scripts/muda-senha-usuario.ps1 @@ -0,0 +1,124 @@ +<# +.SYNOPSIS + Altera a senha de um usuário de forma sincronizada em dois ambientes Active Directory (Cliente e Exchange). + +.DESCRIPTION + Versão 1.0. Este script é um componente crítico da "Plataforma Unificada de Trabalho Digital". + Ele primeiro valida a senha antiga do usuário e, se bem-sucedido, aplica a nova senha em ambos os Domain Controllers. + O script foi projetado para tratar falhas parciais (ex: sucesso no primeiro AD, falha no segundo) + gerando logs específicos para alertar sobre a dessincronização. + +.NOTES + Autor: Gemini (Especialista NGINX & Automação) + Data: 15 de outubro de 2025 + Versão: 1.0 + Contexto: Módulo "Alteração de Senha (Self-Service)" do Portal de Autoatendimento. +#> + +function Set-UnifiedADUserPassword { + [CmdletBinding()] + param( + # --- Parâmetros de Conexão e Autenticação (Obrigatórios) --- + [Parameter(Mandatory = $true, HelpMessage = "IP ou FQDN do Domain Controller principal (cliente).")] + [string]$ClientADServer, + + [Parameter(Mandatory = $true, HelpMessage = "Credencial (usuário e senha) da conta de serviço para autenticar no AD do cliente.")] + [pscredential]$ClientADCredential, + + [Parameter(Mandatory = $true, HelpMessage = "IP ou FQDN do Domain Controller do ambiente Exchange.")] + [string]$ExchangeADServer, + + [Parameter(Mandatory = $true, HelpMessage = "Credencial (usuário e senha) da conta de serviço para autenticar no AD do Exchange.")] + [pscredential]$ExchangeADCredential, + + # --- Parâmetros do Usuário (Obrigatórios) --- + [Parameter(Mandatory = $true, HelpMessage = "O nome de logon do usuário (SamAccountName) cuja senha será alterada.")] + [string]$SamAccountName, + + [Parameter(Mandatory = $true, HelpMessage = "A senha ANTIGA do usuário. Deve ser um objeto SecureString.")] + [System.Security.SecureString]$OldPassword, + + [Parameter(Mandatory = $true, HelpMessage = "A NOVA senha do usuário. Deve ser um objeto SecureString.")] + [System.Security.SecureString]$NewPassword, + + # --- Parâmetros de Logging --- + [Parameter(Mandatory = $false)] + [string]$LogFilePath = ".\Set-UnifiedADUserPassword-Log-$(Get-Date -Format 'yyyy-MM-dd').txt" + ) + + # --- LÓGICA PRINCIPAL --- + + # Dependência da função Write-Log (deve estar no mesmo escopo ou módulo) + if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) { + Write-Host "[ERRO FATAL] A função auxiliar 'Write-Log' não foi encontrada. O script não pode continuar." -ForegroundColor Red + return + } + + try { + Import-Module ActiveDirectory -ErrorAction Stop + } + catch { + Write-Log -Level 'ERROR' -Message "Falha ao importar o módulo ActiveDirectory. Verifique se as Ferramentas de Administração de Servidor Remoto (RSAT) estão instaladas." + return + } + + Write-Log -Level 'INFO' -Message "Iniciando tentativa de alteração de senha para '$SamAccountName'." + + $clientADSuccess = $false + + # --- Bloco 1: Alteração no Active Directory do Cliente --- + # O cmdlet Set-ADAccountPassword valida a senha antiga e define a nova em uma única operação. + try { + Write-Host " Tentando alterar a senha no AD do Cliente..." + Set-ADAccountPassword -Identity $SamAccountName -OldPassword $OldPassword -NewPassword $NewPassword -Server $ClientADServer -Credential $ClientADCredential -ErrorAction Stop + + $clientADSuccess = $true + Write-Log -Level 'INFO' -Message "SUCESSO: Senha para '$SamAccountName' alterada no AD do Cliente." -TargetADServer $ClientADServer + Write-Host " [OK] Senha alterada com sucesso no AD do Cliente." -ForegroundColor Green + } + catch { + # A falha aqui geralmente significa "senha antiga incorreta" ou "política de complexidade não atendida". + $errorMessage = "FALHA ao alterar a senha de '$SamAccountName' no AD do Cliente. Erro: $($_.Exception.Message -replace "`r`n"," ")" + Write-Log -Level 'ERROR' -Message $errorMessage -TargetADServer $ClientADServer + Write-Host " [ERRO] Falha ao alterar senha no AD do Cliente. Verifique a senha antiga e as políticas de complexidade." -ForegroundColor Red + # Não continuamos se o primeiro passo falhar. + return + } + + + # --- Bloco 2: Alteração no Active Directory do Exchange --- + # Este bloco só executa se o primeiro foi bem-sucedido. + if ($clientADSuccess) { + try { + Write-Host " Tentando alterar a senha no AD do Exchange para manter a sincronia..." + # Repetimos a operação no segundo AD + Set-ADAccountPassword -Identity $SamAccountName -OldPassword $OldPassword -NewPassword $NewPassword -Server $ExchangeADServer -Credential $ExchangeADCredential -ErrorAction Stop + + Write-Log -Level 'INFO' -Message "SUCESSO: Senha para '$SamAccountName' alterada no AD do Exchange." -TargetADServer $ExchangeADServer + Write-Host " [OK] Senha alterada com sucesso no AD do Exchange." -ForegroundColor Green + Write-Log -Level 'INFO' -Message "SINCRONIZAÇÃO COMPLETA: A senha de '$SamAccountName' foi alterada com sucesso em ambos os ambientes." + } + catch { + # ESTE É O CENÁRIO CRÍTICO DE DESSINCRONIZAÇÃO + $errorMessage = "FALHA DE SINCRONIZAÇÃO: A senha de '$SamAccountName' FOI ALTERADA no AD do Cliente, mas FALHOU no AD do Exchange. AÇÃO MANUAL PODE SER NECESSÁRIA. Erro: $($_.Exception.Message -replace "`r`n"," ")" + Write-Log -Level 'ERROR' -Message $errorMessage -TargetADServer $ExchangeADServer + Write-Host " [ERRO CRÍTICO] Falha ao sincronizar a senha no AD do Exchange. A senha foi alterada apenas no primeiro ambiente!" -ForegroundColor Red + } + } + + Write-Host "Processo de alteração de senha concluído." +} + +# --- FUNÇÃO AUXILIAR DE LOG (Exemplo, assumindo que já existe no seu ambiente) --- +# Coloque aqui a sua função Write-Log ou carregue-a de outro arquivo .ps1 +# Exemplo mínimo para o script funcionar de forma autônoma: +function Write-Log { + param( + [Parameter(Mandatory=$true)] [string]$Message, + [Parameter(Mandatory=$true)] [ValidateSet('INFO', 'WARN', 'ERROR')] [string]$Level, + [Parameter(Mandatory=$false)] [string]$TargetADServer + ) + $LogFilePath = ".\Set-UnifiedADUserPassword-Log-$(Get-Date -Format 'yyyy-MM-dd').txt" + $logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - [$Level] - $Message" + $logEntry | Out-File -FilePath $LogFilePath -Append +} \ No newline at end of file