org.codice.ddf.security.common.Security.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.security.common.Security.java

Source

/**
 * Copyright (c) Codice Foundation
 * <p/>
 * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or any later version.
 * <p/>
 * 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
 * Lesser General Public License for more details. A copy of the GNU Lesser General Public License
 * is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.security.common;

import static org.apache.commons.lang.Validate.notNull;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessController;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import javax.validation.constraints.NotNull;

import org.apache.karaf.jaas.boot.principal.RolePrincipal;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.subject.ExecutionException;
import org.codice.ddf.security.handler.api.PKIAuthenticationToken;
import org.codice.ddf.security.handler.api.PKIAuthenticationTokenFactory;
import org.codice.ddf.security.handler.api.UPAuthenticationToken;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ddf.security.Subject;
import ddf.security.assertion.SecurityAssertion;
import ddf.security.common.audit.SecurityLogger;
import ddf.security.service.SecurityManager;
import ddf.security.service.SecurityServiceException;

/**
 * Singleton class that provides common security related utility functions.
 */
public class Security {

    private static final Security INSTANCE = new Security();

    private static final Logger LOGGER = LoggerFactory.getLogger(Security.class);

    private static final String INSUFFICIENT_PERMISSIONS_ERROR = "Current user doesn't have sufficient privileges to run this command";

    private static final RolePrincipal ADMIN_ROLE = new RolePrincipal("admin");

    private Subject cachedSystemSubject;

    private Security() {
        // Singleton
    }

    /**
     * @return unique instance of this class. Never {@code null}.
     */
    public static Security getInstance() {
        return INSTANCE;
    }

    /**
     * Gets the {@link Subject} given a user name and password.
     *
     * @param username username
     * @param password password
     * @return {@link Subject} associated with the user name and password provided
     */
    public Subject getSubject(String username, String password) {
        UPAuthenticationToken token = new UPAuthenticationToken(username, password);
        SecurityManager securityManager = getSecurityManager();

        if (securityManager != null) {
            try {
                return securityManager.getSubject(token);
            } catch (SecurityServiceException | RuntimeException e) {
                LOGGER.error("Unable to request subject for {} user.", username, e);
            }
        }
        return null;
    }

    /**
     * Determines if the current Java {@link Subject} has the admin role.
     *
     * @return {@code true} if the Java {@link Subject} exists and has the admin role, {@code false} otherwise
     */
    public boolean javaSubjectHasAdminRole() {
        javax.security.auth.Subject subject = javax.security.auth.Subject.getSubject(AccessController.getContext());
        if (subject != null) {
            return subject.getPrincipals().contains(ADMIN_ROLE);
        }
        return false;
    }

    /**
     * Runs the {@link Callable} in the current thread as the current security framework's
     * {@link Subject}. If the security framework's {@link Subject} is not currently set and
     * the Java Subject contains the admin role, elevates and runs the {@link Callable} as the
     * system {@link Subject}.
     *
     * @param codeToRun code to run
     * @param <T>       type of the returned value
     * @return value returned by the {@link Callable}
     * @throws SecurityServiceException  if the current subject didn' have enough permissions to run
     *                                   the code
     * @throws InvocationTargetException wraps any exception thrown by {@link Callable#call()}.
     *                                   {@link Callable} exception can be retrieved using the
     *                                   {@link InvocationTargetException#getCause()}.
     */
    public <T> T runWithSubjectOrElevate(@NotNull Callable<T> codeToRun)
            throws SecurityServiceException, InvocationTargetException {
        notNull(codeToRun, "Callable cannot be null");

        try {
            try {
                org.apache.shiro.subject.Subject subject = org.apache.shiro.SecurityUtils.getSubject();
                return subject.execute(codeToRun);
            } catch (IllegalStateException | UnavailableSecurityManagerException e) {
                LOGGER.debug("No shiro subject available for running command, trying with Java Subject");
            }

            if (!javaSubjectHasAdminRole()) {
                SecurityLogger.audit(INSUFFICIENT_PERMISSIONS_ERROR);
                throw new SecurityServiceException(INSUFFICIENT_PERMISSIONS_ERROR);
            }

            Subject subject = getSystemSubject();

            if (subject == null) {
                SecurityLogger.audit(INSUFFICIENT_PERMISSIONS_ERROR);
                throw new SecurityServiceException(INSUFFICIENT_PERMISSIONS_ERROR);
            }

            SecurityLogger.auditWarn("Elevating current user permissions to use System subject");
            return subject.execute(codeToRun);
        } catch (ExecutionException e) {
            throw new InvocationTargetException(e.getCause());
        }
    }

    /**
     * Gets the {@link Subject} associated with this system. Uses a cached subject since the subject
     * will not change between calls.
     *
     * @return system's {@link Subject}
     */
    public synchronized Subject getSystemSubject() {

        if (!tokenAboutToExpire(cachedSystemSubject)) {
            return cachedSystemSubject;
        }

        KeyStore keyStore = getSystemKeyStore();
        String alias = null;
        Certificate cert = null;
        try {
            if (keyStore != null) {
                if (keyStore.size() == 1) {
                    alias = keyStore.aliases().nextElement();
                } else if (keyStore.size() > 1) {
                    alias = getCertificateAlias();
                }
                cert = keyStore.getCertificate(alias);
            }
        } catch (KeyStoreException e) {
            LOGGER.error("Unable to get certificate for alias [{}]", alias, e);
            return null;
        }

        if (cert == null) {
            LOGGER.error("Unable to get certificate for alias [{}]", alias);
            return null;
        }

        PKIAuthenticationTokenFactory pkiTokenFactory = createPKITokenFactory();
        PKIAuthenticationToken pkiToken = pkiTokenFactory.getTokenFromCerts(
                new X509Certificate[] { (X509Certificate) cert }, PKIAuthenticationToken.DEFAULT_REALM);
        if (pkiToken != null) {
            SecurityManager securityManager = getSecurityManager();
            if (securityManager != null) {
                try {
                    cachedSystemSubject = securityManager.getSubject(pkiToken);
                } catch (SecurityServiceException sse) {
                    LOGGER.error("Unable to request subject for system user.", sse);
                }
            }
        }
        return cachedSystemSubject;
    }

    /**
     * Determines whether a {@link Subject}'s token is about to expire or not.
     *
     * @param subject subject whose token needs to be checked
     * @return {@code true} only if the {@link Subject}'s token will expire soon
     */
    public boolean tokenAboutToExpire(Subject subject) {
        return !((null != subject) && (null != subject.getPrincipals())
                && (null != subject.getPrincipals().oneByType(SecurityAssertion.class))
                && (!subject.getPrincipals().oneByType(SecurityAssertion.class).getSecurityToken()
                        .isAboutToExpire(TimeUnit.MINUTES.toSeconds(1))));
    }

    /**
     * Gets a reference to the {@link SecurityManager}.
     *
     * @return reference to the {@link SecurityManager}
     */
    public SecurityManager getSecurityManager() {
        BundleContext context = getBundleContext();
        if (context != null) {
            ServiceReference securityManagerRef = context.getServiceReference(SecurityManager.class);
            return (SecurityManager) context.getService(securityManagerRef);
        }
        LOGGER.warn("Unable to get Security Manager");
        return null;
    }

    private BundleContext getBundleContext() {
        Bundle bundle = FrameworkUtil.getBundle(Security.class);
        if (bundle != null) {
            return bundle.getBundleContext();
        }
        return null;
    }

    private PKIAuthenticationTokenFactory createPKITokenFactory() {
        PKIAuthenticationTokenFactory pkiTokenFactory = new PKIAuthenticationTokenFactory();
        pkiTokenFactory.init();
        return pkiTokenFactory;
    }

    private String getCertificateAlias() {
        return System.getProperty("org.codice.ddf.system.hostname");
    }

    private KeyStore getSystemKeyStore() {
        KeyStore keyStore;

        try {
            keyStore = KeyStore.getInstance(System.getProperty("javax.net.ssl.keyStoreType"));

        } catch (KeyStoreException e) {
            LOGGER.error("Unable to create keystore instance of type {}",
                    System.getProperty("javax.net.ssl.keyStoreType"), e);
            return null;
        }

        Path keyStoreFile = new File(System.getProperty("javax.net.ssl.keyStore")).toPath();
        Path ddfHomePath = Paths.get(System.getProperty("ddf.home"));

        if (!keyStoreFile.isAbsolute()) {
            keyStoreFile = Paths.get(ddfHomePath.toString(), keyStoreFile.toString());
        }

        String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword");

        if (!Files.isReadable(keyStoreFile)) {
            LOGGER.error("Unable to read system key/trust store files: [ {} ] ", keyStoreFile);
            return null;
        }

        try (InputStream kfis = Files.newInputStream(keyStoreFile)) {
            keyStore.load(kfis, keyStorePassword.toCharArray());
        } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
            LOGGER.error("Unable to load system key file.", e);
        }

        return keyStore;
    }
}