Skip to content

Commit

Permalink
issue #24
Browse files Browse the repository at this point in the history
Implement deleteZone API and update unit tests
Modified the security configurations to disable session management and CSRF as it's a REST API. Updated the HostZoneServiceTest to include tests related to the new deleteZone API. Also made error handling more precise and reflective of each situation in the IntegrationTest.
  • Loading branch information
th-schwarz committed Jul 23, 2024
1 parent 42ff578 commit ee2e6c9
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 19 deletions.
14 changes: 13 additions & 1 deletion src/main/java/codes/thischwa/dyndrest/ApiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import codes.thischwa.dyndrest.config.AppConfig;
import codes.thischwa.dyndrest.model.IpSetting;
import codes.thischwa.dyndrest.model.UpdateLog;
import codes.thischwa.dyndrest.model.Zone;
import codes.thischwa.dyndrest.provider.Provider;
import codes.thischwa.dyndrest.provider.ProviderException;
import codes.thischwa.dyndrest.service.HostZoneService;
Expand All @@ -13,7 +14,6 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
Expand Down Expand Up @@ -145,6 +145,18 @@ public ResponseEntity<Object> addZone(String name, String ns) {
return ResponseEntity.ok().build();
}

@Override
public ResponseEntity<String> deleteZone(String name) {
log.debug("entered #deleteZone: name={}", name);
Zone zone = hostZoneService.getZone(name);
if (zone == null) {
log.error("Zone with name {} not found.", name);
return ResponseEntity.badRequest().body("Zone with name " + name + " not found.");
}
hostZoneService.deleteZone(zone);
return ResponseEntity.ok().build();
}

private void validateHost(String host, String apitoken) {
try {
boolean valid = hostZoneService.validate(host, apitoken);
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/codes/thischwa/dyndrest/ApiRoutes.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.net.InetAddress;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
Expand Down Expand Up @@ -142,4 +143,12 @@ ResponseEntity<Object> addZone(
examples = {@ExampleObject(value = "ns1.domain.info")}))
@PathVariable
String ns);

@DeleteMapping(value = "/zone/delete/{name}", produces = MediaType.TEXT_PLAIN_VALUE)
ResponseEntity<String> deleteZone(
@Parameter(
description = "The name of the zone, must be a valid domain name.",
content = @Content(mediaType = MediaType.TEXT_PLAIN_VALUE),
examples = {@ExampleObject(value = "domain.com")})
@PathVariable String name);
}
11 changes: 8 additions & 3 deletions src/main/java/codes/thischwa/dyndrest/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
Expand All @@ -43,8 +45,7 @@ public class SecurityConfig {
private final PasswordEncoder encoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();

private final List<String> publicPaths =
new ArrayList<>(List.of("/favicon.ico", "/error"));
private final List<String> publicPaths = new ArrayList<>(List.of("/favicon.ico", "/error"));
private final String[] loguiPaths = {"/log-ui", "/log-ui/*"};

private final boolean updateLogEnabled;
Expand Down Expand Up @@ -165,8 +166,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
req -> req.requestMatchers(buildMatchers(publicPaths.toArray(new String[0]))).permitAll());

// enable basic-auth and ROLE_USER for all other routes
// it's a rest-api, so there is no need for session handling and csrf
http.authorizeHttpRequests(req -> req.anyRequest().hasAnyRole(ROLE_USER))
.httpBasic(Customizer.withDefaults());
.httpBasic(Customizer.withDefaults())
.sessionManagement(
(session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(AbstractHttpConfigurer::disable);

return http.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ public Optional<FullHost> getHost(String fullHostStr) {
/**
* Retrieves the Zone object for the specified zone name.
*
* @param zoneStr the name of the zone
* @param name the name of the zone
* @return the Zone object for the specified zone name or null if not exists.
*/
@Nullable
public Zone getZone(String zoneStr) {
return zoneRepo.findByName(zoneStr);
public Zone getZone(String name) {
return zoneRepo.findByName(name);
}

/**
Expand Down
37 changes: 29 additions & 8 deletions src/test/java/codes/thischwa/dyndrest/IntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import static org.hamcrest.CoreMatchers.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

Expand Down Expand Up @@ -101,19 +103,38 @@ void testFavicon() throws Exception {

@Test
void testAddZone() throws Exception {
mockMvc.perform(get("/zone/add/my.info/ns.my.info").with(httpBasic("dyndns", "test123")))
.andExpect(status().isOk());
mockMvc
.perform(get("/zone/add/my.info/ns.my.info").with(httpBasic("dyndns", "test123")))
.andExpect(status().isOk());
Zone z = hostZoneService.getZone("my.info");
assertNotNull(z);

// test duplicate
mockMvc.perform(get("/zone/add/my.info/ns.my.info").with(httpBasic("dyndns", "test123")))
.andExpect(status().isConflict())
.andExpect(content().string(containsString("Zone already exists.")));
mockMvc
.perform(get("/zone/add/my.info/ns.my.info").with(httpBasic("dyndns", "test123")))
.andExpect(status().isConflict())
.andExpect(content().string(containsString("Zone already exists.")));

// test malformed
mockMvc.perform(get("/zone/add/my.info/ns,my.info").with(httpBasic("dyndns", "test123")))
.andExpect(status().isBadRequest())
.andExpect(content().string(containsString("Zone name or nameserver is malformed.")));
mockMvc
.perform(get("/zone/add/my.info/ns,my.info").with(httpBasic("dyndns", "test123")))
.andExpect(status().isBadRequest())
.andExpect(content().string(containsString("Zone name or nameserver is malformed.")));
}

@Test
void testDeleteZone() throws Exception {
mockMvc
.perform(
delete("/zone/delete/dynhost1.info").with(httpBasic("dyndns", "test123")))
.andExpect(status().isOk());
Zone z = hostZoneService.getZone("dynhost1.info");
assertNull(z);

// test unknown
mockMvc
.perform(
delete("/zone/delete/unknown.info").with(httpBasic("dyndns", "test123")))
.andExpect(status().isBadRequest());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,39 @@
import codes.thischwa.dyndrest.model.FullHost;
import codes.thischwa.dyndrest.model.Host;
import codes.thischwa.dyndrest.model.Zone;
import codes.thischwa.dyndrest.repository.UpdateLogRepo;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import codes.thischwa.dyndrest.repository.UpdateLogRepo;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
import org.springframework.jdbc.core.JdbcTemplate;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class HostZoneServiceTest extends AbstractIntegrationTest {

@Autowired private JdbcTemplate jdbcTemplate;

@Autowired private HostZoneService service;

@Autowired private UpdateLogRepo logRepo;

@Order(0)
@Test
void testGetZone() {
Zone zone = service.getZone("dynhost1.info");
assertNotNull(zone);

zone = service.getZone("unknown.info");
assertNull(zone);
}

@Order(1)
@Test
void testGetConfiguredHosts() {
Expand Down Expand Up @@ -194,6 +205,30 @@ void testAddZone() {
assertNotNull(z.getId());
assertNotNull(z.getChanged());

assertThrows(DbActionExecutionException.class, () -> service.addZone("zone2.org", "ns2.zone.org"));
assertThrows(
DbActionExecutionException.class, () -> service.addZone("zone2.org", "ns2.zone.org"));
}

@Order(12)
@Test
void testDeleteZone() {
Optional<FullHost> h = service.getHost("test0.dynhost0.info");
assertTrue(h.isPresent());
Integer id = h.get().getId();
int count =
jdbcTemplate.queryForObject(
"select count(*) from UPDATE_LOG where HOST_ID=?", Integer.class, id);
assertTrue(count > 0);
Zone zone = service.getZone("dynhost0.info");
assertNotNull(zone);

service.deleteZone(zone);
assertNull(service.getZone("dynhost0.info"));
h = service.getHost("test0.dynhost0.info");
assertFalse(h.isPresent());
count =
jdbcTemplate.queryForObject(
"select count(*) from UPDATE_LOG where HOST_ID=?", Integer.class, id);
assertEquals(0, count);
}
}

0 comments on commit ee2e6c9

Please sign in to comment.