edu.ucsb.eucalyptus.admin.server.EucalyptusWebBackendImpl.java Source code

Java tutorial

Introduction

Here is the source code for edu.ucsb.eucalyptus.admin.server.EucalyptusWebBackendImpl.java

Source

/*******************************************************************************
 *Copyright (c) 2009  Eucalyptus Systems, Inc.
 * 
 *  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, only version 3 of the License.
 * 
 * 
 *  This file 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 <http://www.gnu.org/licenses/>.
 * 
 *  Please contact Eucalyptus Systems, Inc., 130 Castilian
 *  Dr., Goleta, CA 93101 USA or visit <http://www.eucalyptus.com/licenses/>
 *  if you need additional information or have any questions.
 * 
 *  This file may incorporate work covered under the following copyright and
 *  permission notice:
 * 
 *    Software License Agreement (BSD License)
 * 
 *    Copyright (c) 2008, Regents of the University of California
 *    All rights reserved.
 * 
 *    Redistribution and use of this software in source and binary forms, with
 *    or without modification, are permitted provided that the following
 *    conditions are met:
 * 
 *      Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 * 
 *      Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * 
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 *    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 *    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 *    OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 *    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF
 *    THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE
 *    LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS
 *    SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
 *    IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA
 *    BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN
 *    THE REGENTS DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT
 *    OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR
 *    WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH
 *    ANY SUCH LICENSES OR RIGHTS.
 *******************************************************************************/
/*
 *
 * Author: Dmitrii Zagorodnov dmitrii@cs.ucsb.edu
 */

package edu.ucsb.eucalyptus.admin.server;

import com.eucalyptus.auth.Debugging;
import com.eucalyptus.auth.Groups;
import com.eucalyptus.auth.UserInfo;
import com.eucalyptus.auth.Users;
import com.eucalyptus.auth.crypto.Crypto;
import com.eucalyptus.auth.principal.User;
import com.eucalyptus.bootstrap.HttpServerBootstrapper;
import com.eucalyptus.component.Component;
import com.eucalyptus.component.Components;
import com.eucalyptus.component.Service;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.config.ClusterConfiguration;
import com.eucalyptus.config.Configuration;
import com.eucalyptus.system.BaseDirectory;
import com.eucalyptus.system.SubDirectory;
import com.eucalyptus.util.EucalyptusCloudException;
import com.google.gwt.user.client.rpc.SerializableException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.gwt.user.server.rpc.UnexpectedException;
import edu.ucsb.eucalyptus.admin.client.CloudInfoWeb;
import edu.ucsb.eucalyptus.admin.client.ClusterInfoWeb;
import edu.ucsb.eucalyptus.admin.client.DownloadsWeb;
import edu.ucsb.eucalyptus.admin.client.EucalyptusWebBackend;
import edu.ucsb.eucalyptus.admin.client.SystemConfigWeb;
import edu.ucsb.eucalyptus.admin.client.StorageInfoWeb;
import edu.ucsb.eucalyptus.admin.client.UserInfoWeb;
import edu.ucsb.eucalyptus.admin.client.VmTypeWeb;
import edu.ucsb.eucalyptus.admin.client.WalrusInfoWeb;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.ProxyHost;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

public class EucalyptusWebBackendImpl extends RemoteServiceServlet implements EucalyptusWebBackend {

    private static Logger LOG = Logger.getLogger(EucalyptusWebBackendImpl.class);
    private static String PROPERTIES_FILE = BaseDirectory.CONF.toString() + File.separator
            + "eucalyptus-web.properties";
    private static HashMap sessions = new HashMap();
    private static Properties props = new Properties();
    private static long session_timeout_ms = 1000 * 60 * 60 * 24 * 14L; /* 2 weeks (TODO: put into config?) */
    private static long pass_expiration_ms = 1000 * 60 * 60 * 24 * 365L; /* 1 year (TODO: put into config?) */
    private static long recovery_expiration_ms = 1000 * 60 * 30; // 30 minutes (TODO: put into config?)

    /* parameters to be read from config file */
    private static String thanks_for_signup;
    private static String signup_request_subject;
    private static String signup_approval_subject;
    private static String signup_approval_header;
    private static String signup_approval_footer;
    private static String signup_rejection_subject;
    private static String signup_rejection_message;
    private static String password_recovery_message;
    private static String password_recovery_subject;

    /* if these are not in config file, we'll use admin's email */
    private static String signup_email;
    private static String reply_email;
    private boolean system_ready;

    private void load_props() throws SerializableException {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(PROPERTIES_FILE);
            props.load(fileInputStream);
            props.setProperty("version", System.getProperty("euca.version"));
            thanks_for_signup = props.getProperty("thanks-for-signup");
            signup_email = props.getProperty("signup-email-address");
            reply_email = props.getProperty("reply-email-address");
            signup_request_subject = props.getProperty("signup-request-subject");
            signup_approval_subject = props.getProperty("signup-approval-subject");
            signup_approval_header = props.getProperty("signup-approval-header");
            signup_approval_footer = props.getProperty("signup-approval-footer");
            signup_rejection_subject = props.getProperty("signup-rejection-subject");
            signup_rejection_message = props.getProperty("signup-rejection-message");
            password_recovery_message = props.getProperty("password-recovery-message");
            password_recovery_subject = props.getProperty("password-recovery-subject");

            if (thanks_for_signup == null) {
                throw new SerializableException("Server configuration is missing 'thanks-for-signup' value");
            }
            if (signup_request_subject == null) {
                throw new SerializableException("Server configuration is missing 'signup-request-subject' value");
            }
            if (signup_approval_subject == null) {
                throw new SerializableException("Server configuration is missing 'signup-approval-subject' value");
            }
            if (signup_approval_header == null) {
                throw new SerializableException("Server configuration is missing 'signup-approval-header' value");
            }
            if (signup_approval_footer == null) {
                throw new SerializableException("Server configuration is missing 'signup-approval-footer' value");
            }
            if (signup_rejection_subject == null) {
                throw new SerializableException("Server configuration is missing 'signup-rejection-subject' value");
            }
            if (signup_rejection_message == null) {
                throw new SerializableException("Server configuration is missing 'signup-rejection-message' value");
            }
            if (password_recovery_message == null) {
                throw new SerializableException(
                        "Server configuration is missing 'password-recovery-message' value");
            }
            if (password_recovery_subject == null) {
                throw new SerializableException(
                        "Server configuration is missing 'password-recovery-subject' value");
            }
            system_ready = true;
            if (signup_email == null) {
                try {
                    signup_email = EucalyptusManagement.getAdminEmail();
                } catch (Exception e) {
                    signup_email = ""; /* signup will not work until admin email address is set */
                    system_ready = false;
                }
            }
            if (reply_email == null) {
                reply_email = signup_email;
            }

        } catch (IOException e) {
            throw new SerializableException("Could not read server configuration");
        } catch (IllegalArgumentException e) {
            throw new SerializableException("Malformed escape sequence in server configuration");
        } finally {
            if (fileInputStream != null)
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    LOG.error(e);
                }
        }
    }

    public String addUserRecord(UserInfoWeb user) throws SerializableException {
        return addUserRecord(null, user);
    }

    private void notifyAdminOfSignup(UserInfoWeb user) throws SerializableException {
        try {
            String http_eucalyptus = ServletUtils.getRequestUrl(getThreadLocalRequest());
            String approve_link = http_eucalyptus + "?action=approve&user=" + user.getUserName();
            String reject_link = http_eucalyptus + "?action=reject&user=" + user.getUserName();

            String email_message = "Someone has requested an account on the Eucalyptus system\n"
                    + "\n   Name:          " + user.getRealName() + "\n   Username:      " + user.getUserName()
                    + "\n   Email address: " + user.getEmail() + "\n   Telephone:     " + user.getTelephoneNumber()
                    + "\n   Affiliation:   " + user.getAffiliation() + "\n   Project PI:    "
                    + user.getProjectPIName() + "\n   Project description: " + "\n=====\n"
                    + user.getProjectDescription() + "\n=====\n\n"
                    + "To APPROVE this request, click on the following link:\n\n   " + approve_link + "\n\n"
                    + "To REJECT this request, click on the following link:\n\n   " + reject_link + "\n\n";

            ServletUtils.sendMail(reply_email, signup_email, signup_request_subject + " (" + user.getEmail() + ")",
                    email_message);

        } catch (Exception e) {
            LOG.error("Signup mailing problem: " + e.getMessage()); /* TODO: log properly */
            throw new SerializableException("Internal problem (failed to notify administrator by email)");
        }
    }

    public String addUserRecord(String sessionId, UserInfoWeb user) throws SerializableException {
        if (user == null) {
            throw new SerializableException("Invalid RPC arguments");
        }
        if (user.getUserName().equalsIgnoreCase("eucalyptus")) {
            throw new SerializableException("User 'eucalyptus' is not allowed");
        }
        // these two won't happen unless the user hacks the client side
        if (user.getUserName().matches(".*[^\\w\\-\\.@]+.*")) {
            throw new SerializableException("Invalid characters in the username");
        }
        if (user.getUserName().length() < 1 || user.getUserName().length() > 30) {
            throw new SerializableException("Invalid length of username");
        }

        load_props(); /* get parameters from config file */
        boolean admin = false;
        try {
            SessionInfo session = verifySession(sessionId);
            UserInfoWeb requestingUser = verifyUser(session, session.getUserId(), true);
            if (!requestingUser.isAdministrator().booleanValue()) {
                user.setAdministrator(false); // in case someone is trying to be sneaky
                throw new SerializableException("Administrative privileges required");
            } else {
                admin = true;
            }
        } catch (Exception e) {
        } /* that's OK, this was an anonymous request */

        /* add the user */
        long now = System.currentTimeMillis();
        user.setPasswordExpires(new Long(now + pass_expiration_ms));
        user.setConfirmationCode(Crypto.generateSessionToken(user.getUserName()));
        EucalyptusManagement.addWebUser(user);

        String response;
        if (admin) {
            /* enable the new user right away */
            user.setApproved(true);
            user.setEnabled(true);
            response = notifyUserApproved(user);
        } else {
            /* if anonymous, then notify admin */
            user.setApproved(false);
            user.setEnabled(false);
            notifyAdminOfSignup(user);
            response = thanks_for_signup;
        }
        EucalyptusManagement.commitWebUser(user);

        return response;
    }

    private void notifyUserRecovery(UserInfoWeb user) {
        try {
            String http_eucalyptus = ServletUtils.getRequestUrl(getThreadLocalRequest());
            String confirm_link = http_eucalyptus + "?action=recover" + "&code=" + user.getConfirmationCode();

            String email_message = password_recovery_message + "\n\n" + confirm_link + "\n";

            ServletUtils.sendMail(reply_email, user.getEmail(), password_recovery_subject, email_message);

        } catch (Exception e) {
            // TODO: log this using the proper procedure
            LOG.error("Password recovery mailing problem: " + e.getMessage());
            LOG.error("Confirmation code for user '" + user.getUserName() + "' and address " + user.getEmail()
                    + " is " + user.getConfirmationCode());
        }
    }

    public String recoverPassword(UserInfoWeb web_user) throws SerializableException {
        if (web_user == null) {
            throw new SerializableException("Invalid RPC arguments");
        }

        String response;
        if (web_user.getPassword() == null) { // someone is initiating password recovery
            try {
                UserInfoWeb db_user = EucalyptusManagement.getWebUser(web_user.getUserName());
                if (db_user.getEmail().equalsIgnoreCase(web_user.getEmail())) {
                    long expires = System.currentTimeMillis() + recovery_expiration_ms;
                    db_user.setConfirmationCode(
                            String.format("%015d", expires) + Crypto.generateSessionToken(db_user.getUserName()));
                    EucalyptusManagement.commitWebUser(db_user);
                    notifyUserRecovery(db_user);
                }
            } catch (Exception e) {
            } // pretend all is well regardless of the outcome
            response = "Please, check your email for further instructions.";

        } else { // someone is trying to change the password         
            String code = web_user.getConfirmationCode();
            if (code == null) {
                throw new SerializableException("Insufficient parameters");
            }
            UserInfoWeb db_user;
            try {
                db_user = EucalyptusManagement.getWebUserByCode(code);
                long expires = Long.parseLong(code.substring(0, 15));
                long now = System.currentTimeMillis();
                if (now > expires) {
                    throw new SerializableException("Recovery attempt expired");
                }
                db_user.setConfirmationCode("-unset-"); // so the code cannot be reused
                db_user.setPassword(web_user.getPassword());
                db_user.setPasswordExpires(new Long(now + pass_expiration_ms));
                EucalyptusManagement.commitWebUser(db_user);
            } catch (Exception e) {
                throw new SerializableException("Incorrect code");
            }

            response = "Your password has been reset.";
        }
        return response;
    }

    /* ensure the sessionId is (still) valid */
    public static SessionInfo verifySession(String sessionId) throws SerializableException {
        if (sessionId == null) {
            throw new SerializableException("Invalid RPC arguments");
        }
        SessionInfo session = (SessionInfo) sessions.get(sessionId);
        if (session == null) {
            throw new SerializableException("Earlier session not found");
        }
        final long now = System.currentTimeMillis();
        if ((now - session.getLastAccessed()) > session_timeout_ms) {
            sessions.remove(sessionId);
            throw new SerializableException("Earlier session expired");
        }
        session.setLastAccessed(System.currentTimeMillis());
        return session;
    }

    private static boolean isPasswordExpired(UserInfoWeb user) {
        final long now = System.currentTimeMillis();
        if ((now > 0) && (now >= user.getPasswordExpires().longValue())) {
            return true;
        }
        return false;
    }

    /* ensure the user exists and valid */
    private static UserInfoWeb verifyUser(SessionInfo session, String userName, boolean verifyPasswordAge)
            throws SerializableException {
        UserInfoWeb user;
        if (userName == null) {
            throw new SerializableException("Invalid RPC arguments: userIname is missing");
        }
        try {
            user = EucalyptusManagement.getWebUser(userName);
        } catch (Exception e) {
            if (session != null) {
                sessions.remove(session.getSessionId());
            }
            throw new SerializableException("Login incorrect");
        }
        if (!user.isApproved()) {
            throw new SerializableException("User not approved yet");
        }
        if (!user.isEnabled()) {
            throw new SerializableException("Disabled user account");
        }
        if (!user.isConfirmed()) {
            throw new SerializableException("Unconfirmed account (click on the link in confirmation email)");
        }
        if (verifyPasswordAge) {
            if (isPasswordExpired(user)
                    && !(user.isAdministrator() && user.getEmail().equalsIgnoreCase(UserInfo.BOGUS_ENTRY))) { // first-time config will catch that
                throw new SerializableException("Password expired");
            }
        }
        return user;
    }

    public String getNewSessionID(String userId, String md5Password) throws SerializableException {
        String sessionId = null;
        UserInfoWeb user;

        if (md5Password == null) {
            throw new SerializableException("Invalid RPC arguments: password is missing");
        }
        // you can get a sessionId with an expired password so you can change it => false
        user = verifyUser(null, userId, false);
        if (!user.getPassword().equals(md5Password)) {
            throw new SerializableException("Login incorrect");
        }

        sessionId = ServletUtils.genGUID();
        SessionInfo session = new SessionInfo(sessionId, userId, System.currentTimeMillis());
        session.setStartedOn(session.getLastAccessed());
        sessions.put(session.getSessionId(), session);

        return session.getSessionId();
    }

    private String notifyUserApproved(UserInfoWeb user) {
        String confString = " and confirmed";
        try {
            if (!user.isConfirmed().booleanValue()) {
                confString = "";
                String http_eucalyptus = ServletUtils.getRequestUrl(getThreadLocalRequest());
                String confirm_link = http_eucalyptus + "?action=confirm" + "&code=" + user.getConfirmationCode();

                String email_message = signup_approval_header + "\n\n" + confirm_link + "\n\n"
                        + signup_approval_footer;

                ServletUtils.sendMail(reply_email, user.getEmail(), signup_approval_subject, email_message);
            }
        } catch (Exception e) {
            // TODO: log this using the proper procedure
            LOG.error("Approval mailing problem: " + e.getMessage());
            LOG.error("Confirmation code for user " + user.getUserName() + " is " + user.getConfirmationCode());

            return "Internal problem (failed to notify user " + user.getEmail() + " by email)";
        }
        return "User '" + user.getUserName() + "' was approved" + confString + ", thank you.";
    }

    private String notifyUserRejected(UserInfoWeb user) {
        try {
            ServletUtils.sendMail(reply_email, user.getEmail(), signup_rejection_subject, signup_rejection_message);

        } catch (Exception e) {
            LOG.error("Rejection mailing problem: " + e.getMessage());
            return "Internal problem (failed to notify user " + user.getEmail() + ")";
        }
        return "User '" + user.getUserName() + "' was rejected.";
    }

    public String performAction(String sessionId, String action, String param) throws SerializableException {
        load_props();
        if (action == null || param == null) {
            throw new SerializableException("Invalid RPC arguments: action or param are missing");
        }

        /* these don't need a session */
        if (action.equals("recover") || action.equals("confirm")) {
            UserInfoWeb user = EucalyptusManagement.getWebUserByCode(param);
            String response = "OK";

            if (action.equals("confirm")) {
                if (user != null) {
                    user.setConfirmed(true);
                    user.setConfirmationCode("-unset-"); // so the code cannot be reused
                    EucalyptusManagement.commitWebUser(user);
                }
                response = "Your account is now active.";

            } else if (action.equals("recover")) { // this is just a way to verify that the code is valid (TODO: remove?)
                if (user == null) {
                    throw new SerializableException("Invalid code");
                }
                response = "Your password has been reset.";
            }
            return response;
        }

        final SessionInfo session = verifySession(sessionId);
        final UserInfoWeb user = verifyUser(session, session.getUserId(), true);

        String response = "Your `" + action + "` request succeeded."; /* generic response */
        if (action.equals("approve") || action.equals("reject") || action.equals("delete")
                || action.equals("disable") || action.equals("enable")) {
            String userName = param;
            if (!user.isAdministrator()) {
                throw new SerializableException("Administrative privileges required");
            }
            UserInfoWeb new_user = EucalyptusManagement.getWebUser(userName);
            if (action.equals("approve")) {
                new_user.setApproved(true);
                new_user.setEnabled(true);
                new_user.setConfirmed(false);
                EucalyptusManagement.commitWebUser(new_user);
                response = notifyUserApproved(new_user);
            } else if (action.equals("reject")) {
                EucalyptusManagement.deleteWebUser(new_user);
                response = notifyUserRejected(new_user);
            } else if (action.equals("delete")) {
                EucalyptusManagement.deleteWebUser(new_user);
                /* TODO: maybe tell the user that his account was deleted? */
            } else if (action.equals("disable")) {
                new_user.setEnabled(false);
                EucalyptusManagement.commitWebUser(new_user);
            } else if (action.equals("enable")) {
                new_user.setEnabled(true);
                EucalyptusManagement.commitWebUser(new_user);
            }
            response = "Request to " + action + " user '" + userName + "' succeeded.";

        } else if (action.equals("delete_image") || action.equals("enable_image")
                || action.equals("disable_image")) {
            String imageId = param;
            if (!user.isAdministrator()) {
                throw new SerializableException("Administrative privileges required");
            }
            if (action.equals("delete_image")) {
                EucalyptusManagement.deleteImage(imageId);
            } else if (action.equals("disable_image")) {
                EucalyptusManagement.disableImage(imageId);
            } else if (action.equals("enable_image")) {
                EucalyptusManagement.enableImage(imageId);
            }
            response = "Your request succeeded, thank you.";

        } else {
            throw new SerializableException("Action '" + action + "' is not implemented.");
        }

        return response;
    }

    public void logoutSession(String sessionId) throws SerializableException {
        if (sessionId == null) {
            throw new SerializableException("Invalid RPC arguments: sessionId is missing");
        }
        SessionInfo session = (SessionInfo) sessions.get(sessionId);
        if (session != null) {
            sessions.remove(sessionId);
            SessionInfo old = (SessionInfo) sessions.get(sessionId);
        }
    }

    public List getUserRecord(String sessionId, String userId) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);

        List l = new ArrayList();
        if (userId == null) {
            l.add(user);
        } else {
            if (!user.isAdministrator()) {
                throw new SerializableException("Only administrators can view users");
            }
            if (userId.equals("*")) {
                l.addAll(EucalyptusManagement.getWebUsers(userId)); /* NOTE: userId is currently ignored */
            } else {
                l.add(EucalyptusManagement.getWebUser(user.getUserName()));
            }
        }
        return l;
    }

    public static UserInfoWeb getUserRecord(String sessionId) // a *static* getUserRecord, for ImageStoreService
            throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);
        return user;
    }

    public List getImageInfo(String sessionId, String userId) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);

        /* TODO: right now userId parameter is ignored since we only have public images */
        return EucalyptusManagement.getWebImages(userId);
    }

    /* from here on down, all requests require users to be enabled, approved, and confirmed */

    public String getNewCert(String sessionId) throws SerializableException {
        /* perform full checks */
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);

        return user.getToken();
    }

    public HashMap getProperties() throws SerializableException {
        load_props();
        HashMap h = new HashMap();

        for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
            String key = (String) e.nextElement();
            h.put(key, props.getProperty(key));
        }
        h.put("ready", system_ready);

        return h;
    }

    public String changePassword(String sessionId, String oldPassword, String newPassword)
            throws SerializableException {
        /* check everything except password expiration */
        SessionInfo session = verifySession(sessionId);
        // naturally, it is OK to change the password if it expired => false
        UserInfoWeb user = verifyUser(session, session.getUserId(), false);

        /* check old password if the user is changing password voluntarily */
        if (!isPasswordExpired((UserInfoWeb) user)) {
            if (!oldPassword.equals(user.getPassword())) {
                throw new SerializableException("Old password is incorrect");
            }
        }
        user.setPassword(newPassword);
        final long now = System.currentTimeMillis();
        user.setPasswordExpires(new Long(now + pass_expiration_ms));
        EucalyptusManagement.commitWebUser(user);

        return "Password has been changed";
    }

    public String updateUserRecord(String sessionId, UserInfoWeb newRecord) throws SerializableException {
        /* perform full checks */
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb callerRecord = verifyUser(session, session.getUserId(), true);
        String userName = newRecord.getUserName();
        UserInfoWeb oldRecord;
        try {
            oldRecord = EucalyptusManagement.getWebUser(userName);
        } catch (Exception e) {
            throw new SerializableException("Login incorrect");
        }
        if (!callerRecord.isAdministrator() && !callerRecord.getUserName().equals(userName)) {
            throw new SerializableException("Operation restricted to owner and administrator");
        }

        // only an admin should be able to change this settings                                                                    
        if (callerRecord.isAdministrator()) {

            // set password and expiration for admin when logging in for the first time
            if (oldRecord.getEmail().equalsIgnoreCase(UserInfo.BOGUS_ENTRY)) {
                long now = System.currentTimeMillis();
                oldRecord.setPasswordExpires(new Long(now + pass_expiration_ms));
                oldRecord.setPassword(newRecord.getPassword());
            }

            // admin can reset pwd of another user, but
            // to reset his own password he has to use
            // "change password" functionality
            if (!callerRecord.getUserName().equals(userName))
                oldRecord.setPassword(newRecord.getPassword());

            if (oldRecord.isAdministrator() != newRecord.isAdministrator())
                oldRecord.setAdministrator(newRecord.isAdministrator());
            if (oldRecord.isEnabled() != newRecord.isEnabled())
                oldRecord.setEnabled(newRecord.isEnabled());
            // once confirmed, cannot be unconfirmed; also, confirmation implies approval and enablement                       
            if (!oldRecord.isConfirmed() && newRecord.isConfirmed()) {
                oldRecord.setConfirmed(true);
                oldRecord.setEnabled(true);
                oldRecord.setApproved(true);
            }
        }

        oldRecord.setRealName(newRecord.getRealName());
        oldRecord.setEmail(newRecord.getEmail());
        oldRecord.setTelephoneNumber(newRecord.getTelephoneNumber());
        oldRecord.setAffiliation(newRecord.getAffiliation());
        oldRecord.setProjectDescription(newRecord.getProjectDescription());
        oldRecord.setProjectPIName(newRecord.getProjectPIName());

        EucalyptusManagement.commitWebUser(oldRecord);

        return "Account of user '" + userName + "' was updated";
    }

    public List<ClusterInfoWeb> getClusterList(String sessionId) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);
        try {
            return RemoteInfoHandler.getClusterList();
        } catch (EucalyptusCloudException e) {
            throw new SerializableException(e.getMessage());
        }
    }

    public void setClusterList(String sessionId, List<ClusterInfoWeb> clusterList) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);
        try {
            RemoteInfoHandler.setClusterList(clusterList);
        } catch (EucalyptusCloudException e) {
            throw new SerializableException(e.getMessage());
        }
    }

    public List<StorageInfoWeb> getStorageList(String sessionId) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);
        try {
            return RemoteInfoHandler.getStorageList();
        } catch (EucalyptusCloudException e) {
            throw new SerializableException(e.getMessage());
        }
    }

    public void setStorageList(String sessionId, List<StorageInfoWeb> storageList) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);
        try {
            RemoteInfoHandler.setStorageList(storageList);
        } catch (EucalyptusCloudException e) {
            throw new SerializableException(e.getMessage());
        }
    }

    public List<WalrusInfoWeb> getWalrusList(String sessionId) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);
        try {
            return RemoteInfoHandler.getWalrusList();
        } catch (EucalyptusCloudException e) {
            throw new SerializableException(e.getMessage());
        }
    }

    public void setWalrusList(String sessionId, List<WalrusInfoWeb> walrusList) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);

        try {
            RemoteInfoHandler.setWalrusList(walrusList);
        } catch (EucalyptusCloudException e) {
            throw new SerializableException(e.getMessage());
        }
    }

    public SystemConfigWeb getSystemConfig(final String sessionId) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);

        return EucalyptusManagement.getSystemConfig();
    }

    public void setSystemConfig(final String sessionId, final SystemConfigWeb systemConfig)
            throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);

        EucalyptusManagement.setSystemConfig(systemConfig);
    }

    public List<VmTypeWeb> getVmTypes(final String sessionId) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);

        return RemoteInfoHandler.getVmTypes();
    }

    public void setVmTypes(final String sessionId, final List<VmTypeWeb> vmTypes) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);

        RemoteInfoHandler.setVmTypes(vmTypes);
    }

    public CloudInfoWeb getCloudInfo(final String sessionId, final boolean setExternalHostPort)
            throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);

        return EucalyptusManagement.getCloudInfo(setExternalHostPort);
    }

    private static List<DownloadsWeb> getDownloadsFromUrl(final String downloadsUrl) {
        List<DownloadsWeb> downloadsList = new ArrayList<DownloadsWeb>();

        HttpClient httpClient = new HttpClient();
        //support for http proxy
        if (HttpServerBootstrapper.httpProxyHost != null && (HttpServerBootstrapper.httpProxyHost.length() > 0)) {
            String proxyHost = HttpServerBootstrapper.httpProxyHost;
            if (HttpServerBootstrapper.httpProxyPort != null
                    && (HttpServerBootstrapper.httpProxyPort.length() > 0)) {
                int proxyPort = Integer.parseInt(HttpServerBootstrapper.httpProxyPort);
                httpClient.getHostConfiguration().setProxy(proxyHost, proxyPort);
            } else {
                httpClient.getHostConfiguration().setProxyHost(new ProxyHost(proxyHost));
            }
        }
        GetMethod method = new GetMethod(downloadsUrl);
        Integer timeoutMs = new Integer(3 * 1000);
        method.getParams().setSoTimeout(timeoutMs);

        try {
            httpClient.executeMethod(method);
            String str = "";
            InputStream in = method.getResponseBodyAsStream();
            byte[] readBytes = new byte[1024];
            int bytesRead = -1;
            while ((bytesRead = in.read(readBytes)) > 0) {
                str += new String(readBytes, 0, bytesRead);
            }
            String entries[] = str.split("[\\r\\n]+");
            for (int i = 0; i < entries.length; i++) {
                String entry[] = entries[i].split("\\t");
                if (entry.length == 3) {
                    downloadsList.add(new DownloadsWeb(entry[0], entry[1], entry[2]));
                }
            }

        } catch (MalformedURLException e) {
            LOG.warn("Malformed URL exception: " + e.getMessage());
            e.printStackTrace();

        } catch (IOException e) {
            LOG.warn("I/O exception: " + e.getMessage());
            e.printStackTrace();

        } finally {
            method.releaseConnection();
        }

        return downloadsList;
    }

    public List<DownloadsWeb> getDownloads(final String sessionId, final String downloadsUrl)
            throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);
        return getDownloadsFromUrl(downloadsUrl);
    }

    private static String readFileAsString(String filePath) throws java.io.IOException {
        byte[] buffer = new byte[(int) new File(filePath).length()];
        BufferedInputStream f = null;
        try {
            f = new BufferedInputStream(new FileInputStream(filePath));
            f.read(buffer);
        } finally {
            if (f != null)
                try {
                    f.close();
                } catch (IOException ignored) {
                }
        }
        return new String(buffer);
    }

    public String getFileContentsByPath(final String sessionId, final String path) throws SerializableException {
        SessionInfo session = verifySession(sessionId);
        UserInfoWeb user = verifyUser(session, session.getUserId(), true);
        String realPath = BaseDirectory.HOME.toString() + "/var/run/eucalyptus/webapp/" + path;
        LOG.debug("feeding contents of " + realPath);
        // TODO: verify path
        try {
            String result = readFileAsString(realPath);
            LOG.debug("read from " + realPath + " string of size " + result.length());
            return result;
        } catch (java.io.IOException e) {
            LOG.debug("failed to feed " + realPath + " due to exception " + e.getMessage());
            throw new SerializableException(e.getMessage());
        }
    }

    /**
     * Overridden to really throw Jetty RetryRequest Exception (as opposed to sending failure to client).
     *
     * @param caught the exception
     */
    protected void doUnexpectedFailure(Throwable caught) {
        throwIfRetryRequest(caught);
        super.doUnexpectedFailure(caught);
    }

    private static final String JETTY_RETRY_REQUEST_EXCEPTION = "org.mortbay.jetty.RetryRequest";

    /**
     * Throws the Jetty RetryRequest if found.
     *
     * @param caught the exception
     */
    protected void throwIfRetryRequest(Throwable caught) {
        if (caught instanceof UnexpectedException) {
            caught = caught.getCause();
        }

        if (JETTY_RETRY_REQUEST_EXCEPTION.equals(caught.getClass().getName())) {
            throw (RuntimeException) caught;
        }
    }

}