diff --git a/core-java-collections/src/test/java/com/baeldung/queueinterface/CustomBaeldungQueueUnitTest.java b/core-java-collections/src/test/java/com/baeldung/queueInterface/CustomBaeldungQueueUnitTest.java
similarity index 100%
rename from core-java-collections/src/test/java/com/baeldung/queueinterface/CustomBaeldungQueueUnitTest.java
rename to core-java-collections/src/test/java/com/baeldung/queueInterface/CustomBaeldungQueueUnitTest.java
diff --git a/core-java-collections/src/test/java/com/baeldung/queueinterface/PriorityQueueUnitTest.java b/core-java-collections/src/test/java/com/baeldung/queueInterface/PriorityQueueUnitTest.java
similarity index 100%
rename from core-java-collections/src/test/java/com/baeldung/queueinterface/PriorityQueueUnitTest.java
rename to core-java-collections/src/test/java/com/baeldung/queueInterface/PriorityQueueUnitTest.java
diff --git a/software-security/sql-injection-samples/pom.xml b/software-security/sql-injection-samples/pom.xml
index e4510d9ef8..5b33c674d4 100644
--- a/software-security/sql-injection-samples/pom.xml
+++ b/software-security/sql-injection-samples/pom.xml
@@ -16,6 +16,8 @@
+
+
org.springframework.boot
spring-boot-starter-jdbc
@@ -42,6 +44,17 @@
provided
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.hibernate
+ hibernate-jpamodelgen
+
+
+
diff --git a/software-security/sql-injection-samples/src/main/java/com/baeldung/examples/security/sql/Account.java b/software-security/sql-injection-samples/src/main/java/com/baeldung/examples/security/sql/Account.java
new file mode 100644
index 0000000000..3f077d5592
--- /dev/null
+++ b/software-security/sql-injection-samples/src/main/java/com/baeldung/examples/security/sql/Account.java
@@ -0,0 +1,34 @@
+/**
+ *
+ */
+package com.baeldung.examples.security.sql;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+import lombok.Data;
+
+/**
+ * @author Philippe
+ *
+ */
+@Entity
+@Table(name="Accounts")
+@Data
+public class Account {
+
+ @Id
+ @GeneratedValue(strategy=GenerationType.IDENTITY)
+ private Long id;
+
+ private String customerId;
+ private String accNumber;
+ private String branchId;
+ private BigDecimal balance;
+
+}
diff --git a/software-security/sql-injection-samples/src/main/java/com/baeldung/examples/security/sql/AccountDAO.java b/software-security/sql-injection-samples/src/main/java/com/baeldung/examples/security/sql/AccountDAO.java
index 447dcc456d..c7285e5fd3 100644
--- a/software-security/sql-injection-samples/src/main/java/com/baeldung/examples/security/sql/AccountDAO.java
+++ b/software-security/sql-injection-samples/src/main/java/com/baeldung/examples/security/sql/AccountDAO.java
@@ -7,14 +7,24 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
+import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Order;
+import javax.persistence.criteria.Root;
+import javax.persistence.metamodel.SingularAttribute;
import javax.sql.DataSource;
import org.springframework.stereotype.Component;
@@ -27,9 +37,11 @@ import org.springframework.stereotype.Component;
public class AccountDAO {
private final DataSource dataSource;
+ private final EntityManager em;
- public AccountDAO(DataSource dataSource) {
+ public AccountDAO(DataSource dataSource, EntityManager em) {
this.dataSource = dataSource;
+ this.em = em;
}
/**
@@ -63,6 +75,26 @@ public class AccountDAO {
}
}
+ /**
+ * Return all accounts owned by a given customer,given his/her external id - JPA version
+ *
+ * @param customerId
+ * @return
+ */
+ public List unsafeJpaFindAccountsByCustomerId(String customerId) {
+ String jql = "from Account where customerId = '" + customerId + "'";
+ TypedQuery q = em.createQuery(jql, Account.class);
+ return q.getResultList()
+ .stream()
+ .map(a -> AccountDTO.builder()
+ .accNumber(a.getAccNumber())
+ .balance(a.getBalance())
+ .branchId(a.getAccNumber())
+ .customerId(a.getCustomerId())
+ .build())
+ .collect(Collectors.toList());
+ }
+
/**
* Return all accounts owned by a given customer,given his/her external id
*
@@ -71,7 +103,7 @@ public class AccountDAO {
*/
public List safeFindAccountsByCustomerId(String customerId) {
- String sql = "select " + "customer_id,acc_number,branch_id,balance from Accounts where customer_id = ?";
+ String sql = "select customer_id, branch_id,acc_number,balance from Accounts where customer_id = ?";
try (Connection c = dataSource.getConnection(); PreparedStatement p = c.prepareStatement(sql)) {
p.setString(1, customerId);
@@ -93,23 +125,73 @@ public class AccountDAO {
}
}
+ /**
+ * Return all accounts owned by a given customer,given his/her external id - JPA version
+ *
+ * @param customerId
+ * @return
+ */
+ public List safeJpaFindAccountsByCustomerId(String customerId) {
+
+ String jql = "from Account where customerId = :customerId";
+ TypedQuery q = em.createQuery(jql, Account.class)
+ .setParameter("customerId", customerId);
+
+ return q.getResultList()
+ .stream()
+ .map(a -> AccountDTO.builder()
+ .accNumber(a.getAccNumber())
+ .balance(a.getBalance())
+ .branchId(a.getAccNumber())
+ .customerId(a.getCustomerId())
+ .build())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Return all accounts owned by a given customer,given his/her external id - JPA version
+ *
+ * @param customerId
+ * @return
+ */
+ public List safeJpaCriteriaFindAccountsByCustomerId(String customerId) {
+
+ CriteriaBuilder cb = em.getCriteriaBuilder();
+ CriteriaQuery cq = cb.createQuery(Account.class);
+ Root root = cq.from(Account.class);
+ cq.select(root)
+ .where(cb.equal(root.get(Account_.customerId), customerId));
+
+ TypedQuery q = em.createQuery(cq);
+
+ return q.getResultList()
+ .stream()
+ .map(a -> AccountDTO.builder()
+ .accNumber(a.getAccNumber())
+ .balance(a.getBalance())
+ .branchId(a.getAccNumber())
+ .customerId(a.getCustomerId())
+ .build())
+ .collect(Collectors.toList());
+ }
+
private static final Set VALID_COLUMNS_FOR_ORDER_BY = Stream.of("acc_number", "branch_id", "balance")
.collect(Collectors.toCollection(HashSet::new));
+
/**
* Return all accounts owned by a given customer,given his/her external id
*
* @param customerId
* @return
*/
- public List safeFindAccountsByCustomerId(String customerId, String orderBy) {
+ public List safeFindAccountsByCustomerId(String customerId, String orderBy) {
String sql = "select " + "customer_id,acc_number,branch_id,balance from Accounts where customer_id = ? ";
if (VALID_COLUMNS_FOR_ORDER_BY.contains(orderBy)) {
sql = sql + " order by " + orderBy;
- }
- else {
+ } else {
throw new IllegalArgumentException("Nice try!");
}
@@ -135,35 +217,82 @@ public class AccountDAO {
}
}
+
+ private static final Map> VALID_JPA_COLUMNS_FOR_ORDER_BY = Stream.of(
+ new AbstractMap.SimpleEntry<>(Account_.ACC_NUMBER, Account_.accNumber),
+ new AbstractMap.SimpleEntry<>(Account_.BRANCH_ID, Account_.branchId),
+ new AbstractMap.SimpleEntry<>(Account_.BALANCE, Account_.balance)
+ )
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+ /**
+ * Return all accounts owned by a given customer,given his/her external id
+ *
+ * @param customerId
+ * @return
+ */
+ public List safeJpaFindAccountsByCustomerId(String customerId, String orderBy) {
+
+SingularAttribute orderByAttribute = VALID_JPA_COLUMNS_FOR_ORDER_BY.get(orderBy);
+if ( orderByAttribute == null) {
+ throw new IllegalArgumentException("Nice try!");
+}
+
+CriteriaBuilder cb = em.getCriteriaBuilder();
+CriteriaQuery cq = cb.createQuery(Account.class);
+Root root = cq.from(Account.class);
+cq.select(root)
+ .where(cb.equal(root.get(Account_.customerId), customerId))
+ .orderBy(cb.asc(root.get(orderByAttribute)));
+
+TypedQuery q = em.createQuery(cq);
+
+ return q.getResultList()
+ .stream()
+ .map(a -> AccountDTO.builder()
+ .accNumber(a.getAccNumber())
+ .balance(a.getBalance())
+ .branchId(a.getAccNumber())
+ .customerId(a.getCustomerId())
+ .build())
+ .collect(Collectors.toList());
+
+ }
+
/**
* Invalid placeholder usage example
*
* @param tableName
* @return
*/
- public List wrongCountRecordsByTableName(String tableName) {
+ public Long wrongCountRecordsByTableName(String tableName) {
+
+ try (Connection c = dataSource.getConnection(); PreparedStatement p = c.prepareStatement("select count(*) from ?")) {
- try (Connection c = dataSource.getConnection();
- PreparedStatement p = c.prepareStatement("select count(*) from ?")) {
-
p.setString(1, tableName);
ResultSet rs = p.executeQuery();
- List accounts = new ArrayList<>();
- while (rs.next()) {
- AccountDTO acc = AccountDTO.builder()
- .customerId(rs.getString("customerId"))
- .branchId(rs.getString("branch_id"))
- .accNumber(rs.getString("acc_number"))
- .balance(rs.getBigDecimal("balance"))
- .build();
+ rs.next();
+ return rs.getLong(1);
- accounts.add(acc);
- }
-
- return accounts;
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}
+ /**
+ * Invalid placeholder usage example - JPA
+ *
+ * @param tableName
+ * @return
+ */
+ public Long wrongJpaCountRecordsByTableName(String tableName) {
+
+ String jql = "select count(*) from :tableName";
+ TypedQuery q = em.createQuery(jql, Long.class)
+ .setParameter("tableName", tableName);
+
+ return q.getSingleResult();
+
+ }
+
}
diff --git a/software-security/sql-injection-samples/src/main/resources/application.properties b/software-security/sql-injection-samples/src/main/resources/application.properties
index e69de29bb2..8b13789179 100644
--- a/software-security/sql-injection-samples/src/main/resources/application.properties
+++ b/software-security/sql-injection-samples/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/software-security/sql-injection-samples/src/main/resources/db/changelog/create-tables.xml b/software-security/sql-injection-samples/src/main/resources/db/changelog/create-tables.xml
deleted file mode 100644
index a405c02049..0000000000
--- a/software-security/sql-injection-samples/src/main/resources/db/changelog/create-tables.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/software-security/sql-injection-samples/src/main/resources/db/master-changelog.xml b/software-security/sql-injection-samples/src/main/resources/db/master-changelog.xml
deleted file mode 100644
index 047ca2b314..0000000000
--- a/software-security/sql-injection-samples/src/main/resources/db/master-changelog.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/software-security/sql-injection-samples/src/test/java/com/baeldung/examples/security/sql/SqlInjectionSamplesApplicationUnitTest.java b/software-security/sql-injection-samples/src/test/java/com/baeldung/examples/security/sql/SqlInjectionSamplesApplicationUnitTest.java
index 1f37ba04b6..f61b738abc 100644
--- a/software-security/sql-injection-samples/src/test/java/com/baeldung/examples/security/sql/SqlInjectionSamplesApplicationUnitTest.java
+++ b/software-security/sql-injection-samples/src/test/java/com/baeldung/examples/security/sql/SqlInjectionSamplesApplicationUnitTest.java
@@ -40,6 +40,15 @@ public class SqlInjectionSamplesApplicationUnitTest {
assertThat(accounts).hasSize(3);
}
+ @Test
+ public void givenAVulnerableJpaMethod_whenHackedCustomerId_thenReturnAllAccounts() {
+
+ List accounts = target.unsafeJpaFindAccountsByCustomerId("C1' or '1'='1");
+ assertThat(accounts).isNotNull();
+ assertThat(accounts).isNotEmpty();
+ assertThat(accounts).hasSize(3);
+ }
+
@Test
public void givenASafeMethod_whenHackedCustomerId_thenReturnNoAccounts() {
@@ -48,13 +57,36 @@ public class SqlInjectionSamplesApplicationUnitTest {
assertThat(accounts).isEmpty();
}
+ @Test
+ public void givenASafeJpaMethod_whenHackedCustomerId_thenReturnNoAccounts() {
+
+ List accounts = target.safeJpaFindAccountsByCustomerId("C1' or '1'='1");
+ assertThat(accounts).isNotNull();
+ assertThat(accounts).isEmpty();
+ }
+
+
+ @Test
+ public void givenASafeJpaCriteriaMethod_whenHackedCustomerId_thenReturnNoAccounts() {
+
+ List accounts = target.safeJpaCriteriaFindAccountsByCustomerId("C1' or '1'='1");
+ assertThat(accounts).isNotNull();
+ assertThat(accounts).isEmpty();
+ }
+
@Test(expected = IllegalArgumentException.class)
public void givenASafeMethod_whenInvalidOrderBy_thenThroweException() {
target.safeFindAccountsByCustomerId("C1", "INVALID");
}
- @Test(expected = RuntimeException.class)
+ @Test(expected = Exception.class)
public void givenWrongPlaceholderUsageMethod_whenNormalCall_thenThrowsException() {
target.wrongCountRecordsByTableName("Accounts");
}
+
+ @Test(expected = Exception.class)
+ public void givenWrongJpaPlaceholderUsageMethod_whenNormalCall_thenThrowsException() {
+ target.wrongJpaCountRecordsByTableName("Accounts");
+ }
+
}
diff --git a/software-security/sql-injection-samples/src/test/resources/application-test.yml b/software-security/sql-injection-samples/src/test/resources/application-test.yml
index d07ee10aee..3af3f58bff 100644
--- a/software-security/sql-injection-samples/src/test/resources/application-test.yml
+++ b/software-security/sql-injection-samples/src/test/resources/application-test.yml
@@ -2,5 +2,17 @@
# Test profile configuration
#
spring:
+ liquibase:
+ change-log: db/changelog/db.changelog-master.xml
+
+ jpa:
+ hibernate:
+ ddl-auto: none
+
datasource:
- initialization-mode: always
+ initialization-mode: embedded
+
+logging:
+ level:
+ sql: DEBUG
+
\ No newline at end of file
diff --git a/software-security/sql-injection-samples/src/test/resources/schema.sql b/software-security/sql-injection-samples/src/test/resources/schema.sql
index bfb0ae8059..cfc4c44f98 100644
--- a/software-security/sql-injection-samples/src/test/resources/schema.sql
+++ b/software-security/sql-injection-samples/src/test/resources/schema.sql
@@ -1,4 +1,5 @@
create table Accounts (
+ id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
customer_id varchar(16) not null,
acc_number varchar(16) not null,
branch_id decimal(8,0),