Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #1684: Update Client and Admin Console #1685

Merged
merged 3 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@

import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -157,7 +160,7 @@ public String applicationVersionCreate(@PathVariable("applicationId") String id,
* @param model Model with passed parameters.
* @return "callbackCreate" view.
*/
@GetMapping("/application/detail/{applicationId}/callback/create")
@GetMapping("/application/detail/{applicationId}/callback/create/form")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing URL is not backward compatible but for Admin API we may afford that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think we can assume those are just called internally.

public String applicationCreateCallback(@PathVariable("applicationId") String id, Map<String, Object> model) {
model.put("applicationId", id);
return "callbackCreate";
Expand All @@ -171,7 +174,7 @@ public String applicationCreateCallback(@PathVariable("applicationId") String id
* @param model Model with passed parameters.
* @return "callbackUpdate" view.
*/
@PostMapping("/application/detail/{applicationId}/callback/update")
@GetMapping("/application/detail/{applicationId}/callback/update/form")
public String applicationUpdateCallback(@PathVariable("applicationId") String applicationId,
@RequestParam String callbackId,
Map<String, Object> model) {
Expand Down Expand Up @@ -226,6 +229,10 @@ public String applicationUpdateCallback(@PathVariable("applicationId") String ap
model.put("auth_oAuth2Scope", oAuth2.getScope());
model.put("auth_oAuth2TokenUri", oAuth2.getTokenUri());
}

model.put("retentionPeriod", callback.getRetentionPeriod());
model.put("initialBackoff", callback.getInitialBackoff());
model.put("maxAttempts", callback.getMaxAttempts());
}
return "callbackUpdate";
}
Expand Down Expand Up @@ -353,6 +360,19 @@ public String applicationCreateCallbackAction(
if (errorAuth != null) {
error = errorAuth;
}

if (!isValidDurationOrNull(allParams.get("retentionPeriod"))) {
error = "Retention period must be in ISO 8601 Duration format.";
}

if (!isValidDurationOrNull(allParams.get("initialBackoff"))) {
error = "Initial backoff must be in ISO 8601 Duration format.";
}

if (!isPositiveIntegerOrNull(allParams.get("maxAttempts"))) {
error = "Max attempts must be a positive integer.";
}

if (error != null) {
for (String attribute: CALLBACK_ATTRIBUTES_OPTIONAL) {
if (allParams.get(attribute) != null) {
Expand All @@ -362,7 +382,7 @@ public String applicationCreateCallbackAction(
redirectAttributes.addFlashAttribute("error", error);
redirectAttributes.addFlashAttribute("name", name);
redirectAttributes.addFlashAttribute("callbackUrl", callbackUrl);
return "redirect:/application/detail/" + applicationId + "/callback/create";
return "redirect:/application/detail/" + applicationId + "/callback/create/form";
}
List<String> attributes = new ArrayList<>();
attributes.add("activationId");
Expand All @@ -372,7 +392,10 @@ public String applicationCreateCallbackAction(
}
}
HttpAuthenticationPrivate httpAuthentication = prepareHttpAuthentication(allParams);
client.createCallbackUrl(applicationId, name, CallbackUrlType.ACTIVATION_STATUS_CHANGE, callbackUrl, attributes, httpAuthentication);
final Integer maxAttempts = parse(allParams.get("maxAttempts"), Integer::parseInt);
final Duration retentionPeriod = parse(allParams.get("retentionPeriod"), Duration::parse);
final Duration initialBackoff = parse(allParams.get("initialBackoff"), Duration::parse);
client.createCallbackUrl(applicationId, name, CallbackUrlType.ACTIVATION_STATUS_CHANGE, callbackUrl, attributes, httpAuthentication, retentionPeriod, initialBackoff, maxAttempts);
return "redirect:/application/detail/" + applicationId + "#callbacks";
} catch (PowerAuthClientException ex) {
logger.warn(ex.getMessage(), ex);
Expand Down Expand Up @@ -413,12 +436,25 @@ public String applicationUpdateCallbackAction(
if (errorAuth != null) {
error = errorAuth;
}

if (!isValidDurationOrNull(allParams.get("retentionPeriod"))) {
error = "Retention period must be in ISO 8601 Duration format.";
}

if (!isValidDurationOrNull(allParams.get("initialBackoff"))) {
error = "Initial backoff must be in ISO 8601 Duration format.";
}

if (!isPositiveIntegerOrNull(allParams.get("maxAttempts"))) {
error = "Max attempts must be a positive integer.";
}

if (error != null) {
redirectAttributes.addAttribute("callbackId", callbackId);
redirectAttributes.addFlashAttribute("error", error);
redirectAttributes.addFlashAttribute("name", name);
redirectAttributes.addFlashAttribute("callbackUrl", callbackUrl);
return "redirect:/application/detail/" + applicationId + "/callback/update";
return "redirect:/application/detail/" + applicationId + "/callback/update/form";
}
List<String> attributes = new ArrayList<>();
attributes.add("activationId");
Expand All @@ -428,7 +464,10 @@ public String applicationUpdateCallbackAction(
}
}
HttpAuthenticationPrivate httpAuthentication = prepareHttpAuthentication(allParams);
client.updateCallbackUrl(callbackId, applicationId, name, callbackUrl, attributes, httpAuthentication);
final Integer maxAttempts = parse(allParams.get("maxAttempts"), Integer::parseInt);
final Duration retentionPeriod = parse(allParams.get("retentionPeriod"), Duration::parse);
final Duration initialBackoff = parse(allParams.get("initialBackoff"), Duration::parse);
client.updateCallbackUrl(callbackId, applicationId, name, CallbackUrlType.ACTIVATION_STATUS_CHANGE, callbackUrl, attributes, httpAuthentication, retentionPeriod, initialBackoff, maxAttempts);
return "redirect:/application/detail/" + applicationId + "#callbacks";
} catch (PowerAuthClientException ex) {
logger.warn(ex.getMessage(), ex);
Expand Down Expand Up @@ -636,4 +675,26 @@ private static <T> List<T> reverse(final List<T> source) {
return target;
}

private static boolean isValidDurationOrNull(final String value) {
try {
parse(value, Duration::parse);
return true;
} catch (DateTimeParseException e) {
return false;
}
}

private static boolean isPositiveIntegerOrNull(final String value) {
try {
final Integer parsed = parse(value, Integer::parseInt);
return parsed == null || parsed > 0;
} catch (NumberFormatException e) {
return false;
}
}

private static <T> T parse(final String value, final Function<String, T> parser) {
return StringUtils.hasText(value) ? parser.apply(value) : null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title button-sm pull-left">Callbacks</h3>
<a href="${pageContext.request.contextPath}/application/detail/<c:out value="${id}"/>/callback/create" class="btn btn-sm btn-default pull-right">Add Callback</a>
<a href="${pageContext.request.contextPath}/application/detail/<c:out value="${id}"/>/callback/create/form" class="btn btn-sm btn-default pull-right">Add Callback</a>
<div class="clearfix"></div>
</div>

Expand Down Expand Up @@ -178,7 +178,7 @@
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<input type="submit" value="Remove" class="btn btn-sm btn-danger pull-right btn-table"/>
</form>
<form action="${pageContext.request.contextPath}/application/detail/<c:out value="${id}"/>/callback/update" method="POST" class="pull-right">
<form action="${pageContext.request.contextPath}/application/detail/<c:out value="${id}"/>/callback/update/form" method="GET" class="pull-right">
<input type="hidden" name="callbackId" value="<c:out value="${item.id}"/>"/>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<input type="submit" value="Update" class="btn btn-sm btn-success pull-right btn-table"/>
Expand Down
21 changes: 21 additions & 0 deletions powerauth-admin/src/main/webapp/WEB-INF/jsp/callbackCreate.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,27 @@
</div>
</div>
</div>
<div class="row">
<h4 class="panel-heading">Retry Policy and Retention</h4>
</div>
<div class="form-group">
<label for="maxAttempts" class="col-sm-3 control-label">Max Attempts</label>
<div class="col-sm-9">
<input type="number" id="maxAttempts" name="maxAttempts" class="form-control" value="${maxAttempts}" min="1"/>
</div>
</div>
<div class="form-group">
<label for="initialBackoff" class="col-sm-3 control-label">Initial Backoff</label>
<div class="col-sm-9">
<input type="text" id="initialBackoff" name="initialBackoff" class="form-control" value="${initialBackoff}"/>
</div>
</div>
<div class="form-group">
<label for="retentionPeriod" class="col-sm-3 control-label">Retention Period</label>
<div class="col-sm-9">
<input type="text" id="retentionPeriod" name="retentionPeriod" class="form-control" value="${retentionPeriod}"/>
</div>
</div>
<div class="form-group text-right">
<div class="col-sm-11">
<input type="hidden" name="auth_httpBasicPasswordChanged" id="auth_httpBasicPasswordChanged" value="false"/>
Expand Down
21 changes: 21 additions & 0 deletions powerauth-admin/src/main/webapp/WEB-INF/jsp/callbackUpdate.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,27 @@
</div>
</div>
</div>
<div class="row">
<h4 class="panel-heading">Retry Policy and Retention</h4>
</div>
<div class="form-group">
<label for="maxAttempts" class="col-sm-3 control-label">Max Attempts</label>
<div class="col-sm-9">
<input type="number" id="maxAttempts" name="maxAttempts" class="form-control" value="${maxAttempts}" min="1"/>
</div>
</div>
<div class="form-group">
<label for="initialBackoff" class="col-sm-3 control-label">Initial Backoff</label>
<div class="col-sm-9">
<input type="text" id="initialBackoff" name="initialBackoff" class="form-control" value="${initialBackoff}"/>
</div>
</div>
<div class="form-group">
<label for="retentionPeriod" class="col-sm-3 control-label">Retention Period</label>
<div class="col-sm-9">
<input type="text" id="retentionPeriod" name="retentionPeriod" class="form-control" value="${retentionPeriod}"/>
</div>
</div>
<div class="form-group text-right">
<div class="col-sm-11">
<input type="hidden" name="callbackId" value="<c:out value="${callbackId}"/>"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.getlime.core.rest.model.base.response.Response;
import org.springframework.util.MultiValueMap;

import java.time.Duration;
import java.util.Date;
import java.util.List;

Expand Down Expand Up @@ -1154,16 +1155,19 @@ VaultUnlockResponse unlockVault(String activationId, String applicationKey, Stri
/**
* Create a new callback URL with given parameters.
*
* @param applicationId Application ID.
* @param name Callback URL display name.
* @param type Callback type.
* @param callbackUrl Callback URL value.
* @param attributes Attributes to send in the callback data.
* @param authentication Callback request authentication.
* @param applicationId Application ID.
* @param name Callback URL display name.
* @param type Callback type.
* @param callbackUrl Callback URL value.
* @param attributes Attributes to send in the callback data.
* @param authentication Callback request authentication.
* @param retentionPeriod Duration after which a completed callback event is automatically removed.
* @param initialBackoff Initial delay before retry attempt following a callback event failure.
* @param maxAttempts Maximum number of attempts to send a callback event.
* @return Information about new callback URL object.
* @throws PowerAuthClientException In case REST API call fails.
*/
CreateCallbackUrlResponse createCallbackUrl(String applicationId, String name, CallbackUrlType type, String callbackUrl, List<String> attributes, HttpAuthenticationPrivate authentication) throws PowerAuthClientException;
CreateCallbackUrlResponse createCallbackUrl(String applicationId, String name, CallbackUrlType type, String callbackUrl, List<String> attributes, HttpAuthenticationPrivate authentication, Duration retentionPeriod, Duration initialBackoff, Integer maxAttempts) throws PowerAuthClientException;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carthago delenda est

In long term, I prefer using methods with request object only and mark these as deprecated.


/**
* Update a callback URL with given request object.
Expand All @@ -1188,16 +1192,20 @@ VaultUnlockResponse unlockVault(String activationId, String applicationKey, Stri
/**
* Update a callback URL with given parameters.
*
* @param id Callback URL identifier.
* @param applicationId Application ID.
* @param name Callback URL display name.
* @param callbackUrl Callback URL value.
* @param attributes Attributes to send in the callback data.
* @param authentication Callback request authentication.
* @param id Callback URL identifier.
* @param applicationId Application ID.
* @param name Callback URL display name.
* @param type Callback type.
* @param callbackUrl Callback URL value.
* @param attributes Attributes to send in the callback data.
* @param authentication Callback request authentication.
* @param retentionPeriod Duration after which a completed callback event is automatically removed.
* @param initialBackoff Initial delay before retry attempt following a callback event failure.
* @param maxAttempts Maximum number of attempts to send a callback event.
* @return Information about new callback URL object.
* @throws PowerAuthClientException In case REST API call fails.
*/
UpdateCallbackUrlResponse updateCallbackUrl(String id, String applicationId, String name, String callbackUrl, List<String> attributes, HttpAuthenticationPrivate authentication) throws PowerAuthClientException;
UpdateCallbackUrlResponse updateCallbackUrl(String id, String applicationId, String name, CallbackUrlType type, String callbackUrl, List<String> attributes, HttpAuthenticationPrivate authentication, Duration retentionPeriod, Duration initialBackoff, Integer maxAttempts) throws PowerAuthClientException;

/**
* Get the response with list of callback URL objects.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.time.Duration;
import java.util.Date;
import java.util.List;

Expand Down Expand Up @@ -866,7 +867,7 @@ public CreateCallbackUrlResponse createCallbackUrl(CreateCallbackUrlRequest requ
}

@Override
public CreateCallbackUrlResponse createCallbackUrl(String applicationId, String name, CallbackUrlType type, String callbackUrl, List<String> attributes, HttpAuthenticationPrivate authentication) throws PowerAuthClientException {
public CreateCallbackUrlResponse createCallbackUrl(String applicationId, String name, CallbackUrlType type, String callbackUrl, List<String> attributes, HttpAuthenticationPrivate authentication, Duration retentionPeriod, Duration initialBackoff, Integer maxAttempts) throws PowerAuthClientException {
final CreateCallbackUrlRequest request = new CreateCallbackUrlRequest();
request.setApplicationId(applicationId);
request.setName(name);
Expand All @@ -876,6 +877,9 @@ public CreateCallbackUrlResponse createCallbackUrl(String applicationId, String
request.getAttributes().addAll(attributes);
}
request.setAuthentication(authentication);
request.setRetentionPeriod(retentionPeriod);
request.setInitialBackoff(initialBackoff);
request.setMaxAttempts(maxAttempts);
return createCallbackUrl(request, EMPTY_MULTI_MAP, EMPTY_MULTI_MAP);
}

Expand All @@ -890,16 +894,20 @@ public UpdateCallbackUrlResponse updateCallbackUrl(UpdateCallbackUrlRequest requ
}

@Override
public UpdateCallbackUrlResponse updateCallbackUrl(String id, String applicationId, String name, String callbackUrl, List<String> attributes, HttpAuthenticationPrivate authentication) throws PowerAuthClientException {
public UpdateCallbackUrlResponse updateCallbackUrl(String id, String applicationId, String name, CallbackUrlType type, String callbackUrl, List<String> attributes, HttpAuthenticationPrivate authentication, Duration retentionPeriod, Duration initialBackoff, Integer maxAttempts) throws PowerAuthClientException {
final UpdateCallbackUrlRequest request = new UpdateCallbackUrlRequest();
request.setId(id);
request.setApplicationId(applicationId);
request.setName(name);
request.setType(type.toString());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Why is type a type of String instead of Enum? I know, we should solve this in a separate PR if agreed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about that too, but I didn't want to include this type of change in this PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request.setCallbackUrl(callbackUrl);
if (attributes != null) {
request.getAttributes().addAll(attributes);
}
request.setAuthentication(authentication);
request.setRetentionPeriod(retentionPeriod);
request.setInitialBackoff(initialBackoff);
request.setMaxAttempts(maxAttempts);
return updateCallbackUrl(request, EMPTY_MULTI_MAP, EMPTY_MULTI_MAP);
}

Expand Down