diff --git a/backend-java/src/main/java/com/tasteby/controller/RestaurantController.java b/backend-java/src/main/java/com/tasteby/controller/RestaurantController.java index 7640f25..9f3fcc4 100644 --- a/backend-java/src/main/java/com/tasteby/controller/RestaurantController.java +++ b/backend-java/src/main/java/com/tasteby/controller/RestaurantController.java @@ -90,15 +90,34 @@ public class RestaurantController { return r; } + // #332 — Restaurant 업데이트 화이트리스트 (SQL updateFields의 컬럼 가드와 1:1). + // 허용되지 않은 키는 무시(silent drop). DTO 도입은 후속 작업. + private static final java.util.Set ALLOWED_UPDATE_FIELDS = java.util.Set.of( + "name", "address", "region", "cuisine_type", "price_range", + "phone", "website", "tabling_url", "catchtable_url", + "latitude", "longitude", "google_place_id", + "business_status", "rating", "rating_count" + ); + @PutMapping("/{id}") public Map update(@PathVariable String id, @RequestBody Map body) { AuthUtil.requireAdmin(); var r = restaurantService.findById(id); if (r == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Restaurant not found"); + // #332 — 입력 body를 허용 키만 통과시킨 가변 Map으로 정규화 + Map sanitized = new java.util.LinkedHashMap<>(); + for (var e : body.entrySet()) { + if (ALLOWED_UPDATE_FIELDS.contains(e.getKey())) { + sanitized.put(e.getKey(), e.getValue()); + } else { + log.debug("Ignoring non-whitelisted update field: {}", e.getKey()); + } + } + // Re-geocode if name or address changed - String newName = (String) body.get("name"); - String newAddress = (String) body.get("address"); + String newName = (String) sanitized.get("name"); + String newAddress = (String) sanitized.get("address"); boolean nameChanged = newName != null && !newName.equals(r.getName()); boolean addressChanged = newAddress != null && !newAddress.equals(r.getAddress()); if (nameChanged || addressChanged) { @@ -106,26 +125,30 @@ public class RestaurantController { String geoAddr = newAddress != null ? newAddress : r.getAddress(); var geo = geocodingService.geocodeRestaurant(geoName, geoAddr); if (geo != null) { - body.put("latitude", geo.get("latitude")); - body.put("longitude", geo.get("longitude")); - body.put("google_place_id", geo.get("google_place_id")); + sanitized.put("latitude", geo.get("latitude")); + sanitized.put("longitude", geo.get("longitude")); + sanitized.put("google_place_id", geo.get("google_place_id")); if (geo.containsKey("formatted_address")) { - body.put("address", geo.get("formatted_address")); + sanitized.put("address", geo.get("formatted_address")); } - if (geo.containsKey("rating")) body.put("rating", geo.get("rating")); - if (geo.containsKey("rating_count")) body.put("rating_count", geo.get("rating_count")); - if (geo.containsKey("phone")) body.put("phone", geo.get("phone")); - if (geo.containsKey("business_status")) body.put("business_status", geo.get("business_status")); + if (geo.containsKey("rating")) sanitized.put("rating", geo.get("rating")); + if (geo.containsKey("rating_count")) sanitized.put("rating_count", geo.get("rating_count")); + if (geo.containsKey("phone")) sanitized.put("phone", geo.get("phone")); + if (geo.containsKey("business_status")) sanitized.put("business_status", geo.get("business_status")); - // formatted_address에서 region 파싱 (예: "대한민국 서울특별시 강남구 ..." → "한국|서울|강남구") String addr = (String) geo.get("formatted_address"); if (addr != null) { - body.put("region", GeocodingService.parseRegionFromAddress(addr)); + sanitized.put("region", GeocodingService.parseRegionFromAddress(addr)); } } } - restaurantService.update(id, body); + if (sanitized.isEmpty()) { + // 허용 키가 하나도 없으면 no-op + return Map.of("ok", true, "restaurant", r); + } + + restaurantService.update(id, sanitized); cache.flush(); var updated = restaurantService.findById(id); return Map.of("ok", true, "restaurant", updated);