Skip to content

Commit e71943a

Browse files
committed
Implement /health and /version endpoints for Admin API
1 parent 73ea731 commit e71943a

5 files changed

Lines changed: 272 additions & 26 deletions

File tree

pom.xml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,42 @@
462462
</execution>
463463
</executions>
464464
</plugin>
465+
<!-- add the git-commit-id-plugin configuration -->
466+
<plugin>
467+
<groupId>pl.project13.maven</groupId>
468+
<artifactId>git-commit-id-plugin</artifactId>
469+
<version>4.9.10</version>
470+
<executions>
471+
<execution>
472+
<id>get-the-git-infos</id>
473+
<goals>
474+
<goal>revision</goal>
475+
</goals>
476+
<phase>initialize</phase>
477+
</execution>
478+
</executions>
479+
<configuration>
480+
<generateGitPropertiesFile>true</generateGitPropertiesFile>
481+
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
482+
<includeOnlyProperties>
483+
<includeOnlyProperty>git.commit.id</includeOnlyProperty>
484+
<includeOnlyProperty>git.build.time</includeOnlyProperty>
485+
</includeOnlyProperties>
486+
<commitIdGenerationMode>full</commitIdGenerationMode>
487+
</configuration>
488+
</plugin>
489+
<!-- Add Spring Boot Maven plugin to generate build info -->
490+
<plugin>
491+
<groupId>org.springframework.boot</groupId>
492+
<artifactId>spring-boot-maven-plugin</artifactId>
493+
<executions>
494+
<execution>
495+
<goals>
496+
<goal>build-info</goal>
497+
</goals>
498+
</execution>
499+
</executions>
500+
</plugin>
465501
<plugin>
466502
<groupId>org.springframework.boot</groupId>
467503
<artifactId>spring-boot-maven-plugin</artifactId>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.iemr.admin.config;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.boot.web.servlet.FilterRegistrationBean;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
9+
import com.iemr.admin.utils.JwtAuthenticationUtil;
10+
import com.iemr.admin.utils.JwtUserIdValidationFilter;
11+
12+
@Configuration
13+
public class SecurityFilterConfig {
14+
15+
@Autowired
16+
private JwtAuthenticationUtil jwtAuthenticationUtil;
17+
18+
@Value("${cors.allowed-origins}")
19+
private String allowedOrigins;
20+
21+
@Bean
22+
public FilterRegistrationBean<JwtUserIdValidationFilter> jwtFilterRegistration() {
23+
FilterRegistrationBean<JwtUserIdValidationFilter> registration = new FilterRegistrationBean<>();
24+
registration.setFilter(new JwtUserIdValidationFilter(jwtAuthenticationUtil, allowedOrigins));
25+
registration.addUrlPatterns("/*");
26+
27+
// Exclude health and version endpoints
28+
registration.addInitParameter("excludedUrls", "/health,/version");
29+
30+
return registration;
31+
}
32+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* AMRIT – Accessible Medical Records via Integrated Technology
3+
* Integrated EHR (Electronic Health Records) Solution
4+
*
5+
* Copyright (C) "Piramal Swasthya Management and Research Institute"
6+
*
7+
* This file is part of AMRIT.
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with this program. If not, see https://www.gnu.org/licenses/.
21+
*/
22+
package com.iemr.admin.controller.healthcheck;
23+
24+
import org.springframework.http.ResponseEntity;
25+
import org.springframework.web.bind.annotation.GetMapping;
26+
import org.springframework.web.bind.annotation.RestController;
27+
28+
import java.util.HashMap;
29+
import java.util.Map;
30+
31+
@RestController
32+
public class HealthController {
33+
34+
@GetMapping("/health")
35+
public ResponseEntity<Map<String, String>> health() {
36+
Map<String, String> response = new HashMap<>();
37+
response.put("status", "UP");
38+
return ResponseEntity.ok(response);
39+
}
40+
}

src/main/java/com/iemr/admin/controller/version/VersionController.java

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@
2121
*/
2222
package com.iemr.admin.controller.version;
2323

24-
import java.io.BufferedReader;
2524
import java.io.IOException;
2625
import java.io.InputStream;
27-
import java.io.InputStreamReader;
26+
import java.text.SimpleDateFormat;
27+
import java.util.Date;
28+
import java.util.Properties;
29+
import java.util.TimeZone;
2830

2931
import org.slf4j.Logger;
3032
import org.slf4j.LoggerFactory;
31-
33+
import org.springframework.http.MediaType;
3234
import org.springframework.web.bind.annotation.RequestMapping;
3335
import org.springframework.web.bind.annotation.RequestMethod;
3436
import org.springframework.web.bind.annotation.RestController;
@@ -44,35 +46,115 @@ public class VersionController {
4446
private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
4547

4648
@Operation(summary = "Version information")
47-
@RequestMapping(value = "/version", method = { RequestMethod.GET })
49+
@RequestMapping(value = "/version", method = { RequestMethod.GET }, produces = MediaType.APPLICATION_JSON_VALUE)
4850
public String versionInformation() {
4951
OutputResponse output = new OutputResponse();
5052
try {
5153
logger.info("version Controller Start");
52-
output.setResponse(readGitProperties());
54+
// Set the version information as JSON string directly
55+
output.setResponse(readGitPropertiesAsJson());
5356
} catch (Exception e) {
5457
output.setError(e);
5558
}
5659

5760
logger.info("version Controller End");
61+
62+
// Use standard toString() - no custom formatting
5863
return output.toString();
5964
}
6065

61-
private String readGitProperties() throws Exception {
62-
ClassLoader classLoader = getClass().getClassLoader();
63-
InputStream inputStream = classLoader.getResourceAsStream("git.properties");
64-
65-
return readFromInputStream(inputStream);
66+
private String readGitPropertiesAsJson() throws Exception {
67+
StringBuilder json = new StringBuilder();
68+
json.append("{\n");
69+
70+
// Read Git properties
71+
Properties gitProps = loadPropertiesFile("git.properties");
72+
if (gitProps != null) {
73+
// For git.commit.id, look for both standard and abbrev versions
74+
String commitId = gitProps.getProperty("git.commit.id", null);
75+
if (commitId == null) {
76+
commitId = gitProps.getProperty("git.commit.id.abbrev", "unknown");
77+
}
78+
json.append(" \"git.commit.id\": \"").append(commitId).append("\",\n");
79+
80+
// For git.build.time, look for various possible property names
81+
String buildTime = gitProps.getProperty("git.build.time", null);
82+
if (buildTime == null) {
83+
buildTime = gitProps.getProperty("git.commit.time", null);
84+
}
85+
if (buildTime == null) {
86+
buildTime = gitProps.getProperty("git.commit.timestamp", "unknown");
87+
}
88+
json.append(" \"git.build.time\": \"").append(buildTime).append("\",\n");
89+
} else {
90+
logger.warn("git.properties file not found. Git information will be unavailable.");
91+
json.append(" \"git.commit.id\": \"information unavailable\",\n");
92+
json.append(" \"git.build.time\": \"information unavailable\",\n");
93+
}
94+
95+
// Read build properties if available
96+
Properties buildProps = loadPropertiesFile("META-INF/build-info.properties");
97+
if (buildProps != null) {
98+
// Extract version - checking for both standard and nested formats
99+
String version = buildProps.getProperty("build.version", null);
100+
if (version == null) {
101+
version = buildProps.getProperty("build.version.number", null);
102+
}
103+
if (version == null) {
104+
version = buildProps.getProperty("version", "unknown");
105+
}
106+
json.append(" \"build.version\": \"").append(version).append("\",\n");
107+
108+
// Extract time - checking for both standard and alternate formats
109+
String time = buildProps.getProperty("build.time", null);
110+
if (time == null) {
111+
time = buildProps.getProperty("build.timestamp", null);
112+
}
113+
if (time == null) {
114+
time = buildProps.getProperty("timestamp", "unknown");
115+
}
116+
json.append(" \"build.time\": \"").append(time).append("\",\n");
117+
} else {
118+
logger.info("build-info.properties not found, trying Maven properties");
119+
// Fallback to maven project version
120+
Properties mavenProps = loadPropertiesFile("META-INF/maven/com.iemr.admin/admin-api/pom.properties");
121+
if (mavenProps != null) {
122+
String version = mavenProps.getProperty("version", "unknown");
123+
json.append(" \"build.version\": \"").append(version).append("\",\n");
124+
json.append(" \"build.time\": \"").append(getCurrentIstTimeFormatted()).append("\",\n");
125+
} else {
126+
logger.warn("Neither build-info.properties nor Maven properties found.");
127+
json.append(" \"build.version\": \"3.1.0\",\n"); // Default version
128+
json.append(" \"build.time\": \"").append(getCurrentIstTimeFormatted()).append("\",\n");
129+
}
130+
}
131+
json.append(" \"current.time\": \"").append(getCurrentIstTimeFormatted()).append("\"\n");
132+
133+
json.append(" }");
134+
return json.toString();
135+
}
136+
137+
/**
138+
* Get the current time formatted in Indian Standard Time (IST)
139+
* IST is UTC+5:30
140+
*/
141+
private String getCurrentIstTimeFormatted() {
142+
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
143+
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
144+
return sdf.format(new Date());
66145
}
67146

68-
private String readFromInputStream(InputStream inputStream) throws IOException {
69-
StringBuilder resultStringBuilder = new StringBuilder();
70-
try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
71-
String line;
72-
while ((line = br.readLine()) != null) {
73-
resultStringBuilder.append(line).append("\n");
147+
private Properties loadPropertiesFile(String resourceName) {
148+
ClassLoader classLoader = getClass().getClassLoader();
149+
try (InputStream inputStream = classLoader.getResourceAsStream(resourceName)) {
150+
if (inputStream != null) {
151+
Properties props = new Properties();
152+
props.load(inputStream);
153+
return props;
74154
}
155+
} catch (IOException e) {
156+
logger.warn("Could not load properties file: " + resourceName, e);
75157
}
76-
return resultStringBuilder.toString();
158+
return null;
77159
}
78-
}
160+
}

src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55

66
import org.slf4j.Logger;
77
import org.slf4j.LoggerFactory;
8-
8+
import org.springframework.beans.factory.annotation.Autowired;
99

1010
import com.iemr.admin.utils.http.AuthorizationHeaderRequestWrapper;
1111

1212
import jakarta.servlet.Filter;
1313
import jakarta.servlet.FilterChain;
14+
import jakarta.servlet.FilterConfig;
1415
import jakarta.servlet.ServletException;
1516
import jakarta.servlet.ServletRequest;
1617
import jakarta.servlet.ServletResponse;
@@ -20,21 +21,64 @@
2021

2122
public class JwtUserIdValidationFilter implements Filter {
2223

23-
private final JwtAuthenticationUtil jwtAuthenticationUtil;
24+
private JwtAuthenticationUtil jwtAuthenticationUtil;
2425
private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
25-
private final String allowedOrigins;
26-
26+
private String allowedOrigins;
27+
28+
// FilterConfig for storing initialization parameters
29+
private FilterConfig filterConfig;
30+
31+
// Add no-args constructor for bean creation in FilterRegistrationBean
32+
public JwtUserIdValidationFilter() {
33+
// Default constructor for Spring to instantiate
34+
this.allowedOrigins = "*";
35+
}
36+
2737
public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil,
2838
String allowedOrigins) {
2939
this.jwtAuthenticationUtil = jwtAuthenticationUtil;
3040
this.allowedOrigins = allowedOrigins;
3141
}
42+
43+
// Store FilterConfig during initialization
44+
@Override
45+
public void init(FilterConfig filterConfig) throws ServletException {
46+
this.filterConfig = filterConfig;
47+
}
48+
49+
// Method to check if a URL is in the excluded list
50+
private boolean isExcludedUrl(String path) {
51+
if (filterConfig == null) {
52+
return false;
53+
}
54+
55+
String excludedUrls = filterConfig.getInitParameter("excludedUrls");
56+
if (excludedUrls != null) {
57+
String[] urls = excludedUrls.split(",");
58+
for (String url : urls) {
59+
if (path.equals(url.trim())) {
60+
logger.info("Skipping JWT validation for excluded URL: {}", path);
61+
return true;
62+
}
63+
}
64+
}
65+
return false;
66+
}
3267

3368
@Override
3469
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
3570
throws IOException, ServletException {
3671
HttpServletRequest request = (HttpServletRequest) servletRequest;
3772
HttpServletResponse response = (HttpServletResponse) servletResponse;
73+
74+
// Check for excluded URLs first
75+
String path = request.getRequestURI();
76+
if (isExcludedUrl(path) ||
77+
path.equals("/health") ||
78+
path.equals("/version")) {
79+
filterChain.doFilter(servletRequest, servletResponse);
80+
return;
81+
}
3882

3983
String origin = request.getHeader("Origin");
4084
if (origin != null && isOriginAllowed(origin)) {
@@ -52,7 +96,6 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
5296
return;
5397
}
5498

55-
String path = request.getRequestURI();
5699
String contextPath = request.getContextPath();
57100
logger.info("JwtUserIdValidationFilter invoked for path: " + path);
58101

@@ -89,15 +132,15 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
89132
String jwtFromHeader = request.getHeader(Constants.JWT_TOKEN);
90133
String authHeader = request.getHeader("Authorization");
91134

92-
if (jwtFromCookie != null) {
135+
if (jwtFromCookie != null && jwtAuthenticationUtil != null) {
93136
logger.info("Validating JWT token from cookie");
94137
if (jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtFromCookie)) {
95138
AuthorizationHeaderRequestWrapper authorizationHeaderRequestWrapper = new AuthorizationHeaderRequestWrapper(
96139
request, "");
97140
filterChain.doFilter(authorizationHeaderRequestWrapper, servletResponse);
98141
return;
99142
}
100-
} else if (jwtFromHeader != null) {
143+
} else if (jwtFromHeader != null && jwtAuthenticationUtil != null) {
101144
logger.info("Validating JWT token from header");
102145
if (jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtFromHeader)) {
103146
AuthorizationHeaderRequestWrapper authorizationHeaderRequestWrapper = new AuthorizationHeaderRequestWrapper(
@@ -177,4 +220,17 @@ private void clearUserIdCookie(HttpServletResponse response) {
177220
cookie.setMaxAge(0); // Invalidate the cookie
178221
response.addCookie(cookie);
179222
}
180-
}
223+
224+
// Setter methods for Spring to inject dependencies
225+
@Autowired(required = false)
226+
public void setJwtAuthenticationUtil(JwtAuthenticationUtil jwtAuthenticationUtil) {
227+
this.jwtAuthenticationUtil = jwtAuthenticationUtil;
228+
}
229+
230+
@Autowired(required = false)
231+
public void setAllowedOrigins(String allowedOrigins) {
232+
if (allowedOrigins != null) {
233+
this.allowedOrigins = allowedOrigins;
234+
}
235+
}
236+
}

0 commit comments

Comments
 (0)