From a60876be0a79c6fbbdb1f5fad5571cb72ade462e Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 2 Dec 2025 17:17:05 +0100 Subject: [PATCH] poc uaa throttling --- .../reactor/util/RequestLogger.java | 2 +- integration-test/pom.xml | 10 + .../IntegrationTestConfiguration.java | 30 +- .../org/cloudfoundry/ThrottlingUaaClient.java | 321 ++++++++++++++++++ .../src/test/resources/logback-test.xml | 2 +- 5 files changed, 349 insertions(+), 16 deletions(-) create mode 100644 integration-test/src/test/java/org/cloudfoundry/ThrottlingUaaClient.java diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/util/RequestLogger.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/util/RequestLogger.java index d0f3cc5ae3..4c94223c57 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/util/RequestLogger.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/util/RequestLogger.java @@ -34,7 +34,7 @@ public class RequestLogger { private long requestSentTime; public void request(HttpClientRequest request) { - request(String.format("%-6s {}", request.method()), request.uri()); + request(String.format("%-6s {}", request.method()), request.resourceUrl()); } public void response(HttpClientResponse response) { diff --git a/integration-test/pom.xml b/integration-test/pom.xml index ca0b9009f1..63cf18f873 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -70,6 +70,16 @@ ${project.version} test + + io.github.resilience4j + resilience4j-ratelimiter + 1.7.0 + + + io.github.resilience4j + resilience4j-reactor + 1.7.0 + org.cloudfoundry cloudfoundry-util diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index c0b3f44b30..5d1062f9fd 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -192,18 +192,19 @@ NetworkingClient adminNetworkingClient( @Bean @Qualifier("admin") - ReactorUaaClient adminUaaClient( + UaaClient adminUaaClient( ConnectionContext connectionContext, @Value("${test.admin.clientId}") String clientId, @Value("${test.admin.clientSecret}") String clientSecret) { - return ReactorUaaClient.builder() - .connectionContext(connectionContext) - .tokenProvider( - ClientCredentialsGrantTokenProvider.builder() - .clientId(clientId) - .clientSecret(clientSecret) - .build()) - .build(); + return new ThrottlingUaaClient( + ReactorUaaClient.builder() + .connectionContext(connectionContext) + .tokenProvider( + ClientCredentialsGrantTokenProvider.builder() + .clientId(clientId) + .clientSecret(clientSecret) + .build()) + .build()); } @Bean(initMethod = "block") @@ -643,11 +644,12 @@ PasswordGrantTokenProvider tokenProvider( } @Bean - ReactorUaaClient uaaClient(ConnectionContext connectionContext, TokenProvider tokenProvider) { - return ReactorUaaClient.builder() - .connectionContext(connectionContext) - .tokenProvider(tokenProvider) - .build(); + UaaClient uaaClient(ConnectionContext connectionContext, TokenProvider tokenProvider) { + return new ThrottlingUaaClient( + ReactorUaaClient.builder() + .connectionContext(connectionContext) + .tokenProvider(tokenProvider) + .build()); } @Bean(initMethod = "block") diff --git a/integration-test/src/test/java/org/cloudfoundry/ThrottlingUaaClient.java b/integration-test/src/test/java/org/cloudfoundry/ThrottlingUaaClient.java new file mode 100644 index 0000000000..0d8c6de7c9 --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/ThrottlingUaaClient.java @@ -0,0 +1,321 @@ +package org.cloudfoundry; + +import io.github.resilience4j.ratelimiter.RateLimiter; +import io.github.resilience4j.ratelimiter.RateLimiterConfig; +import io.github.resilience4j.reactor.ratelimiter.operator.RateLimiterOperator; +import java.time.Duration; +import org.cloudfoundry.uaa.UaaClient; +import org.cloudfoundry.uaa.authorizations.Authorizations; +import org.cloudfoundry.uaa.clients.Clients; +import org.cloudfoundry.uaa.groups.AddMemberRequest; +import org.cloudfoundry.uaa.groups.AddMemberResponse; +import org.cloudfoundry.uaa.groups.CheckMembershipRequest; +import org.cloudfoundry.uaa.groups.CheckMembershipResponse; +import org.cloudfoundry.uaa.groups.CreateGroupRequest; +import org.cloudfoundry.uaa.groups.CreateGroupResponse; +import org.cloudfoundry.uaa.groups.DeleteGroupRequest; +import org.cloudfoundry.uaa.groups.DeleteGroupResponse; +import org.cloudfoundry.uaa.groups.GetGroupRequest; +import org.cloudfoundry.uaa.groups.GetGroupResponse; +import org.cloudfoundry.uaa.groups.Groups; +import org.cloudfoundry.uaa.groups.ListExternalGroupMappingsRequest; +import org.cloudfoundry.uaa.groups.ListExternalGroupMappingsResponse; +import org.cloudfoundry.uaa.groups.ListGroupsRequest; +import org.cloudfoundry.uaa.groups.ListGroupsResponse; +import org.cloudfoundry.uaa.groups.ListMembersRequest; +import org.cloudfoundry.uaa.groups.ListMembersResponse; +import org.cloudfoundry.uaa.groups.MapExternalGroupRequest; +import org.cloudfoundry.uaa.groups.MapExternalGroupResponse; +import org.cloudfoundry.uaa.groups.RemoveMemberRequest; +import org.cloudfoundry.uaa.groups.RemoveMemberResponse; +import org.cloudfoundry.uaa.groups.UnmapExternalGroupByGroupDisplayNameRequest; +import org.cloudfoundry.uaa.groups.UnmapExternalGroupByGroupDisplayNameResponse; +import org.cloudfoundry.uaa.groups.UnmapExternalGroupByGroupIdRequest; +import org.cloudfoundry.uaa.groups.UnmapExternalGroupByGroupIdResponse; +import org.cloudfoundry.uaa.groups.UpdateGroupRequest; +import org.cloudfoundry.uaa.groups.UpdateGroupResponse; +import org.cloudfoundry.uaa.identityproviders.IdentityProviders; +import org.cloudfoundry.uaa.identityzones.IdentityZones; +import org.cloudfoundry.uaa.serverinformation.ServerInformation; +import org.cloudfoundry.uaa.tokens.Tokens; +import org.cloudfoundry.uaa.users.ChangeUserPasswordRequest; +import org.cloudfoundry.uaa.users.ChangeUserPasswordResponse; +import org.cloudfoundry.uaa.users.CreateUserRequest; +import org.cloudfoundry.uaa.users.CreateUserResponse; +import org.cloudfoundry.uaa.users.DeleteUserRequest; +import org.cloudfoundry.uaa.users.DeleteUserResponse; +import org.cloudfoundry.uaa.users.ExpirePasswordRequest; +import org.cloudfoundry.uaa.users.ExpirePasswordResponse; +import org.cloudfoundry.uaa.users.GetUserVerificationLinkRequest; +import org.cloudfoundry.uaa.users.GetUserVerificationLinkResponse; +import org.cloudfoundry.uaa.users.InviteUsersRequest; +import org.cloudfoundry.uaa.users.InviteUsersResponse; +import org.cloudfoundry.uaa.users.ListUsersRequest; +import org.cloudfoundry.uaa.users.ListUsersResponse; +import org.cloudfoundry.uaa.users.LookupUserIdsRequest; +import org.cloudfoundry.uaa.users.LookupUserIdsResponse; +import org.cloudfoundry.uaa.users.UpdateUserRequest; +import org.cloudfoundry.uaa.users.UpdateUserResponse; +import org.cloudfoundry.uaa.users.UserInfoRequest; +import org.cloudfoundry.uaa.users.UserInfoResponse; +import org.cloudfoundry.uaa.users.Users; +import org.cloudfoundry.uaa.users.VerifyUserRequest; +import org.cloudfoundry.uaa.users.VerifyUserResponse; +import reactor.core.publisher.Mono; + +public class ThrottlingUaaClient implements UaaClient { + + private final UaaClient delegate; + + private final RateLimiter rateLimiter; + private final ThrottledUsers users; + private Groups groups; + + public ThrottlingUaaClient(UaaClient delegate) { + this.delegate = delegate; + RateLimiterConfig config = + RateLimiterConfig.custom() + .limitForPeriod(5) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .build(); + this.rateLimiter = RateLimiter.of("uaa", config); + this.users = new ThrottledUsers(); + this.groups = new ThrottledGroups(); + } + + @Override + public Authorizations authorizations() { + return this.delegate.authorizations(); + } + + @Override + public Clients clients() { + return this.delegate.clients(); + } + + @Override + public Mono getUsername() { + return this.delegate.getUsername(); + } + + @Override + public IdentityProviders identityProviders() { + return this.delegate.identityProviders(); + } + + @Override + public IdentityZones identityZones() { + return this.delegate.identityZones(); + } + + @Override + public ServerInformation serverInformation() { + return this.delegate.serverInformation(); + } + + @Override + public Tokens tokens() { + return this.delegate.tokens(); + } + + @Override + public Users users() { + return users; + } + + @Override + public Groups groups() { + return groups; + } + + public class ThrottledUsers implements Users { + + private final Users usersDelegate; + + public ThrottledUsers() { + this.usersDelegate = delegate.users(); + } + + @Override + public Mono changePassword(ChangeUserPasswordRequest request) { + return this.usersDelegate + .changePassword(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono create(CreateUserRequest request) { + return this.usersDelegate + .create(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono delete(DeleteUserRequest request) { + return this.usersDelegate + .delete(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono expirePassword(ExpirePasswordRequest request) { + return this.usersDelegate + .expirePassword(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono getVerificationLink( + GetUserVerificationLinkRequest request) { + return this.usersDelegate + .getVerificationLink(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono invite(InviteUsersRequest request) { + return this.usersDelegate + .invite(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono list(ListUsersRequest request) { + return this.usersDelegate + .list(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono lookup(LookupUserIdsRequest request) { + return this.usersDelegate + .lookup(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono update(UpdateUserRequest request) { + return this.usersDelegate + .update(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono userInfo(UserInfoRequest request) { + return this.usersDelegate + .userInfo(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono verify(VerifyUserRequest request) { + return this.usersDelegate + .verify(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + } + + public class ThrottledGroups implements Groups { + + public final Groups groupsDelegate; + + public ThrottledGroups() { + this.groupsDelegate = delegate.groups(); + } + + @Override + public Mono addMember(AddMemberRequest request) { + return this.groupsDelegate + .addMember(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono checkMembership(CheckMembershipRequest request) { + return this.groupsDelegate + .checkMembership(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono create(CreateGroupRequest request) { + return this.groupsDelegate + .create(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono delete(DeleteGroupRequest request) { + return this.groupsDelegate + .delete(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono get(GetGroupRequest request) { + return this.groupsDelegate + .get(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono list(ListGroupsRequest request) { + return this.groupsDelegate + .list(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono listExternalGroupMappings( + ListExternalGroupMappingsRequest request) { + return this.groupsDelegate + .listExternalGroupMappings(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono listMembers(ListMembersRequest request) { + return this.groupsDelegate + .listMembers(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono mapExternalGroup(MapExternalGroupRequest request) { + return this.groupsDelegate + .mapExternalGroup(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono removeMember(RemoveMemberRequest request) { + return this.groupsDelegate + .removeMember(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono + unmapExternalGroupByGroupDisplayName( + UnmapExternalGroupByGroupDisplayNameRequest request) { + return this.groupsDelegate + .unmapExternalGroupByGroupDisplayName(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono unmapExternalGroupByGroupId( + UnmapExternalGroupByGroupIdRequest request) { + return this.groupsDelegate + .unmapExternalGroupByGroupId(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + + @Override + public Mono update(UpdateGroupRequest request) { + return this.groupsDelegate + .update(request) + .transformDeferred(RateLimiterOperator.of(rateLimiter)); + } + } +} diff --git a/integration-test/src/test/resources/logback-test.xml b/integration-test/src/test/resources/logback-test.xml index 4d9869fc6e..0872bbe5aa 100644 --- a/integration-test/src/test/resources/logback-test.xml +++ b/integration-test/src/test/resources/logback-test.xml @@ -26,7 +26,7 @@ - +