Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • react
2 results

UserAuthenticationModule.java

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    UserAuthenticationModule.java 11.14 KiB
    /*
    * Copyright (C) 2020 by Savoir-faire Linux
    * Authors: William Enright <william.enright@savoirfairelinux.com>
    *          Ndeye Anna Ndiaye <anna.ndiaye@savoirfairelinux.com>
    *          Johnny Flores <johnny.flores@savoirfairelinux.com>
    *          Mohammed Raza <mohammed.raza@savoirfairelinux.com>
    *          Felix Sidokhine <felix.sidokhine@savoirfairelinux.com>
    *
    *
    * This program 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.
    *
    * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
    */
    package net.jami.jams.authmodule;
    
    import lombok.extern.slf4j.Slf4j;
    import net.jami.datastore.main.DataStore;
    import net.jami.jams.common.authentication.AuthenticationSource;
    import net.jami.jams.common.authentication.AuthenticationSourceType;
    import net.jami.jams.common.authmodule.AuthModuleKey;
    import net.jami.jams.common.authmodule.AuthScope;
    import net.jami.jams.common.authmodule.AuthTokenResponse;
    import net.jami.jams.common.authmodule.AuthenticationModule;
    import net.jami.jams.common.cryptoengineapi.CertificateAuthority;
    import net.jami.jams.common.dao.StatementElement;
    import net.jami.jams.common.dao.StatementList;
    import net.jami.jams.common.jami.NameServer;
    import net.jami.jams.common.objects.user.AccessLevel;
    import net.jami.jams.common.objects.user.User;
    import net.jami.jams.common.utils.LibraryLoader;
    import net.jami.jams.common.utils.X509Utils;
    import org.bouncycastle.cert.X509CRLHolder;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.PrivateKey;
    import java.security.PublicKey;
    import java.security.cert.X509Certificate;
    import java.security.interfaces.RSAPublicKey;
    import java.util.concurrent.ConcurrentHashMap;
    
    
    @Slf4j
    public class UserAuthenticationModule implements AuthenticationModule {
        //This contains the DOMAIN-SOURCE.
        //In general there is at most 2 here.
        private final static String LDAP_CONNECTOR_CLASS = "net.jami.jams.ldap.connector.LDAPConnector";
        private final static String AD_CONNECTOR_CLASS = "net.jami.jams.ad.connector.ADConnector";
        public static DataStore datastore;
        public static CertificateAuthority certificateAuthority;
        private final TokenController tokenController;
        private PrivateKey privateKey = null;
        private PublicKey publicKey = null;
    
    
        private final ConcurrentHashMap<AuthModuleKey, AuthenticationSource> authenticationSources = new ConcurrentHashMap<>();
    
        public UserAuthenticationModule(DataStore dataStore, CertificateAuthority certificateAuthority) throws Exception{
            UserAuthenticationModule.datastore = dataStore;
            UserAuthenticationModule.certificateAuthority = certificateAuthority;
            authenticationSources.put(new AuthModuleKey("LOCAL",AuthenticationSourceType.LOCAL),datastore);
            log.info("Started authentication module - default local source is already enabled!");
    
            File pubkeyFile = new File(System.getProperty("user.dir") + File.separator + "oauth.pub");
            File privateKeyFile = new File(System.getProperty("user.dir") + File.separator + "oauth.key");
    
            if(!privateKeyFile.exists() || !pubkeyFile.exists()){
                log.info("Generating first time private/public keys for OAuth!");
                KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
                keyPairGenerator.initialize(4096);
                KeyPair kp = keyPairGenerator.generateKeyPair();
                privateKey = kp.getPrivate();
                publicKey = kp.getPublic();
                //Store these to file.
                OutputStream os;
                os = new FileOutputStream(System.getProperty("user.dir") + File.separator + "oauth.key");
                os.write(X509Utils.getPEMStringFromPrivateKey(privateKey).getBytes());
                os.flush();
                os.close();
                log.info("Succesfully stored OAuth private key for future use...");
                os = new FileOutputStream(System.getProperty("user.dir") + File.separator + "oauth.pub");
                os.write(X509Utils.getPEMStringFromPubKey(publicKey).getBytes());
                os.flush();
                os.close();
                log.info("Succesfully stored OAuth public key for future use...");
            }
            else{
                InputStream path;
                privateKey = X509Utils.getKeyFromPEMString(new String(new FileInputStream(privateKeyFile).readAllBytes()));
                log.info("Succesfully loaded OAuth private key!");
                publicKey = X509Utils.getPubKeyFromPEMString(new String(new FileInputStream(pubkeyFile).readAllBytes()));
                log.info("Succesfully loaded OAuth public key!");
            }
            //TODO: Read signing key, if file does not exist create it (also create the corresponding public key)
            tokenController = new TokenController(privateKey);
            //Also expose the public key programatically.
            log.info("OAuth2 Token System instantiated succesfully!");
        }
    
        @Override
        public void attachAuthSource(AuthenticationSourceType type, String settings) {
            switch (type){
                case AD: loadAuthConnector(AD_CONNECTOR_CLASS,settings); break;
                case LDAP: loadAuthConnector(LDAP_CONNECTOR_CLASS,settings); break;
                default: break;
            }
        }
    
        private void loadAuthConnector(String className, String settings){
            try {
                Class<?> cls = LibraryLoader.classLoader.loadClass(className);
                AuthenticationSource source = (AuthenticationSource) cls.getConstructor(String.class).newInstance(settings);
                authenticationSources.put(new AuthModuleKey(source.getInfo().getRealm(),source.getInfo().getAuthenticationSourceType()), source);
            }
            catch (Exception e){
                log.error("Could not load connector " + className + " with reason: " + e.toString());
            }
        }
    
        @Override
        public AuthTokenResponse authenticateUser(String username, String password) {
            AuthTokenResponse res = null;
            if(datastore.userExists(username)){
                StatementList statementList = new StatementList();
                StatementElement statementElement = new StatementElement("username","=",username,"");
                statementList.addStatement(statementElement);
                User user = datastore.getUserDao().getObjects(statementList).get(0);
                if(authenticationSources.get(new AuthModuleKey(user.getRealm(),user.getUserType()))
                        .authenticate(username,password))
                    return tokenController.getToken(user,null);
            }
            //The second case is much more violent, because we don't know in advance "where" this user comes
            //from, so we have to infer (this is only really true for "users", all others are usually pre-marked)
            //This is also the case when we store the user into the DAO - because he never existed before.
            for(AuthModuleKey key : authenticationSources.keySet()){
                if(authenticationSources.get(key).authenticate(username,password)){
                    User user = new User();
                    user.setUsername(username);
                    user.setAccessLevel(AccessLevel.USER);
                    user.setRealm(key.getRealm());
                    user.setUserType(key.getType());
                    //This is legal with a null ONLY because in this case there is no relation with a external server.
                    RegisterUserFlow.createUser(user,null);
                    return tokenController.getToken(user,null);
                }
            }
            return res;
        }
    
        @Override
        public AuthTokenResponse authenticateUser(X509Certificate[] certificates, X509CRLHolder crl,
                                                  X509Certificate ca) {
            //Extract the username for the certificate and verify that it is not revoked.
            X509Certificate clientCert = certificates[0];
            try {
                //Check if the certificate is even valid.
                clientCert.checkValidity();
                //Check if the certificate was provided by a valid authority.
                clientCert.verify(ca.getPublicKey());
                //Here we need to make a request to the CRL to find out if it has been revoked.
                if(crl.getRevokedCertificate(clientCert.getSerialNumber()) != null) return null;
                //If the above cases have passed, then this user is indded valid.
                //This is yet to be confirmed.
                String username = clientCert.getSubjectDN().getName();
                StatementList statementList = new StatementList();
                StatementElement statementElement = new StatementElement("username","=",username,"");
                statementList.addStatement(statementElement);
                User user = datastore.getUserDao().getObjects(statementList).get(0);
                return tokenController.getToken(user, AuthScope.DEVICE);
            }
            catch (Exception e){
                return null;
            }
        }
    
        @Override
        public ConcurrentHashMap<AuthModuleKey, AuthenticationSource> getAuthSources(){
            return authenticationSources;
        }
    
        @Override
        public boolean testModuleConfiguration(AuthenticationSourceType type, String settings) {
            try {
                String className = "";
                if (type.equals(AuthenticationSourceType.AD)) className = AD_CONNECTOR_CLASS;
                if (type.equals(AuthenticationSourceType.LDAP)) className = LDAP_CONNECTOR_CLASS;
                Class<?> cls = LibraryLoader.classLoader.loadClass(className);
                AuthenticationSource source = (AuthenticationSource) cls.getConstructor(String.class).newInstance(settings);
                return source.test();
            }
            catch (Exception e){
                log.error("The testing of the source was unsuccessful: " + e.toString());
                return false;
            }
        }
    
        @Override
        public boolean createUser(AuthenticationSourceType type, String realm, NameServer nameServer, User user) {
            //This concept doesn't exist for LDAP or AD or any other hosted directory, in this case we simply run
            //very theoretically, we should allow LDAP to publish to the public registry, but this is a lot
            //more complex.
            return RegisterUserFlow.createUser(user,nameServer);
        }
    
        @Override
        public RSAPublicKey getAuthModulePubKey() {
            return (RSAPublicKey) publicKey;
        }
    
        @Override
        public char[] getOTP(String username) {
            if(datastore.userExists(username)){
                StatementList statementList = new StatementList();
                StatementElement statementElement = new StatementElement("username","=",username,"");
                statementList.addStatement(statementElement);
                User user = datastore.getUserDao().getObjects(statementList).get(0);
                return (user.getPassword()).toCharArray();
            }
            return new char[0];
        }
    
    }