Skip to content

Commit

Permalink
Fix #752: Return activation lists with limits and paging (#941)
Browse files Browse the repository at this point in the history
* Fix #752: Return activation lists with limits and paging
- Add Pageable properties as to GetActivationListForUserRequest
- Create config for default pagination values
- Propagate Pageable config to repository queries
- Update documentation
  • Loading branch information
jandusil authored Aug 1, 2023
1 parent 22e1e9f commit ac97c2d
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 31 deletions.
20 changes: 11 additions & 9 deletions docs/Configuration-Properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions docs/WebServices-Methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Activation> getActivationListForUser(String userId) throws PowerAuthClientException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -30,5 +31,9 @@ public class GetActivationListForUserRequest {

private String userId;
private String applicationId;
@Min(0)
private Integer pageNumber;
@Min(1)
private Integer pageSize;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,6 +34,7 @@
@SpringBootApplication
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
@ConfigurationPropertiesScan
public class Application {

static {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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.
* <p>
* 'defaultPageNumber' is the default page number that is used when no specific
* page number is provided. It has a minimum value of 0.
* <p>
* 'defaultPageSize' is the default number of records per page when no specific
* page size is provided. It has a minimum value of 1.
* <p>
* 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) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -160,18 +157,21 @@ public ObjectResponse<RemoveActivationResponse> 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<GetActivationListForUserResponse> getActivationListForUser(@RequestBody ObjectRequest<GetActivationListForUserRequest> request) throws Exception {
return new ObjectResponse<>("OK", powerAuthService.getActivationListForUser(request.getRequestObject()));
}


/**
* Call {@link PowerAuthService#lookupActivations(LookupActivationsRequest)} method and
* return the response.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -75,7 +76,7 @@ public interface ActivationRepository extends JpaRepository<ActivationRecordEnti
* @param userId User ID
* @return List of activations for given user
*/
List<ActivationRecordEntity> findByUserId(String userId);
List<ActivationRecordEntity> findByUserId(String userId, Pageable pageable);

/**
* Find all activations for given user ID and application ID
Expand All @@ -84,7 +85,7 @@ public interface ActivationRepository extends JpaRepository<ActivationRecordEnti
* @param userId User ID
* @return List of activations for given user and application
*/
List<ActivationRecordEntity> findByApplicationIdAndUserId(String applicationId, String userId);
List<ActivationRecordEntity> findByApplicationIdAndUserId(String applicationId, String userId, Pageable pageable);

/**
* Find the first activation associated with given application by the activation code.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -69,6 +72,8 @@ public class PowerAuthService {

private PowerAuthServiceConfiguration powerAuthServiceConfiguration;

private PowerAuthPageableConfiguration powerAuthPageableConfiguration;

private ServiceBehaviorCatalogue behavior;

private LocalizationProvider localizationProvider;
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -245,9 +251,9 @@ public GetActivationListForUserResponse getActivationList(String applicationId,

List<ActivationRecordEntity> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit ac97c2d

Please sign in to comment.