-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
28 changed files
with
805 additions
and
675 deletions.
There are no files selected for viewing
35 changes: 35 additions & 0 deletions
35
gateway/src/main/java/org/georchestra/gateway/accounts/admin/AccountCreated.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* Copyright (C) 2023 by the geOrchestra PSC | ||
* | ||
* This file is part of geOrchestra. | ||
* | ||
* geOrchestra is free software: you can redistribute it and/or modify it under | ||
* the terms of the GNU General Public License as published by the Free | ||
* Software Foundation, either version 3 of the License, or (at your option) | ||
* any later version. | ||
* | ||
* geOrchestra is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along with | ||
* geOrchestra. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package org.georchestra.gateway.accounts.admin; | ||
|
||
import org.georchestra.security.model.GeorchestraUser; | ||
|
||
import lombok.NonNull; | ||
import lombok.Value; | ||
|
||
/** | ||
* Application event published when a new account was created | ||
*/ | ||
@Value | ||
public class AccountCreated { | ||
|
||
private @NonNull GeorchestraUser user; | ||
|
||
private String oauth2ClientId; | ||
} |
27 changes: 27 additions & 0 deletions
27
gateway/src/main/java/org/georchestra/gateway/accounts/admin/AccountsManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright (C) 2023 by the geOrchestra PSC | ||
* | ||
* This file is part of geOrchestra. | ||
* | ||
* geOrchestra is free software: you can redistribute it and/or modify it under | ||
* the terms of the GNU General Public License as published by the Free | ||
* Software Foundation, either version 3 of the License, or (at your option) | ||
* any later version. | ||
* | ||
* geOrchestra is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along with | ||
* geOrchestra. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package org.georchestra.gateway.accounts.admin; | ||
|
||
import org.georchestra.security.model.GeorchestraUser; | ||
|
||
public interface AccountsManager { | ||
|
||
GeorchestraUser getOrCreate(GeorchestraUser mappedUser); | ||
|
||
} |
161 changes: 161 additions & 0 deletions
161
...eorchestra/gateway/accounts/admin/ldap/GeorchestraLdapAccountManagementConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/* | ||
* Copyright (C) 2023 by the geOrchestra PSC | ||
* | ||
* This file is part of geOrchestra. | ||
* | ||
* geOrchestra is free software: you can redistribute it and/or modify it under | ||
* the terms of the GNU General Public License as published by the Free | ||
* Software Foundation, either version 3 of the License, or (at your option) | ||
* any later version. | ||
* | ||
* geOrchestra is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along with | ||
* geOrchestra. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package org.georchestra.gateway.accounts.admin.ldap; | ||
|
||
import static java.util.Objects.requireNonNull; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import org.georchestra.ds.orgs.OrgsDao; | ||
import org.georchestra.ds.orgs.OrgsDaoImpl; | ||
import org.georchestra.ds.roles.RoleDao; | ||
import org.georchestra.ds.roles.RoleDaoImpl; | ||
import org.georchestra.ds.roles.RoleProtected; | ||
import org.georchestra.ds.security.UserMapperImpl; | ||
import org.georchestra.ds.security.UsersApiImpl; | ||
import org.georchestra.ds.users.AccountDao; | ||
import org.georchestra.ds.users.AccountDaoImpl; | ||
import org.georchestra.ds.users.UserRule; | ||
import org.georchestra.gateway.accounts.admin.AccountsManager; | ||
import org.georchestra.gateway.security.ldap.LdapConfigProperties; | ||
import org.georchestra.gateway.security.ldap.extended.ExtendedLdapConfig; | ||
import org.georchestra.security.api.UsersApi; | ||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
import org.springframework.context.ApplicationEventPublisher; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.ldap.core.LdapTemplate; | ||
import org.springframework.ldap.core.support.LdapContextSource; | ||
import org.springframework.ldap.pool.factory.PoolingContextSource; | ||
import org.springframework.ldap.pool.validation.DefaultDirContextValidator; | ||
|
||
@Configuration(proxyBeanMethods = false) | ||
@EnableConfigurationProperties(LdapConfigProperties.class) | ||
public class GeorchestraLdapAccountManagementConfiguration { | ||
|
||
@Bean | ||
AccountsManager ldapAccountsManager(ApplicationEventPublisher eventPublisher, AccountDao accountDao, | ||
RoleDao roleDao, UsersApi usersApi) { | ||
|
||
return new LdapAccountsManager(eventPublisher::publishEvent, accountDao, roleDao, usersApi); | ||
} | ||
|
||
@Bean | ||
UsersApi ldapUsersApi(AccountDao accountDao, RoleDao roleDao) { | ||
UserMapperImpl mapper = new UserMapperImpl(); | ||
mapper.setRoleDao(roleDao); | ||
List<String> protectedUsers = Collections.emptyList(); | ||
UserRule rule = new UserRule(); | ||
rule.setListOfprotectedUsers(protectedUsers.toArray(String[]::new)); | ||
UsersApiImpl usersApi = new UsersApiImpl(); | ||
usersApi.setAccountsDao(accountDao); | ||
usersApi.setMapper(mapper); | ||
usersApi.setUserRule(rule); | ||
return usersApi; | ||
} | ||
|
||
@Bean | ||
LdapContextSource singleContextSource(LdapConfigProperties config) { | ||
ExtendedLdapConfig ldapConfig = config.extendedEnabled().get(0); | ||
LdapContextSource singleContextSource = new LdapContextSource(); | ||
singleContextSource.setUrl(ldapConfig.getUrl()); | ||
singleContextSource.setBase(ldapConfig.getBaseDn()); | ||
singleContextSource.setUserDn(ldapConfig.getAdminDn().get()); | ||
singleContextSource.setPassword(ldapConfig.getAdminPassword().get()); | ||
return singleContextSource; | ||
} | ||
|
||
@Bean | ||
PoolingContextSource contextSource(LdapContextSource singleContextSource) { | ||
PoolingContextSource contextSource = new PoolingContextSource(); | ||
contextSource.setContextSource(singleContextSource); | ||
contextSource.setDirContextValidator(new DefaultDirContextValidator()); | ||
contextSource.setTestOnBorrow(true); | ||
contextSource.setMaxActive(8); | ||
contextSource.setMinIdle(1); | ||
contextSource.setMaxIdle(8); | ||
contextSource.setMaxTotal(-1); | ||
contextSource.setMaxWait(-1); | ||
return contextSource; | ||
} | ||
|
||
@Bean | ||
LdapTemplate ldapTemplate(PoolingContextSource contextSource) throws Exception { | ||
LdapTemplate ldapTemplate = new LdapTemplate(contextSource); | ||
return ldapTemplate; | ||
} | ||
|
||
@Bean | ||
RoleDao roleDao(LdapTemplate ldapTemplate, LdapConfigProperties config) { | ||
RoleDaoImpl impl = new RoleDaoImpl(); | ||
impl.setLdapTemplate(ldapTemplate); | ||
impl.setRoleSearchBaseDN(config.extendedEnabled().get(0).getRolesRdn()); | ||
return impl; | ||
} | ||
|
||
@Bean | ||
OrgsDao orgsDao(LdapTemplate ldapTemplate, LdapConfigProperties config) { | ||
OrgsDaoImpl impl = new OrgsDaoImpl(); | ||
impl.setLdapTemplate(ldapTemplate); | ||
impl.setOrgSearchBaseDN(config.extendedEnabled().get(0).getOrgsRdn()); | ||
return impl; | ||
} | ||
|
||
@Bean | ||
AccountDao accountDao(LdapTemplate ldapTemplate, LdapConfigProperties config) throws Exception { | ||
ExtendedLdapConfig ldapConfig = config.extendedEnabled().get(0); | ||
String baseDn = ldapConfig.getBaseDn(); | ||
String userSearchBaseDN = ldapConfig.getUsersRdn(); | ||
String roleSearchBaseDN = ldapConfig.getRolesRdn(); | ||
|
||
// we don't need a configuration property for this, | ||
// we don't allow pending users to log in. The LdapAuthenticationProvider won't | ||
// even look them up. | ||
final String pendingUsersSearchBaseDN = "ou=pendingusers"; | ||
|
||
AccountDaoImpl impl = new AccountDaoImpl(ldapTemplate); | ||
impl.setBasePath(baseDn); | ||
impl.setUserSearchBaseDN(userSearchBaseDN); | ||
impl.setRoleSearchBaseDN(roleSearchBaseDN); | ||
if (pendingUsersSearchBaseDN != null) { | ||
impl.setPendingUserSearchBaseDN(pendingUsersSearchBaseDN); | ||
} | ||
|
||
String orgSearchBaseDN = ldapConfig.getOrgsRdn(); | ||
requireNonNull(orgSearchBaseDN); | ||
impl.setOrgSearchBaseDN(orgSearchBaseDN); | ||
|
||
// not needed here, only console cares, we shouldn't allow to authenticate | ||
// pending users, should we? | ||
final String pendingOrgSearchBaseDN = "ou=pendingorgs"; | ||
impl.setPendingOrgSearchBaseDN(pendingOrgSearchBaseDN); | ||
|
||
impl.init(); | ||
return impl; | ||
} | ||
|
||
@Bean | ||
RoleProtected roleProtected() { | ||
RoleProtected roleProtected = new RoleProtected(); | ||
roleProtected.setListOfprotectedRoles( | ||
new String[] { "ADMINISTRATOR", "GN_.*", "ORGADMIN", "REFERENT", "USER", "SUPERUSER" }); | ||
return roleProtected; | ||
} | ||
} |
121 changes: 121 additions & 0 deletions
121
gateway/src/main/java/org/georchestra/gateway/accounts/admin/ldap/LdapAccountsManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/* | ||
* Copyright (C) 2023 by the geOrchestra PSC | ||
* | ||
* This file is part of geOrchestra. | ||
* | ||
* geOrchestra is free software: you can redistribute it and/or modify it under | ||
* the terms of the GNU General Public License as published by the Free | ||
* Software Foundation, either version 3 of the License, or (at your option) | ||
* any later version. | ||
* | ||
* geOrchestra is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along with | ||
* geOrchestra. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package org.georchestra.gateway.accounts.admin.ldap; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.function.Consumer; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import org.georchestra.ds.DataServiceException; | ||
import org.georchestra.ds.roles.RoleDao; | ||
import org.georchestra.ds.users.Account; | ||
import org.georchestra.ds.users.AccountDao; | ||
import org.georchestra.ds.users.AccountFactory; | ||
import org.georchestra.ds.users.DuplicatedEmailException; | ||
import org.georchestra.ds.users.DuplicatedUidException; | ||
import org.georchestra.gateway.accounts.admin.AccountCreated; | ||
import org.georchestra.gateway.accounts.admin.AccountsManager; | ||
import org.georchestra.security.api.UsersApi; | ||
import org.georchestra.security.model.GeorchestraUser; | ||
import org.springframework.ldap.NameNotFoundException; | ||
|
||
import lombok.AccessLevel; | ||
import lombok.NonNull; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE) | ||
@Slf4j(topic = "org.georchestra.gateway.accounts.admin.ldap") | ||
class LdapAccountsManager implements AccountsManager { | ||
|
||
private final @NonNull Consumer<AccountCreated> eventPublisher; | ||
private final @NonNull AccountDao accountDao; | ||
private final @NonNull RoleDao roleDao; | ||
private final @NonNull UsersApi usersApi; | ||
|
||
@Override | ||
public GeorchestraUser getOrCreate(@NonNull GeorchestraUser mappedUser) { | ||
return find(mappedUser).orElseGet(() -> createIfMissing(mappedUser)); | ||
} | ||
|
||
Optional<GeorchestraUser> find(GeorchestraUser mappedUser) { | ||
Optional<GeorchestraUser> userOpt; | ||
if (null != mappedUser.getOAuth2ProviderId()) { | ||
userOpt = usersApi.findByOAuth2ProviderId(mappedUser.getOAuth2ProviderId()); | ||
} else { | ||
userOpt = usersApi.findByUsername(mappedUser.getUsername()); | ||
} | ||
|
||
if (userOpt.isPresent()) { | ||
GeorchestraUser user = userOpt.orElseThrow(); | ||
if (!user.getRoles().contains("ROLE_USER")) { | ||
List<String> roles = Stream.concat(Stream.of("ROLE_USER"), user.getRoles().stream()) | ||
.collect(Collectors.toList()); | ||
user.setRoles(roles); | ||
} | ||
} | ||
return userOpt; | ||
} | ||
|
||
GeorchestraUser createIfMissing(GeorchestraUser preAuth) { | ||
Account newAccount = mapToAccountBrief(preAuth); | ||
try { | ||
accountDao.insert(newAccount); | ||
} catch (DataServiceException | DuplicatedUidException | DuplicatedEmailException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
|
||
try {// account created, add roles | ||
for (String role : preAuth.getRoles()) { | ||
roleDao.addUser(role, newAccount); | ||
} | ||
} catch (NameNotFoundException | DataServiceException e) { | ||
try {// roll-back account | ||
accountDao.delete(newAccount); | ||
} catch (NameNotFoundException | DataServiceException e1) { | ||
log.warn("Error reverting user creation after roleDao update failure", e1); | ||
} | ||
throw new IllegalStateException(e); | ||
} | ||
return findByUsername(preAuth.getUsername()).orElseThrow( | ||
() -> new IllegalStateException("User " + preAuth.getUsername() + " not found right after creation")); | ||
} | ||
|
||
private Account mapToAccountBrief(@NonNull GeorchestraUser preAuth) { | ||
String username = preAuth.getUsername(); | ||
String email = preAuth.getEmail(); | ||
String firstName = preAuth.getFirstName(); | ||
String lastName = preAuth.getLastName(); | ||
String org = preAuth.getOrganization(); | ||
String password = null; | ||
String phone = ""; | ||
String title = ""; | ||
String description = ""; | ||
final @javax.annotation.Nullable String oAuth2ProviderId = preAuth.getOAuth2ProviderId(); | ||
|
||
Account newAccount = AccountFactory.createBrief(username, password, firstName, lastName, email, phone, title, | ||
description, oAuth2ProviderId); | ||
newAccount.setPending(false); | ||
newAccount.setOrg(org); | ||
return newAccount; | ||
} | ||
|
||
} |
Oops, something went wrong.