org.codice.ddf.migration.commands.MigrationCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.migration.commands.MigrationCommand.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.migration.commands;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import ddf.security.SubjectUtils;
import ddf.security.service.SecurityServiceException;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.lang.StringUtils;
import org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Option;
import org.apache.karaf.shell.api.action.lifecycle.Reference;
import org.apache.karaf.shell.api.console.Session;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.ExecutionException;
import org.apache.shiro.subject.Subject;
import org.codice.ddf.configuration.migration.ConfigurationMigrationService;
import org.codice.ddf.migration.MigrationException;
import org.codice.ddf.migration.MigrationMessage;
import org.codice.ddf.migration.MigrationSuccessfulInformation;
import org.codice.ddf.migration.MigrationWarning;
import org.codice.ddf.security.common.Security;
import org.codice.ddf.system.alerts.NoticePriority;
import org.codice.ddf.system.alerts.SystemNotice;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.Ansi.Attribute;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Provides common methods and instance variables that migration commands can use. */
public abstract class MigrationCommand implements Action {

    public static final String ERROR_MESSAGE = "An error was encountered while executing this command; %s.";

    public static final String NAMESPACE = "migration";

    protected static final String EXPORTED = "exported";

    private static final String ALERT_TITLE = "User is %sing configuration settings";

    private static final String USER_MESSAGE = "The user trying to %s configuration settings is [%s].";

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

    protected final Security security;

    @Reference
    protected ConfigurationMigrationService configurationMigrationService;

    @Reference
    protected EventAdmin eventAdmin;

    @Reference
    protected Session session;

    @Option(name = "--user", required = false, aliases = {
            "-u" }, multiValued = false, description = "Run command as a different user.")
    protected String user = null;

    protected Path exportDirectory = Paths.get(System.getProperty("ddf.home"), MigrationCommand.EXPORTED);

    @VisibleForTesting
    MigrationCommand(ConfigurationMigrationService service, Security security, EventAdmin eventAdmin,
            Session session) {
        this.configurationMigrationService = service;
        this.security = security;
        this.eventAdmin = eventAdmin;
        this.session = session;
    }

    public MigrationCommand() {
        this.security = Security.getInstance();
    }

    @Override
    public Object execute() throws Exception {
        try {
            if (StringUtils.isNotBlank(user)) {
                return runWithUserName();
            }
            return security.runWithSubjectOrElevate(this::executeWithSubject);
        } catch (SecurityServiceException e) {
            outputErrorMessage(String.format(MigrationCommand.ERROR_MESSAGE, e.getMessage()));
        } catch (ExecutionException | InvocationTargetException e) {
            outputErrorMessage(String.format(MigrationCommand.ERROR_MESSAGE, e.getCause().getMessage()));
        }
        return null;
    }

    /**
     * Executes the command once the user has been properly authorized.
     *
     * @return result of the command execution
     * @throws Exception if the command failed to run
     */
    @SuppressWarnings("squid:S00112" /* throwing Exception for consistency with execute() */)
    protected abstract Object executeWithSubject() throws Exception;

    protected void outputErrorMessage(String message) {
        outputMessageWithColor(message, Ansi.Color.RED);
    }

    protected void outputMessage(MigrationMessage msg) {
        if (msg instanceof MigrationException) {
            outputErrorMessage(msg.getMessage());
        } else if (msg instanceof MigrationWarning) {
            outputMessageWithColor(msg.getMessage(), Ansi.Color.YELLOW);
        } else if (msg instanceof MigrationSuccessfulInformation) {
            outputMessageWithColor(msg.getMessage(), Ansi.Color.GREEN);
        } else {
            outputMessageWithColor(msg.getMessage(), Ansi.Color.WHITE);
        }
    }

    @SuppressWarnings("squid:S106"
    /* we purposely need to output to the output stream for the info to get to the admin console */
    )
    @VisibleForTesting
    protected PrintStream getConsole() {
        return System.out;
    }

    @VisibleForTesting
    protected String getSubjectName() {
        return SubjectUtils.getName(SecurityUtils.getSubject(), null, true);
    }

    protected void postSystemNotice(String cmd) {
        final String subjectName = getSubjectName();
        final SystemNotice notice = new SystemNotice(getClass().getName() + '.' + System.currentTimeMillis(),
                // add a timestamp to make sure we generate a different one each time
                NoticePriority.IMPORTANT, String.format(MigrationCommand.ALERT_TITLE, cmd),
                ImmutableSet.of(String.format(MigrationCommand.USER_MESSAGE, cmd, subjectName)));

        eventAdmin.postEvent(new Event(SystemNotice.SYSTEM_NOTICE_BASE_TOPIC + MigrationCommand.NAMESPACE,
                notice.getProperties()));
    }

    private void outputMessageWithColor(String message, Ansi.Color color) {
        final String colorAsString = Ansi.ansi().a(Attribute.RESET).fg(color).toString();
        final PrintStream console = getConsole();

        console.print(colorAsString);
        console.print(message);
        console.println(Ansi.ansi().a(Attribute.RESET).toString());
    }

    private Object runWithUserName() throws ExecutionException {
        try {
            final String password = session.readLine("Password for " + user + ": ", '*');
            final Subject subject = security.getSubject(user, password);

            if (subject != null) {
                return subject.execute(this::executeWithSubject);
            }
            outputErrorMessage("Invalid username/password");
        } catch (IOException e) {
            LOGGER.info("Failed to read password", e);
            outputErrorMessage("Failed to read password");
        }
        return null;
    }
}