From 652b9e6ff5780e3c88ff8ebcc565f6fba2242775 Mon Sep 17 00:00:00 2001 From: eric Date: Wed, 29 Jan 2025 20:30:30 +0100 Subject: [PATCH 1/2] chore(dataplane): migrate LoginAttemptRepository to dataplane plugin fixes AM-4618 related-to AM-4569 --- .../am/dataplane/api/DataPlaneProvider.java | 3 + .../repository/LoginAttemptRepository.java | 37 ++++ .../api/search/LoginAttemptCriteria.java | 92 ++++++++ .../provider/MongoDataPlaneProvider.java | 9 + .../MongoLoginAttemptRepository.java | 171 +++++++++++++++ .../repository/model/LoginAttemptMongo.java | 115 ++++++++++ .../LoginAttemptRepositoryTest.java | 206 ++++++++++++++++++ .../services/impl/AccountServiceImpl.java | 1 + .../auth/user/UserAuthenticationService.java | 2 +- .../impl/UserAuthenticationManagerImpl.java | 8 +- .../impl/UserAuthenticationServiceImpl.java | 2 +- .../vertx/web/handler/SSOSessionHandler.java | 14 +- .../auth/UserAuthenticationManagerTest.java | 8 +- .../web/handler/SSOSessionHandlerTest.java | 4 +- .../loginattempt/LoginAttemptHandler.java | 6 +- .../service/user/impl/UserServiceImpl.java | 4 +- .../loginattempt/LoginAttemptHandlerTest.java | 2 +- .../root/service/user/UserServiceTest.java | 16 +- .../impl/ManagementUserServiceImpl.java | 8 +- .../service/ManagementUserServiceTest.java | 20 +- .../dataplane/core/DataPlaneRegistry.java | 3 + .../dataplane/core/DataPlaneRegistryImpl.java | 6 + .../gateway/api/RateLimitRepository.java | 2 +- .../gateway/api/VerifyAttemptRepository.java | 2 +- .../api/search/RateLimitCriteria.java | 2 +- .../api/search/VerifyAttemptCriteria.java | 2 +- .../gateway/api/JdbcRateLimitRepository.java | 2 +- .../api/JdbcVerifyAttemptRepository.java | 2 +- .../gateway/MongoRateLimitRepository.java | 2 +- .../gateway/MongoVerifyAttemptRepository.java | 2 +- .../gateway/api/RateLimitRepositoryTest.java | 2 +- .../api/VerifyAttemptRepositoryTest.java | 2 +- .../user/UpdateUsernameDomainRule.java | 4 +- .../am/service/LoginAttemptService.java | 13 +- .../service/impl/LoginAttemptServiceImpl.java | 46 ++-- .../service/impl/RateLimiterServiceImpl.java | 2 +- .../impl/VerifyAttemptServiceImpl.java | 2 +- .../user/UpdateUsernameDomainRuleTest.java | 12 +- .../am/service/LoginAttemptServiceTest.java | 9 +- 39 files changed, 745 insertions(+), 100 deletions(-) create mode 100644 gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/repository/LoginAttemptRepository.java create mode 100644 gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/search/LoginAttemptCriteria.java create mode 100644 gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/repository/MongoLoginAttemptRepository.java create mode 100644 gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/repository/model/LoginAttemptMongo.java create mode 100644 gravitee-am-dataplane/gravitee-am-dataplane-test/src/test/java/io/gravitee/am/dataplane/api/repository/LoginAttemptRepositoryTest.java rename gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/{management => gateway}/api/search/RateLimitCriteria.java (97%) rename gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/{management => gateway}/api/search/VerifyAttemptCriteria.java (97%) diff --git a/gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/DataPlaneProvider.java b/gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/DataPlaneProvider.java index 9879dc428f1..74becdc3fe8 100644 --- a/gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/DataPlaneProvider.java +++ b/gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/DataPlaneProvider.java @@ -18,6 +18,7 @@ import io.gravitee.am.dataplane.api.repository.CredentialRepository; import io.gravitee.am.dataplane.api.repository.DeviceRepository; import io.gravitee.am.dataplane.api.repository.GroupRepository; +import io.gravitee.am.dataplane.api.repository.LoginAttemptRepository; import io.gravitee.am.dataplane.api.repository.PasswordHistoryRepository; import io.gravitee.am.dataplane.api.repository.ScopeApprovalRepository; import io.gravitee.am.dataplane.api.repository.UserActivityRepository; @@ -40,4 +41,6 @@ public interface DataPlaneProvider { UserRepository getUserRepository(); PasswordHistoryRepository getPasswordHistoryRepository(); + + LoginAttemptRepository getLoginAttemptRepository(); } diff --git a/gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/repository/LoginAttemptRepository.java b/gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/repository/LoginAttemptRepository.java new file mode 100644 index 00000000000..efb237be889 --- /dev/null +++ b/gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/repository/LoginAttemptRepository.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.gravitee.am.dataplane.api.repository; + +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.model.LoginAttempt; +import io.gravitee.am.repository.common.CrudRepository; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Maybe; + +/** + * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) + * @author GraviteeSource Team + */ +public interface LoginAttemptRepository extends CrudRepository { + + Maybe findByCriteria(LoginAttemptCriteria criteria); + + Completable delete(LoginAttemptCriteria criteria); + + default Completable purgeExpiredData() { + return Completable.complete(); + } +} diff --git a/gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/search/LoginAttemptCriteria.java b/gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/search/LoginAttemptCriteria.java new file mode 100644 index 00000000000..32bcf7a9284 --- /dev/null +++ b/gravitee-am-dataplane/gravitee-am-dataplane-api/src/main/java/io/gravitee/am/dataplane/api/search/LoginAttemptCriteria.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.gravitee.am.dataplane.api.search; + +/** + * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) + * @author GraviteeSource Team + */ +public class LoginAttemptCriteria { + + private String domain; + private String client; + private String identityProvider; + private String username; + + public LoginAttemptCriteria(Builder builder) { + domain = builder.domain; + client = builder.client; + identityProvider = builder.identityProvider; + username = builder.username; + } + + public String domain() { + return domain; + } + + public String client() { + return client; + } + + public String identityProvider() { + return identityProvider; + } + + public String username() { + return username; + } + + public static class Builder { + private String domain; + private String client; + private String identityProvider; + private String username; + + public Builder domain(String domain) { + this.domain = domain; + return this; + } + + public Builder client(String client) { + this.client = client; + return this; + } + + public Builder identityProvider(String identityProvider) { + this.identityProvider = identityProvider; + return this; + } + + public Builder username(String username) { + this.username = username; + return this; + } + + public LoginAttemptCriteria build() { + return new LoginAttemptCriteria(this); + } + } + + @Override + public String toString() { + return "{\"_class\":\"LoginAttemptCriteria\", " + + "\"domain\":" + (domain == null ? "null" : "\"" + domain + "\"") + ", " + + "\"client\":" + (client == null ? "null" : "\"" + client + "\"") + ", " + + "\"identityProvider\":" + (identityProvider == null ? "null" : "\"" + identityProvider + "\"") + ", " + + "\"username\":" + (username == null ? "null" : "\"" + username + "\"") + + "}"; + } +} diff --git a/gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/provider/MongoDataPlaneProvider.java b/gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/provider/MongoDataPlaneProvider.java index 33346f43d3a..927542a0322 100644 --- a/gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/provider/MongoDataPlaneProvider.java +++ b/gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/provider/MongoDataPlaneProvider.java @@ -21,6 +21,7 @@ import io.gravitee.am.dataplane.api.repository.CredentialRepository; import io.gravitee.am.dataplane.api.repository.DeviceRepository; import io.gravitee.am.dataplane.api.repository.GroupRepository; +import io.gravitee.am.dataplane.api.repository.LoginAttemptRepository; import io.gravitee.am.dataplane.api.repository.PasswordHistoryRepository; import io.gravitee.am.dataplane.api.repository.ScopeApprovalRepository; import io.gravitee.am.dataplane.api.repository.UserActivityRepository; @@ -63,6 +64,9 @@ public class MongoDataPlaneProvider implements DataPlaneProvider, InitializingBe @Autowired private PasswordHistoryRepository passwordHistoryRepository; + @Autowired + private LoginAttemptRepository loginAttemptRepository; + @Override public void afterPropertiesSet() throws Exception { log.info("DataPlane provider loaded with id {}", dataPlaneDescription.id()); @@ -109,4 +113,9 @@ public UserRepository getUserRepository() { public PasswordHistoryRepository getPasswordHistoryRepository() { return passwordHistoryRepository; } + + @Override + public LoginAttemptRepository getLoginAttemptRepository() { + return loginAttemptRepository; + } } diff --git a/gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/repository/MongoLoginAttemptRepository.java b/gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/repository/MongoLoginAttemptRepository.java new file mode 100644 index 00000000000..67ae7892ef5 --- /dev/null +++ b/gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/repository/MongoLoginAttemptRepository.java @@ -0,0 +1,171 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.gravitee.am.dataplane.mongodb.repository; + +import com.mongodb.BasicDBObject; +import com.mongodb.client.model.IndexOptions; +import com.mongodb.reactivestreams.client.MongoCollection; +import io.gravitee.am.common.utils.RandomString; +import io.gravitee.am.dataplane.api.repository.LoginAttemptRepository; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.dataplane.mongodb.repository.model.LoginAttemptMongo; +import io.gravitee.am.model.LoginAttempt; +import io.gravitee.am.repository.mongodb.common.MongoUtils; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.core.Single; +import jakarta.annotation.PostConstruct; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.mongodb.client.model.Filters.and; +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.gte; +import static io.gravitee.am.repository.mongodb.common.MongoUtils.FIELD_CLIENT; +import static io.gravitee.am.repository.mongodb.common.MongoUtils.FIELD_DOMAIN; +import static io.gravitee.am.repository.mongodb.common.MongoUtils.FIELD_ID; + +/** + * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) + * @author GraviteeSource Team + */ +@Component +public class MongoLoginAttemptRepository extends AbstractDataPlaneMongoRepository implements LoginAttemptRepository { + + private static final String FIELD_IDP = "identityProvider"; + private static final String FIELD_USERNAME = "username"; + private static final String FIELD_EXPIRE_AT = "expireAt"; + private MongoCollection loginAttemptsCollection; + + @Autowired + private Environment environment; + + @PostConstruct + public void init() { + loginAttemptsCollection = mongoDatabase.getCollection("login_attempts", LoginAttemptMongo.class); + MongoUtils.init(loginAttemptsCollection); + + final var indexes = new HashMap(); + indexes.put(new Document(FIELD_DOMAIN, 1).append(FIELD_CLIENT, 1).append(FIELD_USERNAME, 1), new IndexOptions().name("d1c1u1")); + // expire after index + indexes.put(new Document(FIELD_EXPIRE_AT, 1), new IndexOptions().name("e1").expireAfter(0L, TimeUnit.SECONDS)); + + MongoUtils.createIndex(loginAttemptsCollection, indexes, getEnsureIndexOnStart()); + } + + @Override + public Maybe findById(String id) { + return Observable.fromPublisher(loginAttemptsCollection.find(and(eq(FIELD_ID, id), gte(FIELD_EXPIRE_AT, new Date()))).first()).firstElement().map(this::convert); + } + + @Override + public Maybe findByCriteria(LoginAttemptCriteria criteria) { + return Observable.fromPublisher(withMaxTime(loginAttemptsCollection.find(and(query(criteria), gte(FIELD_EXPIRE_AT, new Date())))).first()).firstElement().map(this::convert); + } + + @Override + public Single create(LoginAttempt item) { + LoginAttemptMongo loginAttempt = convert(item); + loginAttempt.setId(loginAttempt.getId() == null ? RandomString.generate() : loginAttempt.getId()); + return Single.fromPublisher(loginAttemptsCollection.insertOne(loginAttempt)).flatMap(success -> { + item.setId(loginAttempt.getId()); + return Single.just(item); + }); + } + + @Override + public Single update(LoginAttempt item) { + LoginAttemptMongo loginAttempt = convert(item); + return Single.fromPublisher(loginAttemptsCollection.replaceOne(eq(FIELD_ID, loginAttempt.getId()), loginAttempt)).flatMap(success -> Single.just(item)); + } + + @Override + public Completable delete(String id) { + return Completable.fromPublisher(loginAttemptsCollection.deleteOne(eq(FIELD_ID, id))); + } + + @Override + public Completable delete(LoginAttemptCriteria criteria) { + return Completable.fromPublisher(loginAttemptsCollection.deleteOne(query(criteria))); + } + + private Bson query(LoginAttemptCriteria criteria) { + List filters = new ArrayList<>(); + // domain + if (criteria.domain() != null && !criteria.domain().isEmpty()) { + filters.add(eq(FIELD_DOMAIN, criteria.domain())); + } + // client + if (criteria.client() != null && !criteria.client().isEmpty()) { + filters.add(eq(FIELD_CLIENT, criteria.client())); + } + // idp + if (criteria.identityProvider() != null && !criteria.identityProvider().isEmpty()) { + filters.add(eq(FIELD_IDP, criteria.identityProvider())); + } + // username + if (criteria.username() != null && !criteria.username().isEmpty()) { + filters.add(eq(FIELD_USERNAME, criteria.username())); + } + // build query + Bson query = (filters.isEmpty()) ? new BasicDBObject() : and(filters); + return query; + } + + private LoginAttempt convert(LoginAttemptMongo loginAttemptMongo) { + if (loginAttemptMongo == null) { + return null; + } + LoginAttempt loginAttempt = new LoginAttempt(); + loginAttempt.setId(loginAttemptMongo.getId()); + loginAttempt.setDomain(loginAttemptMongo.getDomain()); + loginAttempt.setClient(loginAttemptMongo.getClient()); + loginAttempt.setIdentityProvider(loginAttemptMongo.getIdentityProvider()); + loginAttempt.setUsername(loginAttemptMongo.getUsername()); + loginAttempt.setAttempts(loginAttemptMongo.getAttempts()); + loginAttempt.setExpireAt(loginAttemptMongo.getExpireAt()); + loginAttempt.setCreatedAt(loginAttemptMongo.getCreatedAt()); + loginAttempt.setUpdatedAt(loginAttemptMongo.getUpdatedAt()); + return loginAttempt; + } + + private LoginAttemptMongo convert(LoginAttempt loginAttempt) { + if (loginAttempt == null) { + return null; + } + LoginAttemptMongo loginAttemptMongo = new LoginAttemptMongo(); + loginAttemptMongo.setId(loginAttempt.getId()); + loginAttemptMongo.setDomain(loginAttempt.getDomain()); + loginAttemptMongo.setClient(loginAttempt.getClient()); + loginAttemptMongo.setIdentityProvider(loginAttempt.getIdentityProvider()); + loginAttemptMongo.setUsername(loginAttempt.getUsername()); + loginAttemptMongo.setAttempts(loginAttempt.getAttempts()); + loginAttemptMongo.setExpireAt(loginAttempt.getExpireAt()); + loginAttemptMongo.setCreatedAt(loginAttempt.getCreatedAt()); + loginAttemptMongo.setUpdatedAt(loginAttempt.getUpdatedAt()); + return loginAttemptMongo; + } +} diff --git a/gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/repository/model/LoginAttemptMongo.java b/gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/repository/model/LoginAttemptMongo.java new file mode 100644 index 00000000000..a06a872c0e4 --- /dev/null +++ b/gravitee-am-dataplane/gravitee-am-dataplane-mongodb/src/main/java/io/gravitee/am/dataplane/mongodb/repository/model/LoginAttemptMongo.java @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.gravitee.am.dataplane.mongodb.repository.model; + +import io.gravitee.am.repository.mongodb.common.model.Auditable; +import org.bson.codecs.pojo.annotations.BsonId; + +import java.util.Date; + +/** + * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) + * @author GraviteeSource Team + */ +public class LoginAttemptMongo extends Auditable { + + @BsonId + private String id; + private String domain; + private String client; + private String identityProvider; + private String username; + private int attempts; + private Date expireAt; + private Date createdAt; + private Date updatedAt; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public String getClient() { + return client; + } + + public void setClient(String client) { + this.client = client; + } + + public String getIdentityProvider() { + return identityProvider; + } + + public void setIdentityProvider(String identityProvider) { + this.identityProvider = identityProvider; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public int getAttempts() { + return attempts; + } + + public void setAttempts(int attempts) { + this.attempts = attempts; + } + + public Date getExpireAt() { + return expireAt; + } + + public void setExpireAt(Date expireAt) { + this.expireAt = expireAt; + } + + @Override + public Date getCreatedAt() { + return createdAt; + } + + @Override + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + @Override + public Date getUpdatedAt() { + return updatedAt; + } + + @Override + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/gravitee-am-dataplane/gravitee-am-dataplane-test/src/test/java/io/gravitee/am/dataplane/api/repository/LoginAttemptRepositoryTest.java b/gravitee-am-dataplane/gravitee-am-dataplane-test/src/test/java/io/gravitee/am/dataplane/api/repository/LoginAttemptRepositoryTest.java new file mode 100644 index 00000000000..cb1da0995c3 --- /dev/null +++ b/gravitee-am-dataplane/gravitee-am-dataplane-test/src/test/java/io/gravitee/am/dataplane/api/repository/LoginAttemptRepositoryTest.java @@ -0,0 +1,206 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.gravitee.am.dataplane.api.repository; + +import io.gravitee.am.model.LoginAttempt; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.reactivex.rxjava3.observers.TestObserver; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.time.Instant; +import java.util.Date; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * @author Eric LELEU (eric.leleu at graviteesource.com) + * @author GraviteeSource Team + */ +public class LoginAttemptRepositoryTest extends AbstractDataPlaneTest { + @Autowired + protected LoginAttemptRepository repository; + + @Test + public void shouldCreate() { + LoginAttempt attempt = buildLoginAttempt(); + + TestObserver testObserver = repository.create(attempt).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + testObserver.assertValue(l -> l.getId() != null); + assertEqualsTo(attempt, testObserver); + } + + @Test + public void shouldFindById() { + LoginAttempt attempt = buildLoginAttempt(); + LoginAttempt createdAttempt = repository.create(attempt).blockingGet(); + + TestObserver testObserver = repository.findById(createdAttempt.getId()).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + testObserver.assertValue(l -> l.getId().equals(createdAttempt.getId())); + assertEqualsTo(attempt, testObserver); + } + + @Test + public void shouldFindByCriteria() { + LoginAttempt attempt = buildLoginAttempt(); + LoginAttempt createdAttempt = repository.create(attempt).blockingGet(); + + LoginAttempt unexpectedAttempt = buildLoginAttempt(); + repository.create(unexpectedAttempt).blockingGet(); + + TestObserver testObserver = repository.findByCriteria(new LoginAttemptCriteria.Builder() + .client(attempt.getClient()) + .domain(attempt.getDomain()) + .username(attempt.getUsername()) + .identityProvider(attempt.getIdentityProvider()) + .build()).test(); + + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + testObserver.assertValue(l -> l.getId().equals(createdAttempt.getId())); + assertEqualsTo(attempt, testObserver); + } + + @Test + public void shouldNotFindByCriteria_invalidDomain() { + LoginAttempt attempt = buildLoginAttempt(); + repository.create(attempt).blockingGet(); + + TestObserver testObserver = repository.findByCriteria(new LoginAttemptCriteria.Builder() + .client(attempt.getClient()) + .domain("unknown") + .username(attempt.getUsername()) + .identityProvider(attempt.getIdentityProvider()) + .build()).test(); + + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + testObserver.assertNoValues(); + } + + @Test + public void shouldDeleteByCriteria() { + // should be deleted + LoginAttempt attempt = buildLoginAttempt(); + LoginAttempt createdAttempt = repository.create(attempt).blockingGet(); + + TestObserver testObserver = repository.findById(createdAttempt.getId()).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + assertEqualsTo(createdAttempt, testObserver); + + // shouldn't be deleted + LoginAttempt unexpectedAttempt = buildLoginAttempt(); + LoginAttempt createdUnexpectedAttempt = repository.create(unexpectedAttempt).blockingGet(); + + testObserver = repository.findById(createdUnexpectedAttempt.getId()).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + assertEqualsTo(createdUnexpectedAttempt, testObserver); + + // delete one of LoginAttempt + TestObserver deleteObserver = repository.delete(new LoginAttemptCriteria.Builder() + .client(attempt.getClient()) + .domain(attempt.getDomain()) + .username(attempt.getUsername()) + .identityProvider(attempt.getIdentityProvider()) + .build()).test(); + + deleteObserver.awaitDone(10, TimeUnit.SECONDS); + deleteObserver.assertNoErrors(); + + // check delete successful + testObserver = repository.findById(createdAttempt.getId()).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + testObserver.assertNoValues(); + + // shouldn't be deleted + testObserver = repository.findById(createdUnexpectedAttempt.getId()).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + assertEqualsTo(createdUnexpectedAttempt, testObserver); + } + + @Test + public void shouldDeleteById() { + LoginAttempt attempt = buildLoginAttempt(); + LoginAttempt createdAttempt = repository.create(attempt).blockingGet(); + + TestObserver testObserver = repository.findById(createdAttempt.getId()).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + testObserver.assertValue(l -> l != null); + + TestObserver deleteObserver = repository.delete(createdAttempt.getId()).test(); + deleteObserver.awaitDone(10, TimeUnit.SECONDS); + deleteObserver.assertNoErrors(); + + testObserver = repository.findById(createdAttempt.getId()).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + testObserver.assertNoValues(); + } + + @Test + public void shouldUpdate() { + LoginAttempt attempt = buildLoginAttempt(); + LoginAttempt createdAttempt = repository.create(attempt).blockingGet(); + + TestObserver testObserver = repository.findById(createdAttempt.getId()).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + testObserver.assertValue(l -> l != null); + + LoginAttempt updatableAttempt = buildLoginAttempt(); + updatableAttempt.setId(createdAttempt.getId()); + updatableAttempt.setAttempts(654); + + TestObserver updateObserver = repository.update(updatableAttempt).test(); + updateObserver.awaitDone(10, TimeUnit.SECONDS); + updateObserver.assertNoErrors(); + updateObserver.assertValue( l -> l.getId().equals(createdAttempt.getId())); + assertEqualsTo(updatableAttempt, updateObserver); + } + + + private void assertEqualsTo(LoginAttempt attempt, TestObserver testObserver) { + testObserver.assertValue(l -> l.getAttempts() == attempt.getAttempts()); + testObserver.assertValue(l -> l.getClient().equals(attempt.getClient())); + testObserver.assertValue(l -> l.getDomain().equals(attempt.getDomain())); + testObserver.assertValue(l -> l.getUsername().equals(attempt.getUsername())); + testObserver.assertValue(l -> l.getIdentityProvider().equals(attempt.getIdentityProvider())); + } + + private LoginAttempt buildLoginAttempt() { + LoginAttempt attempt = new LoginAttempt(); + String random = UUID.randomUUID().toString(); + attempt.setAttempts(1); + attempt.setClient("client"+random); + attempt.setDomain("domain"+random); + attempt.setIdentityProvider("idp"+random); + attempt.setUsername("user"+random); + Date createdAt = new Date(); + attempt.setCreatedAt(createdAt); + attempt.setUpdatedAt(createdAt); + attempt.setExpireAt(new Date(Instant.now().plusSeconds(60).toEpochMilli())); + return attempt; + } +} diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-account/src/main/java/io/gravitee/am/gateway/handler/account/services/impl/AccountServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-account/src/main/java/io/gravitee/am/gateway/handler/account/services/impl/AccountServiceImpl.java index 6f861f34761..40037f80938 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-account/src/main/java/io/gravitee/am/gateway/handler/account/services/impl/AccountServiceImpl.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-account/src/main/java/io/gravitee/am/gateway/handler/account/services/impl/AccountServiceImpl.java @@ -118,6 +118,7 @@ public class AccountServiceImpl implements AccountService, InitializingBean { @Autowired private ScopeApprovalService scopeApprovalService; + @Autowired private LoginAttemptService loginAttemptService; diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/UserAuthenticationService.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/UserAuthenticationService.java index 24bb3a9426d..2405421d70d 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/UserAuthenticationService.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/UserAuthenticationService.java @@ -16,10 +16,10 @@ package io.gravitee.am.gateway.handler.common.auth.user; import io.gravitee.am.common.jwt.JWT; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.model.User; import io.gravitee.am.model.account.AccountSettings; import io.gravitee.am.model.oidc.Client; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; import io.gravitee.gateway.api.Request; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationManagerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationManagerImpl.java index 202f48b3ad5..bafdc7f1df7 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationManagerImpl.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationManagerImpl.java @@ -25,6 +25,7 @@ import io.gravitee.am.common.exception.authentication.UsernameNotFoundException; import io.gravitee.am.common.jwt.JWT; import io.gravitee.am.common.oauth2.Parameters; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.gateway.handler.common.auth.AuthenticationDetails; import io.gravitee.am.gateway.handler.common.auth.event.AuthenticationEvent; import io.gravitee.am.gateway.handler.common.auth.idp.IdentityProviderManager; @@ -41,7 +42,6 @@ import io.gravitee.am.model.idp.ApplicationIdentityProvider; import io.gravitee.am.model.oidc.Client; import io.gravitee.am.monitoring.provider.GatewayMetricProvider; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.PasswordService; import io.gravitee.common.event.EventManager; @@ -251,7 +251,7 @@ private Completable postAuthentication(Client client, String username, String so // check if user is locked return loginAttemptService - .checkAccount(criteria, accountSettings) + .checkAccount(domain, criteria, accountSettings) .map(Optional::of) .defaultIfEmpty(Optional.empty()) .flatMapCompletable(optLoginAttempt -> { @@ -263,7 +263,7 @@ private Completable postAuthentication(Client client, String username, String so // no exception clear login attempt if (userAuthentication.lastException() == null) { - return loginAttemptService.loginSucceeded(criteria); + return loginAttemptService.loginSucceeded(domain, criteria); } if (userAuthentication.lastException() instanceof BadCredentialsException) { @@ -271,7 +271,7 @@ private Completable postAuthentication(Client client, String username, String so // normally the IdP should respond with Maybe.empty() or UsernameNotFoundException // but we can't control custom IdP that's why we have to check user existence return userService.findByDomainAndUsernameAndSource(criteria.domain(), criteria.username(), criteria.identityProvider(), true) - .flatMapCompletable(user -> loginAttemptService.loginFailed(criteria, accountSettings) + .flatMapCompletable(user -> loginAttemptService.loginFailed(domain, criteria, accountSettings) .flatMapCompletable(loginAttempt -> { if (loginAttempt.isAccountLocked(accountSettings.getMaxLoginAttempts())) { return userAuthenticationService.lockAccount(criteria, accountSettings, client, user); diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationServiceImpl.java index 326c558fb40..36d05db2ae8 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationServiceImpl.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationServiceImpl.java @@ -26,6 +26,7 @@ import io.gravitee.am.common.policy.ExtensionPoint; import io.gravitee.am.common.utils.ConstantKeys; import io.gravitee.am.dataplane.api.repository.UserRepository; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.gateway.handler.common.auth.idp.IdentityProviderManager; import io.gravitee.am.gateway.handler.common.auth.user.EndUserAuthentication; import io.gravitee.am.gateway.handler.common.auth.user.UserAuthenticationService; @@ -45,7 +46,6 @@ import io.gravitee.am.model.account.AccountSettings; import io.gravitee.am.model.login.LoginSettings; import io.gravitee.am.model.oidc.Client; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; import io.gravitee.am.service.AuditService; import io.gravitee.am.service.exception.UserNotFoundException; import io.gravitee.am.service.reporter.builder.AuditBuilder; diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandler.java index e52df0046db..c6fe4e0f42f 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandler.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandler.java @@ -21,32 +21,32 @@ import io.gravitee.am.common.exception.authentication.AccountStatusException; import io.gravitee.am.common.exception.oauth2.InvalidRequestException; import io.gravitee.am.common.oauth2.Parameters; -import io.gravitee.am.gateway.handler.common.client.ClientSyncService; import io.gravitee.am.common.utils.ConstantKeys; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.gateway.handler.common.client.ClientSyncService; import io.gravitee.am.gateway.handler.common.vertx.web.handler.impl.CookieSession; import io.gravitee.am.model.Domain; import io.gravitee.am.model.UserIdentity; import io.gravitee.am.model.account.AccountSettings; import io.gravitee.am.model.oidc.Client; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; + import io.gravitee.am.service.AuthenticationFlowContextService; +import io.gravitee.am.service.LoginAttemptService; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; -import io.gravitee.am.service.LoginAttemptService; import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.ext.web.handler.HttpException; import io.vertx.rxjava3.ext.auth.User; import io.vertx.rxjava3.ext.web.RoutingContext; -import java.util.Date; -import java.util.HashSet; -import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -216,7 +216,7 @@ private void checkClient(RoutingContext context, io.gravitee.am.model.User user, .username(user.getUsername()) .build(); return loginAttemptService - .checkAccount(criteria, accountSettings) + .checkAccount(domain, criteria, accountSettings) .map(loginAttempt -> { if (loginAttempt.isAccountLocked(accountSettings.getMaxLoginAttempts())) { Map details = new HashMap<>(); diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/auth/UserAuthenticationManagerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/auth/UserAuthenticationManagerTest.java index 50172a2bd39..3a0edb67f94 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/auth/UserAuthenticationManagerTest.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/auth/UserAuthenticationManagerTest.java @@ -618,7 +618,7 @@ public Maybe loadUserByUsername(String } })); - when(loginAttemptService.checkAccount(any(), any())).thenReturn(Maybe.empty()); + when(loginAttemptService.checkAccount(any(), any(), any())).thenReturn(Maybe.empty()); TestObserver observer = userAuthenticationManager.authenticate(client, new Authentication() { @Override public Object getCredentials() { @@ -638,7 +638,7 @@ public AuthenticationContext getContext() { observer.assertError(BadCredentialsException.class); verify(userService, never()).findByDomainAndUsernameAndSource(anyString(), anyString(), anyString()); - verify(loginAttemptService, never()).loginFailed(any(), any()); + verify(loginAttemptService, never()).loginFailed(any(), any(), any()); verify(userAuthenticationService, never()).lockAccount(any(), any(), any(), any()); verify(eventManager, times(1)).publishEvent(eq(AuthenticationEvent.FAILURE), any()); } @@ -672,7 +672,7 @@ public Maybe loadUserByUsername(String when(domain.getId()).thenReturn("domain-id"); when(userService.findByDomainAndUsernameAndSource(anyString(), anyString(), anyString(), anyBoolean())).thenReturn(Maybe.empty()); - when(loginAttemptService.checkAccount(any(), any())).thenReturn(Maybe.empty()); + when(loginAttemptService.checkAccount(any(), any(), any())).thenReturn(Maybe.empty()); TestObserver observer = userAuthenticationManager.authenticate(client, new Authentication() { @Override public Object getCredentials() { @@ -692,7 +692,7 @@ public AuthenticationContext getContext() { observer.assertError(BadCredentialsException.class); verify(userService, times(1)).findByDomainAndUsernameAndSource(anyString(), anyString(), anyString(), anyBoolean()); - verify(loginAttemptService, never()).loginFailed(any(), any()); + verify(loginAttemptService, never()).loginFailed(any(), any(), any()); verify(userAuthenticationService, never()).lockAccount(any(), any(), any(), any()); verify(eventManager, times(1)).publishEvent(eq(AuthenticationEvent.FAILURE), any()); } diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandlerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandlerTest.java index 78857ffcb8b..28d38db8a78 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandlerTest.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandlerTest.java @@ -326,7 +326,7 @@ public void shouldInvoke_differentClient_sameIdp_UserBlocked() throws Exception when(domain.getAccountSettings()).thenReturn(accountSettings); final var attempts = new LoginAttempt(); attempts.setAttempts(2); - when(loginAttemptService.checkAccount(any(), any())).thenReturn(Maybe.just(attempts)); + when(loginAttemptService.checkAccount(any(), any(), any())).thenReturn(Maybe.just(attempts)); when(clientSyncService.findById(anyString())).thenReturn(Maybe.empty()); when(clientSyncService.findByClientId(anyString())).thenAnswer( @@ -390,7 +390,7 @@ public void shouldInvoke_differentClient_sameIdp_UserNotBlocked() throws Excepti when(domain.getAccountSettings()).thenReturn(accountSettings); final var attempts = new LoginAttempt(); attempts.setAttempts(1); - when(loginAttemptService.checkAccount(any(), any())).thenReturn(Maybe.just(attempts)); + when(loginAttemptService.checkAccount(any(), any(), any())).thenReturn(Maybe.just(attempts)); when(clientSyncService.findById(anyString())).thenReturn(Maybe.empty()); when(clientSyncService.findByClientId(anyString())).thenAnswer( diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandler.java index 44205fcb489..871d8e33960 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandler.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandler.java @@ -16,6 +16,8 @@ package io.gravitee.am.gateway.handler.root.resources.handler.loginattempt; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria.Builder; import io.gravitee.am.gateway.handler.common.auth.idp.IdentityProviderManager; import io.gravitee.am.gateway.handler.common.service.UserActivityGatewayService; import io.gravitee.am.model.ChallengeSettings; @@ -26,8 +28,6 @@ import io.gravitee.am.model.account.AccountSettings; import io.gravitee.am.model.idp.ApplicationIdentityProvider; import io.gravitee.am.model.oidc.Client; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria.Builder; import io.gravitee.am.service.LoginAttemptService; import io.reactivex.rxjava3.core.Maybe; import io.vertx.core.Handler; @@ -110,6 +110,6 @@ private Maybe> getLoginAttempt(Client client, String user } private Maybe> getLoginAttempt(AccountSettings accountSettings, LoginAttemptCriteria criteria) { - return loginAttemptService.checkAccount(criteria, accountSettings).map(Optional::ofNullable).switchIfEmpty(Maybe.just(Optional.empty())); + return loginAttemptService.checkAccount(domain, criteria, accountSettings).map(Optional::ofNullable).switchIfEmpty(Maybe.just(Optional.empty())); } } diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/service/user/impl/UserServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/service/user/impl/UserServiceImpl.java index 2983f9311e1..6eea013099f 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/service/user/impl/UserServiceImpl.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/service/user/impl/UserServiceImpl.java @@ -53,7 +53,7 @@ import io.gravitee.am.model.account.AccountSettings; import io.gravitee.am.model.factor.EnrolledFactor; import io.gravitee.am.model.oidc.Client; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.service.AuditService; import io.gravitee.am.service.DomainReadService; import io.gravitee.am.service.LoginAttemptService; @@ -447,7 +447,7 @@ public Single resetPassword(Client client, User user, io. .client(client.getId()) .username(user1.getUsername()) .build(); - return loginAttemptService.reset(criteria).andThen(Single.just(user1)); + return loginAttemptService.reset(domain, criteria).andThen(Single.just(user1)); }) // delete passwordless devices .flatMap(user1 -> { diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandlerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandlerTest.java index 1fd6c670829..f189bf5fba2 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandlerTest.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandlerTest.java @@ -91,7 +91,7 @@ public void setUp() { final LoginAttempt attempts = new LoginAttempt(); attempts.setAttempts(5); - doReturn(Maybe.just(attempts)).when(loginAttemptService).checkAccount(any(), any()); + doReturn(Maybe.just(attempts)).when(loginAttemptService).checkAccount(any(), any(), any()); spyRoutingContext = spy(new SpyRoutingContext()); loginAttemptHandler = new LoginAttemptHandler(domain, identityProviderManager, loginAttemptService, userActivityService); diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/service/user/UserServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/service/user/UserServiceTest.java index bcd51452ab2..031e5a71426 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/service/user/UserServiceTest.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/service/user/UserServiceTest.java @@ -206,7 +206,7 @@ public void shouldResetPassword_setForceResetPasswordToFalse() { when(identityProviderManager.getUserProvider(user.getSource())).thenReturn(Maybe.just(userProvider)); when(commonUserService.update(any())).thenReturn(Single.just(user)); when(commonUserService.enhance(any())).thenReturn(Single.just(user)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); var testObserver = userService.resetPassword(client, user).test(); testObserver.assertComplete(); @@ -258,7 +258,7 @@ public void shouldResetPassword_userInactive_forceRegistration() { when(identityProviderManager.getUserProvider(user.getSource())).thenReturn(Maybe.just(userProvider)); when(commonUserService.update(any())).thenReturn(Single.just(user)); when(commonUserService.enhance(any())).thenReturn(Single.just(user)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); var testObserver = userService.resetPassword(client, user).test(); testObserver.assertComplete(); @@ -266,7 +266,7 @@ public void shouldResetPassword_userInactive_forceRegistration() { verify(credentialService, never()).deleteByUserId(any(), any()); verify(tokenService, never()).deleteByUser(any()); - verify(loginAttemptService).reset(argThat(criteria -> criteria.client().equals(clientIdFromClient))); + verify(loginAttemptService).reset(any(), argThat(criteria -> criteria.client().equals(clientIdFromClient))); } @Test @@ -290,7 +290,7 @@ public void shouldResetPassword_userActive() { when(identityProviderManager.getUserProvider(user.getSource())).thenReturn(Maybe.just(userProvider)); when(commonUserService.update(any())).thenReturn(Single.just(user)); when(commonUserService.enhance(any())).thenReturn(Single.just(user)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); var testObserver = userService.resetPassword(client, user).test(); testObserver.assertComplete(); @@ -321,7 +321,7 @@ public void shouldResetPassword_externalIdEmpty() { when(identityProviderManager.getUserProvider(user.getSource())).thenReturn(Maybe.just(userProvider)); when(commonUserService.update(any())).thenReturn(Single.just(user)); when(commonUserService.enhance(any())).thenReturn(Single.just(user)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); var testObserver = userService.resetPassword(client, user).test(); testObserver.assertComplete(); @@ -356,7 +356,7 @@ public void shouldResetPassword_idpUserNotFound() { when(identityProviderManager.getUserProvider(user.getSource())).thenReturn(Maybe.just(userProvider)); when(commonUserService.update(any())).thenReturn(Single.just(user)); when(commonUserService.enhance(any())).thenReturn(Single.just(user)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); var testObserver = userService.resetPassword(client, user).test(); testObserver.assertComplete(); @@ -976,7 +976,7 @@ public void shouldResetPassword_delete_passwordless_devices() { when(identityProviderManager.getUserProvider(user.getSource())).thenReturn(Maybe.just(userProvider)); when(commonUserService.update(any())).thenReturn(Single.just(user)); when(commonUserService.enhance(any())).thenReturn(Single.just(user)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); when(credentialService.deleteByUserId(any(), any())).thenReturn(Completable.complete()); var testObserver = userService.resetPassword(client, user).test(); @@ -1012,7 +1012,7 @@ public void shouldResetPassword_Invalidate_Tokens() { when(identityProviderManager.getUserProvider(user.getSource())).thenReturn(Maybe.just(userProvider)); when(commonUserService.update(any())).thenReturn(Single.just(user)); when(commonUserService.enhance(any())).thenReturn(Single.just(user)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); when(tokenService.deleteByUser(any())).thenReturn(Completable.complete()); var testObserver = userService.resetPassword(client, user).test(); diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/ManagementUserServiceImpl.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/ManagementUserServiceImpl.java index f13feb811e7..bed3c5e8b5c 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/ManagementUserServiceImpl.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/ManagementUserServiceImpl.java @@ -48,7 +48,7 @@ import io.gravitee.am.model.oidc.Client; import io.gravitee.am.plugins.dataplane.core.DataPlaneRegistry; import io.gravitee.am.repository.management.api.search.FilterCriteria; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.service.ApplicationService; import io.gravitee.am.service.AuditService; import io.gravitee.am.service.LoginAttemptService; @@ -470,7 +470,7 @@ public Completable resetPassword(Domain domain, String userId, String password, .client(user.getClient()) .username(user.getUsername()) .build(); - return loginAttemptService.reset(criteria); + return loginAttemptService.reset(domain, criteria); }); } @@ -536,7 +536,7 @@ public Completable lock(Domain domain, String userId, io.gravitee.am.identitypro .username(user.getUsername()) .build(); final var action = new UpdateUserRule(userValidator, dataPlaneRegistry.getUserRepository(domain)::update); - return loginAttemptService.reset(criteria).andThen(action.update(user)); + return loginAttemptService.reset(domain, criteria).andThen(action.update(user)); }); }) .doOnSuccess(user1 -> auditService.report(AuditBuilder.builder(UserAuditBuilder.class).principal(principal).type(EventType.USER_LOCKED).user(user1))) @@ -560,7 +560,7 @@ public Completable unlock(Domain domain, String userId, io.gravitee.am.identityp .username(user.getUsername()) .build(); final var action = new UpdateUserRule(userValidator, dataPlaneRegistry.getUserRepository(domain)::update); - return loginAttemptService.reset(criteria).andThen(action.update(user)); + return loginAttemptService.reset(domain, criteria).andThen(action.update(user)); }) .doOnSuccess(user1 -> auditService.report(AuditBuilder.builder(UserAuditBuilder.class).principal(principal).type(EventType.USER_UNLOCKED).user(user1))) .doOnError(throwable -> auditService.report(AuditBuilder.builder(UserAuditBuilder.class).principal(principal).type(EventType.USER_UNLOCKED).reference(domain.asReference()).throwable(throwable))) diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/ManagementUserServiceTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/ManagementUserServiceTest.java index 659d4a248be..9cc18c0d40d 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/ManagementUserServiceTest.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/ManagementUserServiceTest.java @@ -529,7 +529,7 @@ void shouldResetPassword_externalIdEmpty() { when(userRepository.findById(any(),any())).thenReturn(Maybe.just(user)); when(identityProviderManager.getUserProvider(user.getSource())).thenReturn(Maybe.just(userProvider)); when(userRepository.update(any(), any())).thenReturn(Single.just(user)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); when(tokenService.deleteByUser(any())).thenReturn(Completable.complete()); when(passwordHistoryService.addPasswordToHistory(any(), any(), any(), any(), any())).thenReturn(Maybe.just(new PasswordHistory())); when(passwordPolicyService.retrievePasswordPolicy(any(), any(), any())).thenReturn(Maybe.empty()); @@ -564,7 +564,7 @@ void shouldResetPassword_idpUserNotFound() { when(userRepository.findById(any(),any())).thenReturn(Maybe.just(user)); when(identityProviderManager.getUserProvider(user.getSource())).thenReturn(Maybe.just(userProvider)); when(userRepository.update(any(), any())).thenReturn(Single.just(user)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); when(tokenService.deleteByUser(any())).thenReturn(Completable.complete()); when(passwordHistoryService.addPasswordToHistory(any(), any(), any(), any(), any())).thenReturn(Maybe.just(new PasswordHistory())); when(passwordPolicyService.retrievePasswordPolicy(any(), any(), any())).thenReturn(Maybe.empty()); @@ -962,7 +962,7 @@ void must_reset_username() { when(identityProviderManager.getUserProvider(anyString())).thenReturn(Maybe.just(userProvider)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); when(credentialService.findByUsername(any(), eq(user.getUsername()))).thenReturn(Flowable.empty()); @@ -975,7 +975,7 @@ void must_reset_username() { verify(userProvider, times(1)).updateUsername(any(), anyString()); verify(credentialService, times(1)).findByUsername(any(), eq(USERNAME)); verify(credentialService, never()).update(any(), any()); - verify(loginAttemptService, times(1)).reset(any()); + verify(loginAttemptService, times(1)).reset(any(), any()); } @Test @@ -1008,7 +1008,7 @@ void must_update_user_webauth_credential() { when(identityProviderManager.getUserProvider(anyString())).thenReturn(Maybe.just(userProvider)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); var credential = new Credential(); credential.setUsername(user.getUsername()); @@ -1023,7 +1023,7 @@ void must_update_user_webauth_credential() { verify(userRepository, times(1)).update(any(), any()); verify(userProvider, times(1)).updateUsername(any(), anyString()); - verify(loginAttemptService, times(1)).reset(any()); + verify(loginAttemptService, times(1)).reset(any(), any()); verify(credentialService, times(1)).findByUsername(any(), eq(USERNAME)); verify(credentialService, times(1)).update(any(), argThat(argument -> NEW_USERNAME.equals(argument.getUsername()))); } @@ -1067,7 +1067,7 @@ void must_update_user_moving_factor() { when(identityProviderManager.getUserProvider(anyString())).thenReturn(Maybe.just(userProvider)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); when(credentialService.findByUsername(any(), eq(user.getUsername()))).thenReturn(Flowable.empty()); var observer = userService.updateUsername(domain, user.getId(), NEW_USERNAME, null).test(); @@ -1077,7 +1077,7 @@ void must_update_user_moving_factor() { verify(userRepository, times(1)).update(any(), any()); verify(userProvider, times(1)).updateUsername(any(), anyString()); - verify(loginAttemptService, times(1)).reset(any()); + verify(loginAttemptService, times(1)).reset(any(), any()); verify(credentialService, times(1)).findByUsername(any(), eq(USERNAME)); assertEquals(1, user.getFactors().size()); @@ -1258,7 +1258,7 @@ void must_reset_username_and_unlock_user() { when(userProvider.updateUsername(any(), anyString())).thenReturn(Single.just(defaultUser)); when(identityProviderManager.getUserProvider(anyString())).thenReturn(Maybe.just(userProvider)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); when(credentialService.findByUsername(any(), eq(user.getUsername()))).thenReturn(Flowable.empty()); var observer = userService.updateUsername(domain, user.getId(), NEW_USERNAME, null).test(); @@ -1274,7 +1274,7 @@ void must_reset_username_and_unlock_user() { return true; }), any()); verify(userProvider, times(1)).updateUsername(any(), anyString()); - verify(loginAttemptService, times(1)).reset(any()); + verify(loginAttemptService, times(1)).reset(any(), any()); } @Test diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-dataplane/src/main/java/io/gravitee/am/plugins/dataplane/core/DataPlaneRegistry.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-dataplane/src/main/java/io/gravitee/am/plugins/dataplane/core/DataPlaneRegistry.java index 2ce1ae81951..0ed0fcf5ea3 100644 --- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-dataplane/src/main/java/io/gravitee/am/plugins/dataplane/core/DataPlaneRegistry.java +++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-dataplane/src/main/java/io/gravitee/am/plugins/dataplane/core/DataPlaneRegistry.java @@ -22,6 +22,7 @@ import io.gravitee.am.dataplane.api.repository.CredentialRepository; import io.gravitee.am.dataplane.api.repository.DeviceRepository; import io.gravitee.am.dataplane.api.repository.GroupRepository; +import io.gravitee.am.dataplane.api.repository.LoginAttemptRepository; import io.gravitee.am.dataplane.api.repository.PasswordHistoryRepository; import io.gravitee.am.dataplane.api.repository.ScopeApprovalRepository; import io.gravitee.am.dataplane.api.repository.UserActivityRepository; @@ -54,4 +55,6 @@ public interface DataPlaneRegistry { UserRepository getUserRepository(Domain domain); PasswordHistoryRepository getPasswordHistoryRepository(Domain domain); + + LoginAttemptRepository getLoginAttemptRepository(Domain domain); } diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-dataplane/src/main/java/io/gravitee/am/plugins/dataplane/core/DataPlaneRegistryImpl.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-dataplane/src/main/java/io/gravitee/am/plugins/dataplane/core/DataPlaneRegistryImpl.java index e8a7cd10816..c8b03314d67 100644 --- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-dataplane/src/main/java/io/gravitee/am/plugins/dataplane/core/DataPlaneRegistryImpl.java +++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-dataplane/src/main/java/io/gravitee/am/plugins/dataplane/core/DataPlaneRegistryImpl.java @@ -20,6 +20,7 @@ import io.gravitee.am.dataplane.api.repository.CredentialRepository; import io.gravitee.am.dataplane.api.repository.DeviceRepository; import io.gravitee.am.dataplane.api.repository.GroupRepository; +import io.gravitee.am.dataplane.api.repository.LoginAttemptRepository; import io.gravitee.am.dataplane.api.repository.PasswordHistoryRepository; import io.gravitee.am.dataplane.api.repository.ScopeApprovalRepository; import io.gravitee.am.dataplane.api.repository.UserActivityRepository; @@ -108,6 +109,11 @@ public PasswordHistoryRepository getPasswordHistoryRepository(Domain domain) { return getProvider(domain).getPasswordHistoryRepository(); } + @Override + public LoginAttemptRepository getLoginAttemptRepository(Domain domain) { + return getProvider(domain).getLoginAttemptRepository(); + } + @Override protected void doStart() throws Exception { super.doStart(); diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/RateLimitRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/RateLimitRepository.java index 59b12736ef9..a09a976597c 100644 --- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/RateLimitRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/RateLimitRepository.java @@ -18,7 +18,7 @@ import io.gravitee.am.model.RateLimit; import io.gravitee.am.model.ReferenceType; import io.gravitee.am.repository.common.CrudRepository; -import io.gravitee.am.repository.management.api.search.RateLimitCriteria; +import io.gravitee.am.repository.gateway.api.search.RateLimitCriteria; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/VerifyAttemptRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/VerifyAttemptRepository.java index 4cdf97796b4..53b3071062b 100644 --- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/VerifyAttemptRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/VerifyAttemptRepository.java @@ -18,7 +18,7 @@ import io.gravitee.am.model.ReferenceType; import io.gravitee.am.model.VerifyAttempt; import io.gravitee.am.repository.common.CrudRepository; -import io.gravitee.am.repository.management.api.search.VerifyAttemptCriteria; +import io.gravitee.am.repository.gateway.api.search.VerifyAttemptCriteria; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/search/RateLimitCriteria.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/search/RateLimitCriteria.java similarity index 97% rename from gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/search/RateLimitCriteria.java rename to gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/search/RateLimitCriteria.java index 56d9fef9e85..1b9f5a6a083 100644 --- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/search/RateLimitCriteria.java +++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/search/RateLimitCriteria.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.gravitee.am.repository.management.api.search; +package io.gravitee.am.repository.gateway.api.search; /** * @author Ashraful Hasan (ashraful.hasan at graviteesource.com) diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/search/VerifyAttemptCriteria.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/search/VerifyAttemptCriteria.java similarity index 97% rename from gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/search/VerifyAttemptCriteria.java rename to gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/search/VerifyAttemptCriteria.java index 8db88f1fe18..7be8194d49f 100644 --- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/search/VerifyAttemptCriteria.java +++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/gateway/api/search/VerifyAttemptCriteria.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.gravitee.am.repository.management.api.search; +package io.gravitee.am.repository.gateway.api.search; /** * @author Ashraful Hasan (ashraful.hasan at graviteesource.com) diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/gateway/api/JdbcRateLimitRepository.java b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/gateway/api/JdbcRateLimitRepository.java index d283810cce2..cd3650f008e 100644 --- a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/gateway/api/JdbcRateLimitRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/gateway/api/JdbcRateLimitRepository.java @@ -23,7 +23,7 @@ import io.gravitee.am.repository.jdbc.gateway.api.model.JdbcRateLimit; import io.gravitee.am.repository.jdbc.gateway.api.spring.SpringRateLimitRepository; import io.gravitee.am.repository.gateway.api.RateLimitRepository; -import io.gravitee.am.repository.management.api.search.RateLimitCriteria; +import io.gravitee.am.repository.gateway.api.search.RateLimitCriteria; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/gateway/api/JdbcVerifyAttemptRepository.java b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/gateway/api/JdbcVerifyAttemptRepository.java index d94af49b314..463bc571fbe 100644 --- a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/gateway/api/JdbcVerifyAttemptRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/gateway/api/JdbcVerifyAttemptRepository.java @@ -23,7 +23,7 @@ import io.gravitee.am.repository.jdbc.gateway.api.model.JdbcVerifyAttempt; import io.gravitee.am.repository.jdbc.gateway.api.spring.SpringVerifyAttemptRepository; import io.gravitee.am.repository.jdbc.management.AbstractJdbcRepository; -import io.gravitee.am.repository.management.api.search.VerifyAttemptCriteria; +import io.gravitee.am.repository.gateway.api.search.VerifyAttemptCriteria; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/gateway/MongoRateLimitRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/gateway/MongoRateLimitRepository.java index 4052f31350d..7c8186e0186 100644 --- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/gateway/MongoRateLimitRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/gateway/MongoRateLimitRepository.java @@ -22,7 +22,7 @@ import io.gravitee.am.model.RateLimit; import io.gravitee.am.model.ReferenceType; import io.gravitee.am.repository.gateway.api.RateLimitRepository; -import io.gravitee.am.repository.management.api.search.RateLimitCriteria; +import io.gravitee.am.repository.gateway.api.search.RateLimitCriteria; import io.gravitee.am.repository.mongodb.management.internal.model.RateLimitMongo; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/gateway/MongoVerifyAttemptRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/gateway/MongoVerifyAttemptRepository.java index 59d4df4c803..c91b8645448 100644 --- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/gateway/MongoVerifyAttemptRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/gateway/MongoVerifyAttemptRepository.java @@ -22,7 +22,7 @@ import io.gravitee.am.model.ReferenceType; import io.gravitee.am.model.VerifyAttempt; import io.gravitee.am.repository.gateway.api.VerifyAttemptRepository; -import io.gravitee.am.repository.management.api.search.VerifyAttemptCriteria; +import io.gravitee.am.repository.gateway.api.search.VerifyAttemptCriteria; import io.gravitee.am.repository.mongodb.management.internal.model.VerifyAttemptMongo; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; diff --git a/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/gateway/api/RateLimitRepositoryTest.java b/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/gateway/api/RateLimitRepositoryTest.java index f151e02bad2..fdd2051f121 100644 --- a/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/gateway/api/RateLimitRepositoryTest.java +++ b/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/gateway/api/RateLimitRepositoryTest.java @@ -18,7 +18,7 @@ import io.gravitee.am.model.RateLimit; import io.gravitee.am.model.ReferenceType; import io.gravitee.am.repository.gateway.AbstractGatewayTest; -import io.gravitee.am.repository.management.api.search.RateLimitCriteria; +import io.gravitee.am.repository.gateway.api.search.RateLimitCriteria; import io.reactivex.rxjava3.observers.TestObserver; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/gateway/api/VerifyAttemptRepositoryTest.java b/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/gateway/api/VerifyAttemptRepositoryTest.java index 22d865447ce..a60383db659 100644 --- a/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/gateway/api/VerifyAttemptRepositoryTest.java +++ b/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/gateway/api/VerifyAttemptRepositoryTest.java @@ -18,7 +18,7 @@ import io.gravitee.am.model.ReferenceType; import io.gravitee.am.model.VerifyAttempt; import io.gravitee.am.repository.gateway.AbstractGatewayTest; -import io.gravitee.am.repository.management.api.search.VerifyAttemptCriteria; +import io.gravitee.am.repository.gateway.api.search.VerifyAttemptCriteria; import io.reactivex.rxjava3.observers.TestObserver; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/business/user/UpdateUsernameDomainRule.java b/gravitee-am-service/src/main/java/io/gravitee/am/business/user/UpdateUsernameDomainRule.java index d7562098b6f..d2ade807590 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/business/user/UpdateUsernameDomainRule.java +++ b/gravitee-am-service/src/main/java/io/gravitee/am/business/user/UpdateUsernameDomainRule.java @@ -21,12 +21,12 @@ import io.gravitee.am.common.factor.FactorDataKeys; import io.gravitee.am.common.utils.MovingFactorUtils; import io.gravitee.am.dataplane.api.repository.UserRepository; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.identityprovider.api.DefaultUser; import io.gravitee.am.identityprovider.api.UserProvider; import io.gravitee.am.model.Domain; import io.gravitee.am.model.Reference; import io.gravitee.am.model.User; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; import io.gravitee.am.service.AuditService; import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.dataplane.CredentialCommonService; @@ -105,7 +105,7 @@ public Single updateUsername(Domain domain, String username, io.gravitee.a )) .doOnSuccess(user1 -> { auditService.report(AuditBuilder.builder(UserAuditBuilder.class).principal(principal).type(EventType.USERNAME_UPDATED).user(user1)); - loginAttemptService.reset(createLoginAttemptCriteria(user1.getReferenceId(), oldUsername.get())) + loginAttemptService.reset(domain, createLoginAttemptCriteria(user1.getReferenceId(), oldUsername.get())) .onErrorResumeNext(error -> { log.warn("Could not delete login attempt {}", error.getMessage()); return Completable.complete(); diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/LoginAttemptService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/LoginAttemptService.java index f45c16c96e3..319af09f048 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/service/LoginAttemptService.java +++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/LoginAttemptService.java @@ -15,9 +15,10 @@ */ package io.gravitee.am.service; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.model.Domain; import io.gravitee.am.model.LoginAttempt; import io.gravitee.am.model.account.AccountSettings; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; @@ -28,15 +29,15 @@ */ public interface LoginAttemptService { - Completable loginSucceeded(LoginAttemptCriteria criteria); + Completable loginSucceeded(Domain domain, LoginAttemptCriteria criteria); - Single loginFailed(LoginAttemptCriteria criteria, AccountSettings accountSettings); + Single loginFailed(Domain domain, LoginAttemptCriteria criteria, AccountSettings accountSettings); - Completable reset(LoginAttemptCriteria criteria); + Completable reset(Domain domain, LoginAttemptCriteria criteria); - Maybe checkAccount(LoginAttemptCriteria criteria, AccountSettings accountSettings); + Maybe checkAccount(Domain domain, LoginAttemptCriteria criteria, AccountSettings accountSettings); - Maybe findById(String id); + Maybe findById(Domain domain, String id); } diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/LoginAttemptServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/LoginAttemptServiceImpl.java index 7356b701fd4..60e95a411ed 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/LoginAttemptServiceImpl.java +++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/LoginAttemptServiceImpl.java @@ -16,10 +16,11 @@ package io.gravitee.am.service.impl; import io.gravitee.am.common.utils.RandomString; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.model.Domain; import io.gravitee.am.model.LoginAttempt; import io.gravitee.am.model.account.AccountSettings; -import io.gravitee.am.repository.gateway.api.LoginAttemptRepository; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; +import io.gravitee.am.plugins.dataplane.core.DataPlaneRegistry; import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.exception.AbstractManagementException; import io.gravitee.am.service.exception.LoginAttemptNotFoundException; @@ -27,8 +28,7 @@ import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -41,17 +41,17 @@ * @author GraviteeSource Team */ @Component +@Slf4j public class LoginAttemptServiceImpl implements LoginAttemptService { - private static final Logger LOGGER = LoggerFactory.getLogger(LoginAttemptServiceImpl.class); - @Lazy @Autowired - private LoginAttemptRepository loginAttemptRepository; + private DataPlaneRegistry dataPlaneRegistry; @Override - public Single loginFailed(LoginAttemptCriteria criteria, AccountSettings accountSettings) { - LOGGER.debug("Add login attempt for {}", criteria); + public Single loginFailed(Domain domain, LoginAttemptCriteria criteria, AccountSettings accountSettings) { + log.debug("Add login attempt for {}", criteria); + final var loginAttemptRepository = dataPlaneRegistry.getLoginAttemptRepository(domain); return loginAttemptRepository.findByCriteria(criteria) .map(Optional::of) .defaultIfEmpty(Optional.empty()) @@ -86,46 +86,46 @@ public Single loginFailed(LoginAttemptCriteria criteria, AccountSe if (ex instanceof AbstractManagementException) { return Single.error(ex); } - LOGGER.error("An error occurs while trying to add a login attempt", ex); + log.error("An error occurs while trying to add a login attempt", ex); return Single.error(new TechnicalManagementException("An error occurs while trying to add a login attempt", ex)); }); } @Override - public Completable loginSucceeded(LoginAttemptCriteria criteria) { - LOGGER.debug("Delete login attempt for {}", criteria); - return loginAttemptRepository.delete(criteria) + public Completable loginSucceeded(Domain domain, LoginAttemptCriteria criteria) { + log.debug("Delete login attempt for {}", criteria); + return dataPlaneRegistry.getLoginAttemptRepository(domain).delete(criteria) .onErrorResumeNext(ex -> { if (ex instanceof AbstractManagementException) { return Completable.error(ex); } - LOGGER.error("An error occurs while trying to delete login attempt for {}", criteria, ex); + log.error("An error occurs while trying to delete login attempt for {}", criteria, ex); return Completable.error(new TechnicalManagementException( String.format("An error occurs while trying to delete login attempt: %s", criteria), ex)); }); } @Override - public Completable reset(LoginAttemptCriteria criteria) { - return loginSucceeded(criteria); + public Completable reset(Domain domain, LoginAttemptCriteria criteria) { + return loginSucceeded(domain, criteria); } @Override - public Maybe checkAccount(LoginAttemptCriteria criteria, AccountSettings accountSettings) { - LOGGER.debug("Check account status for {}", criteria); - return loginAttemptRepository.findByCriteria(criteria); + public Maybe checkAccount(Domain domain, LoginAttemptCriteria criteria, AccountSettings accountSettings) { + log.debug("Check account status for {}", criteria); + return dataPlaneRegistry.getLoginAttemptRepository(domain).findByCriteria(criteria); } @Override - public Maybe findById(String id) { - LOGGER.debug("Find login attempt by id {}", id); - return loginAttemptRepository.findById(id) + public Maybe findById(Domain domain, String id) { + log.debug("Find login attempt by id {}", id); + return dataPlaneRegistry.getLoginAttemptRepository(domain).findById(id) .switchIfEmpty(Maybe.error(new LoginAttemptNotFoundException(id))) .onErrorResumeNext(ex -> { if (ex instanceof AbstractManagementException) { return Maybe.error(ex); } - LOGGER.error("An error occurs while trying to find login attempt by id {}", id, ex); + log.error("An error occurs while trying to find login attempt by id {}", id, ex); return Maybe.error(new TechnicalManagementException( String.format("An error occurs while trying to fin login attempt by id: %s", id), ex)); }); diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RateLimiterServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RateLimiterServiceImpl.java index dee2b8c9c8c..d4408726ec5 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RateLimiterServiceImpl.java +++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RateLimiterServiceImpl.java @@ -20,7 +20,7 @@ import io.gravitee.am.model.ReferenceType; import io.gravitee.am.model.User; import io.gravitee.am.repository.gateway.api.RateLimitRepository; -import io.gravitee.am.repository.management.api.search.RateLimitCriteria; +import io.gravitee.am.repository.gateway.api.search.RateLimitCriteria; import io.gravitee.am.service.RateLimiterService; import io.gravitee.am.service.exception.AbstractManagementException; import io.gravitee.am.service.exception.TechnicalManagementException; diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/VerifyAttemptServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/VerifyAttemptServiceImpl.java index 781118ed460..ccbaf571977 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/VerifyAttemptServiceImpl.java +++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/VerifyAttemptServiceImpl.java @@ -22,7 +22,7 @@ import io.gravitee.am.model.account.AccountSettings; import io.gravitee.am.model.oidc.Client; import io.gravitee.am.repository.gateway.api.VerifyAttemptRepository; -import io.gravitee.am.repository.management.api.search.VerifyAttemptCriteria; +import io.gravitee.am.repository.gateway.api.search.VerifyAttemptCriteria; import io.gravitee.am.service.EmailService; import io.gravitee.am.service.VerifyAttemptService; import io.gravitee.am.service.exception.MFAValidationAttemptException; diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/business/user/UpdateUsernameDomainRuleTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/business/user/UpdateUsernameDomainRuleTest.java index 83cb39d4e1b..f80f06e328a 100644 --- a/gravitee-am-service/src/test/java/io/gravitee/am/business/user/UpdateUsernameDomainRuleTest.java +++ b/gravitee-am-service/src/test/java/io/gravitee/am/business/user/UpdateUsernameDomainRuleTest.java @@ -281,7 +281,7 @@ void must_reset_username() { when(userProvider.findByUsername(anyString())).thenReturn(Maybe.just(defaultUser)); when(userProvider.updateUsername(any(), anyString())).thenReturn(Single.just(idpUserUpdated)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); when(credentialService.findByUsername(any(), eq(user.getUsername()))).thenReturn(Flowable.empty()); @@ -294,7 +294,7 @@ void must_reset_username() { verify(userProvider, times(1)).updateUsername(any(), anyString()); verify(credentialService, times(1)).findByUsername(any(), eq(USERNAME)); verify(credentialService, never()).update(any(), any()); - verify(loginAttemptService, times(1)).reset(any()); + verify(loginAttemptService, times(1)).reset(any(), any()); } @Test @@ -324,7 +324,7 @@ void must_update_user_webauth_credential() { when(userProvider.findByUsername(anyString())).thenReturn(Maybe.just(defaultUser)); when(userProvider.updateUsername(any(), anyString())).thenReturn(Single.just(idpUserUpdated)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); var credential = new Credential(); credential.setUsername(user.getUsername()); @@ -339,7 +339,7 @@ void must_update_user_webauth_credential() { verify(userRepository, times(1)).update(any(), any()); verify(userProvider, times(1)).updateUsername(any(), anyString()); - verify(loginAttemptService, times(1)).reset(any()); + verify(loginAttemptService, times(1)).reset(any(), any()); verify(credentialService, times(1)).findByUsername(any(), eq(USERNAME)); verify(credentialService, times(1)).update(any(), argThat(argument -> NEW_USERNAME.equals(argument.getUsername()))); } @@ -380,7 +380,7 @@ void must_update_user_moving_factor() { when(userProvider.findByUsername(anyString())).thenReturn(Maybe.just(defaultUser)); when(userProvider.updateUsername(any(), anyString())).thenReturn(Single.just(idpUserUpdated)); - when(loginAttemptService.reset(any())).thenReturn(Completable.complete()); + when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); when(credentialService.findByUsername(any(), eq(user.getUsername()))).thenReturn(Flowable.empty()); var observer = rule.updateUsername(domain, NEW_USERNAME, null, (User user1) -> Single.just(userProvider), () -> Single.just(user)).test(); @@ -390,7 +390,7 @@ void must_update_user_moving_factor() { verify(userRepository, times(1)).update(any(), any()); verify(userProvider, times(1)).updateUsername(any(), anyString()); - verify(loginAttemptService, times(1)).reset(any()); + verify(loginAttemptService, times(1)).reset(any(), any()); verify(credentialService, times(1)).findByUsername(any(), eq(USERNAME)); assertEquals(1, user.getFactors().size()); diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/LoginAttemptServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/LoginAttemptServiceTest.java index 273afa2ca92..ae4751124ea 100644 --- a/gravitee-am-service/src/test/java/io/gravitee/am/service/LoginAttemptServiceTest.java +++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/LoginAttemptServiceTest.java @@ -15,11 +15,12 @@ */ package io.gravitee.am.service; +import io.gravitee.am.dataplane.api.repository.LoginAttemptRepository; +import io.gravitee.am.model.Domain; import io.gravitee.am.model.LoginAttempt; import io.gravitee.am.model.account.AccountSettings; -import io.gravitee.am.repository.gateway.api.LoginAttemptRepository; -import io.gravitee.am.repository.management.api.search.LoginAttemptCriteria; import io.gravitee.am.service.impl.LoginAttemptServiceImpl; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.observers.TestObserver; @@ -66,7 +67,7 @@ public void shouldCreateUser_accountLockFirstConnection() { when(loginAttemptRepository.findByCriteria(loginAttemptCriteria)).thenReturn(Maybe.just(loginAttempt)); when(loginAttemptRepository.update(loginAttempt)).thenReturn(Single.just(loginAttempt)); - TestObserver testObserver = loginAttemptService.loginFailed(loginAttemptCriteria, accountSettings).test(); + TestObserver testObserver = loginAttemptService.loginFailed(new Domain("domain-1"), loginAttemptCriteria, accountSettings).test(); testObserver.awaitDone(10, TimeUnit.SECONDS); testObserver.assertNoErrors(); } @@ -91,7 +92,7 @@ public void shouldUpdateUser_accountLockAlreadyRegistered() { when(loginAttemptRepository.findByCriteria(loginAttemptCriteria)).thenReturn(Maybe.just(loginAttempt)); when(loginAttemptRepository.update(loginAttempt)).thenReturn(Single.just(loginAttempt)); - TestObserver testObserver = loginAttemptService.loginFailed(loginAttemptCriteria, accountSettings).test(); + TestObserver testObserver = loginAttemptService.loginFailed(new Domain("domain-1"), loginAttemptCriteria, accountSettings).test(); testObserver.awaitDone(10, TimeUnit.SECONDS); testObserver.assertNoErrors(); } From f720740d76db26321ccc2afaf4e5b84b408cf885 Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 30 Jan 2025 10:41:08 +0100 Subject: [PATCH 2/2] chore(dataplane): create two distinct services for LoginAttempt fixes AM-4618 --- .../services/impl/AccountServiceImpl.java | 6 +- .../impl/UserAuthenticationManagerImpl.java | 4 +- .../service/LoginAttemptGatewayService.java | 15 +-- .../impl/LoginAttemptGatewayServiceImpl.java | 31 ++---- .../common/spring/CommonConfiguration.java | 7 ++ .../common/spring/web/WebConfiguration.java | 4 +- .../vertx/web/handler/SSOSessionHandler.java | 7 +- .../auth/UserAuthenticationManagerTest.java | 4 +- .../LoginAttemptGatewayServiceTest.java | 82 +++++++++++++--- .../web/handler/SSOSessionHandlerTest.java | 4 +- .../am/gateway/handler/root/RootProvider.java | 4 +- .../loginattempt/LoginAttemptHandler.java | 6 +- .../service/user/impl/UserServiceImpl.java | 6 +- .../loginattempt/LoginAttemptHandlerTest.java | 4 +- .../root/service/user/UserServiceTest.java | 4 +- .../LoginAttemptManagementService.java | 25 +++++ .../LoginAttemptManagementServiceImpl.java | 51 ++++++++++ .../impl/ManagementUserServiceImpl.java | 6 +- .../service/ManagementUserServiceTest.java | 4 +- .../LoginAttemptManagementServiceTest.java | 98 +++++++++++++++++++ .../user/UpdateUsernameDomainRule.java | 9 +- .../user/UpdateUsernameDomainRuleTest.java | 18 ++-- 22 files changed, 309 insertions(+), 90 deletions(-) rename gravitee-am-service/src/main/java/io/gravitee/am/service/LoginAttemptService.java => gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/service/LoginAttemptGatewayService.java (82%) rename gravitee-am-service/src/main/java/io/gravitee/am/service/impl/LoginAttemptServiceImpl.java => gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/service/impl/LoginAttemptGatewayServiceImpl.java (86%) rename gravitee-am-service/src/test/java/io/gravitee/am/service/LoginAttemptServiceTest.java => gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/service/LoginAttemptGatewayServiceTest.java (52%) create mode 100644 gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/dataplane/LoginAttemptManagementService.java create mode 100644 gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/dataplane/impl/LoginAttemptManagementServiceImpl.java create mode 100644 gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/dataplane/LoginAttemptManagementServiceTest.java diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-account/src/main/java/io/gravitee/am/gateway/handler/account/services/impl/AccountServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-account/src/main/java/io/gravitee/am/gateway/handler/account/services/impl/AccountServiceImpl.java index 40037f80938..917e3939fb2 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-account/src/main/java/io/gravitee/am/gateway/handler/account/services/impl/AccountServiceImpl.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-account/src/main/java/io/gravitee/am/gateway/handler/account/services/impl/AccountServiceImpl.java @@ -29,6 +29,7 @@ import io.gravitee.am.gateway.handler.common.jwt.SubjectManager; import io.gravitee.am.gateway.handler.common.password.PasswordPolicyManager; import io.gravitee.am.gateway.handler.common.service.CredentialGatewayService; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.root.service.response.ResetPasswordResponse; import io.gravitee.am.identityprovider.api.DefaultUser; import io.gravitee.am.model.Credential; @@ -47,7 +48,6 @@ import io.gravitee.am.reporter.api.audit.model.Audit; import io.gravitee.am.service.AuditService; import io.gravitee.am.service.FactorService; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.PasswordService; import io.gravitee.am.service.ScopeApprovalService; import io.gravitee.am.service.exception.CredentialNotFoundException; @@ -120,7 +120,7 @@ public class AccountServiceImpl implements AccountService, InitializingBean { private ScopeApprovalService scopeApprovalService; @Autowired - private LoginAttemptService loginAttemptService; + private LoginAttemptGatewayService loginAttemptService; @Autowired private AuditService auditService; @@ -198,7 +198,7 @@ public Single updateUsername(User user, UpdateUsername newUsername, io.gra userRepository::findByUsernameAndSource, auditService, credentialService, - loginAttemptService).updateUsername( + loginAttemptService::reset).updateUsername( domain, newUsername.getUsername(), principal, diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationManagerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationManagerImpl.java index bafdc7f1df7..fb126b46f7b 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationManagerImpl.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/auth/user/impl/UserAuthenticationManagerImpl.java @@ -33,6 +33,7 @@ import io.gravitee.am.gateway.handler.common.auth.user.UserAuthenticationManager; import io.gravitee.am.gateway.handler.common.auth.user.UserAuthenticationService; import io.gravitee.am.gateway.handler.common.password.PasswordPolicyManager; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.user.UserGatewayService; import io.gravitee.am.identityprovider.api.Authentication; import io.gravitee.am.identityprovider.api.DefaultUser; @@ -42,7 +43,6 @@ import io.gravitee.am.model.idp.ApplicationIdentityProvider; import io.gravitee.am.model.oidc.Client; import io.gravitee.am.monitoring.provider.GatewayMetricProvider; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.PasswordService; import io.gravitee.common.event.EventManager; import io.gravitee.gateway.api.Request; @@ -84,7 +84,7 @@ public class UserAuthenticationManagerImpl implements UserAuthenticationManager private EventManager eventManager; @Autowired - private LoginAttemptService loginAttemptService; + private LoginAttemptGatewayService loginAttemptService; @Autowired private UserAuthenticationService userAuthenticationService; diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/LoginAttemptService.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/service/LoginAttemptGatewayService.java similarity index 82% rename from gravitee-am-service/src/main/java/io/gravitee/am/service/LoginAttemptService.java rename to gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/service/LoginAttemptGatewayService.java index 319af09f048..f7bbbfa56e4 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/service/LoginAttemptService.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/service/LoginAttemptGatewayService.java @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.gravitee.am.service; + +package io.gravitee.am.gateway.handler.common.service; + import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.model.Domain; @@ -24,20 +26,19 @@ import io.reactivex.rxjava3.core.Single; /** - * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) + * @author Eric LELEU (eric.leleu at graviteesource.com) * @author GraviteeSource Team */ -public interface LoginAttemptService { - +public interface LoginAttemptGatewayService { Completable loginSucceeded(Domain domain, LoginAttemptCriteria criteria); Single loginFailed(Domain domain, LoginAttemptCriteria criteria, AccountSettings accountSettings); - Completable reset(Domain domain, LoginAttemptCriteria criteria); + default Completable reset(Domain domain, LoginAttemptCriteria criteria) { + return loginSucceeded(domain, criteria); + } Maybe checkAccount(Domain domain, LoginAttemptCriteria criteria, AccountSettings accountSettings); Maybe findById(Domain domain, String id); - - } diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/LoginAttemptServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/service/impl/LoginAttemptGatewayServiceImpl.java similarity index 86% rename from gravitee-am-service/src/main/java/io/gravitee/am/service/impl/LoginAttemptServiceImpl.java rename to gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/service/impl/LoginAttemptGatewayServiceImpl.java index 60e95a411ed..5b3f01573d0 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/LoginAttemptServiceImpl.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/service/impl/LoginAttemptGatewayServiceImpl.java @@ -13,40 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.gravitee.am.service.impl; + +package io.gravitee.am.gateway.handler.common.service.impl; + import io.gravitee.am.common.utils.RandomString; import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.model.Domain; import io.gravitee.am.model.LoginAttempt; import io.gravitee.am.model.account.AccountSettings; import io.gravitee.am.plugins.dataplane.core.DataPlaneRegistry; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.exception.AbstractManagementException; import io.gravitee.am.service.exception.LoginAttemptNotFoundException; import io.gravitee.am.service.exception.TechnicalManagementException; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; import java.util.Date; import java.util.Optional; /** - * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) + * @author Eric LELEU (eric.leleu at graviteesource.com) * @author GraviteeSource Team */ -@Component @Slf4j -public class LoginAttemptServiceImpl implements LoginAttemptService { +@AllArgsConstructor +public class LoginAttemptGatewayServiceImpl implements LoginAttemptGatewayService { - @Lazy - @Autowired - private DataPlaneRegistry dataPlaneRegistry; + private final DataPlaneRegistry dataPlaneRegistry; @Override public Single loginFailed(Domain domain, LoginAttemptCriteria criteria, AccountSettings accountSettings) { @@ -83,9 +81,6 @@ public Single loginFailed(Domain domain, LoginAttemptCriteria crit } }) .onErrorResumeNext(ex -> { - if (ex instanceof AbstractManagementException) { - return Single.error(ex); - } log.error("An error occurs while trying to add a login attempt", ex); return Single.error(new TechnicalManagementException("An error occurs while trying to add a login attempt", ex)); }); @@ -96,20 +91,12 @@ public Completable loginSucceeded(Domain domain, LoginAttemptCriteria criteria) log.debug("Delete login attempt for {}", criteria); return dataPlaneRegistry.getLoginAttemptRepository(domain).delete(criteria) .onErrorResumeNext(ex -> { - if (ex instanceof AbstractManagementException) { - return Completable.error(ex); - } log.error("An error occurs while trying to delete login attempt for {}", criteria, ex); return Completable.error(new TechnicalManagementException( String.format("An error occurs while trying to delete login attempt: %s", criteria), ex)); }); } - @Override - public Completable reset(Domain domain, LoginAttemptCriteria criteria) { - return loginSucceeded(domain, criteria); - } - @Override public Maybe checkAccount(Domain domain, LoginAttemptCriteria criteria, AccountSettings accountSettings) { log.debug("Check account status for {}", criteria); diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/CommonConfiguration.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/CommonConfiguration.java index 405c9e14b16..99972e0aa9f 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/CommonConfiguration.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/CommonConfiguration.java @@ -57,9 +57,11 @@ import io.gravitee.am.gateway.handler.common.ruleengine.SpELRuleEngine; import io.gravitee.am.gateway.handler.common.service.CredentialGatewayService; import io.gravitee.am.gateway.handler.common.service.DeviceGatewayService; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.service.UserActivityGatewayService; import io.gravitee.am.gateway.handler.common.service.impl.CredentialGatewayServiceImpl; import io.gravitee.am.gateway.handler.common.service.impl.DeviceGatewayServiceImpl; +import io.gravitee.am.gateway.handler.common.service.impl.LoginAttemptGatewayServiceImpl; import io.gravitee.am.gateway.handler.common.service.impl.UserActivityGatewayServiceImpl; import io.gravitee.am.gateway.handler.common.spring.web.WebConfiguration; import io.gravitee.am.gateway.handler.common.user.UserGatewayService; @@ -355,4 +357,9 @@ public UserActivityGatewayService userActivityGatewayService(UserActivityConfigu public DeviceGatewayService deviceGatewayService(DataPlaneRegistry dataPlaneRegistry) { return new DeviceGatewayServiceImpl(dataPlaneRegistry); } + + @Bean + public LoginAttemptGatewayService loginAttemptGatewayService(DataPlaneRegistry dataPlaneRegistry) { + return new LoginAttemptGatewayServiceImpl(dataPlaneRegistry); + } } diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/web/WebConfiguration.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/web/WebConfiguration.java index 0363585dad8..c80e5faa60d 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/web/WebConfiguration.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/web/WebConfiguration.java @@ -19,6 +19,7 @@ import io.gravitee.am.gateway.handler.common.client.ClientSyncService; import io.gravitee.am.gateway.handler.common.jwt.JWTService; import io.gravitee.am.gateway.handler.common.jwt.SubjectManager; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.vertx.web.handler.AuthenticationFlowHandler; import io.gravitee.am.gateway.handler.common.vertx.web.handler.CSPHandlerFactory; import io.gravitee.am.gateway.handler.common.vertx.web.handler.CSRFHandlerFactory; @@ -32,7 +33,6 @@ import io.gravitee.am.gateway.handler.common.vertx.web.handler.impl.PolicyChainHandlerImpl; import io.gravitee.am.model.Domain; import io.gravitee.am.service.AuthenticationFlowContextService; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.impl.user.UserEnhancer; import io.vertx.core.http.CookieSameSite; import org.springframework.beans.factory.annotation.Value; @@ -68,7 +68,7 @@ public CookieSessionHandler sessionHandler(JWTService jwtService, CertificateMan } @Bean - public SSOSessionHandler ssoSessionHandler(ClientSyncService clientSyncService, AuthenticationFlowContextService authenticationFlowContextService, LoginAttemptService loginAttemptService, Domain domain) { + public SSOSessionHandler ssoSessionHandler(ClientSyncService clientSyncService, AuthenticationFlowContextService authenticationFlowContextService, LoginAttemptGatewayService loginAttemptService, Domain domain) { return new SSOSessionHandler(clientSyncService, authenticationFlowContextService, loginAttemptService, domain); } diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandler.java index c6fe4e0f42f..2c44e5af6bb 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandler.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandler.java @@ -24,14 +24,13 @@ import io.gravitee.am.common.utils.ConstantKeys; import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.gateway.handler.common.client.ClientSyncService; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.vertx.web.handler.impl.CookieSession; import io.gravitee.am.model.Domain; import io.gravitee.am.model.UserIdentity; import io.gravitee.am.model.account.AccountSettings; import io.gravitee.am.model.oidc.Client; - import io.gravitee.am.service.AuthenticationFlowContextService; -import io.gravitee.am.service.LoginAttemptService; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; @@ -69,10 +68,10 @@ public class SSOSessionHandler implements Handler { private static final Logger LOGGER = LoggerFactory.getLogger(SSOSessionHandler.class); private ClientSyncService clientSyncService; private AuthenticationFlowContextService authenticationFlowContextService; - private LoginAttemptService loginAttemptService; + private LoginAttemptGatewayService loginAttemptService; private Domain domain; - public SSOSessionHandler(ClientSyncService clientSyncService, AuthenticationFlowContextService authenticationFlowContextService, LoginAttemptService loginAttemptService, Domain domain) { + public SSOSessionHandler(ClientSyncService clientSyncService, AuthenticationFlowContextService authenticationFlowContextService, LoginAttemptGatewayService loginAttemptService, Domain domain) { this.clientSyncService = clientSyncService; this.authenticationFlowContextService = authenticationFlowContextService; this.loginAttemptService = loginAttemptService; diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/auth/UserAuthenticationManagerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/auth/UserAuthenticationManagerTest.java index 3a0edb67f94..79aaeab7330 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/auth/UserAuthenticationManagerTest.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/auth/UserAuthenticationManagerTest.java @@ -25,6 +25,7 @@ import io.gravitee.am.gateway.handler.common.auth.user.UserAuthenticationService; import io.gravitee.am.gateway.handler.common.auth.user.impl.UserAuthenticationManagerImpl; import io.gravitee.am.gateway.handler.common.password.PasswordPolicyManager; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.user.UserGatewayService; import io.gravitee.am.identityprovider.api.Authentication; import io.gravitee.am.identityprovider.api.AuthenticationContext; @@ -38,7 +39,6 @@ import io.gravitee.am.model.idp.ApplicationIdentityProvider; import io.gravitee.am.model.oidc.Client; import io.gravitee.am.monitoring.provider.GatewayMetricProvider; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.PasswordService; import io.gravitee.common.event.EventManager; import io.reactivex.rxjava3.core.Maybe; @@ -88,7 +88,7 @@ public class UserAuthenticationManagerTest { private EventManager eventManager; @Mock - private LoginAttemptService loginAttemptService; + private LoginAttemptGatewayService loginAttemptService; @Mock private UserGatewayService userService; diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/LoginAttemptServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/service/LoginAttemptGatewayServiceTest.java similarity index 52% rename from gravitee-am-service/src/test/java/io/gravitee/am/service/LoginAttemptServiceTest.java rename to gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/service/LoginAttemptGatewayServiceTest.java index ae4751124ea..4c1820bf42c 100644 --- a/gravitee-am-service/src/test/java/io/gravitee/am/service/LoginAttemptServiceTest.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/service/LoginAttemptGatewayServiceTest.java @@ -13,40 +13,55 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.gravitee.am.service; + +package io.gravitee.am.gateway.handler.common.service; + import io.gravitee.am.dataplane.api.repository.LoginAttemptRepository; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.gateway.handler.common.service.impl.LoginAttemptGatewayServiceImpl; import io.gravitee.am.model.Domain; import io.gravitee.am.model.LoginAttempt; import io.gravitee.am.model.account.AccountSettings; -import io.gravitee.am.service.impl.LoginAttemptServiceImpl; -import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.plugins.dataplane.core.DataPlaneRegistry; +import io.gravitee.am.service.exception.TechnicalManagementException; +import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.observers.TestObserver; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.concurrent.TimeUnit; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** - * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) + * @author Eric LELEU (eric.leleu at graviteesource.com) * @author GraviteeSource Team */ -@RunWith(MockitoJUnitRunner.class) -public class LoginAttemptServiceTest { - - @InjectMocks - private LoginAttemptService loginAttemptService = new LoginAttemptServiceImpl(); +@ExtendWith(MockitoExtension.class) +public class LoginAttemptGatewayServiceTest { @Mock private LoginAttemptRepository loginAttemptRepository; + @Mock + private DataPlaneRegistry dataPlaneRegistry; + + private LoginAttemptGatewayService service; + + @BeforeEach + public void setUp() { + when(dataPlaneRegistry.getLoginAttemptRepository(any())).thenReturn(loginAttemptRepository); + service = new LoginAttemptGatewayServiceImpl(dataPlaneRegistry); + } + @Test public void shouldCreateUser_accountLockFirstConnection() { final LoginAttemptCriteria loginAttemptCriteria = new LoginAttemptCriteria.Builder() @@ -67,7 +82,7 @@ public void shouldCreateUser_accountLockFirstConnection() { when(loginAttemptRepository.findByCriteria(loginAttemptCriteria)).thenReturn(Maybe.just(loginAttempt)); when(loginAttemptRepository.update(loginAttempt)).thenReturn(Single.just(loginAttempt)); - TestObserver testObserver = loginAttemptService.loginFailed(new Domain("domain-1"), loginAttemptCriteria, accountSettings).test(); + TestObserver testObserver = service.loginFailed(new Domain("domain-1"), loginAttemptCriteria, accountSettings).test(); testObserver.awaitDone(10, TimeUnit.SECONDS); testObserver.assertNoErrors(); } @@ -92,8 +107,45 @@ public void shouldUpdateUser_accountLockAlreadyRegistered() { when(loginAttemptRepository.findByCriteria(loginAttemptCriteria)).thenReturn(Maybe.just(loginAttempt)); when(loginAttemptRepository.update(loginAttempt)).thenReturn(Single.just(loginAttempt)); - TestObserver testObserver = loginAttemptService.loginFailed(new Domain("domain-1"), loginAttemptCriteria, accountSettings).test(); + TestObserver testObserver = service.loginFailed(new Domain("domain-1"), loginAttemptCriteria, accountSettings).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + } + + @Test + public void should_reset_login_attempt() { + final LoginAttemptCriteria loginAttemptCriteria = new LoginAttemptCriteria.Builder() + .client("client-1") + .domain("domain-1") + .username("user-1") + .identityProvider("idp-1") + .build(); + + when(loginAttemptRepository.delete(any(LoginAttemptCriteria.class))).thenReturn(Completable.complete()); + + TestObserver testObserver = service.reset(new Domain("domain-1"), loginAttemptCriteria).test(); testObserver.awaitDone(10, TimeUnit.SECONDS); testObserver.assertNoErrors(); + + verify(loginAttemptRepository).delete(any(LoginAttemptCriteria.class)); + } + + @Test + public void should_propagate_reset_exception() throws Exception { + final LoginAttemptCriteria loginAttemptCriteria = new LoginAttemptCriteria.Builder() + .client("client-1") + .domain("domain-1") + .username("user-1") + .identityProvider("idp-1") + .build(); + + when(loginAttemptRepository.delete(any(LoginAttemptCriteria.class))).thenReturn(Completable.error(new RuntimeException("test"))); + + var testObserver = service.reset(new Domain("domain-1"), loginAttemptCriteria).test(); + testObserver.await(10, TimeUnit.SECONDS); + testObserver.assertError(TechnicalManagementException.class); + testObserver.assertError(throwable -> throwable.getCause().getMessage().equals("test")); + + verify(loginAttemptRepository).delete(any(LoginAttemptCriteria.class)); } } diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandlerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandlerTest.java index 28d38db8a78..8efe8e9050b 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandlerTest.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/SSOSessionHandlerTest.java @@ -21,6 +21,7 @@ import io.gravitee.am.gateway.handler.common.client.ClientSyncService; import io.gravitee.am.gateway.handler.common.jwt.JWTService; import io.gravitee.am.gateway.handler.common.jwt.SubjectManager; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.vertx.RxWebTestBase; import io.gravitee.am.gateway.handler.common.vertx.web.handler.impl.CookieSessionHandler; import io.gravitee.am.model.Domain; @@ -31,7 +32,6 @@ import io.gravitee.am.model.idp.ApplicationIdentityProvider; import io.gravitee.am.model.oidc.Client; import io.gravitee.am.service.AuthenticationFlowContextService; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.impl.user.UserEnhancer; import io.gravitee.common.http.HttpStatusCode; import io.reactivex.rxjava3.core.Completable; @@ -78,7 +78,7 @@ public class SSOSessionHandlerTest extends RxWebTestBase { private AuthenticationFlowContextService authenticationFlowContextService; @Mock - private LoginAttemptService loginAttemptService; + private LoginAttemptGatewayService loginAttemptService; @Mock private Domain domain; diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/RootProvider.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/RootProvider.java index dd3742dc0de..0c5465682c1 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/RootProvider.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/RootProvider.java @@ -31,6 +31,7 @@ import io.gravitee.am.gateway.handler.common.ruleengine.RuleEngine; import io.gravitee.am.gateway.handler.common.service.CredentialGatewayService; import io.gravitee.am.gateway.handler.common.service.DeviceGatewayService; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.service.UserActivityGatewayService; import io.gravitee.am.gateway.handler.common.vertx.web.auth.provider.UserAuthProvider; import io.gravitee.am.gateway.handler.common.vertx.web.endpoint.ErrorEndpoint; @@ -132,7 +133,6 @@ import io.gravitee.am.service.AuditService; import io.gravitee.am.service.AuthenticationFlowContextService; import io.gravitee.am.service.FactorService; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.PasswordService; import io.gravitee.am.service.RateLimiterService; import io.gravitee.am.service.VerifyAttemptService; @@ -270,7 +270,7 @@ public class RootProvider extends AbstractProtocolProvider { private BotDetectionManager botDetectionManager; @Autowired - private LoginAttemptService loginAttemptService; + private LoginAttemptGatewayService loginAttemptService; @Autowired private DeviceIdentifierManager deviceIdentifierManager; diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandler.java index 871d8e33960..d177505ea61 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandler.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandler.java @@ -19,6 +19,7 @@ import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria.Builder; import io.gravitee.am.gateway.handler.common.auth.idp.IdentityProviderManager; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.service.UserActivityGatewayService; import io.gravitee.am.model.ChallengeSettings; import io.gravitee.am.model.Domain; @@ -28,7 +29,6 @@ import io.gravitee.am.model.account.AccountSettings; import io.gravitee.am.model.idp.ApplicationIdentityProvider; import io.gravitee.am.model.oidc.Client; -import io.gravitee.am.service.LoginAttemptService; import io.reactivex.rxjava3.core.Maybe; import io.vertx.core.Handler; import io.vertx.rxjava3.ext.web.RoutingContext; @@ -53,13 +53,13 @@ public class LoginAttemptHandler implements Handler { private final Domain domain; private final IdentityProviderManager identityProviderManager; - private final LoginAttemptService loginAttemptService; + private final LoginAttemptGatewayService loginAttemptService; private final UserActivityGatewayService userActivityService; public LoginAttemptHandler( Domain domain, IdentityProviderManager identityProviderManager, - LoginAttemptService loginAttemptService, + LoginAttemptGatewayService loginAttemptService, UserActivityGatewayService userActivityService ) { this.domain = domain; diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/service/user/impl/UserServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/service/user/impl/UserServiceImpl.java index 6eea013099f..a52917b184e 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/service/user/impl/UserServiceImpl.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/service/user/impl/UserServiceImpl.java @@ -21,6 +21,7 @@ import io.gravitee.am.common.oidc.StandardClaims; import io.gravitee.am.common.utils.ConstantKeys; import io.gravitee.am.common.utils.RandomString; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.gateway.handler.common.auth.idp.IdentityProviderManager; import io.gravitee.am.gateway.handler.common.auth.user.EndUserAuthentication; import io.gravitee.am.gateway.handler.common.client.ClientSyncService; @@ -29,6 +30,7 @@ import io.gravitee.am.gateway.handler.common.jwt.SubjectManager; import io.gravitee.am.gateway.handler.common.password.PasswordPolicyManager; import io.gravitee.am.gateway.handler.common.service.CredentialGatewayService; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.user.UserGatewayService; import io.gravitee.am.gateway.handler.root.service.response.RegistrationResponse; import io.gravitee.am.gateway.handler.root.service.response.ResetPasswordResponse; @@ -53,10 +55,8 @@ import io.gravitee.am.model.account.AccountSettings; import io.gravitee.am.model.factor.EnrolledFactor; import io.gravitee.am.model.oidc.Client; -import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.service.AuditService; import io.gravitee.am.service.DomainReadService; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.TokenService; import io.gravitee.am.service.exception.ClientNotFoundException; import io.gravitee.am.service.exception.EmailFormatInvalidException; @@ -146,7 +146,7 @@ public class UserServiceImpl implements UserService { private AuditService auditService; @Autowired - private LoginAttemptService loginAttemptService; + private LoginAttemptGatewayService loginAttemptService; @Autowired private CredentialGatewayService credentialService; diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandlerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandlerTest.java index f189bf5fba2..8398b8664a5 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandlerTest.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/resources/handler/loginattempt/LoginAttemptHandlerTest.java @@ -18,6 +18,7 @@ import io.gravitee.am.common.utils.ConstantKeys; import io.gravitee.am.gateway.handler.common.auth.idp.IdentityProviderManager; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.service.UserActivityGatewayService; import io.gravitee.am.gateway.handler.root.resources.handler.dummies.SpyRoutingContext; import io.gravitee.am.model.ChallengeSettings; @@ -27,7 +28,6 @@ import io.gravitee.am.model.MFASettings; import io.gravitee.am.model.idp.ApplicationIdentityProvider; import io.gravitee.am.model.oidc.Client; -import io.gravitee.am.service.LoginAttemptService; import io.reactivex.rxjava3.core.Maybe; import org.junit.Before; import org.junit.Test; @@ -59,7 +59,7 @@ public class LoginAttemptHandlerTest { private IdentityProviderManager identityProviderManager; @Mock - private LoginAttemptService loginAttemptService; + private LoginAttemptGatewayService loginAttemptService; @Mock private UserActivityGatewayService userActivityService; diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/service/user/UserServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/service/user/UserServiceTest.java index 031e5a71426..aa989ee599e 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/service/user/UserServiceTest.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/test/java/io/gravitee/am/gateway/handler/root/service/user/UserServiceTest.java @@ -28,6 +28,7 @@ import io.gravitee.am.gateway.handler.common.jwt.SubjectManager; import io.gravitee.am.gateway.handler.common.password.PasswordPolicyManager; import io.gravitee.am.gateway.handler.common.service.CredentialGatewayService; +import io.gravitee.am.gateway.handler.common.service.LoginAttemptGatewayService; import io.gravitee.am.gateway.handler.common.user.UserGatewayService; import io.gravitee.am.gateway.handler.root.service.user.impl.UserServiceImpl; import io.gravitee.am.gateway.handler.root.service.user.model.ForgotPasswordParameters; @@ -49,7 +50,6 @@ import io.gravitee.am.repository.management.api.search.FilterCriteria; import io.gravitee.am.service.AuditService; import io.gravitee.am.service.DomainReadService; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.TokenService; import io.gravitee.am.service.exception.EnforceUserIdentityException; import io.gravitee.am.service.exception.PasswordHistoryException; @@ -120,7 +120,7 @@ public class UserServiceTest { private UserGatewayService commonUserService; @Mock - private LoginAttemptService loginAttemptService; + private LoginAttemptGatewayService loginAttemptService; @Mock private CredentialGatewayService credentialService; diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/dataplane/LoginAttemptManagementService.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/dataplane/LoginAttemptManagementService.java new file mode 100644 index 00000000000..4886dd9abef --- /dev/null +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/dataplane/LoginAttemptManagementService.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gravitee.am.management.service.dataplane; + +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.model.Domain; +import io.reactivex.rxjava3.core.Completable; + +public interface LoginAttemptManagementService { + Completable reset(Domain domain, LoginAttemptCriteria criteria); +} diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/dataplane/impl/LoginAttemptManagementServiceImpl.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/dataplane/impl/LoginAttemptManagementServiceImpl.java new file mode 100644 index 00000000000..e39738118e3 --- /dev/null +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/dataplane/impl/LoginAttemptManagementServiceImpl.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gravitee.am.management.service.dataplane.impl; + + +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.management.service.dataplane.LoginAttemptManagementService; +import io.gravitee.am.model.Domain; +import io.gravitee.am.plugins.dataplane.core.DataPlaneRegistry; +import io.gravitee.am.service.exception.TechnicalManagementException; +import io.reactivex.rxjava3.core.Completable; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author Eric LELEU (eric.leleu at graviteesource.com) + * @author GraviteeSource Team + */ +@Component +@Slf4j +public class LoginAttemptManagementServiceImpl implements LoginAttemptManagementService { + + @Autowired + private DataPlaneRegistry dataPlaneRegistry; + + @Override + public Completable reset(Domain domain, LoginAttemptCriteria criteria) { + log.debug("Delete login attempt for {}", criteria); + return dataPlaneRegistry.getLoginAttemptRepository(domain).delete(criteria) + .onErrorResumeNext(ex -> { + log.error("An error occurs while trying to delete login attempt for {}", criteria, ex); + return Completable.error(new TechnicalManagementException( + String.format("An error occurs while trying to delete login attempt: %s", criteria), ex)); + }); + } +} diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/ManagementUserServiceImpl.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/ManagementUserServiceImpl.java index bed3c5e8b5c..f0181d7147f 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/ManagementUserServiceImpl.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/ManagementUserServiceImpl.java @@ -30,6 +30,7 @@ import io.gravitee.am.management.service.IdentityProviderManager; import io.gravitee.am.management.service.ManagementUserService; import io.gravitee.am.management.service.dataplane.CredentialManagementService; +import io.gravitee.am.management.service.dataplane.LoginAttemptManagementService; import io.gravitee.am.management.service.dataplane.UserActivityManagementService; import io.gravitee.am.model.Application; import io.gravitee.am.model.Domain; @@ -51,7 +52,6 @@ import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; import io.gravitee.am.service.ApplicationService; import io.gravitee.am.service.AuditService; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.PasswordPolicyService; import io.gravitee.am.service.PasswordService; import io.gravitee.am.service.RateLimiterService; @@ -123,7 +123,7 @@ public class ManagementUserServiceImpl implements ManagementUserService { private JWTBuilder jwtBuilder; @Autowired - private LoginAttemptService loginAttemptService; + private LoginAttemptManagementService loginAttemptService; @Autowired private ApplicationService applicationService; @@ -741,7 +741,7 @@ public Single updateUsername(Domain domain, String id, String username, io repository::findByUsernameAndSource, auditService, credentialService, - loginAttemptService) + loginAttemptService::reset) .updateUsername(domain, username, principal, (User user) -> identityProviderManager.getUserProvider(user.getSource()) .switchIfEmpty(Single.error(() -> new UserProviderNotFoundException(user.getSource()))), diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/ManagementUserServiceTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/ManagementUserServiceTest.java index 9cc18c0d40d..d302e92338f 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/ManagementUserServiceTest.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/ManagementUserServiceTest.java @@ -25,6 +25,7 @@ import io.gravitee.am.identityprovider.api.UserProvider; import io.gravitee.am.jwt.JWTBuilder; import io.gravitee.am.management.service.dataplane.CredentialManagementService; +import io.gravitee.am.management.service.dataplane.LoginAttemptManagementService; import io.gravitee.am.management.service.impl.ManagementUserServiceImpl; import io.gravitee.am.model.Application; import io.gravitee.am.model.Credential; @@ -49,7 +50,6 @@ import io.gravitee.am.repository.exceptions.TechnicalException; import io.gravitee.am.service.ApplicationService; import io.gravitee.am.service.AuditService; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.MembershipService; import io.gravitee.am.service.PasswordPolicyService; import io.gravitee.am.service.PasswordService; @@ -164,7 +164,7 @@ public class ManagementUserServiceTest { private DataPlaneRegistry dataPlaneRegistry; @Mock - private LoginAttemptService loginAttemptService; + private LoginAttemptManagementService loginAttemptService; @Mock private RoleService roleService; diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/dataplane/LoginAttemptManagementServiceTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/dataplane/LoginAttemptManagementServiceTest.java new file mode 100644 index 00000000000..3b27aacea1e --- /dev/null +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/dataplane/LoginAttemptManagementServiceTest.java @@ -0,0 +1,98 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gravitee.am.management.service.dataplane; + + +import io.gravitee.am.dataplane.api.repository.LoginAttemptRepository; +import io.gravitee.am.dataplane.api.search.LoginAttemptCriteria; +import io.gravitee.am.management.service.dataplane.impl.LoginAttemptManagementServiceImpl; +import io.gravitee.am.model.Domain; +import io.gravitee.am.plugins.dataplane.core.DataPlaneRegistry; +import io.gravitee.am.service.exception.TechnicalManagementException; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.observers.TestObserver; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.concurrent.TimeUnit; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Eric LELEU (eric.leleu at graviteesource.com) + * @author GraviteeSource Team + */ +@ExtendWith(MockitoExtension.class) +public class LoginAttemptManagementServiceTest { + + @Mock + private LoginAttemptRepository loginAttemptRepository; + + @Mock + private DataPlaneRegistry dataPlaneRegistry; + + @InjectMocks + private LoginAttemptManagementService service = new LoginAttemptManagementServiceImpl(); + + @BeforeEach + public void setUp() { + when(dataPlaneRegistry.getLoginAttemptRepository(any())).thenReturn(loginAttemptRepository); + } + + @Test + public void should_reset_login_attempt() { + final LoginAttemptCriteria loginAttemptCriteria = new LoginAttemptCriteria.Builder() + .client("client-1") + .domain("domain-1") + .username("user-1") + .identityProvider("idp-1") + .build(); + + when(loginAttemptRepository.delete(any(LoginAttemptCriteria.class))).thenReturn(Completable.complete()); + + TestObserver testObserver = service.reset(new Domain("domain-1"), loginAttemptCriteria).test(); + testObserver.awaitDone(10, TimeUnit.SECONDS); + testObserver.assertNoErrors(); + + verify(loginAttemptRepository).delete(any(LoginAttemptCriteria.class)); + } + + @Test + public void should_propagate_reset_exception() throws Exception { + final LoginAttemptCriteria loginAttemptCriteria = new LoginAttemptCriteria.Builder() + .client("client-1") + .domain("domain-1") + .username("user-1") + .identityProvider("idp-1") + .build(); + + when(loginAttemptRepository.delete(any(LoginAttemptCriteria.class))).thenReturn(Completable.error(new RuntimeException("test"))); + + var testObserver = service.reset(new Domain("domain-1"), loginAttemptCriteria).test(); + testObserver.await(10, TimeUnit.SECONDS); + testObserver.assertError(TechnicalManagementException.class); + testObserver.assertError(throwable -> throwable.getCause().getMessage().equals("test")); + + verify(loginAttemptRepository).delete(any(LoginAttemptCriteria.class)); + } +} diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/business/user/UpdateUsernameDomainRule.java b/gravitee-am-service/src/main/java/io/gravitee/am/business/user/UpdateUsernameDomainRule.java index d2ade807590..d1b0aac0c01 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/business/user/UpdateUsernameDomainRule.java +++ b/gravitee-am-service/src/main/java/io/gravitee/am/business/user/UpdateUsernameDomainRule.java @@ -28,7 +28,6 @@ import io.gravitee.am.model.Reference; import io.gravitee.am.model.User; import io.gravitee.am.service.AuditService; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.dataplane.CredentialCommonService; import io.gravitee.am.service.exception.InvalidUserException; import io.gravitee.am.service.exception.UserNotFoundException; @@ -55,21 +54,21 @@ @Slf4j public class UpdateUsernameDomainRule extends UpdateUserRule { private Function3> findUserByUsernameAndSource; + private BiFunction resetLoginAttempts; private AuditService auditService; private CredentialCommonService credentialService; - private LoginAttemptService loginAttemptService; public UpdateUsernameDomainRule(UserValidator validator, BiFunction> userUpdater, Function3> findUserByUsernameAndSource, AuditService auditService, CredentialCommonService credentialService, - LoginAttemptService loginAttemptService) { + BiFunction resetLoginAttempts) { super(validator, userUpdater); this.findUserByUsernameAndSource = findUserByUsernameAndSource; this.auditService = auditService; this.credentialService = credentialService; - this.loginAttemptService = loginAttemptService; + this.resetLoginAttempts = resetLoginAttempts; } public Single updateUsername(Domain domain, String username, io.gravitee.am.identityprovider.api.User principal, Function> userProviderSupplier, Supplier> userSupplier) { @@ -105,7 +104,7 @@ public Single updateUsername(Domain domain, String username, io.gravitee.a )) .doOnSuccess(user1 -> { auditService.report(AuditBuilder.builder(UserAuditBuilder.class).principal(principal).type(EventType.USERNAME_UPDATED).user(user1)); - loginAttemptService.reset(domain, createLoginAttemptCriteria(user1.getReferenceId(), oldUsername.get())) + resetLoginAttempts.apply(domain, createLoginAttemptCriteria(user1.getReferenceId(), oldUsername.get())) .onErrorResumeNext(error -> { log.warn("Could not delete login attempt {}", error.getMessage()); return Completable.complete(); diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/business/user/UpdateUsernameDomainRuleTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/business/user/UpdateUsernameDomainRuleTest.java index f80f06e328a..91d5fd93c4d 100644 --- a/gravitee-am-service/src/test/java/io/gravitee/am/business/user/UpdateUsernameDomainRuleTest.java +++ b/gravitee-am-service/src/test/java/io/gravitee/am/business/user/UpdateUsernameDomainRuleTest.java @@ -29,7 +29,6 @@ import io.gravitee.am.model.factor.EnrolledFactorSecurity; import io.gravitee.am.model.factor.FactorStatus; import io.gravitee.am.service.AuditService; -import io.gravitee.am.service.LoginAttemptService; import io.gravitee.am.service.dataplane.CredentialCommonService; import io.gravitee.am.service.exception.InvalidUserException; import io.gravitee.am.service.exception.TechnicalManagementException; @@ -50,6 +49,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; import static io.gravitee.am.model.ReferenceType.DOMAIN; import static io.gravitee.am.service.validators.email.EmailValidatorImpl.EMAIL_PATTERN; @@ -84,7 +84,7 @@ public class UpdateUsernameDomainRuleTest { private UserRepository userRepository; @Mock - private LoginAttemptService loginAttemptService; + private BiFunction resetLoginAttemptFunction; @Mock private CredentialCommonService credentialService; @@ -106,7 +106,7 @@ public class UpdateUsernameDomainRuleTest { @BeforeEach public void initRule() { - this.rule = new UpdateUsernameDomainRule(userValidator, userRepository::update, userRepository::findByUsernameAndSource, auditService, credentialService, loginAttemptService); + this.rule = new UpdateUsernameDomainRule(userValidator, userRepository::update, userRepository::findByUsernameAndSource, auditService, credentialService, resetLoginAttemptFunction); } @Test @@ -281,7 +281,7 @@ void must_reset_username() { when(userProvider.findByUsername(anyString())).thenReturn(Maybe.just(defaultUser)); when(userProvider.updateUsername(any(), anyString())).thenReturn(Single.just(idpUserUpdated)); - when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); + when(resetLoginAttemptFunction.apply(any(), any())).thenReturn(Completable.complete()); when(credentialService.findByUsername(any(), eq(user.getUsername()))).thenReturn(Flowable.empty()); @@ -294,7 +294,7 @@ void must_reset_username() { verify(userProvider, times(1)).updateUsername(any(), anyString()); verify(credentialService, times(1)).findByUsername(any(), eq(USERNAME)); verify(credentialService, never()).update(any(), any()); - verify(loginAttemptService, times(1)).reset(any(), any()); + verify(resetLoginAttemptFunction, times(1)).apply(any(), any()); } @Test @@ -324,7 +324,7 @@ void must_update_user_webauth_credential() { when(userProvider.findByUsername(anyString())).thenReturn(Maybe.just(defaultUser)); when(userProvider.updateUsername(any(), anyString())).thenReturn(Single.just(idpUserUpdated)); - when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); + when(resetLoginAttemptFunction.apply(any(), any())).thenReturn(Completable.complete()); var credential = new Credential(); credential.setUsername(user.getUsername()); @@ -339,7 +339,7 @@ void must_update_user_webauth_credential() { verify(userRepository, times(1)).update(any(), any()); verify(userProvider, times(1)).updateUsername(any(), anyString()); - verify(loginAttemptService, times(1)).reset(any(), any()); + verify(resetLoginAttemptFunction, times(1)).apply(any(), any()); verify(credentialService, times(1)).findByUsername(any(), eq(USERNAME)); verify(credentialService, times(1)).update(any(), argThat(argument -> NEW_USERNAME.equals(argument.getUsername()))); } @@ -380,7 +380,7 @@ void must_update_user_moving_factor() { when(userProvider.findByUsername(anyString())).thenReturn(Maybe.just(defaultUser)); when(userProvider.updateUsername(any(), anyString())).thenReturn(Single.just(idpUserUpdated)); - when(loginAttemptService.reset(any(), any())).thenReturn(Completable.complete()); + when(resetLoginAttemptFunction.apply(any(), any())).thenReturn(Completable.complete()); when(credentialService.findByUsername(any(), eq(user.getUsername()))).thenReturn(Flowable.empty()); var observer = rule.updateUsername(domain, NEW_USERNAME, null, (User user1) -> Single.just(userProvider), () -> Single.just(user)).test(); @@ -390,7 +390,7 @@ void must_update_user_moving_factor() { verify(userRepository, times(1)).update(any(), any()); verify(userProvider, times(1)).updateUsername(any(), anyString()); - verify(loginAttemptService, times(1)).reset(any(), any()); + verify(resetLoginAttemptFunction, times(1)).apply(any(), any()); verify(credentialService, times(1)).findByUsername(any(), eq(USERNAME)); assertEquals(1, user.getFactors().size());