Skip to content

Commit

Permalink
tmp
Browse files Browse the repository at this point in the history
  • Loading branch information
groldan committed Oct 14, 2023
1 parent 8396c59 commit 37f38bc
Show file tree
Hide file tree
Showing 28 changed files with 805 additions and 675 deletions.
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;
}
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);

}
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;
}
}
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;
}

}
Loading

0 comments on commit 37f38bc

Please sign in to comment.