com.redhat.rhn.frontend.action.kickstart.KickstartHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.redhat.rhn.frontend.action.kickstart.KickstartHelper.java

Source

/**
 * Copyright (c) 2009--2014 Red Hat, Inc.
 *
 * This software is licensed to you under the GNU General Public License,
 * version 2 (GPLv2). There is NO WARRANTY for this software, express or
 * implied, including the implied warranties of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
 * along with this software; if not, see
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 * Red Hat trademarks are not licensed under GPLv2. No permission is
 * granted to use or replicate Red Hat trademarks that are incorporated
 * in this software or its documentation.
 */
package com.redhat.rhn.frontend.action.kickstart;

import com.redhat.rhn.common.conf.ConfigDefaults;
import com.redhat.rhn.common.localization.LocalizationService;
import com.redhat.rhn.common.security.SessionSwap;
import com.redhat.rhn.domain.channel.Channel;
import com.redhat.rhn.domain.kickstart.KickstartData;
import com.redhat.rhn.domain.kickstart.KickstartFactory;
import com.redhat.rhn.domain.org.Org;
import com.redhat.rhn.domain.org.OrgFactory;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.manager.channel.ChannelManager;
import com.redhat.rhn.manager.kickstart.IpAddress;
import com.redhat.rhn.manager.kickstart.KickstartFormatter;
import com.redhat.rhn.manager.kickstart.KickstartManager;
import com.redhat.rhn.manager.kickstart.KickstartSessionUpdateCommand;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

/**
 * KickstartHelper - helper class for KS UI processing
 * @version $Rev$
 */
public class KickstartHelper {

    private static Logger log = Logger.getLogger(KickstartHelper.class);

    private HttpServletRequest request;
    private static final String VIEW_LABEL = "view_label";
    private static final String LABEL = "label";
    private static final String ORG_DEFAULT = "org_default";
    private static final String IP_RANGE = "ip_range";
    private static final String SESSION = "session";
    private static final String SESSION_ID = "session_id";
    private static final String ORG = "org";
    private static final String HOST = "host";
    private static final String ORG_ID = "org_id";
    private static final String XFORWARD_HOST = "X-Forwarded-Host";
    private static final String XFORWARD = "X-Forwarded-For";
    private static final String XFORWARD_REGEX = "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}.\\d{1,3})";
    private static final String KSDATA = "ksdata";

    public static final String XRHNPROXYAUTH = "X-RHN-Proxy-Auth";

    /**
     * Constructor
     * @param reqIn associated with this helper.
     */
    public KickstartHelper(HttpServletRequest reqIn) {
        this.request = reqIn;
    }

    /**
     * Parse a ks url and return a Map of options
     * Example:
     *     "ks/org/3756992x3d9f6e2d5717e264c89b5ac18eb0c59e/label/kslabelfoo";
     *
     * NOTE: This method also updates the KickstartSession.state field
     * to "configuration_accessed"
     *
     * @param url to parse
     * @return Map of options.  Usually containing host, ksdata, label and org_id
     */
    public Map<String, Object> parseKickstartUrl(String url) {
        Map<String, Object> retval = new HashMap<String, Object>();
        KickstartData ksdata = null;
        Map<String, String> options = new HashMap<String, String>();
        log.debug("url: " + url);
        List<String> rawopts = Arrays.asList(StringUtils.split(url, '/'));

        for (Iterator<String> iter = rawopts.iterator(); iter.hasNext();) {
            String name = iter.next();
            if (iter.hasNext()) {
                String value = iter.next();
                options.put(name, value);
            }
        }

        log.debug("Options: " + options);

        String remoteAddr = getClientIp();

        // Process the org
        if (options.containsKey(ORG)) {
            String id = options.get(ORG);
            retval.put(ORG_ID, id);
        } else {
            retval.put(ORG_ID, OrgFactory.getSatelliteOrg().getId().toString());
        }
        String mode = ORG_DEFAULT; // go ahead and make this the default profile
        // Process the session
        // /kickstart/ks/session/2xb7d56e8958b0425e762cc74e8705d8e7
        if (options.containsKey(SESSION)) {
            // update session
            String hashed = options.get(SESSION);
            String[] ids = SessionSwap.extractData(hashed);
            retval.put(SESSION_ID, ids[0]);
            Long kssid = new Long(ids[0]);
            log.debug("sessionid: " + kssid);
            KickstartSessionUpdateCommand cmd = new KickstartSessionUpdateCommand(kssid);
            ksdata = cmd.getKsdata();
            retval.put(SESSION, cmd.getKickstartSession());
            log.debug("session: " + retval.get(SESSION));
            cmd.setSessionState(KickstartFactory.SESSION_STATE_CONFIG_ACCESSED);
            cmd.store();
            mode = SESSION;
        }

        log.debug("org_id: " + retval.get(ORG_ID));

        //TODO: reconsider/cleanup this logic flow
        if (retval.get(ORG_ID) != null) {

            // Process label
            if (options.containsKey(LABEL)) {
                retval.put(LABEL, options.get(LABEL));
                mode = LABEL;
            } else if (options.containsKey(VIEW_LABEL)) {
                retval.put(VIEW_LABEL, options.get(VIEW_LABEL));
                retval.put(LABEL, options.get(VIEW_LABEL));
                mode = LABEL;
            } else if (options.containsValue(IP_RANGE)) {
                mode = IP_RANGE;
            }

            Org org = OrgFactory.lookupById(new Long((String) retval.get(ORG_ID)));
            if (mode.equals(LABEL)) {
                String label = (String) retval.get(LABEL);
                ksdata = KickstartFactory.lookupKickstartDataByLabelAndOrgId(label, org.getId());
            } else if (mode.equals(IP_RANGE)) {
                log.debug("Ip_range mode");
                IpAddress clientIp = new IpAddress(remoteAddr);
                ksdata = KickstartManager.getInstance().findProfileForIpAddress(clientIp, org);
            } else if (mode.equals(ORG_DEFAULT)) {
                //process org_default
                log.debug("Org_default mode.");
                ksdata = getOrgDefaultProfile(org);
            }

            if (log.isDebugEnabled()) {
                log.debug("session                        : " + retval.get(SESSION));
                log.debug("options.containsKey(VIEW_LABEL): " + options.containsKey(VIEW_LABEL));
                log.debug("ksdata                         : " + ksdata);
            }
        }
        // Add the host.
        retval.put(HOST, getKickstartHost());
        // Add ksdata
        retval.put(KSDATA, ksdata);

        if (retval.size() == 0) {
            retval = null;
        }
        return retval;
    }

    /**
     * Check to see if this request came through a proxy
     * @return boolean if proxied or not.
     */
    public boolean isProxyRequest() {
        return request.getHeader(XFORWARD) != null;
    }

    private String getClientIp() {
        String remoteAddr = request.getRemoteAddr();
        String proxyHeader = request.getHeader(XFORWARD);

        // check if we are going through a proxy, grab real IP if so
        if (proxyHeader != null) {
            log.debug("proxy header in: " + proxyHeader);
            Matcher matcher = Pattern.compile(XFORWARD_REGEX).matcher(proxyHeader);
            if (matcher.lookingAt()) {
                remoteAddr = matcher.group(1);
                log.debug("origination ip from pchain: " + remoteAddr);
            }
        }

        return remoteAddr;
    }

    /**
     * Check to see if this request came through a proxy
     * @return String of hostname of first proxy in chain or null otherwise.
     */
    public String getForwardedHost() {
        String host = null;
        String forwardHosts = request.getHeader(XFORWARD_HOST);
        if (forwardHosts != null) {
            host = forwardHosts.split(",")[0];
        }
        return host;
    }

    /**
     *
     * @param orgIdIn Org Id
     * @return Default Kickstart Data for Org
     */
    private KickstartData getOrgDefaultProfile(Org orgIn) {
        return KickstartFactory.lookupOrgDefault(orgIn);
    }

    /**
     * Get the kickstart host to use. Will use the host of the proxy if the header is
     * present. If not the code then resorts to getting the cobbler hostname from our
     * rhn.conf Config.
     *
     * @return String representing the Kickstart Host
     */
    public String getKickstartHost() {
        log.debug("KickstartHelper.getKickstartHost()");

        // Example proxy header:
        // X-RHN-Proxy-Auth : 1006681409::1151513167.96:21600.0:VV/xF
        // NEmCYOuHxEBAs7BEw==:fjs-0-08.rhndev.redhat.com,1006681408
        // ::1151513034.3:21600.0:w2lm+XWSFJMVCGBK1dZXXQ==:fjs-0-11.
        // rhndev.redhat.com,1006678487::1152567362.02:21600.0:t15l
        // gsaTRKpX6AxkUFQ11A==:fjs-0-12.rhndev.redhat.com

        String proxyHeader = request.getHeader(XRHNPROXYAUTH);
        log.debug("X-RHN-Proxy-Auth : " + proxyHeader);

        if (!StringUtils.isEmpty(proxyHeader)) {
            String[] proxies = StringUtils.split(proxyHeader, ",");
            String firstProxy = proxies[0];
            // Now we have: 1006681409::1151513167.96:21600.0:VV/xF
            // NEmCYOuHxEBAs7BEw==:fjs-0-08.rhndev.redhat.com
            log.debug("first1: " + firstProxy);
            String[] chunks = StringUtils.split(firstProxy, ":");
            firstProxy = chunks[chunks.length - 1];
            log.debug("first2: " + firstProxy);
            log.debug("Kickstart host from proxy header: " + firstProxy);
            return firstProxy;
        }
        return ConfigDefaults.get().getCobblerHost();
    }

    /**
     * @return String representing the kickstart protocol.
     */
    public String getKickstartProtocol() {

        String protocol = null;
        try {
            URL url = new URL(request.getRequestURL().toString());
            protocol = url.getProtocol();
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("Bad argument when determining kickstart protocol.");
        }

        return protocol;
    }

    /**
     * Get the protocol plus the host:
     *
     * http://host1.rhndev.redhat.com
     *
     * @return proto plus host url
     */
    public String getKickstartProtocolAndHost() {
        String retval = getKickstartProtocol();

        retval = retval + "://" + getKickstartHost();
        return retval;
    }

    /**
     * @param org The Org to generate the token for.
     * @return A session-specific token for the given Org.
     */
    public String generateOrgToken(Org org) {
        String orgStr = org.getId().toString();
        return orgStr + "x" + SessionSwap.generateSwapKey(orgStr);
    }

    /**
     * Verify that the kickstart channel is valid.
     * Valid kickstart channels must have a set list of packages described
     * by KickstartFormatter.UPDATE_PKG_NAMES and KickstartFormatter.FRESH_PKG_NAMES_RHEL34
     *
     * Also checks for auto-kickstart packages.
     * @param ksdata kickstart data containing the kickstart channel.
     * @param user The logged in user.
     * @return Whether the kickstart channel is a valid one.
     */
    public boolean verifyKickstartChannel(KickstartData ksdata, User user) {
        return verifyKickstartChannel(ksdata, user, true);
    }

    /**
     * Verify that the kickstart channel is valid.
     * Valid kickstart channels must have a set list of packages described
     * by KickstartFormatter.UPDATE_PKG_NAMES and KickstartFormatter.FRESH_PKG_NAMES_RHEL34
     *
     * Non bare metal kickstarts also must have auto kickstart packages.
     * @param ksdata kickstart data containing the kickstart channel.
     * @param user The logged in user.
     * @param checkAutoKickstart Whether or not to verify the existence of
     *        auto-kickstart files. These are needed for many tasks, but are
     *        not necessary for generating kickstart files.
     * @return Whether the kickstart channel is a valid one.
     */
    public boolean verifyKickstartChannel(KickstartData ksdata, User user, boolean checkAutoKickstart) {
        if (ksdata.isRawData()) {
            //well this is Rawdata I am going to assume
            // its fine and dandy
            // In the future if we instead decide
            // that we need to do a channel
            // check on  a rawdata this is the place to fix that
            return true;
        }
        //I tried to make this readable while still maintaining all the boolean
        //shortcutting. Here is the one liner boolean:
        if (hasUpdates(ksdata) && hasFresh(ksdata) && (!checkAutoKickstart || hasKickstartPackage(ksdata, user))) {
            return true;
        }
        return false;
    }

    private boolean hasUpdates(KickstartData ksdata) {
        if (ksdata.isRhel4() || ksdata.isRhel3() || ksdata.isRhel2()) {
            return hasPackages(ksdata.getChannel(), KickstartFormatter.UPDATE_PKG_NAMES);
        }
        return true;
    }

    private boolean hasFresh(KickstartData ksdata) {
        //There are different 'fresh packages' for different RHEL releases.
        //TODO: right now we do this a pretty ugly way -> we have a static
        //      list of fresh packages for the different releases and we
        //      check which one to use based on the install type suffix number.
        //      If we need to support more than two lists, we should probably
        //      make this a little more data driven.
        if (ksdata.isRhel2()) {
            return hasPackages(ksdata.getChannel(), KickstartFormatter.FRESH_PKG_NAMES_RHEL2);
        }
        if (ksdata.isRhel3() || ksdata.isRhel4()) {
            return hasPackages(ksdata.getChannel(), KickstartFormatter.FRESH_PKG_NAMES_RHEL34);
        }
        return true;
    }

    private boolean hasPackages(Channel c, String[] packageNames) {
        log.debug("HasPackages: " + c.getId());
        //Go through every package name.
        for (int i = 0; i < packageNames.length; i++) {
            log.debug("hasPackages : Checking for package: " + packageNames[i]);
            Long pid = ChannelManager.getLatestPackageEqual(c.getId(), packageNames[i]);
            //No package by this name exists in this package.
            if (pid == null) {
                log.debug("hasPackages : not found");
                return false;
            }
        }
        //We have a pid from every package.
        return true;
    }

    private boolean hasKickstartPackage(KickstartData ksdata, User user) {
        //We expect this to be in the RHN Tools channel.
        //Check in the channel and all of its children channels
        Channel channel = ksdata.getChannel();
        log.debug("Checking on auto-ks in channel : " + channel.getId());
        List<Channel> channelsToCheck = ChannelManager.userAccessibleChildChannels(user.getOrg().getId(),
                channel.getId());
        channelsToCheck.add(channel);

        Iterator<Channel> i = channelsToCheck.iterator();
        while (i.hasNext()) {
            Channel current = i.next();
            log.debug("Current.channel : " + current.getId());
            //Look for the auto-kickstart package.
            List<Map<String, Object>> kspackages = ChannelManager.listLatestPackagesLike(current.getId(),
                    ksdata.getKickstartPackageName());
            //found it, this channel is good.
            if (kspackages.size() > 0) {
                return true;
            }
            log.debug("package not found");
        }
        //We have checked every channel without luck.
        return false;
    }

    /**
     * Create a message to the user about having a kickstart channel that is missing
     * required packages.
     * @param ksdata The kickstart data that contains the kickstart channel.
     * @return Messages to add to the request.
     */
    public ActionMessages createInvalidChannelMsg(KickstartData ksdata) {
        ActionMessages msg = new ActionMessages();
        Object[] args = new Object[] { createPackageNameList(ksdata) };
        msg.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("kickstart.invalidchannel.message", args));
        if (ksdata.getChannel().getOrg() == null) { //if not a custom channel
            //Tell them that they should sync the RHN Tools channel.
            msg.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("kickstart.invalidchannel.satmessage"));
        }
        return msg;
    }

    private String createPackageNameList(KickstartData ksdata) {
        //First create a list of all the packages needed
        List<String> packages = new ArrayList<String>();
        packages.addAll(Arrays.asList(KickstartFormatter.UPDATE_PKG_NAMES));
        //different 'fresh' packages for RHEL2
        if (ksdata.isRhel2()) {
            packages.addAll(Arrays.asList(KickstartFormatter.FRESH_PKG_NAMES_RHEL2));
        }
        if (ksdata.isRhel3() || ksdata.isRhel4()) {
            packages.addAll(Arrays.asList(KickstartFormatter.FRESH_PKG_NAMES_RHEL34));
        }
        //add a '*' at the end because the auto kickstart is a prefix
        packages.add(ksdata.getKickstartPackageName() + "*");

        //Now convert the list to a delimited string.
        String delimiter = LocalizationService.getInstance().getMessage("list delimiter");
        return StringUtils.join(packages.toArray(), delimiter);
    }
}