All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.security.credential.source.impl.CommandCredentialSource Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2016 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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.wildfly.security.credential.source.impl;

import static org.wildfly.common.Assert.checkNotEmptyParam;
import static org.wildfly.common.Assert.checkNotNullParam;
import static org.wildfly.security.credential.source.impl.ElytronMessages.log;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.UndeclaredThrowableException;
import java.nio.charset.Charset;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.GeneralSecurityException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.Provider;
import java.security.spec.AlgorithmParameterSpec;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import org.wildfly.security.SecurityFactory;
import org.wildfly.security.auth.SupportLevel;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.credential.source.CredentialSource;
import org.wildfly.security.password.PasswordFactory;
import org.wildfly.security.password.interfaces.ClearPassword;

/**
 * A credential source which acquires a credential from the command line.
 *
 * @author David M. Lloyd
 */
public final class CommandCredentialSource implements CredentialSource {

    private static final File NULL_FILE = new File(System.getProperty("os.name").startsWith("Windows") ? "NUL" : "/dev/null");

    private final Function builderProcessor;
    private final PasswordFactory passwordFactory;
    private final AccessControlContext context;
    private final Charset outputCharset;

    CommandCredentialSource(final Builder builder) throws GeneralSecurityException {
        builderProcessor = builder.builderProcessor;
        this.passwordFactory = builder.passwordFactoryFactory.create();
        context = AccessController.getContext();
        outputCharset = builder.outputCharset;
    }

    public SupportLevel getCredentialAcquireSupport(final Class credentialType, final String algorithmName, final AlgorithmParameterSpec parameterSpec) throws IOException {
        return credentialType == PasswordCredential.class ? SupportLevel.SUPPORTED : SupportLevel.UNSUPPORTED;
    }

    public  C getCredential(final Class credentialType, final String algorithmName, final AlgorithmParameterSpec parameterSpec) throws IOException {
        if (credentialType != PasswordCredential.class) {
            return null;
        }
        final ProcessBuilder processBuilder = builderProcessor.apply(new ProcessBuilder());
        processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE); // we want to capture the output of the process
        processBuilder.redirectError(NULL_FILE);
        final Process process;
        try {
            process = AccessController.doPrivileged((PrivilegedExceptionAction) processBuilder::start);
        } catch (PrivilegedActionException e) {
            try {
                throw e.getCause();
            } catch (IOException | RuntimeException | Error e2) {
                throw e2;
            } catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
        try {
            final String line;
            process.getOutputStream().close();
            try (InputStream output = process.getInputStream()) {
                try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(output, outputCharset))) {
                    line = outputReader.readLine();
                }
            }
            final int exitCode;
            try {
                exitCode = process.waitFor();
            } catch (InterruptedException e) {
                process.destroyForcibly();
                while (process.isAlive()) try {
                    process.waitFor();
                } catch (InterruptedException ignored) {}
                Thread.currentThread().interrupt();
                throw log.credentialCommandInterrupted();
            }
            if (log.isTraceEnabled()) {
                log.tracef("Exit code from password command = %d", Integer.valueOf(exitCode));
            }

            if (line == null) {
                return null;
            }

            return credentialType.cast(new PasswordCredential(ClearPassword.createRaw(ClearPassword.ALGORITHM_CLEAR, line.toCharArray())));
        } finally {
            // better clean up just in case
            process.destroyForcibly();
        }
    }

    /**
     * Construct a new builder instance.
     *
     * @return the new builder instance
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * A builder for a command credential source.
     */
    public static final class Builder {
        Charset outputCharset = Charset.defaultCharset();
        SecurityFactory passwordFactoryFactory = () -> PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR);
        Function builderProcessor = Function.identity();

        Builder() {
        }

        /**
         * Add a command string to the list of command strings.
         *
         * @param commandString the literal string to add (must not be {@code null})
         * @return this builder
         */
        public Builder addCommand(String commandString) {
            checkNotNullParam("commandString", commandString);
            checkNotEmptyParam("commandString", commandString);
            builderProcessor = builderProcessor.andThen(pb -> {
                pb.command().add(commandString);
                return pb;
            });
            return this;
        }

        /**
         * Add a command string supplier result to the list of command strings.  If the supplier returns {@code null} or
         * an empty string, no string is added at that time.  The supplier is evaluated every time a command is run.
         *
         * @param commandStringSupplier the string supplier to get the string from (must not be {@code null})
         * @return this builder
         */
        public Builder addCommand(Supplier commandStringSupplier) {
            checkNotNullParam("commandString", commandStringSupplier);
            builderProcessor = builderProcessor.andThen(pb -> {
                final String string = commandStringSupplier.get();
                if (string != null && ! string.isEmpty()) pb.command().add(string);
                return pb;
            });
            return this;
        }

        /**
         * Add a command string provider to the list of command strings.  The provider can add multiple strings to
         * the consumer that is provided to it.  The provider must not provide {@code null} or empty strings.
         *
         * @param consumer the consumer which can provide the command strings to add (must not be {@code null})
         * @return this builder
         */
        public Builder addCommand(Consumer> consumer) {
            checkNotNullParam("commandString", consumer);
            builderProcessor = builderProcessor.andThen(pb -> {
                consumer.accept(string -> pb.command().add(checkNotEmptyParam("string", checkNotNullParam("string", string))));
                return pb;
            });
            return this;
        }

        /**
         * Add an environment value to the process environment.
         *
         * @param key the environment variable name (must not be {@code null})
         * @param value the environment variable value (must not be {@code null})
         * @return this builder
         */
        public Builder addEnvironment(String key, String value) {
            checkNotNullParam("key", key);
            checkNotEmptyParam("key", key);
            checkNotNullParam("value", value);
            checkNotEmptyParam("value", value);
            builderProcessor = builderProcessor.andThen(pb -> {
                pb.environment().put(key, value);
                return pb;
            });
            return this;
        }

        /**
         * Add multiple environment values to the process environment.  The consumer is called once for every command
         * execution.
         *
         * @param consumer a consumer which can provide key-value pairs to add to the environment (must not be {@code null})
         * @return this builder
         */
        public Builder addEnvironment(Consumer> consumer) {
            checkNotNullParam("consumer", consumer);
            builderProcessor = builderProcessor.andThen(pb -> {
                consumer.accept((key, value) -> pb.environment().put(checkNotEmptyParam("key", checkNotNullParam("key", key)), checkNotEmptyParam("value", checkNotNullParam("value", value))));
                return pb;
            });
            return this;
        }

        /**
         * Remove an environment variable from the process environment.
         *
         * @param key the environment variable name (must not be {@code null})
         * @return this builder
         */
        public Builder removeEnvironment(String key) {
            checkNotNullParam("key", key);
            checkNotEmptyParam("key", key);
            builderProcessor = builderProcessor.andThen(pb -> {
                pb.environment().remove(key);
                return pb;
            });
            return this;
        }

        /**
         * Set the working directory of the target process.
         *
         * @param directory the directory (must not be {@code null})
         * @return this builder
         */
        public Builder setWorkingDirectory(File directory) {
            checkNotNullParam("directory", directory);
            builderProcessor = builderProcessor.andThen(pb -> pb.directory(directory));
            return this;
        }

        /**
         * Set the provider to use to find the password factory.  If this method is not called, the default is used.
         *
         * @param provider the provider to use (must not be {@code null})
         * @return this builder
         */
        public Builder setPasswordFactoryProvider(Provider provider) {
            checkNotNullParam("provider", provider);
            passwordFactoryFactory = () -> PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, provider);
            return this;
        }

        /**
         * Set the output character set (encoding) to expect from the process.  If this method is not called, the
         * system default character set is used.
         *
         * @param charset the character set to use (must not be {@code null})
         * @return this builder
         */
        public Builder setOutputCharset(Charset charset) {
            checkNotNullParam("charset", charset);
            this.outputCharset = charset;
            return this;
        }

        /**
         * Construct the credential source instance.
         *
         * @return the credential source
         * @throws GeneralSecurityException if there was a failure constructing the password factory
         */
        public CommandCredentialSource build() throws GeneralSecurityException {
            return new CommandCredentialSource(this);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy