diff --git a/docs/Configuration-Properties.md b/docs/Configuration-Properties.md index cd522705e..24f678e7e 100644 --- a/docs/Configuration-Properties.md +++ b/docs/Configuration-Properties.md @@ -26,15 +26,17 @@ The PowerAuth Server uses the following public configuration properties: ## Activation and Cryptography Configuration -| Property | Default | Note | -|---------------------------------------------------------------|-----------|---------------------------------------------------------------------------| -| `powerauth.service.crypto.activationValidityInMilliseconds` | `120000` | Default activation validity period in miliseconds | -| `powerauth.service.crypto.signatureMaxFailedAttempts` | `5` | Maximum failed attempts for signature verification | -| `powerauth.service.token.timestamp.validity` | `7200000` | PowerAuth MAC token timestamp validity in miliseconds | -| `powerauth.service.recovery.maxFailedAttempts` | `5` | Maximum failed attempts for activation recovery | -| `powerauth.service.secureVault.enableBiometricAuthentication` | `false` | Whether biometric authentication is enabled when accessing Secure Vault | -| `powerauth.server.db.master.encryption.key` | `_empty_` | Master DB encryption key for decryption of server private key in database | -| `powerauth.service.proximity-check.otp.length` | `8` | Length of OTP generated for proximity check | +| Property | Default | Note | +|---------------------------------------------------------------|-----------|-----------------------------------------------------------------------------------------| +| `powerauth.service.crypto.activationValidityInMilliseconds` | `120000` | Default activation validity period in miliseconds | +| `powerauth.service.crypto.signatureMaxFailedAttempts` | `5` | Maximum failed attempts for signature verification | +| `powerauth.service.token.timestamp.validity` | `7200000` | PowerAuth MAC token timestamp validity in miliseconds | +| `powerauth.service.recovery.maxFailedAttempts` | `5` | Maximum failed attempts for activation recovery | +| `powerauth.service.secureVault.enableBiometricAuthentication` | `false` | Whether biometric authentication is enabled when accessing Secure Vault | +| `powerauth.server.db.master.encryption.key` | `_empty_` | Master DB encryption key for decryption of server private key in database | +| `powerauth.service.proximity-check.otp.length` | `8` | Length of OTP generated for proximity check | +| `powerauth.service.pagination.default-page-size` | `100` | The default number of records per page when paginated results are requested | +| `powerauth.service.pagination.default-page-number` | `0` | The default page number when paginated results are requested. Page numbers start from 0 | ## HTTP Configuration diff --git a/docs/WebServices-Methods.md b/docs/WebServices-Methods.md index eb269b19c..6aaaff030 100644 --- a/docs/WebServices-Methods.md +++ b/docs/WebServices-Methods.md @@ -637,6 +637,8 @@ REST endpoint: `POST /rest/v3/activation/list` |----------|------|-------------| | `String` | `userId` | An identifier of a user | | `String` | `applicationId` | An identifier of an application | +| `Integer` | `pageNumber` | Optional. The number of the page to fetch in the paginated results. Starts from 0, where 0 refers to the first page. If not provided, defaults to 0. | +| `Integer` | `pageSize` | Optional. The number of records per page in the paginated results. This determines the total number of records shown in each page of results. If not provided, defaults to 100. | #### Response diff --git a/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/PowerAuthClient.java b/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/PowerAuthClient.java index d99cfa1ac..a64a545e8 100644 --- a/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/PowerAuthClient.java +++ b/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/PowerAuthClient.java @@ -413,10 +413,11 @@ CreateActivationResponse createActivation(String userId, Date timestampActivatio /** * Call the getActivationListForUser method of the PowerAuth 3.0 Server interface. + * This method will fetch the first page (page 0) of activations for the user, with a page size of 100. * * @param userId User ID to fetch the activations for. - * @return List of activation instances for given user. - * @throws PowerAuthClientException In case REST API call fails. + * @return List of activation instances for given user. Returns the first 100 activations. + * @throws PowerAuthClientException In case the REST API call fails. */ List getActivationListForUser(String userId) throws PowerAuthClientException; diff --git a/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/request/GetActivationListForUserRequest.java b/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/request/GetActivationListForUserRequest.java index 07ad2f4dc..79092b772 100644 --- a/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/request/GetActivationListForUserRequest.java +++ b/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/request/GetActivationListForUserRequest.java @@ -18,10 +18,11 @@ package com.wultra.security.powerauth.client.model.request; +import jakarta.validation.constraints.Min; import lombok.Data; /** - * Model class representing request for activation lisf for a given user. + * Model class representing request for activation list for a given user. * * @author Petr Dvorak, petr@wultra.com */ @@ -30,5 +31,9 @@ public class GetActivationListForUserRequest { private String userId; private String applicationId; + @Min(0) + private Integer pageNumber; + @Min(1) + private Integer pageSize; } diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/Application.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/Application.java index 10439c176..911ae0ea1 100644 --- a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/Application.java +++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/Application.java @@ -21,6 +21,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.scheduling.annotation.EnableScheduling; import java.security.Security; @@ -33,6 +34,7 @@ @SpringBootApplication @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "10m") +@ConfigurationPropertiesScan public class Application { static { diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/configuration/PowerAuthPageableConfiguration.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/configuration/PowerAuthPageableConfiguration.java new file mode 100644 index 000000000..0dddb4ba7 --- /dev/null +++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/configuration/PowerAuthPageableConfiguration.java @@ -0,0 +1,40 @@ +/* + * PowerAuth Server and related software components + * Copyright (C) 2023 Wultra s.r.o. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package io.getlime.security.powerauth.app.server.configuration; + +import jakarta.validation.constraints.Min; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration class that handles pagination settings in PowerAuth Server. + * This includes parameters for default page number and page size. + *

+ * 'defaultPageNumber' is the default page number that is used when no specific + * page number is provided. It has a minimum value of 0. + *

+ * 'defaultPageSize' is the default number of records per page when no specific + * page size is provided. It has a minimum value of 1. + *

+ * Both properties are read from the "powerauth.service.pagination" configuration block. + * + * @author Jan Dusil, jan.dusil@wultra.com + */ + +@ConfigurationProperties("powerauth.service.pagination") +public record PowerAuthPageableConfiguration(@Min(0) int defaultPageNumber, @Min(1) int defaultPageSize) { +} diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/controller/api/PowerAuthController.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/controller/api/PowerAuthController.java index ad89c2436..52b020d09 100644 --- a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/controller/api/PowerAuthController.java +++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/controller/api/PowerAuthController.java @@ -25,10 +25,7 @@ import io.getlime.security.powerauth.app.server.service.PowerAuthService; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; /** * Class implementing the RESTful controller for PowerAuth service. @@ -160,18 +157,21 @@ public ObjectResponse removeActivation(@RequestBody Ob } /** - * Call {@link PowerAuthService#getActivationListForUser(GetActivationListForUserRequest)} method and - * return the response. + * This endpoint calls the {@link PowerAuthService#getActivationListForUser(GetActivationListForUserRequest)} + * method and returns the response. It provides a list of activations for a given user and application ID. * - * @param request Activation list request. - * @return Activation list response. - * @throws Exception In case the service throws exception. + * @param request This is an {@link ObjectRequest} that contains a {@link GetActivationListForUserRequest}, which + * includes the user identifier and application identifier for which to retrieve activations. + * @return This endpoint returns an {@link ObjectResponse} that contains a {@link GetActivationListForUserResponse}, + * which includes the list of activations for the given user and application ID. + * @throws Exception In case the service throws an exception, it will be propagated and should be handled by the caller. */ @PostMapping("/activation/list") public ObjectResponse getActivationListForUser(@RequestBody ObjectRequest request) throws Exception { return new ObjectResponse<>("OK", powerAuthService.getActivationListForUser(request.getRequestObject())); } + /** * Call {@link PowerAuthService#lookupActivations(LookupActivationsRequest)} method and * return the response. diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/database/repository/ActivationRepository.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/database/repository/ActivationRepository.java index 3a48c2644..482bfe7d9 100644 --- a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/database/repository/ActivationRepository.java +++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/database/repository/ActivationRepository.java @@ -20,6 +20,7 @@ import io.getlime.security.powerauth.app.server.database.model.entity.ActivationRecordEntity; import io.getlime.security.powerauth.app.server.database.model.enumeration.ActivationStatus; import jakarta.persistence.LockModeType; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; @@ -75,7 +76,7 @@ public interface ActivationRepository extends JpaRepository findByUserId(String userId); + List findByUserId(String userId, Pageable pageable); /** * Find all activations for given user ID and application ID @@ -84,7 +85,7 @@ public interface ActivationRepository extends JpaRepository findByApplicationIdAndUserId(String applicationId, String userId); + List findByApplicationIdAndUserId(String applicationId, String userId, Pageable pageable); /** * Find the first activation associated with given application by the activation code. diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/PowerAuthService.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/PowerAuthService.java index 7e48b1deb..0a36908e8 100644 --- a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/PowerAuthService.java +++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/PowerAuthService.java @@ -24,6 +24,7 @@ import com.wultra.security.powerauth.client.model.request.*; import com.wultra.security.powerauth.client.model.response.*; import com.wultra.security.powerauth.client.model.validator.*; +import io.getlime.security.powerauth.app.server.configuration.PowerAuthPageableConfiguration; import io.getlime.security.powerauth.app.server.configuration.PowerAuthServiceConfiguration; import io.getlime.security.powerauth.app.server.converter.ActivationStatusConverter; import io.getlime.security.powerauth.app.server.database.model.enumeration.ActivationStatus; @@ -43,6 +44,8 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.info.BuildProperties; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -69,6 +72,8 @@ public class PowerAuthService { private PowerAuthServiceConfiguration powerAuthServiceConfiguration; + private PowerAuthPageableConfiguration powerAuthPageableConfiguration; + private ServiceBehaviorCatalogue behavior; private LocalizationProvider localizationProvider; @@ -82,6 +87,11 @@ public void setPowerAuthServiceConfiguration(PowerAuthServiceConfiguration power this.powerAuthServiceConfiguration = powerAuthServiceConfiguration; } + @Autowired + public void setPowerAuthPageableConfiguration(PowerAuthPageableConfiguration powerAuthPageableConfiguration) { + this.powerAuthPageableConfiguration = powerAuthPageableConfiguration; + } + @Autowired public void setBehavior(ServiceBehaviorCatalogue behavior) { this.behavior = behavior; @@ -146,8 +156,11 @@ public GetActivationListForUserResponse getActivationListForUser(GetActivationLi try { final String userId = request.getUserId(); final String applicationId = request.getApplicationId(); + final int pageNumber = request.getPageNumber() != null ? request.getPageNumber() : powerAuthPageableConfiguration.defaultPageNumber(); + final int pageSize = request.getPageSize() != null ? request.getPageSize() : powerAuthPageableConfiguration.defaultPageSize(); + final Pageable pageable = PageRequest.of(pageNumber, pageSize); logger.info("GetActivationListForUserRequest received, user ID: {}, application ID: {}", userId, applicationId); - final GetActivationListForUserResponse response = behavior.getActivationServiceBehavior().getActivationList(applicationId, userId); + final GetActivationListForUserResponse response = behavior.getActivationServiceBehavior().getActivationList(applicationId, userId, pageable); logger.info("GetActivationListForUserRequest succeeded"); return response; } catch (RuntimeException | Error ex) { diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/behavior/tasks/ActivationServiceBehavior.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/behavior/tasks/ActivationServiceBehavior.java index 5c0985fe2..d73d7ee2d 100644 --- a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/behavior/tasks/ActivationServiceBehavior.java +++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/behavior/tasks/ActivationServiceBehavior.java @@ -64,6 +64,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -229,13 +230,18 @@ private void validateCreatedActivation(ActivationRecordEntity activation, Applic } /** - * Get activations for application ID and user ID + * Fetch a paginated list of activations for a given application ID and user ID. * - * @param applicationId Application ID - * @param userId User ID - * @return Response with list of matching activations + * @param applicationId The Application ID for which to retrieve activations. If this is null, activations for all + * applications associated with the provided user ID are retrieved. + * @param userId The User ID for which to retrieve activations. This is required and cannot be null. + * @param pageable An object that defines the pagination properties, including the page number and the size of each page. + * It is used to retrieve the activations in a paginated format. + * @return A {@link GetActivationListForUserResponse} object that includes the list of matching activations. Each + * activation is represented as an {@link Activation} object. The response also includes the user ID associated + * with the activations. */ - public GetActivationListForUserResponse getActivationList(String applicationId, String userId) { + public GetActivationListForUserResponse getActivationList(String applicationId, String userId, Pageable pageable) { // Generate timestamp in advance final Date timestamp = new Date(); @@ -245,9 +251,9 @@ public GetActivationListForUserResponse getActivationList(String applicationId, List activationsList; if (applicationId == null) { - activationsList = activationRepository.findByUserId(userId); + activationsList = activationRepository.findByUserId(userId, pageable); } else { - activationsList = activationRepository.findByApplicationIdAndUserId(applicationId, userId); + activationsList = activationRepository.findByApplicationIdAndUserId(applicationId, userId, pageable); } final GetActivationListForUserResponse response = new GetActivationListForUserResponse(); diff --git a/powerauth-java-server/src/main/resources/application.properties b/powerauth-java-server/src/main/resources/application.properties index dfe6aa125..06cb7e837 100644 --- a/powerauth-java-server/src/main/resources/application.properties +++ b/powerauth-java-server/src/main/resources/application.properties @@ -105,6 +105,10 @@ spring.application.name=powerauth-java-server spring.cloud.vault.enabled=false spring.cloud.vault.kv.enabled=true +# Configure Spring Pageable properties +powerauth.service.pagination.default-page-size=100 +powerauth.service.pagination.default-page-number=0 + # Configure Correlation HTTP Header powerauth.service.correlation-header.enabled=false powerauth.service.correlation-header.name=X-Correlation-ID