feature/Add Redis Log Widget

This commit is contained in:
Marko Milić 2024-10-25 12:10:23 +02:00
parent f7406bbdee
commit 6768c38f85
14 changed files with 182 additions and 5 deletions

View File

@ -71,6 +71,10 @@
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.62</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@ -0,0 +1,20 @@
package com.openbankproject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}

View File

@ -0,0 +1,47 @@
package com.openbankproject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import javax.servlet.http.HttpSession;
import java.util.concurrent.TimeUnit;
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void save(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
// Save a value with a TTL (Time-to-Live)
public void saveWithTTL(String key, String value, long timeoutInSeconds) {
save(key, value);
// Set TTL for the key
redisTemplate.expire(key, timeoutInSeconds, TimeUnit.SECONDS);
}
// Save a value with a TTL (Time-to-Live)
public void appendWithTTL(String key, String value, long timeoutInSeconds) {
String currentValue = get(key);
String currentValuePrintFriendly = currentValue != null ? currentValue : "";
save(key, currentValuePrintFriendly + value);
// Set TTL for the key
redisTemplate.expire(key, timeoutInSeconds, TimeUnit.SECONDS);
}
public String get(String key) {
return (String) redisTemplate.opsForValue().get(key);
}
public void readLogFromRedis(HttpSession session, Model model) {
String key = "log-entry-for-session-id: " + session.getId();
String logEntries = get(key);
model.addAttribute("logEntriesForHtml", logEntries);
}
}

View File

@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
@ -33,6 +34,7 @@ import java.util.function.Function;
import static com.openbankproject.oauth2.util.ControllerUtils.buildDirectLoginHeader;
@SpringBootApplication
@ComponentScan(basePackages = "com.openbankproject") // Ensure this package is scanned
public class Oauth2Application {
private static final Logger logger = LoggerFactory.getLogger(Oauth2Application.class);

View File

@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.jwk.RSAKey;
import com.openbankproject.JwsUtil;
import com.openbankproject.RedisService;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
@ -17,6 +18,7 @@ import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -41,6 +43,7 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@ -120,24 +123,78 @@ public class RestTemplateConfig {
private String getSessionId() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
if (attributes != null && attributes.getRequest() != null && attributes.getRequest().getSession(false) != null ) {
return attributes.getRequest().getSession(false).getId(); // Get the existing session, don't create a new one
}
return "";
}
@Autowired
private RedisService redisService;
public String printUtcDateTime() {
Instant now = Instant.now();
return "at UTC Time: " + now.toString();
}
private StringBuilder saveRequestInfoToRedis(HttpRequest request, String body) {
System.out.println("Saving to Redis...");
StringBuilder logEntry = buildRequestInfo(request, body);
String key = "log-entry-for-session-id: " + getSessionId();
String value = logEntry.toString();
redisService.appendWithTTL(key, value, 300);
return logEntry;
}
private StringBuilder buildRequestInfo(HttpRequest request, String body) {
StringBuilder logEntry = new StringBuilder();
logEntry.append("============= Request begin " + printUtcDateTime() + " =============\n")
.append("=== Session ID: ").append(getSessionId()).append("\n")
.append("=== Status Line : ").append(request.getRequestLine()).append("\n")
.append("=== Headers : ").append(StringUtils.join(request.getAllHeaders(), "; ")).append("\n")
.append("=== Request body: ").append(body).append("\n")
.append("============= Request end " + printUtcDateTime() + " =============\n");
return logEntry;
}
private StringBuilder saveResponseInfoTRedis(HttpResponse response, String body) {
System.out.println("Saving to Redis...");
StringBuilder logEntry = buildResponseInfo(response, body);
String key = "log-entry-for-session-id: " + getSessionId();
String value = logEntry.toString();
redisService.appendWithTTL(key, value, 300);
return logEntry;
}
private StringBuilder buildResponseInfo(HttpResponse response, String body) {
StringBuilder logEntry = new StringBuilder();
logEntry.append("============= Response begin " + printUtcDateTime() + " =============\n")
.append("=== Session ID: ").append(getSessionId()).append("\n")
.append("=== Status Line : ").append(response.getStatusLine()).append("\n")
.append("=== Headers : ").append(StringUtils.join(response.getAllHeaders(), "; ")).append("\n")
.append("=== Response body: ").append(body).append("\n")
.append("============= Response end " + printUtcDateTime() + " =============\n");
return logEntry;
}
private void traceRequest(HttpRequest request, String body) throws IOException {
// Standard output
logger.info("=========================== request begin ================================================ Session ID : {}", getSessionId());
logger.info("=== Request Line : {}, Session ID : {}", request.getRequestLine(), getSessionId());
logger.info("=== Headers : {}, Session ID : {}", StringUtils.join(request.getAllHeaders(), "; "), getSessionId());
logger.info("=== Request body: {}, Session ID : {}", body, getSessionId());
logger.info("============================= request end ================================================ Session ID : {}", getSessionId());
// Save to Redis
saveRequestInfoToRedis(request, body).toString();
}
private void traceResponse(HttpResponse response, String body) throws IOException {
// Standard output
logger.info("=========================== response begin ================================================ Session ID : {}", getSessionId());
logger.info("=== Status Line : {}, Session ID : {}", response.getStatusLine(), getSessionId());
logger.info("=== Headers : {}, Session ID : {}", StringUtils.join(response.getAllHeaders(), "; "), getSessionId());
logger.info("=== Response body: {}, Session ID : {}", body, getSessionId());
logger.info("=========================== response end =================================================== Session ID : {}", getSessionId());
// Save to Redis
saveResponseInfoTRedis(response, body);
}
private void responseIntercept(org.apache.http.HttpResponse response, HttpContext httpContext) throws IOException {
HttpRequest req = (HttpRequest)httpContext.getAttribute("http.request");

View File

@ -1,6 +1,7 @@
package com.openbankproject.oauth2.controller;
import com.nimbusds.jose.util.X509CertUtils;
import com.openbankproject.RedisService;
import com.openbankproject.oauth2.model.AccessToViewRequest;
import com.openbankproject.oauth2.model.AccountMini;
import com.openbankproject.oauth2.model.Accounts;
@ -9,6 +10,7 @@ import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@ -95,6 +97,9 @@ public class ConsentController {
private String idTokenSignHashAlg;
@Autowired
private RedisService redisService;
@PostConstruct
private void initiate() {
final Map<String, List<Map<String, String>>> keySet = restTemplate.getForObject(keySetUrl, Map.class);
@ -228,7 +233,8 @@ public class ConsentController {
}
}
}
redisService.readLogFromRedis(session, model);
model.addAttribute("consents", consents);
model.addAttribute("showBankLogo", showBankLogo);
model.addAttribute("obpBaseUrl", obpBaseUrl);
@ -280,6 +286,9 @@ public class ConsentController {
ResponseEntity<Map> response2 = restTemplate.exchange(url, method, entity2, Map.class);
String redirect = (String) session.getAttribute("acceptConsentResponse.getRedirectTo()");
logger.info("redirect:" + redirect);
redisService.readLogFromRedis(session, model);
return "redirect:" + redirect;
} catch (Exception e) {
String error = "Sorry! The one time password (OTP) you supplied is incorrect.";
@ -294,8 +303,8 @@ public class ConsentController {
return "error";
}
}
@PostMapping(value="/revoke_consents", params = "consent_challenge")
public String revokeConsents(@RequestParam String consent_challenge,
@RequestParam(value="consents", required = false) String[] consentIds,
@ -325,6 +334,7 @@ public class ConsentController {
return "error";
}
model.addAttribute("errorMsg", "All selected consents have been deleted!");
redisService.readLogFromRedis(session, model);
return "error";
}
@ -489,7 +499,9 @@ public class ConsentController {
ResponseEntity<Map> authorizationIds = restTemplate.exchange(getConsentAuthorisation.replace("CONSENT_ID", consentId), HttpMethod.GET, entity, Map.class);
ArrayList<String> list = (ArrayList<String>)authorizationIds.getBody().get("authorisationIds");
model.addAttribute("authorization_ids", String.join(", ", list));
redisService.readLogFromRedis(session, model);
model.addAttribute("consent_challenge", consent_challenge);
session.setAttribute("acceptConsentResponse.getRedirectTo()", acceptConsentResponse.getRedirectTo());
logger.info("acceptConsentResponse.getRedirectTo():" + acceptConsentResponse.getRedirectTo());
@ -498,8 +510,13 @@ public class ConsentController {
model.addAttribute("consent_challenge", consent_challenge);
session.setAttribute("acceptConsentResponse.getRedirectTo()", acceptConsentResponse.getRedirectTo());
logger.info("acceptConsentResponse.getRedirectTo():" + acceptConsentResponse.getRedirectTo());
redisService.readLogFromRedis(session, model);
return "sca_modal";
} else {
redisService.readLogFromRedis(session, model);
return "redirect:" + acceptConsentResponse.getRedirectTo();
}
} catch (Exception unhandledException) {

View File

@ -1,8 +1,10 @@
package com.openbankproject.oauth2.controller;
import com.openbankproject.RedisService;
import com.openbankproject.oauth2.model.DirectLoginResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@ -68,6 +70,9 @@ public class LoginController implements ServletContextAware {
private String showBankLogo;
@Value("${logo.bank.url:#}")
private String bankLogoUrl;
@Autowired
private RedisService redisService;
/**
* initiate global variable
@ -179,8 +184,10 @@ public class LoginController implements ServletContextAware {
AcceptLoginRequest acceptLoginRequest = new AcceptLoginRequest();
acceptLoginRequest.setSubject(loginRequest.getSubject());
CompletedRequest response = hydraAdmin.acceptLoginRequest(login_challenge, acceptLoginRequest);
redisService.readLogFromRedis(session, model);
return "redirect:" + response.getRedirectTo();
} else {
redisService.readLogFromRedis(session, model);
return "login";
}
} catch (ApiException e) {

View File

@ -105,6 +105,8 @@
</div>
</div>
<div th:insert="redis-log-widget :: redisLogWidget"></div>
</div>
<script type="text/javascript" th:src="@{/js/jquery-1.12.4.min.js}" ></script>

View File

@ -51,6 +51,7 @@
</div>
</div>
</div>
<div th:insert="redis-log-widget :: redisLogWidget"></div>
</div>
</div>
<script type="text/javascript" th:src="@{/js/jquery-1.12.4.min.js}" ></script>

View File

@ -53,6 +53,7 @@
</div>
</div>
<div th:insert="redis-log-widget :: redisLogWidget"></div>
</div>
<script type="text/javascript" th:src="@{/js/jquery-1.12.4.min.js}" ></script>

View File

@ -32,6 +32,7 @@
<span id="warning_msg" th:utext="${errorMsg}">He show some error message.</span>
</div>
</div>
<div th:insert="redis-log-widget :: redisLogWidget"></div>
</div>
</div>
<script type="text/javascript" th:src="@{/js/jquery-1.12.4.min.js}" ></script>

View File

@ -58,6 +58,7 @@
</div>
</div>
</div>
<div th:insert="redis-log-widget :: redisLogWidget"></div>
</div>
</div>
<script type="text/javascript" th:src="@{/js/jquery-1.12.4.min.js}" ></script>

View File

@ -0,0 +1,15 @@
<!-- redis-log-widget.html -->
<div th:fragment="redisLogWidget">
<hr>
<div class="text-center">
<button class="btn btn-info" type="button" data-toggle="collapse" data-target="#logEntriesContent" aria-expanded="false" aria-controls="logEntriesContent">
Show/Hide log of Hydra Identity Provider app
</button>
</div>
<div class="collapse mt-3" id="logEntriesContent">
<div class="alert alert-info alert-dismissible col-md-12" role="alert" th:if="${logEntriesForHtml}">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span id="log_entries_msg" th:utext="${logEntriesForHtml}" style="white-space: pre-wrap;">Not implemented</span>
</div>
</div>
</div>

View File

@ -64,6 +64,8 @@
</div>
</div>
</div>
<div th:insert="redis-log-widget :: redisLogWidget"></div>
</div>
</div>
</div>
<script type="text/javascript" th:src="@{/js/jquery-1.12.4.min.js}" ></script>