Skip to content

Commit ddfd736

Browse files
committed
Initial Commit
- Add Task manager REST API - Secure endpoints with spring security - Add global exception handling - Add role based access management - Add project and task management using MySQL and JPA - Add basic documentation readme.md
0 parents  commit ddfd736

31 files changed

+1509
-0
lines changed

.gitignore

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
HELP.md
2+
target/
3+
!.mvn/wrapper/maven-wrapper.jar
4+
!**/src/main/**/target/
5+
!**/src/test/**/target/
6+
7+
### STS ###
8+
.apt_generated
9+
.classpath
10+
.factorypath
11+
.project
12+
.settings
13+
.springBeans
14+
.sts4-cache
15+
16+
### IntelliJ IDEA ###
17+
.idea
18+
*.iws
19+
*.iml
20+
*.ipr
21+
22+
### NetBeans ###
23+
/nbproject/private/
24+
/nbbuild/
25+
/dist/
26+
/nbdist/
27+
/.nb-gradle/
28+
build/
29+
!**/src/main/**/build/
30+
!**/src/test/**/build/
31+
32+
### VS Code ###
33+
.vscode/

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Task Manager REST API
2+
3+
## Pre-requisite
4+
1. Java 11
5+
1. Maven 3.6.0
6+
1. MySQL 5.7.33
7+
8+
## Build Locally. Follow the steps one after the other.
9+
 1.Download dependency
10+
11+
2.Go to project directory
12+
13+
3.Build project with test:
14+
> mvn clean package
15+
16+
4.Build project without test:
17+
> mvn clean package -DskipTests
18+
19+
5.Running the project with the JAR:
20+
21+
## GET
22+
http://localhost:8080/task
23+
24+
## GET By ID
25+
http://localhost:8080/task/1
26+
27+
## POST
28+
http://localhost:8080/task
29+
30+
# PUT
31+
http://localhost:8080/task
32+
33+
# DELETE
34+
http://localhost:8080/task/1
35+
36+
# Requests
37+
38+

pom.xml

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>2.4.2</version>
9+
<relativePath/> <!-- lookup parent from repository -->
10+
</parent>
11+
<groupId>com.ssmtariq</groupId>
12+
<artifactId>taskmanager-rest-api-spring-security-jpa</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
<name>taskmanager-rest-api-spring-security-jpa</name>
15+
<description>Task Management Application using Spring Boot, Security, JPA</description>
16+
<properties>
17+
<java.version>11</java.version>
18+
</properties>
19+
<dependencies>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-starter-data-jpa</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.springframework.boot</groupId>
26+
<artifactId>spring-boot-starter-security</artifactId>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.springframework.boot</groupId>
30+
<artifactId>spring-boot-starter-web</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.springframework.boot</groupId>
34+
<artifactId>spring-boot-starter-mail</artifactId>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.springframework.boot</groupId>
38+
<artifactId>spring-boot-devtools</artifactId>
39+
<scope>runtime</scope>
40+
<optional>true</optional>
41+
</dependency>
42+
<dependency>
43+
<groupId>mysql</groupId>
44+
<artifactId>mysql-connector-java</artifactId>
45+
<scope>runtime</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.springframework.boot</groupId>
49+
<artifactId>spring-boot-starter-test</artifactId>
50+
<scope>test</scope>
51+
</dependency>
52+
<dependency>
53+
<groupId>org.springframework.security</groupId>
54+
<artifactId>spring-security-test</artifactId>
55+
<scope>test</scope>
56+
</dependency>
57+
<dependency>
58+
<groupId>org.projectlombok</groupId>
59+
<artifactId>lombok</artifactId>
60+
<scope>provided</scope>
61+
</dependency>
62+
<dependency>
63+
<groupId>com.sun.jersey</groupId>
64+
<artifactId>jersey-json</artifactId>
65+
<version>1.4</version>
66+
</dependency>
67+
</dependencies>
68+
69+
<build>
70+
<plugins>
71+
<plugin>
72+
<groupId>org.springframework.boot</groupId>
73+
<artifactId>spring-boot-maven-plugin</artifactId>
74+
</plugin>
75+
</plugins>
76+
</build>
77+
78+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.ssmtariq.taskmanager;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
6+
7+
@SpringBootApplication
8+
@EnableJpaRepositories(basePackages = "com.ssmtariq.taskmanager.repository")
9+
public class TaskManagerApplication {
10+
11+
public static void main(String[] args) {
12+
SpringApplication.run(TaskManagerApplication.class, args);
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package com.ssmtariq.taskmanager.component;
2+
3+
import static com.ssmtariq.taskmanager.constant.TaskManagerApiConstants.DATE_FORMAT;
4+
import static com.ssmtariq.taskmanager.constant.TaskManagerApiConstants.EMPTY;
5+
import static com.ssmtariq.taskmanager.constant.TaskManagerApiConstants.ERROR_INVALID_REQUEST_BODY_APPLICATION_EXCEPTION;
6+
import static com.ssmtariq.taskmanager.constant.TaskManagerApiConstants.ERROR_MISSING_REQUIRED_PARAM_APPLICATION_EXCEPTION;
7+
import static com.ssmtariq.taskmanager.constant.TaskManagerApiConstants.ERROR_WRONG_DATE_FIELD_APPLICATION_EXCEPTION;
8+
import static com.ssmtariq.taskmanager.constant.TaskManagerApiConstants.ERROR_WRONG_PARAM_APPLICATION_EXCEPTION;
9+
10+
import java.io.IOException;
11+
import java.text.ParseException;
12+
import java.text.SimpleDateFormat;
13+
import java.util.ArrayList;
14+
import java.util.HashMap;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.stream.Collectors;
18+
import java.util.stream.Stream;
19+
20+
import org.springframework.stereotype.Component;
21+
22+
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import com.ssmtariq.taskmanager.exception.ApiError;
24+
import com.ssmtariq.taskmanager.exception.ApplicationException;
25+
import com.ssmtariq.taskmanager.model.RequestParameterEnum;
26+
import com.ssmtariq.taskmanager.model.StatusEnum;
27+
28+
/**
29+
* @author SSM Tariq
30+
*/
31+
32+
@Component
33+
public class RequestValidator {
34+
35+
public void validate(String requestBody) {
36+
if (hasInvalidParam(requestBody)) {
37+
throw new ApplicationException(new ApiError(ERROR_WRONG_PARAM_APPLICATION_EXCEPTION));
38+
}
39+
validateRequiredParams(requestBody);
40+
if (!isValidDateType(requestBody)) {
41+
throw new ApplicationException(new ApiError(ERROR_WRONG_DATE_FIELD_APPLICATION_EXCEPTION));
42+
}
43+
}
44+
45+
@SuppressWarnings("unchecked")
46+
private static boolean hasInvalidParam(String requestBody) {
47+
try {
48+
var requestMap = (Map<String, Object>) parseRequestBody(requestBody, Map.class);
49+
var paramKeys = new ArrayList<String>((requestMap.keySet()));
50+
List<String> possibleParams = Stream.of(RequestParameterEnum.values())
51+
.map(RequestParameterEnum::getValue)
52+
.collect(Collectors.toList());
53+
for (String param : paramKeys) {
54+
if (!possibleParams.contains(param)) {
55+
return true;
56+
}
57+
}
58+
String status = (String) requestMap.get(RequestParameterEnum.STATUS.getValue());
59+
List<String> possibleStatus = Stream.of(StatusEnum.values())
60+
.map(StatusEnum::getValue)
61+
.collect(Collectors.toList());
62+
if (!possibleStatus.contains(status.toLowerCase())) {
63+
return true;
64+
}
65+
return false;
66+
} catch (Exception e) {
67+
return true;
68+
}
69+
}
70+
71+
@SuppressWarnings("unchecked")
72+
private static void validateRequiredParams(String jsonInString) {
73+
var requestMap = (Map<String, Object>) parseRequestBody(jsonInString, Map.class);
74+
var requiredParamMap = getRequiredParams(requestMap);
75+
if(requiredParamMap.containsValue(EMPTY)) {
76+
throw new ApplicationException(new ApiError(ERROR_INVALID_REQUEST_BODY_APPLICATION_EXCEPTION));
77+
}
78+
if (!requiredParamMap.isEmpty()) {
79+
requiredParamMap.forEach((k, v) -> {
80+
if (v == null) {
81+
var apiError = new ApiError(String.format(ERROR_MISSING_REQUIRED_PARAM_APPLICATION_EXCEPTION));
82+
apiError.setMessage(String.format(apiError.getMessage(), k));
83+
throw new ApplicationException(apiError);
84+
}
85+
});
86+
}
87+
}
88+
89+
@SuppressWarnings("unchecked")
90+
private static boolean isValidDateType(String jsonInString) {
91+
var requestMap = (Map<String, Object>) parseRequestBody(jsonInString, Map.class);
92+
try {
93+
String dueDate = String.valueOf(requestMap.get(RequestParameterEnum.DUE_DATE.getValue()));
94+
if (dueDate.isEmpty()) {
95+
return false;
96+
}
97+
var simpleDateFormat = new SimpleDateFormat(DATE_FORMAT);
98+
simpleDateFormat.setLenient(false);
99+
simpleDateFormat.parse(dueDate);
100+
return true;
101+
} catch (ParseException | IllegalArgumentException ex) {
102+
return false;
103+
}
104+
}
105+
106+
private static Object parseRequestBody(String jsonInString, Class<?> classname) {
107+
try {
108+
final ObjectMapper mapper = new ObjectMapper();
109+
Object requestObject = mapper.readValue(jsonInString, classname);
110+
return requestObject;
111+
} catch (IOException e) {
112+
throw new ApplicationException(new ApiError(ERROR_INVALID_REQUEST_BODY_APPLICATION_EXCEPTION));
113+
}
114+
}
115+
116+
private static Map<String, Object> getRequiredParams(Map<String, Object> paramMap) {
117+
var requiredParamMap = new HashMap<String, Object>();
118+
requiredParamMap.put(RequestParameterEnum.DESCRIPTION.getValue(), paramMap.get(RequestParameterEnum.DESCRIPTION.getValue()));
119+
requiredParamMap.put(RequestParameterEnum.STATUS.getValue(), paramMap.get(RequestParameterEnum.STATUS.getValue()));
120+
requiredParamMap.put(RequestParameterEnum.PROJECT.getValue(), paramMap.get(RequestParameterEnum.PROJECT.getValue()));
121+
return requiredParamMap;
122+
}
123+
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.ssmtariq.taskmanager.configuration;
2+
import static com.ssmtariq.taskmanager.constant.TaskManagerApiConstants.CONTENT_TYPE_APPLICATION_JSON_UTF_8;
3+
import static com.ssmtariq.taskmanager.constant.TaskManagerApiConstants.ERROR_RESPONSE_TEMPLATE;
4+
5+
import java.io.IOException;
6+
import java.text.MessageFormat;
7+
8+
import javax.servlet.http.HttpServletRequest;
9+
import javax.servlet.http.HttpServletResponse;
10+
11+
import org.codehaus.jettison.json.JSONException;
12+
import org.codehaus.jettison.json.JSONObject;
13+
import org.springframework.security.core.AuthenticationException;
14+
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
15+
import org.springframework.stereotype.Component;
16+
17+
@Component
18+
public class AuthenticationEntryPointConfig extends BasicAuthenticationEntryPoint {
19+
20+
@Override
21+
public void commence(HttpServletRequest request, HttpServletResponse response,
22+
AuthenticationException authException) throws IOException {
23+
try {
24+
response.addHeader("WWW-Authenticate", "Basic Realm - " + getRealmName());
25+
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
26+
response.setContentType(CONTENT_TYPE_APPLICATION_JSON_UTF_8);
27+
response.getWriter()
28+
.println(new JSONObject(MessageFormat.format(ERROR_RESPONSE_TEMPLATE, 401, authException.getMessage())));
29+
} catch (JSONException e) {
30+
e.printStackTrace();
31+
}
32+
}
33+
34+
@Override
35+
public void afterPropertiesSet() {
36+
setRealmName("ssmtariq");
37+
super.afterPropertiesSet();
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.ssmtariq.taskmanager.configuration;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
6+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
8+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
9+
import org.springframework.security.config.http.SessionCreationPolicy;
10+
import org.springframework.security.core.userdetails.UserDetailsService;
11+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
12+
import org.springframework.security.web.AuthenticationEntryPoint;
13+
14+
import static com.ssmtariq.taskmanager.constant.TaskManagerApiConstants.ROLE_ADMIN;
15+
import static com.ssmtariq.taskmanager.constant.TaskManagerApiConstants.ROLE_USER;
16+
17+
@Configuration
18+
@EnableWebSecurity
19+
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
20+
21+
@Autowired
22+
private AuthenticationEntryPoint entryPoint;
23+
24+
@Autowired
25+
private UserDetailsService userDetailsService;
26+
27+
@Override
28+
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
29+
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
30+
}
31+
32+
@Override
33+
protected void configure(HttpSecurity http) throws Exception {
34+
http.csrf().disable().authorizeRequests()
35+
.antMatchers("/prepare/demo/**").permitAll()
36+
.antMatchers("/projects/user/{id}").hasRole(ROLE_ADMIN)
37+
.antMatchers("/project/**").hasRole(ROLE_USER)
38+
.antMatchers("/projects/**").hasRole(ROLE_USER)
39+
.antMatchers("/tasks/user/{id}").hasRole(ROLE_ADMIN)
40+
.antMatchers("/task/**").hasRole(ROLE_USER)
41+
.antMatchers("/tasks/**").hasRole(ROLE_USER)
42+
.and().httpBasic()
43+
.authenticationEntryPoint(entryPoint)
44+
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
45+
}
46+
}

0 commit comments

Comments
 (0)