feature/Add utility to list and delete consents

This commit is contained in:
Marko Milić 2021-05-07 13:51:54 +02:00
parent 7164fa1970
commit d6e1991ac8
5 changed files with 278 additions and 53 deletions

View File

@ -125,7 +125,9 @@ public class RestTemplateConfig {
// Get HTTP body from the response
String httpBody = "";
try {
httpBody = getHttpResponseBody(response.getEntity().getContent()).toString();
if(response.getEntity() != null && response.getEntity().getContent() != null) {
httpBody = getHttpResponseBody(response.getEntity().getContent()).toString();
}
} catch (IOException e) {
e.printStackTrace();
}

View File

@ -3,6 +3,7 @@ package com.openbankproject.oauth2.controller;
import com.nimbusds.jose.util.X509CertUtils;
import com.openbankproject.oauth2.model.AccessToViewRequest;
import com.openbankproject.oauth2.model.Accounts;
import com.openbankproject.oauth2.model.ConsentsInfo;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@ -47,10 +48,14 @@ public class ConsentController {
private String getAccountsUrl;
@Value("${obp.base_url}/berlin-group/v1.3/consents")
private String createBerlinGroupConsentsUrl;
@Value("${obp.base_url}/obp/v4.0.0/banks/BANK_ID/my/consents")
private String getConsentsUrl;
@Value("${obp.base_url}/berlin-group/v1.3/consents/CONSENT_ID/authorisations")
private String startConsentAuthorisation;
@Value("${obp.base_url}/berlin-group/v1.3/consents/CONSENT_ID/authorisations/AUTHORISATION_ID")
private String updateConsentsPsuData;
@Value("${obp.base_url}/berlin-group/v1.3/consents/CONSENT_ID")
private String deleteConsentBerlinGroup;
@Value("${oauth2.admin_url}/keys/${oauth2.broadcast_keys:hydra.jwt.access-token}")
private String keySetUrl;
@Value("${show_unhandled_errors:false}")
@ -94,8 +99,17 @@ public class ConsentController {
return "error";
}
String bankId = (String) session.getAttribute("bank_id");
String consentId = (String) session.getAttribute("consent_id");
if(consentId.equalsIgnoreCase("Utility-List-Consents")) {
HttpHeaders headers = buildDirectLoginHeader(session);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<ConsentsInfo> consents = restTemplate.exchange(getConsentsUrl.replace("BANK_ID", bankId), HttpMethod.GET, entity, ConsentsInfo.class);
model.addAttribute("consents", consents.getBody().getConsents());
return "consents";
}
{ // prepare account list
String bankId = (String) session.getAttribute("bank_id");
String apiStandard = (String) session.getAttribute("api_standard");
model.addAttribute("apiStandard", apiStandard);
HttpHeaders headers = buildDirectLoginHeader(session);
@ -169,6 +183,38 @@ public class ConsentController {
}
@PostMapping(value="/revoke_consents", params = "consent_challenge")
public String revokeConsents(@RequestParam String consent_challenge,
@RequestParam(value="consents", required = false) String[] consentIds,
@RequestParam(value="deny",required = false) String deny,
HttpSession session, Model model) throws NoSuchAlgorithmException, ApiException {
if(StringUtils.isNotBlank(deny)) {
final RejectRequest rejectRequest = new RejectRequest().error("access_denied").errorDescription("The resource owner denied the request");
final CompletedRequest completedRequest = adminApi.rejectConsentRequest(consent_challenge, rejectRequest);
return "redirect:" + completedRequest.getRedirectTo();
}
if(ArrayUtils.isEmpty(consentIds)) {
model.addAttribute("errorMsg", "consents field is mandatory!");
return "error";
}
try { // Delete all selected consents
HttpHeaders headers = buildDirectLoginHeader(session);
HttpEntity<String> entity = new HttpEntity<>(headers);
for (String consentId : consentIds) {
String url = deleteConsentBerlinGroup.replace("CONSENT_ID", consentId);
ResponseEntity<Map> deletedConsent = restTemplate.exchange(url, HttpMethod.DELETE, entity, Map.class);
logger.debug("ConsentID: " + consentId + " is deleted: " + deletedConsent.getStatusCodeValue());
}
} catch (Exception e) {
logger.error("Cannot delete consents!", e);
model.addAttribute("errorMsg", "Cannot delete consents!");
return "error";
}
model.addAttribute("errorMsg", "All selected consents have been deleted!");
return "error";
}
@PostMapping(value="/sca1", params = "consent_challenge")
public String resetAccessToViews(@RequestParam String consent_challenge,
@RequestParam(value="accounts", required = false) String[] accountIs,

View File

@ -86,32 +86,36 @@ public class LoginController implements ServletContextAware {
String requestUrl = loginRequest.getRequestUrl();
String consentId = getConsentId(requestUrl);
String bankId = getBankId(requestUrl);
String iban = getIban(requestUrl);
String recurringIndicator = getRecurringIndicator(requestUrl);
String frequencyPerDay = getFrequencyPerDay(requestUrl);
String expirationTime = getExpirationTime(requestUrl);
String apiStandard = getApiStandard(requestUrl);
final List<String> acrValues = loginRequest.getOidcContext().getAcrValues();
if(bankId == null) {
model.addAttribute("errorMsg", "Query parameter `bank_id` is mandatory! ");
return "error";
}
if(consentId == null) {
final String createConsentUrl = getConsentUrl.replace("/CONSENT_ID", "");
model.addAttribute(
"errorMsg", "Query parameter `consent_id` is mandatory! " +
"Hint: create client_credentials accessToken, create Account Access Consents by call endpoint `CreateAccountAccessConsents` (" +
createConsentUrl +
"), " +
"with header Authorization: Authorization: Bearer <accessToken>");
return "error";
}
// TODO acr value should do more validation
if(consentId.equalsIgnoreCase("Utility-List-Consents")) {
session.setAttribute("consent_id", consentId);
session.setAttribute("bank_id", bankId);
} else {
String iban = getIban(requestUrl);
String recurringIndicator = getRecurringIndicator(requestUrl);
String frequencyPerDay = getFrequencyPerDay(requestUrl);
String expirationTime = getExpirationTime(requestUrl);
String apiStandard = getApiStandard(requestUrl);
final List<String> acrValues = loginRequest.getOidcContext().getAcrValues();
if(bankId == null) {
model.addAttribute("errorMsg", "Query parameter `bank_id` is mandatory! ");
return "error";
}
if(consentId == null) {
final String createConsentUrl = getConsentUrl.replace("/CONSENT_ID", "");
model.addAttribute(
"errorMsg", "Query parameter `consent_id` is mandatory! " +
"Hint: create client_credentials accessToken, create Account Access Consents by call endpoint `CreateAccountAccessConsents` (" +
createConsentUrl +
"), " +
"with header Authorization: Authorization: Bearer <accessToken>");
return "error";
}
// TODO acr value should do more validation
// if(CollectionUtils.isEmpty(acrValues)) {
// model.addAttribute("errorMsg", "Query parameter `acr_values` is mandatory! ");
// return "error";
// }
// TODO in order make old consumer works, the request and request_uri are optional.
// TODO in order make old consumer works, the request and request_uri are optional.
// if(!requestUrl.contains("request") && !requestUrl.contains("request_uri")) {
// model.addAttribute(
// "errorMsg", "Query parameter `request` and `request_uri` at least one must be supplied! " +
@ -119,39 +123,39 @@ public class LoginController implements ServletContextAware {
// return "error";
// }
try {
if(!apiStandard.equalsIgnoreCase("BerlinGroup"))
{// validate consentId
Map<String, Object> responseBody = idVerifier.apply(getConsentUrl.replace("CONSENT_ID", consentId));
Map<String, Object> data = ((Map<String, Object>) responseBody.get("Data"));
if(data == null || data.isEmpty()) {
model.addAttribute("errorMsg", "Consent content have no required Data field");
return "error";
}
try {
if(!apiStandard.equalsIgnoreCase("BerlinGroup"))
{// validate consentId
Map<String, Object> responseBody = idVerifier.apply(getConsentUrl.replace("CONSENT_ID", consentId));
Map<String, Object> data = ((Map<String, Object>) responseBody.get("Data"));
if(data == null || data.isEmpty()) {
model.addAttribute("errorMsg", "Consent content have no required Data field");
return "error";
}
String status = ((String) data.get("Status"));
if(!"AWAITINGAUTHORISATION".equals(status)) {
model.addAttribute("errorMsg", "The Consent status should be AWAITINGAUTHORISATION, but current status is " + status);
return "error";
String status = ((String) data.get("Status"));
if(!"AWAITINGAUTHORISATION".equals(status)) {
model.addAttribute("errorMsg", "The Consent status should be AWAITINGAUTHORISATION, but current status is " + status);
return "error";
}
}
{// validate bankId
idVerifier.apply(getBankUrl.replace("BANK_ID", bankId));
}
} catch (Exception e) {
model.addAttribute("errorMsg", e.getMessage());
return "error";
}
{// validate bankId
idVerifier.apply(getBankUrl.replace("BANK_ID", bankId));
}
} catch (Exception e) {
model.addAttribute("errorMsg", e.getMessage());
return "error";
session.setAttribute("consent_id", consentId);
session.setAttribute("bank_id", bankId);
session.setAttribute("iban", iban);
session.setAttribute("recurring_indicator", recurringIndicator);
session.setAttribute("frequency_per_day", frequencyPerDay);
session.setAttribute("expiration_time", expirationTime);
session.setAttribute("api_standard", apiStandard);
session.setAttribute("acr_values", acrValues);
}
session.setAttribute("consent_id", consentId);
session.setAttribute("bank_id", bankId);
session.setAttribute("iban", iban);
session.setAttribute("recurring_indicator", recurringIndicator);
session.setAttribute("frequency_per_day", frequencyPerDay);
session.setAttribute("expiration_time", expirationTime);
session.setAttribute("api_standard", apiStandard);
session.setAttribute("acr_values", acrValues);
// login before and checked rememberMe.
if(loginRequest.getSkip() && session.getAttribute("directLoginToken") != null) {
AcceptLoginRequest acceptLoginRequest = new AcceptLoginRequest();

View File

@ -0,0 +1,88 @@
package com.openbankproject.oauth2.model;
public class ConsentsInfo {
private ConsentInfo[] consents;
public ConsentInfo[] getConsents() {
return consents;
}
public void setConsents(ConsentInfo[] consents) {
this.consents = consents;
}
}
class ConsentInfo {
private String consent_id;
private String consumer_id;
private String created_by_user_id;
private String last_action_date;
private String last_usage_date;
private String status;
private String api_standard;
private String api_version;
public String getConsent_id() {
return consent_id;
}
public void setConsent_id(String consent_id) {
this.consent_id = consent_id;
}
public String getConsumer_id() {
return consumer_id;
}
public void setConsumer_id(String consumer_id) {
this.consumer_id = consumer_id;
}
public String getCreated_by_user_id() {
return created_by_user_id;
}
public void setCreated_by_user_id(String created_by_user_id) {
this.created_by_user_id = created_by_user_id;
}
public String getLast_action_date() {
return last_action_date;
}
public void setLast_action_date(String last_action_date) {
this.last_action_date = last_action_date;
}
public String getLast_usage_date() {
return last_usage_date;
}
public void setLast_usage_date(String last_usage_date) {
this.last_usage_date = last_usage_date;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getApi_standard() {
return api_standard;
}
public void setApi_standard(String api_standard) {
this.api_standard = api_standard;
}
public String getApi_version() {
return api_version;
}
public void setApi_version(String api_version) {
this.api_version = api_version;
}
}

View File

@ -0,0 +1,85 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OBP Identity Provider - List Consents</title>
<!-- Bootstrap -->
<link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap.min.css}" />
<!--[if lt IE 9]>
<script type="text/javascript" th:src="@{/js/html5shiv.min.js}"></script>
<script type="text/javascript" th:src="@{/js/respond.min.js}" ></script>
<![endif]-->
</head>
<body>
<nav class="navbar navbar-light bg-light">
<a class="navbar-brand" th:href="@{${application.obp_url}}">
<img th:src="@{/images/bank-logo.png}" height="60" class="d-inline-block align-top" alt="">
</a>
</nav>
<div class="container">
<div class="row">
<div class="col-sm-6 col-lg-offset-3">
<h2>My Consents</h2>
<form th:action="@{/revoke_consents}" method="post" id="accept_form">
<div class="form-group">
<div>
<div class="checkbox" th:each="consent : ${consents}">
<label>
<input type="checkbox" name="consents" th:value="${consent.consent_id}">
<span th:text="${consent.consent_id}">Some account</span> - <span th:text="${consent.status}">Some account</span>
</label>
</div>
</div>
<span class="text-danger" id="accounts_error"></span>
</div>
<input type="hidden" th:name="consent_challenge" th:value="${consent_challenge}">
<div class="btn-toolbar">
<button type="button" class="btn btn-danger" id="deny_btn">Cancel</button>
<button type="submit" class="btn btn-success" name="submit" value="Confirm">Delete</button>
</div>
</form>
<form th:action="@{/revoke_consents}" method="post" id="reject_form" class="hidden">
<input type="hidden" th:name="consent_challenge" th:value="${consent_challenge}">
<input type="hidden" th:name="deny" th:value="deny">
</form>
</div>
</div>
</div>
<script type="text/javascript" th:src="@{/js/jquery-1.12.4.min.js}" ></script>
<script type="text/javascript" th:src="@{/js/jquery-validate-1.19.2.min.js}" ></script>
<script type="text/javascript" th:src="@{/js/bootstrap.min.js}" ></script>
<script type="text/javascript">
$(function(){
$("#accept_form").validate({
rules: {
consents: "required",
},
messages: {
consents: "At least select one consent",
},
errorPlacement: function(error, element) {
if (element.attr("name") == "consents") {
error.appendTo("#accounts_error");
} else {
error.insertAfter(element);
}
}
});
$('#deny_btn').click(function(){
$('#reject_form').submit();
});
});
</script>
</body>
</html>