Skip to content

Commit c1043cb

Browse files
Broken Object Property Level Authorization Demo
1 parent 42ee72c commit c1043cb

File tree

7 files changed

+321
-4
lines changed

7 files changed

+321
-4
lines changed

‎online-store.core/src/main/java/com/itbulls/learnit/onlinestore/core/facades/PurchaseFacade.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@ public interface PurchaseFacade {
1616

1717
void markFulfilmentStageForPurchaseIdAsCompleted(Integer purchaseId);
1818

19+
Purchase getPurchaseById(Integer purchaseId);
20+
21+
void updatePurchase(Purchase purchase);
22+
1923
}

‎online-store.core/src/main/java/com/itbulls/learnit/onlinestore/core/facades/impl/DefaultPurchaseFacade.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public void markFulfilmentStageForPurchaseIdAsCompleted(Integer purchaseId) {
5959
purchaseStatus.setId(newPurchaseStatusId);
6060
purchase.setPurchaseStatus(purchaseStatus);
6161

62-
purchaseDao.updatePurchase(purchaseConverter.convertPurchaseToPurchaseDto(purchase));
62+
purchaseDao.updatePurchaseStatus(purchaseConverter.convertPurchaseToPurchaseDto(purchase));
6363

6464
if (LAST_STATUS_OF_ORDER_FULFILMENT_ID.equals(newPurchaseStatusId)
6565
&& purchase.getCustomer().getReferrerUser() != null) {
@@ -70,4 +70,14 @@ public void markFulfilmentStageForPurchaseIdAsCompleted(Integer purchaseId) {
7070
}
7171
}
7272

73+
@Override
74+
public Purchase getPurchaseById(Integer purchaseId) {
75+
return purchaseConverter.convertPurchaseDtoToPurchase(purchaseDao.getPurchaseById(purchaseId));
76+
}
77+
78+
@Override
79+
public void updatePurchase(Purchase purchase) {
80+
purchaseDao.updatePurchase(purchaseConverter.convertPurchaseToPurchaseDto(purchase));
81+
}
82+
7383
}

‎online-store.persistence/src/main/java/com/itbulls/learnit/onlinestore/persistence/dao/PurchaseDao.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public interface PurchaseDao {
1717

1818
PurchaseDto getPurchaseById(Integer purchaseId);
1919

20-
void updatePurchase(PurchaseDto convertPurchaseToPurchaseDto);
20+
void updatePurchaseStatus(PurchaseDto purchaseDto);
21+
22+
void updatePurchase(PurchaseDto purchaseDto);
2123

2224
}

‎online-store.persistence/src/main/java/com/itbulls/learnit/onlinestore/persistence/dao/impl/MySqlJdbcPurchaseDao.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public void savePurchase(PurchaseDto purchase) {
3131
try (var conn = DBUtils.getConnection();
3232
var ps = conn.prepareStatement("INSERT INTO purchase (fk_purchase_user, fk_purchase_purchase_status) VALUES (?, ?);", Statement.RETURN_GENERATED_KEYS);
3333
var psPurchaseProduct = conn.prepareStatement("INSERT INTO purchase_product (purchase_id, product_id) VALUES (?, ?)")) {
34-
System.out.println("=============== " + purchase.getPurchaseStatusDto().getId());
3534
ps.setInt(1, purchase.getUserDto().getId());
3635
ps.setInt(2, purchase.getPurchaseStatusDto().getId());
3736
ps.executeUpdate();
@@ -162,7 +161,7 @@ public PurchaseDto getPurchaseById(Integer purchaseId) {
162161
}
163162

164163
@Override
165-
public void updatePurchase(PurchaseDto purchase) {
164+
public void updatePurchaseStatus(PurchaseDto purchase) {
166165
try (var conn = DBUtils.getConnection();
167166
var ps = conn.prepareStatement("UPDATE `learn_it_db`.`purchase` SET `fk_purchase_purchase_status` = ? WHERE (`id` = ?);")) {
168167
ps.setInt(1, purchase.getPurchaseStatusDto().getId());
@@ -173,4 +172,35 @@ public void updatePurchase(PurchaseDto purchase) {
173172
}
174173
}
175174

175+
@Override
176+
public void updatePurchase(PurchaseDto purchase) {
177+
try (var conn = DBUtils.getConnection()) {
178+
// Update the purchase status
179+
try (var ps = conn.prepareStatement("UPDATE purchase SET fk_purchase_purchase_status = ? WHERE id = ?;")) {
180+
ps.setInt(1, purchase.getPurchaseStatusDto().getId());
181+
ps.setInt(2, purchase.getId());
182+
ps.executeUpdate();
183+
}
184+
185+
// Clear existing products for the purchase
186+
try (var psDelete = conn.prepareStatement("DELETE FROM purchase_product WHERE purchase_id = ?")) {
187+
psDelete.setInt(1, purchase.getId());
188+
psDelete.executeUpdate();
189+
}
190+
191+
// Insert updated products for the purchase
192+
try (var psInsert = conn.prepareStatement("INSERT INTO purchase_product (purchase_id, product_id) VALUES (?, ?)")) {
193+
for (ProductDto product : purchase.getProductDtos()) {
194+
psInsert.setInt(1, purchase.getId());
195+
psInsert.setInt(2, product.getId());
196+
psInsert.addBatch();
197+
}
198+
psInsert.executeBatch();
199+
}
200+
201+
} catch (SQLException e) {
202+
e.printStackTrace();
203+
}
204+
}
205+
176206
}

‎online-store.persistence/src/main/java/com/itbulls/learnit/onlinestore/persistence/enteties/impl/DefaultUser.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.itbulls.learnit.onlinestore.persistence.enteties.impl;
22

3+
import java.util.Objects;
4+
35
import com.itbulls.learnit.onlinestore.persistence.enteties.User;
46
import com.itbulls.learnit.onlinestore.persistence.utils.validation.Validate;
57

@@ -176,5 +178,28 @@ public void setReferrerUser(User referrerUser) {
176178
public User getReferrerUser() {
177179
return this.referrerUser;
178180
}
181+
182+
@Override
183+
public int hashCode() {
184+
return Objects.hash(creditCard, email, firstName, id, lastName, money, partnerCode, password, referrerUser,
185+
roleName);
186+
}
187+
188+
@Override
189+
public boolean equals(Object obj) {
190+
if (this == obj)
191+
return true;
192+
if (obj == null)
193+
return false;
194+
if (getClass() != obj.getClass())
195+
return false;
196+
DefaultUser other = (DefaultUser) obj;
197+
return Objects.equals(creditCard, other.creditCard) && Objects.equals(email, other.email)
198+
&& Objects.equals(firstName, other.firstName) && id == other.id
199+
&& Objects.equals(lastName, other.lastName)
200+
&& Double.doubleToLongBits(money) == Double.doubleToLongBits(other.money)
201+
&& Objects.equals(partnerCode, other.partnerCode) && Objects.equals(password, other.password)
202+
&& Objects.equals(referrerUser, other.referrerUser) && Objects.equals(roleName, other.roleName);
203+
}
179204

180205
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.itbulls.learnit.onlinestore.web.owasp.bopla;
2+
3+
import java.io.BufferedReader;
4+
import java.io.IOException;
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
8+
import com.google.gson.Gson;
9+
import com.itbulls.learnit.onlinestore.persistence.dao.PurchaseDao;
10+
import com.itbulls.learnit.onlinestore.persistence.dao.impl.MySqlJdbcPurchaseDao;
11+
import com.itbulls.learnit.onlinestore.persistence.dto.ProductDto;
12+
import com.itbulls.learnit.onlinestore.persistence.dto.PurchaseDto;
13+
import com.itbulls.learnit.onlinestore.persistence.dto.PurchaseStatusDto;
14+
import com.itbulls.learnit.onlinestore.persistence.dto.UserDto;
15+
16+
import jakarta.servlet.ServletException;
17+
import jakarta.servlet.annotation.WebServlet;
18+
import jakarta.servlet.http.HttpServlet;
19+
import jakarta.servlet.http.HttpServletRequest;
20+
import jakarta.servlet.http.HttpServletResponse;
21+
22+
@WebServlet("/bopla/problem/purchases/*")
23+
public class ProblemStatementPurchaseServlet extends HttpServlet {
24+
25+
private PurchaseDao purchaseDao = new MySqlJdbcPurchaseDao(); // Assume this DAO handles data operations
26+
private Gson gson = new Gson(); // For JSON conversion
27+
28+
@Override
29+
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
30+
Integer purchaseId = Integer.parseInt(request.getPathInfo().substring(1)); // Extract purchaseId
31+
PurchaseDto purchaseDto = purchaseDao.getPurchaseById(purchaseId); // Retrieve DTO directly from DAO
32+
response.setContentType("application/json");
33+
response.getWriter().write(gson.toJson(purchaseDto)); // Exposes all fields, including sensitive data
34+
}
35+
36+
@Override
37+
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
38+
Integer purchaseId = Integer.parseInt(request.getPathInfo().substring(1)); // Extract purchaseId
39+
BufferedReader reader = request.getReader();
40+
PurchaseDto updatedPurchaseDto = gson.fromJson(reader, PurchaseDto.class); // Directly bind request body to DTO
41+
42+
PurchaseDto existingPurchaseDto = purchaseDao.getPurchaseById(purchaseId); // Retrieve the existing purchase from the database
43+
copyProperties(updatedPurchaseDto, existingPurchaseDto); // Vulnerable to mass assignment
44+
purchaseDao.updatePurchase(existingPurchaseDto); // Save the updated purchase
45+
46+
response.setContentType("application/json");
47+
response.getWriter().write(gson.toJson(existingPurchaseDto)); // Expose updated purchase directly
48+
}
49+
50+
51+
// === Helper Methods, decided to keep them in one class for the sake of the demo
52+
public void copyProperties(PurchaseDto source, PurchaseDto target) {
53+
// Copy basic fields
54+
if (source.getId() != null) {
55+
target.setId(source.getId());
56+
}
57+
58+
// Copy nested UserDto
59+
if (source.getUserDto() != null) {
60+
target.setUserDto(copyUserDto(source.getUserDto(), target.getUserDto()));
61+
}
62+
63+
// Copy nested ProductDtos
64+
if (source.getProductDtos() != null) {
65+
target.setProductDtos(copyProductDtos(source.getProductDtos(), target.getProductDtos()));
66+
}
67+
68+
// Copy nested PurchaseStatusDto
69+
if (source.getPurchaseStatusDto() != null) {
70+
target.setPurchaseStatusDto(copyPurchaseStatusDto(source.getPurchaseStatusDto(), target.getPurchaseStatusDto()));
71+
}
72+
}
73+
74+
// Helper methods for copying nested objects
75+
private UserDto copyUserDto(UserDto source, UserDto target) {
76+
if (target == null) {
77+
target = new UserDto();
78+
}
79+
target.setId(source.getId()); // Ensure you handle ID carefully
80+
// Add other fields if necessary, avoid sensitive info exposure
81+
return target;
82+
}
83+
84+
private List<ProductDto> copyProductDtos(List<ProductDto> source, List<ProductDto> target) {
85+
if (target == null) {
86+
target = new ArrayList<>();
87+
} else {
88+
target.clear(); // Clear the target list before copying
89+
}
90+
for (ProductDto sourceProduct : source) {
91+
ProductDto targetProduct = new ProductDto();
92+
targetProduct.setId(sourceProduct.getId()); // Ensure you handle ID carefully
93+
// Add other fields if necessary, avoid sensitive info exposure
94+
target.add(targetProduct);
95+
}
96+
return target;
97+
}
98+
99+
100+
private PurchaseStatusDto copyPurchaseStatusDto(PurchaseStatusDto source, PurchaseStatusDto target) {
101+
if (target == null) {
102+
target = new PurchaseStatusDto();
103+
}
104+
target.setId(source.getId()); // Ensure you handle ID carefully
105+
// Add other fields if necessary, avoid sensitive info exposure
106+
return target;
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.itbulls.learnit.onlinestore.web.owasp.bopla;
2+
3+
import static com.itbulls.learnit.onlinestore.persistence.dto.RoleDto.ADMIN_ROLE_NAME;
4+
5+
import java.io.BufferedReader;
6+
import java.io.IOException;
7+
import java.util.Base64;
8+
9+
import com.google.gson.Gson;
10+
import com.itbulls.learnit.onlinestore.core.facades.PurchaseFacade;
11+
import com.itbulls.learnit.onlinestore.core.facades.UserFacade;
12+
import com.itbulls.learnit.onlinestore.core.facades.impl.DefaultPurchaseFacade;
13+
import com.itbulls.learnit.onlinestore.core.facades.impl.DefaultUserFacade;
14+
import com.itbulls.learnit.onlinestore.persistence.dto.PurchaseDto;
15+
import com.itbulls.learnit.onlinestore.persistence.dto.converters.PurchaseDtoToPurchaseConverter;
16+
import com.itbulls.learnit.onlinestore.persistence.enteties.Purchase;
17+
import com.itbulls.learnit.onlinestore.persistence.enteties.User;
18+
19+
import jakarta.servlet.ServletException;
20+
import jakarta.servlet.annotation.WebServlet;
21+
import jakarta.servlet.http.HttpServlet;
22+
import jakarta.servlet.http.HttpServletRequest;
23+
import jakarta.servlet.http.HttpServletResponse;
24+
25+
@WebServlet("/bopla/solution/purchases/*")
26+
public class SolutionPurchaseServlet extends HttpServlet {
27+
28+
private PurchaseFacade purchaseFacade = DefaultPurchaseFacade.getInstance(); // Assume this facade handles data operations
29+
private UserFacade userFacade = DefaultUserFacade.getInstance(); // Assume this facade handles user operations
30+
private Gson gson = new Gson(); // For JSON conversion
31+
private PurchaseDtoToPurchaseConverter purchaseConverter = new PurchaseDtoToPurchaseConverter();
32+
33+
@Override
34+
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
35+
try {
36+
Integer purchaseId = Integer.parseInt(request.getPathInfo().substring(1)); // Extract purchaseId from URL
37+
38+
// Extract user from Basic Auth
39+
User authenticatedUser = extractUserFromBasicAuth(request, response);
40+
if (authenticatedUser == null) {
41+
return; // If user is null, it means authentication failed
42+
}
43+
Purchase purchase = purchaseFacade.getPurchaseById(purchaseId); // Facade retrieves DTO from DAO, and converts it to business object
44+
45+
// Authorization check
46+
if (!purchase.getCustomer().equals(authenticatedUser) && !authenticatedUser.getRoleName().equals(ADMIN_ROLE_NAME)) {
47+
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
48+
response.getWriter().write("Access is denied.");
49+
return;
50+
}
51+
52+
// Optionally apply any additional business logic to the Purchase object
53+
// For example, let's set Credit Card Number and Customer information to null before returning the purchase information back to the client
54+
purchase.setCreditCardNumber("");
55+
purchase.setCustomer(null);
56+
response.setContentType("application/json");
57+
response.getWriter().write(gson.toJson(purchase)); // Expose safe, business-relevant object
58+
} catch (Exception e) {
59+
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
60+
response.getWriter().write("An error occurred.");
61+
}
62+
}
63+
64+
65+
@Override
66+
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
67+
try {
68+
// Extract user from Basic Auth
69+
User authenticatedUser = extractUserFromBasicAuth(request, response);
70+
if (authenticatedUser == null) {
71+
return; // If user is null, it means authentication failed
72+
}
73+
74+
Integer purchaseId = Integer.parseInt(request.getPathInfo().substring(1)); // Extract purchaseId
75+
BufferedReader reader = request.getReader();
76+
PurchaseDto updatedPurchaseDto = gson.fromJson(reader, PurchaseDto.class); // Directly bind request body to DTO
77+
Purchase updatedPurchase = purchaseConverter.convertPurchaseDtoToPurchase(updatedPurchaseDto);
78+
79+
// Purchase Facade retrieves the purchase DTO from the database and converts it to the business object
80+
Purchase originalPurchase = purchaseFacade.getPurchaseById(purchaseId);
81+
82+
// Authorization check
83+
if (!originalPurchase.getCustomer().equals(authenticatedUser) && !authenticatedUser.getRoleName().equals(ADMIN_ROLE_NAME)) {
84+
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
85+
response.getWriter().write("Access is denied.");
86+
return;
87+
}
88+
89+
// Apply ONLY allowed updates - update list of products
90+
originalPurchase.setProducts(updatedPurchase.getProducts());
91+
// Additional validation can be applied here if necessary
92+
93+
// Save the updated purchase
94+
purchaseFacade.updatePurchase(originalPurchase);
95+
96+
// Convert updated purchase to DTO and return JSON
97+
response.setContentType("application/json");
98+
originalPurchase.setCreditCardNumber("");
99+
originalPurchase.setCustomer(null);
100+
response.getWriter().write(gson.toJson(originalPurchase));
101+
} catch (Exception e) {
102+
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
103+
response.getWriter().write("An error occurred.");
104+
}
105+
}
106+
107+
private User extractUserFromBasicAuth(HttpServletRequest request, HttpServletResponse response) throws IOException {
108+
// Extract Basic Auth credentials
109+
String authHeader = request.getHeader("Authorization");
110+
if (authHeader == null || !authHeader.startsWith("Basic ")) {
111+
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
112+
response.getWriter().write("Authorization header missing or invalid.");
113+
return null;
114+
}
115+
116+
String encodedCredentials = authHeader.substring("Basic ".length()).trim();
117+
String decodedCredentials = new String(Base64.getDecoder().decode(encodedCredentials), "UTF-8");
118+
String[] credentials = decodedCredentials.split(":", 2);
119+
if (credentials.length != 2) {
120+
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
121+
response.getWriter().write("Invalid authorization credentials.");
122+
return null;
123+
}
124+
125+
String email = credentials[0];
126+
String password = credentials[1];
127+
User authenticatedUser = userFacade.getUserByEmail(email);
128+
129+
// Verify user credentials
130+
if (authenticatedUser == null || !authenticatedUser.getPassword().equals(password)) {
131+
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
132+
response.getWriter().write("Invalid email or password.");
133+
return null;
134+
}
135+
136+
return authenticatedUser;
137+
}
138+
}

0 commit comments

Comments
 (0)