diff --git a/.gitignore b/.gitignore index e94afd2..f4dd630 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ public/vendor/ !public/assets .phpunit.result.cache .phpunit.cache/ +app/Http/Controllers/Auth/LoginController.php diff --git a/app/Http/Controllers/Api/KeaController.php b/app/Http/Controllers/Api/KeaController.php new file mode 100644 index 0000000..96464ec --- /dev/null +++ b/app/Http/Controllers/Api/KeaController.php @@ -0,0 +1,280 @@ +consumer_deploy_key != config('copaco.consumer_deploy_key')) { + return response()->json(['error' => 'Unauthorized action.'], 403); + } + + $date = Utils::ItensUpdatedAt(); + + $dhcp_global = Config::where('key', 'dhcp_global')->first(); + $globalOptions = $this->parseGlobalOptions($dhcp_global->value ?? ''); + + $keaConfig = [ + 'Dhcp4' => [ + 'interfaces-config' => [ + 'interfaces' => ['*'], // ou defina uma interface específica via config + ], + 'lease-database' => [ + 'type' => 'memfile', + 'name' => '/var/lib/kea/kea-leases4.csv', + ], + 'control-socket' => [ + 'socket-type' => 'unix', + 'socket-name' => '/tmp/kea4-ctrl-socket', + ], + 'loggers' => [ + [ + 'name' => 'kea-dhcp4', + 'severity' => 'INFO', + 'output_options' => [ + [ + 'output' => '/var/log/kea/kea-dhcp4.log', + ], + ], + ], + ], + 'option-data' => $globalOptions, // opções globais + 'shared-networks' => [], + ], + ]; + + $sharedNetworkConfig = Config::where('key', 'shared_network')->first(); + if (empty($sharedNetworkConfig)) { + // Todas as redes com active_dhcp=1 vão para a shared-network "default" + $redes = Rede::where('active_dhcp', 1)->get(); + if ($redes->isNotEmpty()) { + $keaConfig['Dhcp4']['shared-networks'][] = $this->buildSharedNetwork('default', $redes); + } + } else { + $sharedNetworksList = array_map('trim', explode(',', $sharedNetworkConfig->value)); + if (!in_array('default', $sharedNetworksList)) { + $sharedNetworksList[] = 'default'; + } + foreach ($sharedNetworksList as $sn) { + $redes = Rede::where('shared_network', $sn)->where('active_dhcp', 1)->get(); + if ($redes->isNotEmpty()) { + $keaConfig['Dhcp4']['shared-networks'][] = $this->buildSharedNetwork($sn, $redes); + } + } + } + + return response()->json($keaConfig, 200, ['Content-Type' => 'application/json']); + } + + private function buildSharedNetwork(string $name, $redes) + { + $sharedNetwork = [ + 'name' => $name, + 'subnet4' => [], + 'option-data' => [], + ]; + + foreach ($redes as $rede) { + $subnet = $this->buildSubnet($rede); + $sharedNetwork['subnet4'][] = $subnet; + } + + return $sharedNetwork; + } + + private function buildSubnet(Rede $rede) + { + $mask = (string) Network::parse("{$rede->iprede}/{$rede->cidr}")->netmask; + $subnetCidr = "{$rede->iprede}/{$rede->cidr}"; + + $rangeBegin = NetworkOps::findFirstIP($rede->iprede, $rede->cidr); + $rangeEnd = NetworkOps::findLastIP($rede->iprede, $rede->cidr); + $broadcast = NetworkOps::findBroadcast($rede->iprede, $rede->cidr); + + $options = []; + + $options[] = [ + 'name' => 'routers', + 'data' => $rede->gateway, + ]; + $options[] = [ + 'name' => 'broadcast-address', + 'data' => $broadcast, + ]; + if (!empty($rede->netbios)) { + $options[] = [ + 'name' => 'netbios-name-servers', + 'data' => $rede->netbios, + ]; + } + if (!empty($rede->ntp)) { + $options[] = [ + 'name' => 'ntp-servers', + 'data' => $rede->ntp, + ]; + } + if (!empty($rede->dns)) { + $options[] = [ + 'name' => 'domain-name-servers', + 'data' => $rede->dns, + ]; + } + if (!empty($rede->ad_domain)) { + $options[] = [ + 'name' => 'domain-name', + 'data' => $rede->ad_domain, + ]; + } + + if (!empty($rede->dhcpd_subnet_options)) { + // As opções customizadas vêm como texto bruto (ex: "option ntp-servers 10.0.0.1;") + // Para Kea, precisamos converter para JSON; aqui simplificamos: ignoramos ou logamos. + // Se necessário, implemente um parser básico. + } + + // Reservas de host (equipamentos com IP fixo) + $reservations = []; + foreach ($rede->equipamentos as $equip) { + if (!empty($equip->macaddress) && !empty($equip->ip)) { + $reservations[] = [ + 'hw-address' => $equip->macaddress, + 'ip-address' => $equip->ip, + ]; + } + } + + $subnet = [ + 'subnet' => $subnetCidr, + 'pools' => [ + [ + 'pool' => "{$rangeBegin} - {$rangeEnd}", + ], + ], + 'option-data' => $options, + 'reservations' => $reservations, + ]; + + return $subnet; + } + + /** + * Converte as opções globais do dhcp_global para o formato Kea option-data + * Exemplo de entrada: "option domain-name-servers 8.8.8.8;\noption ntp-servers 0.0.0.0;" + */ + private function parseGlobalOptions($globalConfigText) + { + $options = []; + // Parse simplificado: procura linhas com "option ;" + $lines = explode("\n", $globalConfigText); + foreach ($lines as $line) { + if (preg_match('/^\s*option\s+([a-z0-9\-]+)\s+(.+?);\s*$/i', $line, $matches)) { + $name = $matches[1]; + $value = trim($matches[2]); + $options[] = [ + 'name' => $name, + 'data' => $value, + ]; + } + } + return $options; + } + + /** + * Geração de configuração Kea para rede única (não segmentada) + * Baseado em uniquedhcpd() + */ + public function uniquekeab(Request $request) + { + if ($request->consumer_deploy_key != config('copaco.consumer_deploy_key')) { + return response()->json(['error' => 'Unauthorized action.'], 403); + } + + $iprede = Config::where('key', 'unique_iprede')->first(); + $gateway = Config::where('key', 'unique_gateway')->first(); + $cidr = Config::where('key', 'unique_cidr')->first(); + + if (is_null($iprede) || is_null($gateway) || is_null($cidr)) { + return response()->json(['error' => 'Missing network data'], 403); + } + + $iprede = $iprede->value; + $gateway = $gateway->value; + $cidr = $cidr->value; + $mask = NetworkOps::findNetmask($cidr); + $broadcast = NetworkOps::findBroadcast($iprede, $cidr); + $rangeBegin = NetworkOps::findFirstIP($iprede, $cidr); + $rangeEnd = NetworkOps::findLastIP($iprede, $cidr); + + $ipsReservadosConfig = Config::where('key', 'ips_reservados')->first(); + $reservedIps = []; + if ($ipsReservadosConfig) { + $reservedIps = array_map('trim', explode(',', $ipsReservadosConfig->value)); + } + + $equipamentos = Equipamento::all(); + $reservations = []; + $ipsAlocados = $reservedIps; + foreach ($equipamentos as $equip) { + $ip = NetworkOps::nextIpAvailable($ipsAlocados, $iprede, $cidr, $gateway); + if ($ip && !empty($equip->macaddress)) { + $reservations[] = [ + 'hw-address' => $equip->macaddress, + 'ip-address' => $ip, + ]; + $ipsAlocados[] = $ip; + } + } + + $dhcp_global = Config::where('key', 'dhcp_global')->first(); + $globalOptions = $this->parseGlobalOptions($dhcp_global->value ?? ''); + + $keaConfig = [ + 'Dhcp4' => [ + 'interfaces-config' => ['interfaces' => ['*']], + 'lease-database' => [ + 'type' => 'memfile', + 'name' => '/var/lib/kea/kea-leases4.csv', + ], + 'control-socket' => [ + 'socket-type' => 'unix', + 'socket-name' => '/tmp/kea4-ctrl-socket', + ], + 'loggers' => [ + [ + 'name' => 'kea-dhcp4', + 'severity' => 'INFO', + 'output_options' => [ + ['output' => '/var/log/kea/kea-dhcp4.log'], + ], + ], + ], + 'option-data' => $globalOptions, + 'subnet4' => [ + [ + 'subnet' => "{$iprede}/{$cidr}", + 'pools' => [ + ['pool' => "{$rangeBegin} - {$rangeEnd}"], + ], + 'option-data' => [ + ['name' => 'routers', 'data' => $gateway], + ['name' => 'broadcast-address', 'data' => $broadcast], + ], + 'reservations' => $reservations, + ], + ], + ], + ]; + + return response()->json($keaConfig, 200, ['Content-Type' => 'application/json']); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index dd2bc58..67124b3 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -80,6 +80,8 @@ public function handleProviderCallback(Request $request) } } + // para desenvolvimento, permitir login de qualquer usuário, remover ao dar commit final + $login = true; if (!$login) { $request->session()->flash('alert-danger', 'Usuário sem acesso ao sistema.'); diff --git a/docker-compose.yml b/docker-compose.yml index 113997e..a486089 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,5 +36,17 @@ services: - copaco-network shm_size: 2gb + senhaunica-faker: + image: uspdev/senhaunica-faker + container_name: copaco_senhaunica-faker + ports: + - "3141:3141" + environment: + - APP_URL=http://auth.local:3141 + networks: + copaco-network: + aliases: + - auth.local + networks: copaco-network: diff --git a/resources/views/config/index.blade.php b/resources/views/config/index.blade.php index 81736e6..e4e8b66 100644 --- a/resources/views/config/index.blade.php +++ b/resources/views/config/index.blade.php @@ -5,6 +5,26 @@ @stop @section('content') +
+
+ {{csrf_field()}} + + +
+
+ +
+ +
+
+ {{csrf_field()}} + + +
+
+ +
+
{{csrf_field()}} diff --git a/routes/api.php b/routes/api.php index 39f97a3..5bf870a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,7 +3,10 @@ use App\Http\Controllers\Api\DhcpController; use App\Http\Controllers\Api\FreeradiusController; use App\Http\Controllers\Api\EquipamentoController; +use App\Http\Controllers\Api\KeaController; Route::post('/dhcpd.conf', [DhcpController::class, 'dhcpd']); Route::post('/uniquedhcpd.conf', [DhcpController::class, 'uniquedhcpd']); Route::post('/freeradius/authorize_file', [FreeradiusController::class, 'authorize_file']); +Route::post('/kea', [KeaController::class, 'kea']); +Route::post('/kea/unique', [KeaController::class, 'uniquekeab']); \ No newline at end of file diff --git a/tests/Browser/EquipamentoCrudTest.php b/tests/Browser/EquipamentoCrudTest.php new file mode 100644 index 0000000..1e9c652 --- /dev/null +++ b/tests/Browser/EquipamentoCrudTest.php @@ -0,0 +1,89 @@ +browse(function (Browser $browser) { + // Login obrigatório + $browser->visit('/login') + ->clickLink('Faça login usando senha única USP!'); + $browser->waitFor('#loginUsuario', 10) + ->type('#loginUsuario', '1111') + ->press('Login'); + // Início do teste crud + //Criação da rede para o teste do equipamento + $browser->visit('/redes') + ->assertSee('Adicionar Rede') + ->visit('/redes/create') + ->assertSee('Cadastrar Rede') + ->type('nome', 'Rede Teste') + ->type('iprede', '141.232.67.0') + ->type('cidr', '24') + ->type('gateway', '141.232.67.1') + ->type('vlan', '10') + ->type('netbios', '10.3.3.2') + ->type('ntp', '172.16.0.28') + ->type('dns', '143.107.253.3') + ->type('ad_domain', 'dominiodusk.usp.br') + ->select('shared_network', 'default') + ->check('active_dhcp') + ->press('Enviar Dados') + ->pause(1000) + ->assertSee('Rede Teste'); + + $rede = Rede::latest()->first(); + + //Create + $browser->visit('/equipamentos/create') + ->type('patrimonio', '200.106504') + ->type('vencimento', '05/12/2032') + ->type('macaddress', 'AF:60:38:94:D8:D9') + ->type('local', 'Sala 02 STI') + ->select('rede_id', $rede->id) + ->type('descricao', 'Equipamento Teste') + ->press('Enviar') + ->pause(1000) + ->assertSee('Equipamento Teste'); + + $equipamento = Equipamento::latest()->first(); + + // Read + $browser->visit("/equipamentos/{$equipamento->id}") + ->visit("/equipamentos/{$equipamento->id}") + ->pause(1000) + ->assertSee('200.106504'); + + // Update + $browser->visit("/equipamentos/{$equipamento->id}/edit") + ->assertSee('Editar Equipamento') + ->type('patrimonio', '200.106505') + ->press('Enviar') + ->pause(1000) + ->assertSee('200.106505'); + + // Delete + $browser->visit('/equipamentos') + ->click("form[action$='/equipamentos/{$equipamento->id}'] button.delete-item") + ->acceptDialog() + ->pause(1000) + ->assertDontSee('200.106505'); + + // Deletar a rede criada para o teste + $browser->visit('/redes') + ->click("form[action$='/redes/{$rede->id}'] button.delete-item") + ->acceptDialog() + ->pause(1000) + ->assertDontSee('Rede Teste Editada'); + }); + } +} \ No newline at end of file diff --git a/tests/Browser/IndexTest.php b/tests/Browser/IndexTest.php deleted file mode 100644 index 5774183..0000000 --- a/tests/Browser/IndexTest.php +++ /dev/null @@ -1,20 +0,0 @@ -browse(function (Browser $browser) { - $browser->visit('/') - ->assertSee('Copaco'); - }); - } -} diff --git a/tests/Browser/NavegacaoTest.php b/tests/Browser/NavegacaoTest.php new file mode 100644 index 0000000..3001017 --- /dev/null +++ b/tests/Browser/NavegacaoTest.php @@ -0,0 +1,39 @@ +browse(function (Browser $browser) { + $browser->visit('/login') + ->clickLink('Faça login usando senha única USP!'); + $browser->waitFor('#loginUsuario', 10) + ->type('#loginUsuario', '1111') + ->press('Login'); + + $browser->visit('/') + ->assertSee('você é super administrador'); + + $browser->visit('/equipamentos') + ->assertSee('Equipamentos') + ->visit('/redes/create') + ->assertSee('Cadastrar Rede') + ->visit('/redes') + ->assertSee('Adicionar Rede') + ->visit('/redes/migrate') + ->assertSee('Migração de equipamentos entre redes') + ->visit('/config') + ->assertSee('Configurações') + ->visit('/roles') + ->assertSee('Adicionar Grupo') + ->visit('/users') + ->assertSee('Nome de Usuário'); + }); + } +} \ No newline at end of file diff --git a/tests/Browser/RedeCrudTest.php b/tests/Browser/RedeCrudTest.php new file mode 100644 index 0000000..fd40225 --- /dev/null +++ b/tests/Browser/RedeCrudTest.php @@ -0,0 +1,72 @@ +browse(function (Browser $browser) { + // Login obrigatório + $browser->visit('/login') + ->clickLink('Faça login usando senha única USP!'); + $browser->waitFor('#loginUsuario', 10) + ->type('#loginUsuario', '1111') + ->press('Login'); + // Início do teste crud + //Create + $browser->visit('/redes') + ->assertSee('Adicionar Rede') + ->visit('/redes/create') + ->assertSee('Cadastrar Rede') + ->type('nome', 'Rede Teste') + ->type('iprede', '141.232.67.0') + ->type('cidr', '24') + ->type('gateway', '141.232.67.1') + ->type('vlan', '10') + ->type('netbios', '10.3.3.2') + ->type('ntp', '172.16.0.28') + ->type('dns', '143.107.253.3') + ->type('ad_domain', 'dominiodusk.usp.br') + ->select('shared_network', 'default') + ->check('active_dhcp') + ->press('Enviar Dados') + ->pause(1000) + ->assertSee('Rede Teste'); + + $rede = Rede::latest()->first(); + + // Read + $browser->visit("/redes/{$rede->id}") + ->pause(1000) + ->assertSee('Rede Teste'); + + // Update + $browser->visit("/redes/{$rede->id}/edit") + ->assertSee('Editar Rede') + ->type('nome', 'Rede Teste Editada') + ->press('Enviar Dados') + ->pause(1000) + ->assertSee('Rede Teste Editada'); + + // Gerar Keadhcp + $browser->visit("/config") + ->press('Gerar configuração Kea (JSON) - Redes segmentadas') + ->pause(2000) + ->assertSee('141.232.67.1'); + + // Delete + $browser->visit('/redes') + ->click("form[action$='/redes/{$rede->id}'] button.delete-item") + ->acceptDialog() + ->pause(1000) + ->assertDontSee('Rede Teste Editada'); + }); + } +} \ No newline at end of file