This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch postgresql
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/postgresql by this push:
new decec6ffc5 JAMES-2586 PostgresDelegationStore (#1851)
decec6ffc5 is described below
commit decec6ffc5ac8f5037572d47a099e257ed644029
Author: hungphan227 <[email protected]>
AuthorDate: Wed Dec 13 16:24:11 2023 +0700
JAMES-2586 PostgresDelegationStore (#1851)
---
.../org/apache/james/PostgresJamesServerMain.java | 3 +-
...ule.java => PostgresDelegationStoreModule.java} | 25 +++---
.../data/PostgresUsersRepositoryModule.java | 22 -----
.../user/postgres/PostgresDelegationStore.java | 89 ++++++++++++++++++++
.../james/user/postgres/PostgresUserModule.java | 8 +-
.../james/user/postgres/PostgresUsersDAO.java | 98 ++++++++++++++++++++++
.../user/postgres/PostgresDelegationStoreTest.java | 67 +++++++++++++++
7 files changed, 272 insertions(+), 40 deletions(-)
diff --git
a/server/apps/postgres-app/src/main/java/org/apache/james/PostgresJamesServerMain.java
b/server/apps/postgres-app/src/main/java/org/apache/james/PostgresJamesServerMain.java
index 1191382350..24cfa7d1cd 100644
---
a/server/apps/postgres-app/src/main/java/org/apache/james/PostgresJamesServerMain.java
+++
b/server/apps/postgres-app/src/main/java/org/apache/james/PostgresJamesServerMain.java
@@ -24,6 +24,7 @@ import org.apache.james.modules.MailboxModule;
import org.apache.james.modules.MailetProcessingModule;
import org.apache.james.modules.RunArgumentsModule;
import org.apache.james.modules.data.PostgresDataModule;
+import org.apache.james.modules.data.PostgresDelegationStoreModule;
import org.apache.james.modules.data.PostgresUsersRepositoryModule;
import org.apache.james.modules.data.SievePostgresRepositoryModules;
import org.apache.james.modules.mailbox.DefaultEventModule;
@@ -79,7 +80,7 @@ public class PostgresJamesServerMain implements
JamesServerMain {
private static final Module POSTGRES_SERVER_MODULE = Modules.combine(
new ActiveMQQueueModule(),
- new NaiveDelegationStoreModule(),
+ new PostgresDelegationStoreModule(),
new DefaultProcessorsConfigurationProviderModule(),
new PostgresMailboxModule(),
new PostgresDataModule(),
diff --git
a/server/container/guice/postgres-common/src/main/java/org/apache/james/modules/data/PostgresUsersRepositoryModule.java
b/server/container/guice/postgres-common/src/main/java/org/apache/james/modules/data/PostgresDelegationStoreModule.java
similarity index 74%
copy from
server/container/guice/postgres-common/src/main/java/org/apache/james/modules/data/PostgresUsersRepositoryModule.java
copy to
server/container/guice/postgres-common/src/main/java/org/apache/james/modules/data/PostgresDelegationStoreModule.java
index 575f7621f0..f6e5521ead 100644
---
a/server/container/guice/postgres-common/src/main/java/org/apache/james/modules/data/PostgresUsersRepositoryModule.java
+++
b/server/container/guice/postgres-common/src/main/java/org/apache/james/modules/data/PostgresDelegationStoreModule.java
@@ -22,27 +22,29 @@ package org.apache.james.modules.data;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.james.backends.postgres.PostgresModule;
import org.apache.james.server.core.configuration.ConfigurationProvider;
-import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.api.DelegationStore;
+import org.apache.james.user.api.DelegationUsernameChangeTaskStep;
+import org.apache.james.user.api.UsernameChangeTaskStep;
import org.apache.james.user.lib.UsersDAO;
+import org.apache.james.user.postgres.PostgresDelegationStore;
import org.apache.james.user.postgres.PostgresUserModule;
import org.apache.james.user.postgres.PostgresUsersDAO;
-import org.apache.james.user.postgres.PostgresUsersRepository;
import org.apache.james.user.postgres.PostgresUsersRepositoryConfiguration;
-import org.apache.james.utils.InitializationOperation;
-import org.apache.james.utils.InitilizationOperationBuilder;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.multibindings.Multibinder;
-import com.google.inject.multibindings.ProvidesIntoSet;
-public class PostgresUsersRepositoryModule extends AbstractModule {
+public class PostgresDelegationStoreModule extends AbstractModule {
@Override
public void configure() {
- bind(PostgresUsersRepository.class).in(Scopes.SINGLETON);
- bind(UsersRepository.class).to(PostgresUsersRepository.class);
+ bind(DelegationStore.class).to(PostgresDelegationStore.class);
+
bind(PostgresDelegationStore.UserExistencePredicate.class).to(PostgresDelegationStore.UserExistencePredicateImplementation.class);
+
+ Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class)
+ .addBinding().to(DelegationUsernameChangeTaskStep.class);
bind(PostgresUsersDAO.class).in(Scopes.SINGLETON);
bind(UsersDAO.class).to(PostgresUsersDAO.class);
@@ -57,11 +59,4 @@ public class PostgresUsersRepositoryModule extends
AbstractModule {
return PostgresUsersRepositoryConfiguration.from(
configurationProvider.getConfiguration("usersrepository"));
}
-
- @ProvidesIntoSet
- InitializationOperation configureInitialization(ConfigurationProvider
configurationProvider, PostgresUsersRepository usersRepository) {
- return InitilizationOperationBuilder
- .forClass(PostgresUsersRepository.class)
- .init(() ->
usersRepository.configure(configurationProvider.getConfiguration("usersrepository")));
- }
}
diff --git
a/server/container/guice/postgres-common/src/main/java/org/apache/james/modules/data/PostgresUsersRepositoryModule.java
b/server/container/guice/postgres-common/src/main/java/org/apache/james/modules/data/PostgresUsersRepositoryModule.java
index 575f7621f0..ff30223bb8 100644
---
a/server/container/guice/postgres-common/src/main/java/org/apache/james/modules/data/PostgresUsersRepositoryModule.java
+++
b/server/container/guice/postgres-common/src/main/java/org/apache/james/modules/data/PostgresUsersRepositoryModule.java
@@ -19,23 +19,14 @@
package org.apache.james.modules.data;
-import org.apache.commons.configuration2.ex.ConfigurationException;
-import org.apache.james.backends.postgres.PostgresModule;
import org.apache.james.server.core.configuration.ConfigurationProvider;
import org.apache.james.user.api.UsersRepository;
-import org.apache.james.user.lib.UsersDAO;
-import org.apache.james.user.postgres.PostgresUserModule;
-import org.apache.james.user.postgres.PostgresUsersDAO;
import org.apache.james.user.postgres.PostgresUsersRepository;
-import org.apache.james.user.postgres.PostgresUsersRepositoryConfiguration;
import org.apache.james.utils.InitializationOperation;
import org.apache.james.utils.InitilizationOperationBuilder;
import com.google.inject.AbstractModule;
-import com.google.inject.Provides;
import com.google.inject.Scopes;
-import com.google.inject.Singleton;
-import com.google.inject.multibindings.Multibinder;
import com.google.inject.multibindings.ProvidesIntoSet;
public class PostgresUsersRepositoryModule extends AbstractModule {
@@ -43,19 +34,6 @@ public class PostgresUsersRepositoryModule extends
AbstractModule {
public void configure() {
bind(PostgresUsersRepository.class).in(Scopes.SINGLETON);
bind(UsersRepository.class).to(PostgresUsersRepository.class);
-
- bind(PostgresUsersDAO.class).in(Scopes.SINGLETON);
- bind(UsersDAO.class).to(PostgresUsersDAO.class);
-
- Multibinder<PostgresModule> postgresDataDefinitions =
Multibinder.newSetBinder(binder(), PostgresModule.class);
-
postgresDataDefinitions.addBinding().toInstance(PostgresUserModule.MODULE);
- }
-
- @Provides
- @Singleton
- public PostgresUsersRepositoryConfiguration
provideConfiguration(ConfigurationProvider configurationProvider) throws
ConfigurationException {
- return PostgresUsersRepositoryConfiguration.from(
- configurationProvider.getConfiguration("usersrepository"));
}
@ProvidesIntoSet
diff --git
a/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresDelegationStore.java
b/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresDelegationStore.java
new file mode 100644
index 0000000000..4f04f45075
--- /dev/null
+++
b/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresDelegationStore.java
@@ -0,0 +1,89 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you 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 org.apache.james.user.postgres;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.user.api.DelegationStore;
+import org.apache.james.user.api.UsersRepository;
+import org.reactivestreams.Publisher;
+
+import reactor.core.publisher.Mono;
+
+public class PostgresDelegationStore implements DelegationStore {
+ public interface UserExistencePredicate {
+ Mono<Boolean> exists(Username username);
+ }
+
+ public static class UserExistencePredicateImplementation implements
UserExistencePredicate {
+ private final UsersRepository usersRepository;
+
+ @Inject
+ UserExistencePredicateImplementation(UsersRepository usersRepository) {
+ this.usersRepository = usersRepository;
+ }
+
+ @Override
+ public Mono<Boolean> exists(Username username) {
+ return Mono.from(usersRepository.containsReactive(username));
+ }
+ }
+
+ private PostgresUsersDAO postgresUsersDAO;
+ private final UserExistencePredicate userExistencePredicate;
+
+ @Inject
+ public PostgresDelegationStore(PostgresUsersDAO postgresUsersDAO,
UserExistencePredicate userExistencePredicate) {
+ this.postgresUsersDAO = postgresUsersDAO;
+ this.userExistencePredicate = userExistencePredicate;
+ }
+
+ @Override
+ public Publisher<Username> authorizedUsers(Username baseUser) {
+ return postgresUsersDAO.getAuthorizedUsers(baseUser);
+ }
+
+ @Override
+ public Publisher<Void> clear(Username baseUser) {
+ return postgresUsersDAO.removeAllAuthorizedUsers(baseUser);
+ }
+
+ @Override
+ public Publisher<Void> addAuthorizedUser(Username baseUser, Username
userWithAccess) {
+ return userExistencePredicate.exists(userWithAccess)
+ .flatMap(targetUserExists ->
postgresUsersDAO.addAuthorizedUser(baseUser, userWithAccess, targetUserExists));
+ }
+
+ @Override
+ public Publisher<Void> removeAuthorizedUser(Username baseUser, Username
userWithAccess) {
+ return postgresUsersDAO.removeAuthorizedUser(baseUser, userWithAccess);
+ }
+
+ @Override
+ public Publisher<Username> delegatedUsers(Username baseUser) {
+ return postgresUsersDAO.getDelegatedToUsers(baseUser);
+ }
+
+ @Override
+ public Publisher<Void> removeDelegatedUser(Username baseUser, Username
delegatedToUser) {
+ return postgresUsersDAO.removeDelegatedToUser(baseUser,
delegatedToUser);
+ }
+}
diff --git
a/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUserModule.java
b/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUserModule.java
index 6aae9183f8..e5bc618d31 100644
---
a/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUserModule.java
+++
b/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUserModule.java
@@ -32,14 +32,18 @@ public interface PostgresUserModule {
Table<Record> TABLE_NAME = DSL.table("users");
Field<String> USERNAME = DSL.field("username",
SQLDataType.VARCHAR(255).notNull());
- Field<String> HASHED_PASSWORD = DSL.field("hashed_password",
SQLDataType.VARCHAR.notNull());
- Field<String> ALGORITHM = DSL.field("algorithm",
SQLDataType.VARCHAR(100).notNull());
+ Field<String> HASHED_PASSWORD = DSL.field("hashed_password",
SQLDataType.VARCHAR);
+ Field<String> ALGORITHM = DSL.field("algorithm",
SQLDataType.VARCHAR(100));
+ Field<String[]> AUTHORIZED_USERS = DSL.field("authorized_users",
SQLDataType.VARCHAR.getArrayDataType());
+ Field<String[]> DELEGATED_USERS = DSL.field("delegated_users",
SQLDataType.VARCHAR.getArrayDataType());
PostgresTable TABLE = PostgresTable.name(TABLE_NAME.getName())
.createTableStep(((dsl, tableName) ->
dsl.createTableIfNotExists(tableName)
.column(USERNAME)
.column(HASHED_PASSWORD)
.column(ALGORITHM)
+ .column(AUTHORIZED_USERS)
+ .column(DELEGATED_USERS)
.constraint(DSL.primaryKey(USERNAME))))
.disableRowLevelSecurity();
}
diff --git
a/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUsersDAO.java
b/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUsersDAO.java
index d8447e527f..d0467bf847 100644
---
a/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUsersDAO.java
+++
b/server/data/data-postgres/src/main/java/org/apache/james/user/postgres/PostgresUsersDAO.java
@@ -22,7 +22,10 @@ package org.apache.james.user.postgres;
import static
org.apache.james.backends.postgres.utils.PostgresExecutor.DEFAULT_INJECT;
import static
org.apache.james.backends.postgres.utils.PostgresUtils.UNIQUE_CONSTRAINT_VIOLATION_PREDICATE;
import static
org.apache.james.user.postgres.PostgresUserModule.PostgresUserTable.ALGORITHM;
+import static
org.apache.james.user.postgres.PostgresUserModule.PostgresUserTable.AUTHORIZED_USERS;
+import static
org.apache.james.user.postgres.PostgresUserModule.PostgresUserTable.DELEGATED_USERS;
import static
org.apache.james.user.postgres.PostgresUserModule.PostgresUserTable.HASHED_PASSWORD;
+import static
org.apache.james.user.postgres.PostgresUserModule.PostgresUserTable.TABLE;
import static
org.apache.james.user.postgres.PostgresUserModule.PostgresUserTable.TABLE_NAME;
import static
org.apache.james.user.postgres.PostgresUserModule.PostgresUserTable.USERNAME;
import static org.jooq.impl.DSL.count;
@@ -41,8 +44,14 @@ import org.apache.james.user.api.model.User;
import org.apache.james.user.lib.UsersDAO;
import org.apache.james.user.lib.model.Algorithm;
import org.apache.james.user.lib.model.DefaultUser;
+import org.jooq.DSLContext;
+import org.jooq.Field;
+import org.jooq.Record;
+import org.jooq.UpdateConditionStep;
+import org.jooq.impl.DSL;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -141,4 +150,93 @@ public class PostgresUsersDAO implements UsersDAO {
e -> new AlreadyExistInUsersRepositoryException("User with
username " + username + " already exist!"))
.block();
}
+
+ public Mono<Void> addAuthorizedUser(Username baseUser, Username
userWithAccess, boolean targetUserExists) {
+ return addUserToList(AUTHORIZED_USERS, baseUser, userWithAccess)
+ .then(addDelegatedUser(baseUser, userWithAccess,
targetUserExists));
+ }
+
+ private Mono<Void> addDelegatedUser(Username baseUser, Username
userWithAccess, boolean targetUserExists) {
+ if (targetUserExists) {
+ return addUserToList(DELEGATED_USERS, userWithAccess, baseUser);
+ } else {
+ return Mono.empty();
+ }
+ }
+
+ private Mono<Void> addUserToList(Field<String[]> field, Username baseUser,
Username targetUser) {
+ String fullAuthorizedUsersColumnName = TABLE.getName() + "." +
field.getName();
+ return postgresExecutor.executeVoid(dslContext ->
+ Mono.from(dslContext.insertInto(TABLE_NAME)
+ .set(USERNAME, baseUser.asString())
+ .set(field, DSL.array(targetUser.asString()))
+ .onConflict(USERNAME)
+ .doUpdate()
+ .set(DSL.field(field.getName()),
+ (Object) DSL.field("array_append(coalesce(" +
fullAuthorizedUsersColumnName + ", array[]::varchar[]), ?)",
+ targetUser.asString()))
+ .where(DSL.field(fullAuthorizedUsersColumnName).isNull()
+
.or(DSL.field(fullAuthorizedUsersColumnName).notContains(new
String[]{targetUser.asString()})))));
+ }
+
+ public Mono<Void> removeAuthorizedUser(Username baseUser, Username
userWithAccess) {
+ return removeUserInAuthorizedList(baseUser, userWithAccess)
+ .then(removeUserInDelegatedList(userWithAccess, baseUser));
+ }
+
+ public Mono<Void> removeDelegatedToUser(Username baseUser, Username
delegatedToUser) {
+ return removeUserInDelegatedList(baseUser, delegatedToUser)
+ .then(removeUserInAuthorizedList(delegatedToUser, baseUser));
+ }
+
+ private Mono<Void> removeUserInAuthorizedList(Username baseUser, Username
targetUser) {
+ return removeUserFromList(AUTHORIZED_USERS, baseUser, targetUser);
+ }
+
+ private Mono<Void> removeUserInDelegatedList(Username baseUser, Username
targetUser) {
+ return removeUserFromList(DELEGATED_USERS, baseUser, targetUser);
+ }
+
+ private Mono<Void> removeUserFromList(Field<String[]> field, Username
baseUser, Username targetUser) {
+ return postgresExecutor.executeVoid(dslContext ->
+ Mono.from(createQueryRemoveUserFromList(dslContext, field,
baseUser, targetUser)));
+ }
+
+ private UpdateConditionStep<Record>
createQueryRemoveUserFromList(DSLContext dslContext, Field<String[]> field,
Username baseUser, Username targetUser) {
+ return dslContext.update(TABLE_NAME)
+ .set(DSL.field(field.getName()),
+ (Object) DSL.field("array_remove(" + field.getName() + ", ?)",
+ targetUser.asString()))
+ .where(USERNAME.eq(baseUser.asString()))
+ .and(DSL.field(field.getName()).isNotNull());
+ }
+
+ public Mono<Void> removeAllAuthorizedUsers(Username baseUser) {
+ return getAuthorizedUsers(baseUser)
+ .collect(ImmutableList.toImmutableList())
+ .flatMap(usernames -> postgresExecutor.executeVoid(dslContext ->
+ Mono.from(dslContext.batch(usernames.stream()
+ .map(username -> createQueryRemoveUserFromList(dslContext,
DELEGATED_USERS, username, baseUser))
+ .collect(ImmutableList.toImmutableList())))))
+ .then(postgresExecutor.executeVoid(dslContext ->
Mono.from(dslContext.update(TABLE_NAME)
+ .setNull(AUTHORIZED_USERS)
+ .where(USERNAME.eq(baseUser.asString())))));
+ }
+
+ public Flux<Username> getAuthorizedUsers(Username name) {
+ return getUsersFromList(AUTHORIZED_USERS, name);
+ }
+
+ public Flux<Username> getDelegatedToUsers(Username name) {
+ return getUsersFromList(DELEGATED_USERS, name);
+ }
+
+ public Flux<Username> getUsersFromList(Field<String[]> field, Username
name) {
+ return postgresExecutor.executeRow(dslContext ->
Mono.from(dslContext.select(field)
+ .from(TABLE_NAME)
+ .where(USERNAME.eq(name.asString()))))
+ .flatMapMany(record -> Optional.ofNullable(record.get(field))
+ .map(Flux::fromArray).orElse(Flux.empty()))
+ .map(Username::of);
+ }
}
diff --git
a/server/data/data-postgres/src/test/java/org/apache/james/user/postgres/PostgresDelegationStoreTest.java
b/server/data/data-postgres/src/test/java/org/apache/james/user/postgres/PostgresDelegationStoreTest.java
new file mode 100644
index 0000000000..cae65185a6
--- /dev/null
+++
b/server/data/data-postgres/src/test/java/org/apache/james/user/postgres/PostgresDelegationStoreTest.java
@@ -0,0 +1,67 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you 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 org.apache.james.user.postgres;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.core.Username;
+import org.apache.james.user.api.DelegationStore;
+import org.apache.james.user.api.DelegationStoreContract;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import reactor.core.publisher.Mono;
+
+public class PostgresDelegationStoreTest implements DelegationStoreContract {
+ @RegisterExtension
+ static PostgresExtension postgresExtension =
PostgresExtension.withoutRowLevelSecurity(PostgresUserModule.MODULE);
+
+ private PostgresUsersDAO postgresUsersDAO;
+ private PostgresDelegationStore postgresDelegationStore;
+
+ @BeforeEach
+ void beforeEach() {
+ postgresUsersDAO = new
PostgresUsersDAO(postgresExtension.getPostgresExecutor(),
PostgresUsersRepositoryConfiguration.DEFAULT);
+ postgresDelegationStore = new
PostgresDelegationStore(postgresUsersDAO, any -> Mono.just(true));
+ }
+
+ @Override
+ public DelegationStore testee() {
+ return postgresDelegationStore;
+ }
+
+ @Override
+ public void addUser(Username username) {
+ postgresUsersDAO.addUser(username, "password");
+ }
+
+ @Test
+ void virtualUsersShouldNotBeListed() {
+ postgresDelegationStore = new
PostgresDelegationStore(postgresUsersDAO, any -> Mono.just(false));
+ addUser(BOB);
+
+ Mono.from(testee().addAuthorizedUser(ALICE).forUser(BOB)).block();
+
+ assertThat(postgresUsersDAO.listReactive().collectList().block())
+ .containsOnly(BOB);
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]