org.dasein.cloud.aws.identity.IAM.java Source code

Java tutorial

Introduction

Here is the source code for org.dasein.cloud.aws.identity.IAM.java

Source

/**
 * Copyright (C) 2009-2013 Dell, Inc.
 * See annotations for authorship information
 *
 * ====================================================================
 * 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.dasein.cloud.aws.identity;

import org.apache.log4j.Logger;
import org.dasein.cloud.CloudException;
import org.dasein.cloud.InternalException;
import org.dasein.cloud.ProviderContext;
import org.dasein.cloud.admin.PrepaymentSupport;
import org.dasein.cloud.aws.AWSCloud;
import org.dasein.cloud.aws.compute.EC2Exception;
import org.dasein.cloud.aws.compute.EC2Method;
import org.dasein.cloud.aws.network.ELBMethod;
import org.dasein.cloud.aws.network.Route53Method;
import org.dasein.cloud.aws.platform.CloudFrontMethod;
import org.dasein.cloud.aws.platform.RDS;
import org.dasein.cloud.aws.platform.SNS;
import org.dasein.cloud.aws.platform.SQS;
import org.dasein.cloud.aws.platform.SimpleDB;
import org.dasein.cloud.aws.storage.S3Method;
import org.dasein.cloud.compute.AutoScalingSupport;
import org.dasein.cloud.compute.ComputeServices;
import org.dasein.cloud.compute.MachineImageSupport;
import org.dasein.cloud.compute.SnapshotSupport;
import org.dasein.cloud.compute.VirtualMachineSupport;
import org.dasein.cloud.compute.VolumeSupport;
import org.dasein.cloud.identity.AccessKey;
import org.dasein.cloud.identity.CloudGroup;
import org.dasein.cloud.identity.CloudPermission;
import org.dasein.cloud.identity.CloudPolicy;
import org.dasein.cloud.identity.CloudUser;
import org.dasein.cloud.identity.IdentityAndAccessSupport;
import org.dasein.cloud.identity.ServiceAction;
import org.dasein.cloud.identity.ShellKeySupport;
import org.dasein.cloud.network.DNSSupport;
import org.dasein.cloud.network.FirewallSupport;
import org.dasein.cloud.network.IpAddressSupport;
import org.dasein.cloud.network.LoadBalancerSupport;
import org.dasein.cloud.platform.CDNSupport;
import org.dasein.cloud.platform.KeyValueDatabaseSupport;
import org.dasein.cloud.platform.MQSupport;
import org.dasein.cloud.platform.PushNotificationSupport;
import org.dasein.cloud.platform.RelationalDatabaseSupport;
import org.dasein.cloud.storage.BlobStoreSupport;
import org.dasein.cloud.util.APITrace;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Implementation of the AWS IAM APIs based on the Dasein Cloud identity and access support.
 * @author George Reese (george.reese@imaginary.com)
 * @since 2012.02
 * @version 2012.02
 */
public class IAM implements IdentityAndAccessSupport {
    static private final Logger logger = AWSCloud.getLogger(IAM.class);

    private AWSCloud provider;

    public IAM(@Nonnull AWSCloud cloud) {
        provider = cloud;
    }

    @Override
    public void addUserToGroups(@Nonnull String providerUserId, @Nonnull String... providerGroupIds)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.addUserToGroups");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for the attempt at addUserToGroups()");
                throw new InternalException("No context was established for this call.");
            }
            if (logger.isInfoEnabled()) {
                logger.info("Adding " + providerUserId + " to " + providerGroupIds.length + " groups...");
            }
            for (String groupId : providerGroupIds) {
                addUserToGroup(ctx, providerUserId, groupId);
            }
            if (logger.isInfoEnabled()) {
                logger.info("User " + providerUserId + " successfully added to all groups.");
            }
        } finally {
            APITrace.end();
        }
    }

    /**
     * Executes the function of adding a user to a specific group as AWS does not support bulk adding of a user to a group.
     * @param ctx the current cloud context
     * @param providerUserId the user to be added
     * @param providerGroupId the group to which the user will be added
     * @throws CloudException an error occurred in the cloud provider adding this user to the specified group
     * @throws InternalException an error occurred within Dasein Cloud adding the user to the group
     */
    private void addUserToGroup(@Nonnull ProviderContext ctx, @Nonnull String providerUserId,
            @Nonnull String providerGroupId) throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER: " + IAM.class.getName() + ".addUserToGroup(" + ctx + "," + providerUserId + ","
                    + providerGroupId + ")");
        }
        try {
            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.ADD_USER_TO_GROUP, IAMMethod.VERSION);
            EC2Method method;

            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }

            CloudGroup group = getGroup(providerGroupId);

            if (group == null) {
                throw new CloudException("No such group: " + providerGroupId);
            }
            parameters.put("GroupName", group.getName());
            parameters.put("UserName", user.getUserName());
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Adding " + providerUserId + " to " + providerGroupId + "...");
                }
                method.invoke();
                if (logger.isInfoEnabled()) {
                    logger.info("Added.");
                }
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT: " + IAM.class.getName() + ".addUserToGroup()");
            }
        }
    }

    @Override
    public @Nonnull CloudGroup createGroup(@Nonnull String groupName, @Nullable String path, boolean asAdminGroup)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.createGroup");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for the attempt at createGroup()");
                throw new InternalException("No context was established for this call.");
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.CREATE_GROUP, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            groupName = validateName(groupName);
            parameters.put("GroupName", groupName);
            if (path != null) {
                if (!path.endsWith("/")) {
                    path = path + "/";
                }
                parameters.put("Path", path);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Creating group " + groupName + " in " + path + "...");
                }
                doc = method.invoke();
                blocks = doc.getElementsByTagName("Group");
                for (int i = 0; i < blocks.getLength(); i++) {
                    CloudGroup cloudGroup = toGroup(ctx, blocks.item(i));

                    if (logger.isDebugEnabled()) {
                        logger.debug("cloudGroup=" + cloudGroup);
                    }
                    if (cloudGroup != null) {
                        if (logger.isInfoEnabled()) {
                            logger.info("Created.");
                        }
                        if (asAdminGroup) {
                            logger.info("Setting up admin group rights for new group " + cloudGroup);
                            saveGroupPolicy(cloudGroup.getProviderGroupId(), "AdminGroup", CloudPermission.ALLOW,
                                    null, null);
                        }
                        return cloudGroup;
                    }
                }
                logger.error("No group was created as a result of the request");
                throw new CloudException("No group was created as a result of the request");
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull CloudUser createUser(@Nonnull String userName, @Nullable String path,
            @Nullable String... autoJoinGroupIds) throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.createUser");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for the attempt at createUser()");
                throw new InternalException("No context was established for this call.");
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.CREATE_USER, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("UserName", userName);
            if (path != null) {
                if (!path.endsWith("/")) {
                    path = path + "/";
                }
                parameters.put("Path", path);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Creating user " + userName + " in " + path + "...");
                }
                doc = method.invoke();
                blocks = doc.getElementsByTagName("User");
                for (int i = 0; i < blocks.getLength(); i++) {
                    CloudUser cloudUser = toUser(ctx, blocks.item(i));

                    if (logger.isDebugEnabled()) {
                        logger.debug("cloudUser=" + cloudUser);
                    }
                    if (cloudUser != null) {
                        if (logger.isInfoEnabled()) {
                            logger.info("Created.");
                        }
                        return cloudUser;
                    }
                }
                logger.error("No user was created as a result of the request");
                throw new CloudException("No user was created as a result of the request");
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull AccessKey enableAPIAccess(@Nonnull String providerUserId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "enableAPIAccess");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.CREATE_ACCESS_KEY, IAMMethod.VERSION);
            IAMMethod method;
            NodeList blocks;
            Document doc;

            parameters.put("UserName", user.getUserName());
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Creating access keys for " + providerUserId);
                }
                doc = method.invoke();
                blocks = doc.getElementsByTagName("AccessKey");
                for (int i = 0; i < blocks.getLength(); i++) {
                    AccessKey key = toAccessKey(ctx, blocks.item(i));

                    if (logger.isDebugEnabled()) {
                        logger.debug("key=" + key);
                    }
                    if (key != null) {
                        if (logger.isInfoEnabled()) {
                            logger.info("Created.");
                        }
                        return key;
                    }
                }
                logger.error("No access key was created as a result of the request");
                throw new CloudException("No access key was created as a result of the request");
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void enableConsoleAccess(@Nonnull String providerUserId, @Nonnull byte[] password)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.enableConsoleAccess");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.CREATE_LOGIN_PROFILE, IAMMethod.VERSION);
            IAMMethod method;
            NodeList blocks;
            Document doc;

            parameters.put("UserName", user.getUserName());
            try {
                parameters.put("Password", new String(password, "utf-8"));
            } catch (UnsupportedEncodingException e) {
                throw new InternalException(e);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=[omitted due to password sensitivity]");
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Creating console access for " + providerUserId);
                }
                doc = method.invoke();
                blocks = doc.getElementsByTagName("LoginProfile");
                if (blocks.getLength() < 1) {
                    logger.error("No console access was created as a result of the request");
                    throw new CloudException("No console access was created as a result of the request");
                }
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nullable CloudGroup getGroup(@Nonnull String providerGroupId) throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.getGroup");
        try {
            for (CloudGroup group : listGroups(null)) {
                if (providerGroupId.equals(group.getProviderGroupId())) {
                    return group;
                }
            }
            return null;
        } finally {
            APITrace.end();
        }
    }

    private @Nullable CloudPolicy[] getGroupPolicy(@Nonnull CloudGroup group, @Nonnull String policyName)
            throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER: " + IAM.class.getName() + ".getGroupPolicy(" + group + "," + policyName + ")");
        }
        try {
            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.GET_GROUP_POLICY, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("GroupName", group.getName());
            parameters.put("PolicyName", policyName);
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                doc = method.invoke();
                blocks = doc.getElementsByTagName("GetGroupPolicyResult");
                for (int i = 0; i < blocks.getLength(); i++) {
                    Node policyNode = blocks.item(i);

                    if (policyNode.hasChildNodes()) {
                        NodeList attrs = policyNode.getChildNodes();

                        for (int j = 0; j < attrs.getLength(); j++) {
                            Node attr = attrs.item(j);

                            if (attr.getNodeName().equalsIgnoreCase("PolicyDocument")) {
                                String json = URLDecoder.decode(attr.getFirstChild().getNodeValue().trim(),
                                        "utf-8");
                                JSONObject stmt = new JSONObject(json);

                                if (stmt.has("Statement")) {
                                    return toPolicy(policyName, stmt.getJSONArray("Statement"));
                                }
                            }
                        }
                    }
                }
                return null;
            } catch (EC2Exception e) {
                if (e.getStatus() == 404) {
                    return null;
                }
                logger.error(e.getSummary());
                throw new CloudException(e);
            } catch (JSONException e) {
                logger.error("Failed to parse policy statement: " + e.getMessage());
                throw new CloudException(e);
            } catch (UnsupportedEncodingException e) {
                logger.error("Unknown encoding in utf-8: " + e.getMessage());
                throw new InternalException(e);
            }
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT: " + IAM.class.getName() + ".getGroupPolicy()");
            }
        }
    }

    @Override
    public @Nullable CloudUser getUser(@Nonnull String providerUserId) throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.getUser");
        try {
            for (CloudUser user : this.listUsersInPath(null)) {
                if (providerUserId.equals(user.getProviderUserId())) {
                    return user;
                }
            }
            return null;
        } finally {
            APITrace.end();
        }
    }

    private @Nullable CloudUser getUserByName(@Nonnull String userName) throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER: " + IAM.class.getName() + ".getUserByName(" + userName + ")");
        }
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.LIST_USERS, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("UserName", userName);
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                doc = method.invoke();
                blocks = doc.getElementsByTagName("member");
                for (int i = 0; i < blocks.getLength(); i++) {
                    CloudUser cloudUser = toUser(ctx, blocks.item(i));

                    if (cloudUser != null) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("cloudUser=" + cloudUser);
                        }
                        return cloudUser;
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("cloudUser=null");
                }
                return null;
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT: " + IAM.class.getName() + ".getUserByName()");
            }
        }
    }

    private @Nullable CloudPolicy[] getUserPolicy(@Nonnull CloudUser user, @Nonnull String policyName)
            throws CloudException, InternalException {
        if (logger.isTraceEnabled()) {
            logger.trace("ENTER: " + IAM.class.getName() + ".getUserPolicy(" + user + "," + policyName + ")");
        }
        try {
            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.GET_USER_POLICY, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("UserName", user.getUserName());
            parameters.put("PolicyName", policyName);
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                doc = method.invoke();
                blocks = doc.getElementsByTagName("GetUserPolicyResult");
                for (int i = 0; i < blocks.getLength(); i++) {
                    Node policyNode = blocks.item(i);

                    if (policyNode.hasChildNodes()) {
                        NodeList attrs = policyNode.getChildNodes();

                        for (int j = 0; j < attrs.getLength(); j++) {
                            Node attr = attrs.item(j);

                            if (attr.getNodeName().equalsIgnoreCase("PolicyDocument")) {
                                String json = URLDecoder.decode(attr.getFirstChild().getNodeValue().trim(),
                                        "utf-8");
                                JSONObject stmt = new JSONObject(json);

                                if (stmt.has("Statement")) {
                                    return toPolicy(policyName, stmt.getJSONArray("Statement"));
                                }
                            }
                        }
                    }
                }
                return null;
            } catch (EC2Exception e) {
                if (e.getStatus() == 404) {
                    return null;
                }
                logger.error(e.getSummary());
                throw new CloudException(e);
            } catch (JSONException e) {
                logger.error("Failed to parse policy statement: " + e.getMessage());
                throw new CloudException(e);
            } catch (UnsupportedEncodingException e) {
                logger.error("Unknown encoding in utf-8: " + e.getMessage());
                throw new InternalException(e);
            }
        } finally {
            if (logger.isTraceEnabled()) {
                logger.trace("EXIT: " + IAM.class.getName() + ".getUserPolicy()");
            }
        }
    }

    @Override
    public boolean isSubscribed() throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.isSubscribed");
        try {
            ComputeServices svc = provider.getComputeServices();

            if (svc == null) {
                return false;
            }
            VirtualMachineSupport support = svc.getVirtualMachineSupport();

            return (support != null && support.isSubscribed());
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable<CloudGroup> listGroups(@Nullable String pathBase)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.listGroups");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.LIST_GROUPS, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            if (pathBase != null) {
                parameters.put("PathPrefix", pathBase);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                ArrayList<CloudGroup> groups = new ArrayList<CloudGroup>();

                doc = method.invoke();
                blocks = doc.getElementsByTagName("member");
                for (int i = 0; i < blocks.getLength(); i++) {
                    CloudGroup cloudGroup = toGroup(ctx, blocks.item(i));

                    if (cloudGroup != null) {
                        groups.add(cloudGroup);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("groups=" + groups);
                }
                return groups;
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable<CloudGroup> listGroupsForUser(@Nonnull String providerUserId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.listGroupsForUser");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.LIST_GROUPS_FOR_USER, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("UserName", user.getUserName());
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                ArrayList<CloudGroup> groups = new ArrayList<CloudGroup>();

                doc = method.invoke();
                blocks = doc.getElementsByTagName("member");
                for (int i = 0; i < blocks.getLength(); i++) {
                    CloudGroup cloudGroup = toGroup(ctx, blocks.item(i));

                    if (cloudGroup != null) {
                        groups.add(cloudGroup);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("groups=" + groups);
                }
                return groups;
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable<CloudPolicy> listPoliciesForGroup(@Nonnull String providerGroupId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.listPoliciesForGroup");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            CloudGroup group = getGroup(providerGroupId);

            if (group == null) {
                throw new CloudException("No such group: " + providerGroupId);
            }
            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.LIST_GROUP_POLICIES, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("GroupName", group.getName());
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                ArrayList<String> names = new ArrayList<String>();

                doc = method.invoke();
                blocks = doc.getElementsByTagName("member");
                for (int i = 0; i < blocks.getLength(); i++) {
                    Node member = blocks.item(i);

                    if (member.hasChildNodes()) {
                        String name = member.getFirstChild().getNodeValue().trim();

                        if (name.length() > 0) {
                            names.add(name);
                        }
                    }
                }
                ArrayList<CloudPolicy> policies = new ArrayList<CloudPolicy>();

                for (String name : names) {
                    Collections.addAll(policies, getGroupPolicy(group, name));
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("policies=" + policies);
                }
                return policies;
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable<CloudPolicy> listPoliciesForUser(@Nonnull String providerUserId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.listPoliciesForUser");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }
            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.LIST_USER_POLICIES, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("UserName", user.getUserName());
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                ArrayList<String> names = new ArrayList<String>();

                doc = method.invoke();
                blocks = doc.getElementsByTagName("member");
                for (int i = 0; i < blocks.getLength(); i++) {
                    Node member = blocks.item(i);

                    if (member.hasChildNodes()) {
                        String name = member.getFirstChild().getNodeValue().trim();

                        if (name.length() > 0) {
                            names.add(name);
                        }
                    }
                }
                ArrayList<CloudPolicy> policies = new ArrayList<CloudPolicy>();

                for (String name : names) {
                    Collections.addAll(policies, getUserPolicy(user, name));
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("policies=" + policies);
                }
                return policies;
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable<CloudUser> listUsersInGroup(@Nonnull String inProviderGroupId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.listUsersInGroup");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            CloudGroup group = getGroup(inProviderGroupId);

            if (group == null) {
                throw new CloudException("No such group: " + inProviderGroupId);
            }
            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.GET_GROUP, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("GroupName", group.getName());
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                ArrayList<CloudUser> users = new ArrayList<CloudUser>();

                doc = method.invoke();
                blocks = doc.getElementsByTagName("member");
                for (int i = 0; i < blocks.getLength(); i++) {
                    CloudUser user = toUser(ctx, blocks.item(i));

                    if (user != null) {
                        users.add(user);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("users=" + users);
                }
                return users;
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable<CloudUser> listUsersInPath(@Nullable String pathBase)
            throws CloudException, InternalException {
        APITrace.begin(provider, "listUsersInPath");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.LIST_USERS, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            if (pathBase != null) {
                parameters.put("PathPrefix", pathBase);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                ArrayList<CloudUser> users = new ArrayList<CloudUser>();

                doc = method.invoke();
                blocks = doc.getElementsByTagName("member");
                for (int i = 0; i < blocks.getLength(); i++) {
                    CloudUser cloudUser = toUser(ctx, blocks.item(i));

                    if (cloudUser != null) {
                        users.add(cloudUser);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("users=" + users);
                }
                return users;
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull String[] mapServiceAction(@Nonnull ServiceAction action) {
        if (action.equals(IdentityAndAccessSupport.ANY)) {
            return new String[] { IAMMethod.IAM_PREFIX + "*" };
        } else if (action.equals(IdentityAndAccessSupport.ADD_GROUP_ACCESS)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.PUT_GROUP_POLICY };
        } else if (action.equals(IdentityAndAccessSupport.ADD_USER_ACCESS)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.PUT_USER_POLICY };
        } else if (action.equals(IdentityAndAccessSupport.CREATE_GROUP)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.CREATE_GROUP };
        } else if (action.equals(IdentityAndAccessSupport.CREATE_USER)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.CREATE_USER };
        } else if (action.equals(IdentityAndAccessSupport.DISABLE_API)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.DELETE_ACCESS_KEY };
        } else if (action.equals(IdentityAndAccessSupport.DISABLE_CONSOLE)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.DELETE_LOGIN_PROFILE };
        } else if (action.equals(IdentityAndAccessSupport.DROP_FROM_GROUP)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.REMOVE_USER_FROM_GROUP };
        } else if (action.equals(IdentityAndAccessSupport.ENABLE_API)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.CREATE_ACCESS_KEY };
        } else if (action.equals(IdentityAndAccessSupport.ENABLE_CONSOLE)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.CREATE_LOGIN_PROFILE };
        } else if (action.equals(IdentityAndAccessSupport.GET_ACCESS_KEY)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.GET_ACCESS_KEY };
        } else if (action.equals(IdentityAndAccessSupport.GET_GROUP)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.GET_GROUP };
        } else if (action.equals(IdentityAndAccessSupport.GET_GROUP_POLICY)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.GET_GROUP_POLICY,
                    IAMMethod.IAM_PREFIX + IAMMethod.LIST_GROUP_POLICIES };
        } else if (action.equals(IdentityAndAccessSupport.GET_USER)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.GET_USER };
        } else if (action.equals(IdentityAndAccessSupport.GET_USER_POLICY)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.GET_USER_POLICY,
                    IAMMethod.IAM_PREFIX + IAMMethod.LIST_USER_POLICIES };
        } else if (action.equals(IdentityAndAccessSupport.JOIN_GROUP)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.ADD_USER_TO_GROUP };
        } else if (action.equals(IdentityAndAccessSupport.LIST_ACCESS_KEY)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.LIST_ACCESS_KEY };
        } else if (action.equals(IdentityAndAccessSupport.LIST_GROUP)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.LIST_GROUPS + "*" };
        } else if (action.equals(IdentityAndAccessSupport.LIST_USER)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.LIST_USERS };
        } else if (action.equals(IdentityAndAccessSupport.REMOVE_GROUP)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.DELETE_GROUP };
        } else if (action.equals(IdentityAndAccessSupport.REMOVE_GROUP_ACCESS)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.PUT_GROUP_POLICY };
        } else if (action.equals(IdentityAndAccessSupport.REMOVE_USER)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.DELETE_USER };
        } else if (action.equals(IdentityAndAccessSupport.REMOVE_USER_ACCESS)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.PUT_USER_POLICY };
        } else if (action.equals(IdentityAndAccessSupport.UPDATE_GROUP)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.UPDATE_GROUP };
        } else if (action.equals(IdentityAndAccessSupport.UPDATE_USER)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.UPDATE_USER };
        }

        /* SSL certificates were explicitly requested to be included into LoadBalancingSupport
         * by the upstream author */
        else if (action.equals(LoadBalancerSupport.LIST_SSL_CERTIFICATES)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.LIST_SSL_CERTIFICATES };
        } else if (action.equals(LoadBalancerSupport.GET_SSL_CERTIFICATE)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.GET_SSL_CERTIFICATE };
        } else if (action.equals(LoadBalancerSupport.CREATE_SSL_CERTIFICATE)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.CREATE_SSL_CERTIFICATE };
        } else if (action.equals(LoadBalancerSupport.DELETE_SSL_CERTIFICATE)) {
            return new String[] { IAMMethod.IAM_PREFIX + IAMMethod.DELETE_SSL_CERTIFICATE };
        }
        return new String[0];
    }

    public void removeAccessKey(@Nonnull String sharedKeyPart) throws CloudException, InternalException {
        //nothing for aws
    }

    @Override
    public void removeAccessKey(@Nonnull String sharedKeyPart, @Nonnull String providerUserId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.removeAccessKey");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }
            CloudUser user = getUser(providerUserId);

            if (user == null) {
                return;
            }
            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.DELETE_ACCESS_KEY, IAMMethod.VERSION);
            IAMMethod method;

            parameters.put("AccessKeyId", sharedKeyPart);
            parameters.put("UserName", user.getUserName());

            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Removing access key for " + sharedKeyPart);
                }
                method.invoke();
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void removeConsoleAccess(@Nonnull String providerUserId) throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.removeConsoleAccess");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }
            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }
            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.DELETE_LOGIN_PROFILE, IAMMethod.VERSION);
            IAMMethod method;

            parameters.put("UserName", user.getUserName());
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Removing console access for " + providerUserId);
                }
                method.invoke();
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void removeGroup(@Nonnull String providerGroupId) throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.removeGroup");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }
            CloudGroup group = getGroup(providerGroupId);

            if (group == null) {
                throw new CloudException("No such group: " + providerGroupId);
            }
            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.DELETE_GROUP, IAMMethod.VERSION);
            IAMMethod method;

            parameters.put("GroupName", group.getName());
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Removing group " + providerGroupId);
                }
                method.invoke();
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void removeGroupPolicy(@Nonnull String providerGroupId, @Nonnull String providerPolicyId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.removeGroupPolicy");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }
            CloudGroup group = getGroup(providerGroupId);

            if (group == null) {
                throw new CloudException("No such group: " + providerGroupId);
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.DELETE_GROUP_POLICY, IAMMethod.VERSION);
            EC2Method method;

            parameters.put("GroupName", group.getName());
            parameters.put("PolicyName", providerPolicyId);

            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Removing policy for group " + providerGroupId);
                }
                method.invoke();
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void removeUser(@Nonnull String providerUserId) throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.removeUser");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }
            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }
            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.DELETE_USER, IAMMethod.VERSION);
            IAMMethod method;

            parameters.put("UserName", user.getUserName());
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Removing user " + providerUserId);
                }
                method.invoke();
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void removeUserPolicy(@Nonnull String providerUserId, @Nonnull String providerPolicyId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.removeUserPolicy");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }
            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.DELETE_USER_POLICY, IAMMethod.VERSION);
            EC2Method method;

            parameters.put("UserName", user.getUserName());
            parameters.put("PolicyName", providerPolicyId);

            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Removing policy for user " + providerUserId);
                }
                method.invoke();
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void removeUserFromGroup(@Nonnull String providerUserId, @Nonnull String providerGroupId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.removeUserFromGroup");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }

            CloudGroup group = getGroup(providerGroupId);

            if (group == null) {
                throw new CloudException("No such group: " + providerGroupId);
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.REMOVE_USER_FROM_GROUP, IAMMethod.VERSION);
            IAMMethod method;

            parameters.put("UserName", user.getUserName());
            parameters.put("GroupName", group.getName());
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Removing user " + providerUserId + " from " + providerGroupId);
                }
                method.invoke();
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void saveGroup(@Nonnull String providerGroupId, @Nullable String newGroupName, @Nullable String newPath)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.saveGroup");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }

            CloudGroup group = getGroup(providerGroupId);

            if (group == null) {
                throw new CloudException("No such group: " + providerGroupId);
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.UPDATE_GROUP, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("GroupName", group.getName());
            if (newGroupName != null) {
                parameters.put("NewGroupName", providerGroupId);
            }
            if (newPath != null) {
                parameters.put("NewPath", newPath);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Updating group " + providerGroupId + " with " + newPath + " - " + newGroupName
                            + "...");
                }
                doc = method.invoke();
                blocks = doc.getElementsByTagName("Group");
                if (blocks.getLength() < 1) {
                    logger.error("No group was updated as a result of the request");
                    throw new CloudException("No group was updated as a result of the request");
                }
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull String[] saveGroupPolicy(@Nonnull String providerGroupId, @Nonnull String name,
            @Nonnull CloudPermission permission, @Nullable ServiceAction action, @Nullable String resourceId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.saveGroupPolicy");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }
            CloudGroup group = getGroup(providerGroupId);

            if (group == null) {
                throw new CloudException("No such group: " + providerGroupId);
            }

            String[] actions = (action == null ? new String[] { "*" } : action.map(provider));
            String[] ids = new String[actions.length];
            int i = 0;

            for (String actionId : actions) {
                Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                        IAMMethod.PUT_GROUP_POLICY, IAMMethod.VERSION);
                String policyName = name + "+" + (actionId.equals("*") ? "ANY" : actionId.replaceAll(":", "_"));

                EC2Method method;

                parameters.put("GroupName", group.getName());
                parameters.put("PolicyName", policyName);

                ArrayList<Map<String, Object>> policies = new ArrayList<Map<String, Object>>();
                HashMap<String, Object> statement = new HashMap<String, Object>();
                HashMap<String, Object> policy = new HashMap<String, Object>();

                policy.put("Effect", permission.equals(CloudPermission.ALLOW) ? "Allow" : "Deny");
                policy.put("Action", actionId);
                policy.put("Resource", resourceId == null ? "*" : resourceId);
                policies.add(policy);
                statement.put("Statement", policies);

                parameters.put("PolicyDocument", (new JSONObject(statement)).toString());
                if (logger.isDebugEnabled()) {
                    logger.debug("parameters=" + parameters);
                }
                method = new IAMMethod(provider, parameters);
                try {
                    if (logger.isInfoEnabled()) {
                        logger.info("Updating policy for group " + providerGroupId);
                    }
                    method.invoke();
                } catch (EC2Exception e) {
                    logger.error(e.getSummary());
                    throw new CloudException(e);
                }
                ids[i++] = policyName;
            }
            return ids;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public String[] saveUserPolicy(@Nonnull String providerUserId, @Nonnull String name,
            @Nonnull CloudPermission permission, @Nullable ServiceAction action, @Nullable String resourceId)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.saveUserPolicy");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for this request.");
                throw new InternalException("No context was established for this request");
            }
            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }

            String[] actions = (action == null ? new String[] { "*" } : action.map(provider));
            String[] ids = new String[actions.length];
            int i = 0;

            for (String actionId : actions) {
                Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                        IAMMethod.PUT_USER_POLICY, IAMMethod.VERSION);
                String policyName = name + "+" + (actionId.equals("*") ? "ANY" : actionId.replaceAll(":", "_"));
                EC2Method method;

                parameters.put("UserName", user.getUserName());
                parameters.put("PolicyName", policyName);

                ArrayList<Map<String, Object>> policies = new ArrayList<Map<String, Object>>();
                HashMap<String, Object> statement = new HashMap<String, Object>();
                HashMap<String, Object> policy = new HashMap<String, Object>();

                policy.put("Effect", permission.equals(CloudPermission.ALLOW) ? "Allow" : "Deny");
                policy.put("Action", actionId);
                policy.put("Resource", resourceId == null ? "*" : resourceId);
                policies.add(policy);
                statement.put("Statement", policies);

                parameters.put("PolicyDocument", (new JSONObject(statement)).toString());
                if (logger.isDebugEnabled()) {
                    logger.debug("parameters=" + parameters);
                }
                method = new IAMMethod(provider, parameters);
                try {
                    if (logger.isInfoEnabled()) {
                        logger.info("Updating policy for user " + providerUserId);
                    }
                    method.invoke();
                } catch (EC2Exception e) {
                    logger.error(e.getSummary());
                    throw new CloudException(e);
                }
                ids[i++] = policyName;
            }
            return ids;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void saveUser(@Nonnull String providerUserId, @Nullable String newUserName, @Nullable String newPath)
            throws CloudException, InternalException {
        APITrace.begin(provider, "IAM.saveUser");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                logger.error("No context was established for the request.");
                throw new InternalException("No context was established for this request");
            }
            CloudUser user = getUser(providerUserId);

            if (user == null) {
                throw new CloudException("No such user: " + providerUserId);
            }

            Map<String, String> parameters = provider.getStandardParameters(provider.getContext(),
                    IAMMethod.UPDATE_USER, IAMMethod.VERSION);
            EC2Method method;
            NodeList blocks;
            Document doc;

            parameters.put("UserName", user.getUserName());
            if (newUserName != null) {
                parameters.put("NewUserName", newUserName);
            }
            if (newPath != null) {
                parameters.put("NewPath", newPath);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("parameters=" + parameters);
            }
            method = new IAMMethod(provider, parameters);
            try {
                if (logger.isInfoEnabled()) {
                    logger.info(
                            "Updating user " + providerUserId + " with " + newPath + " - " + newUserName + "...");
                }
                doc = method.invoke();
                blocks = doc.getElementsByTagName("User");
                if (blocks.getLength() < 1) {
                    logger.error("No user was updated as a result of the request");
                    throw new CloudException("No user was updated as a result of the request");
                }
            } catch (EC2Exception e) {
                logger.error(e.getSummary());
                throw new CloudException(e);
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public boolean supportsAccessControls() throws CloudException, InternalException {
        return true;
    }

    @Override
    public boolean supportsConsoleAccess() throws CloudException, InternalException {
        return true;
    }

    @Override
    public boolean supportsAPIAccess() throws CloudException, InternalException {
        return true;
    }

    private @Nullable AccessKey toAccessKey(@Nonnull ProviderContext ctx, @Nullable Node node)
            throws CloudException, InternalException {
        if (node == null) {
            return null;
        }
        NodeList attributes = node.getChildNodes();
        AccessKey key = new AccessKey();

        key.setProviderOwnerId(ctx.getAccountNumber());

        String userName = null;

        for (int i = 0; i < attributes.getLength(); i++) {
            Node attribute = attributes.item(i);
            String attrName = attribute.getNodeName();

            if (attrName.equalsIgnoreCase("UserName") && attribute.hasChildNodes()) {
                userName = attribute.getFirstChild().getNodeValue().trim();
            } else if (attrName.equalsIgnoreCase("AccessKeyId") && attribute.hasChildNodes()) {
                key.setSharedPart(attribute.getFirstChild().getNodeValue().trim());
            } else if (attrName.equalsIgnoreCase("SecretAccessKey") && attribute.hasChildNodes()) {
                try {
                    key.setSecretPart(attribute.getFirstChild().getNodeValue().trim().getBytes("utf-8"));
                } catch (UnsupportedEncodingException e) {
                    throw new InternalException(e);
                }
            } else if (attrName.equalsIgnoreCase("Status")) {
                if (!attribute.hasChildNodes()
                        || !attribute.getFirstChild().getNodeValue().trim().equalsIgnoreCase("Active")) {
                    return null;
                }
            }
        }
        if (userName != null) {
            CloudUser user = getUserByName(userName);

            if (user == null) {
                logger.warn("Found key " + key.getSharedPart() + " belonging to " + userName
                        + ", but no matching user");
                return null;
            }
            String userId = user.getProviderUserId();

            if (userId == null) {
                logger.warn("Found key " + key.getSharedPart() + " belonging to " + userName
                        + ", but no matching user");
                return null;
            }
            key.setProviderUserId(userId);
        }
        if (key.getSharedPart() == null || key.getSecretPart() == null) {
            return null;
        }
        return key;
    }

    private @Nullable CloudGroup toGroup(@Nonnull ProviderContext ctx, @Nullable Node node)
            throws CloudException, InternalException {
        if (node == null) {
            return null;
        }
        NodeList attributes = node.getChildNodes();
        CloudGroup group = new CloudGroup();

        group.setPath("/");
        group.setProviderOwnerId(ctx.getAccountNumber());
        for (int i = 0; i < attributes.getLength(); i++) {
            Node attribute = attributes.item(i);
            String attrName = attribute.getNodeName();

            if (attrName.equalsIgnoreCase("Path") && attribute.hasChildNodes()) {
                group.setPath(attribute.getFirstChild().getNodeValue().trim());
            } else if (attrName.equalsIgnoreCase("GroupId") && attribute.hasChildNodes()) {
                group.setProviderGroupId(attribute.getFirstChild().getNodeValue().trim());
            } else if (attrName.equalsIgnoreCase("GroupName") && attribute.hasChildNodes()) {
                group.setName(attribute.getFirstChild().getNodeValue().trim());
            }
        }
        if (group.getName() == null || group.getProviderGroupId() == null) {
            return null;
        }
        return group;
    }

    private @Nullable CloudPolicy[] toPolicy(@Nonnull String policyName, @Nonnull JSONArray policyStatements)
            throws JSONException {
        ArrayList<CloudPolicy> policyList = new ArrayList<CloudPolicy>();

        for (int i = 0; i < policyStatements.length(); i++) {
            JSONObject policyStatement = policyStatements.getJSONObject(i);
            String effect = (policyStatement.has("Effect") ? policyStatement.getString("Effect") : null);
            String action = (policyStatement.has("Action") ? policyStatement.getString("Action") : null);
            String resource = (policyStatement.has("Resource") ? policyStatement.getString("Resource") : null);

            if (effect == null) {
                return null;
            }
            CloudPermission permission = (effect.equalsIgnoreCase("allow") ? CloudPermission.ALLOW
                    : CloudPermission.DENY);
            ServiceAction[] serviceActions = null;
            String resourceId = null;

            if (action != null) {
                if (action.equals("*")) {
                    serviceActions = null;
                } else {
                    int idx = action.indexOf(":");
                    String svc;

                    if (idx < 1) {
                        svc = "ec2";
                        if (idx == 0) {
                            if (action.length() > 1) {
                                action = action.substring(1);
                            } else {
                                action = "*";
                            }
                        }
                    } else if (idx == action.length() - 1) {
                        svc = action.substring(0, idx);
                        action = "*";
                    } else {
                        svc = action.substring(0, idx);
                        action = action.substring(idx + 1);
                    }
                    if (action.equals("*")) {
                        action = null;
                    }
                    svc = svc + ":";
                    if (svc.equals(IAMMethod.IAM_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { IdentityAndAccessSupport.ANY };
                        } else {
                            serviceActions = IAMMethod.asIAMServiceAction(action);
                        }
                    } else if (svc.equals(EC2Method.EC2_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { PrepaymentSupport.ANY, VirtualMachineSupport.ANY,
                                    MachineImageSupport.ANY, VolumeSupport.ANY, SnapshotSupport.ANY,
                                    IpAddressSupport.ANY, FirewallSupport.ANY, ShellKeySupport.ANY };
                        } else {
                            serviceActions = EC2Method.asEC2ServiceAction(action);
                        }
                    } else if (svc.equals(Route53Method.R53_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { DNSSupport.ANY };
                        } else {
                            serviceActions = Route53Method.asRoute53ServiceAction(action);
                        }
                    } else if (svc.equals(ELBMethod.ELB_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { LoadBalancerSupport.ANY };
                        } else {
                            serviceActions = ELBMethod.asELBServiceAction(action);
                        }
                    } else if (svc.equals(CloudFrontMethod.CF_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { CDNSupport.ANY };
                        } else {
                            serviceActions = CloudFrontMethod.asCloudFrontServiceAction(action);
                        }
                    } else if (svc.equals(EC2Method.AUTOSCALING_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { AutoScalingSupport.ANY };
                        } else {
                            serviceActions = EC2Method.asAutoScalingServiceAction(action);
                        }
                    } else if (svc.equals(EC2Method.RDS_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { RelationalDatabaseSupport.ANY };
                        } else {
                            serviceActions = RDS.asRDSServiceAction(action);
                        }
                    } else if (svc.equals(EC2Method.SDB_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { KeyValueDatabaseSupport.ANY };
                        } else {
                            serviceActions = SimpleDB.asSimpleDBServiceAction(action);
                        }
                    } else if (svc.equals(EC2Method.SNS_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { PushNotificationSupport.ANY };
                        } else {
                            serviceActions = SNS.asSNSServiceAction(action);
                        }
                    } else if (svc.equals(EC2Method.SQS_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { MQSupport.ANY };
                        } else {
                            serviceActions = SQS.asSQSServiceAction(action);
                        }
                    } else if (svc.equals(S3Method.S3_PREFIX)) {
                        if (action == null) {
                            serviceActions = new ServiceAction[] { BlobStoreSupport.ANY };
                        } else {
                            serviceActions = S3Method.asS3ServiceAction(action);
                        }
                    } else {
                        serviceActions = new ServiceAction[0];
                    }
                }
            }
            if (resource != null && !resource.equals("*")) {
                resourceId = resource;
            }
            if (serviceActions == null) {
                return new CloudPolicy[] {
                        CloudPolicy.getInstance(policyName, policyName, permission, null, resourceId) };
            }
            for (ServiceAction sa : serviceActions) {
                policyList.add(CloudPolicy.getInstance(policyName, policyName, permission, sa, resourceId));
            }
        }
        return policyList.toArray(new CloudPolicy[policyList.size()]);
    }

    private @Nullable CloudUser toUser(@Nonnull ProviderContext ctx, @Nullable Node node)
            throws CloudException, InternalException {
        if (node == null) {
            return null;
        }
        NodeList attributes = node.getChildNodes();
        CloudUser user = new CloudUser();

        user.setPath("/");
        user.setProviderOwnerId(ctx.getAccountNumber());
        for (int i = 0; i < attributes.getLength(); i++) {
            Node attribute = attributes.item(i);
            String attrName = attribute.getNodeName();

            if (attrName.equalsIgnoreCase("Path") && attribute.hasChildNodes()) {
                user.setPath(attribute.getFirstChild().getNodeValue().trim());
            } else if (attrName.equalsIgnoreCase("UserId") && attribute.hasChildNodes()) {
                user.setProviderUserId(attribute.getFirstChild().getNodeValue().trim());
            } else if (attrName.equalsIgnoreCase("UserName") && attribute.hasChildNodes()) {
                user.setUserName(attribute.getFirstChild().getNodeValue().trim());
            }
        }
        if (user.getUserName() == null || user.getProviderUserId() == null) {
            return null;
        }
        return user;
    }

    private @Nonnull String validateName(@Nonnull String name) {
        // It must contain only alphanumeric characters and/or the following: +=,.@_-
        StringBuilder str = new StringBuilder();

        for (int i = 0; i < name.length(); i++) {
            char c = name.charAt(i);

            if (Character.isLetterOrDigit(c)) {
                str.append(c);
            } else if (c == '+' || c == '=' || c == ',' || c == '.' || c == '@' || c == '_' || c == '-') {
                if (i == 0) {
                    str.append("a");
                }
                str.append(c);
            } else if (c == ' ') {
                str.append("-");
            }
        }
        if (str.length() < 1) {
            return String.valueOf(System.currentTimeMillis());
        }
        return str.toString();
    }
}