rapture.kernel.AdminApiImpl.java Source code

Java tutorial

Introduction

Here is the source code for rapture.kernel.AdminApiImpl.java

Source

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2011-2016 Incapture Technologies LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package rapture.kernel;

import java.net.HttpURLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;

import org.apache.commons.codec.binary.Hex;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;

import com.google.common.collect.Lists;

import rapture.common.CallingContext;
import rapture.common.CallingContextStorage;
import rapture.common.EnvironmentInfo;
import rapture.common.EnvironmentInfoStorage;
import rapture.common.MessageFormat;
import rapture.common.Messages;
import rapture.common.RaptureConstants;
import rapture.common.RaptureIPWhiteList;
import rapture.common.RaptureIPWhiteListStorage;
import rapture.common.RaptureURI;
import rapture.common.Scheme;
import rapture.common.TypeArchiveConfig;
import rapture.common.TypeArchiveConfigStorage;
import rapture.common.api.AdminApi;
import rapture.common.exception.RaptureException;
import rapture.common.exception.RaptureExceptionFactory;
import rapture.common.impl.jackson.JacksonUtil;
import rapture.common.impl.jackson.JsonContent;
import rapture.common.model.RaptureEntitlementGroup;
import rapture.common.model.RaptureEntitlementGroupStorage;
import rapture.common.model.RaptureUser;
import rapture.common.model.RaptureUserStorage;
import rapture.common.model.RepoConfig;
import rapture.common.model.RepoConfigStorage;
import rapture.common.storable.helpers.RaptureUserHelper;
import rapture.dsl.tparse.TemplateF;
import rapture.kernel.repo.TypeConversionExecutor;
import rapture.mail.Mailer;
import rapture.object.storage.ObjectFilter;
import rapture.repo.RepoVisitor;
import rapture.repo.Repository;
import rapture.util.IDGenerator;
import rapture.util.RaptureURLCoder;
import rapture.util.encode.RaptureURLCoderFilter;

/**
 * Admin is really dealing with the topLevel admin needs of a RaptureServer
 *
 * @author amkimian
 */
public class AdminApiImpl extends KernelBase implements AdminApi {
    private static final String NAME = "Name"; //$NON-NLS-1$
    private static final String AUTHORITYNAME = "Authority"; //$NON-NLS-1$
    private static final String TEMPLATE = "TEMPLATE"; //$NON-NLS-1$
    private static Logger log = Logger.getLogger(AdminApiImpl.class);

    private Map<String, String> templates = new HashMap<>();
    Messages adminMessageCatalog;

    public AdminApiImpl(Kernel raptureKernel) {
        super(raptureKernel);

        adminMessageCatalog = new Messages("Admin");

        // Update templates from the command line (Environment and Defines)
        for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
            if (entry.getKey().startsWith(TEMPLATE)) {
                String templateName = entry.getKey().substring(TEMPLATE.length() + 1);
                templates.put(templateName, entry.getValue());
            }
        }

        Enumeration<Object> e = System.getProperties().keys();
        while (e.hasMoreElements()) {
            String name = e.nextElement().toString();
            if (name.startsWith(TEMPLATE)) {
                String templateName = name.substring(TEMPLATE.length() + 1);
                templates.put(templateName, System.getProperty(name));
            }
        }
    }

    /**
     * The ip white list is a document that is stored in the settings repo
     */
    @Override
    public void addIPToWhiteList(CallingContext context, String ipAddress) {
        RaptureIPWhiteList wlist = RaptureIPWhiteListStorage.readByFields();
        wlist.getIpWhiteList().add(ipAddress);
        RaptureIPWhiteListStorage.add(wlist, context.getUser(),
                adminMessageCatalog.getMessage("AddedToWhiteList").toString()); //$NON-NLS-1$
    }

    @Override
    public void addTemplate(CallingContext context, String name, String template, Boolean overwrite) {
        if (templates.containsKey(name) && !templates.get(name).isEmpty() && !overwrite) {
            log.info(adminMessageCatalog.getMessage("NoOverwriteTemplate", name)); //$NON-NLS-1$
        } else {
            log.info(adminMessageCatalog.getMessage("AddingTemplate", new String[] { name, template }));
        }
    }

    @Override
    public void addUser(CallingContext context, String userName, String description, String hashPassword,
            String email) {
        addNamedUser(context, userName, description, hashPassword, email, "");
    }

    @Override
    public void addNamedUser(CallingContext context, String userName, String description, String hashPassword,
            String email, String realName) {
        checkParameter("User", userName); //$NON-NLS-1$
        // Does the user already exist?
        RaptureUser usr = getUser(context, userName);
        if (usr == null) {
            String iAm = context.getUser();
            // High-level audit log message.
            Kernel.getAudit().writeAuditEntry(context, RaptureConstants.DEFAULT_AUDIT_URI, "admin", 2,
                    "New user " + userName + " added by " + iAm);
            usr = new RaptureUser();
            usr.setUsername(userName);
            usr.setUserId(realName);
            usr.setDescription(description);
            usr.setHashPassword(hashPassword);
            usr.setEmailAddress(email);
            RaptureUserHelper.validateSalt(usr);
            usr.setInactive(false);
            RaptureUserStorage.add(usr, context.getUser(),
                    adminMessageCatalog.getMessage("AddedUser", userName).toString()); //$NON-NLS-1$
        } else {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    Messages.getMessage("Admin", "UserAlreadyExists", null, null)); //$NON-NLS-1$
        }
    }

    /**
     * Clone the data from the src type to the target
     */
    @Override
    public void copyDocumentRepo(CallingContext context, String srcAuthority, String targAuthority,
            final Boolean wipe) {
        Repository srcRepo = Kernel.getKernel().getRepo(srcAuthority); // $NON-NLS-1$
        final Repository targRepo = Kernel.getKernel().getRepo(targAuthority); // $NON-NLS-1$
        if (wipe) {
            targRepo.drop();
        }

        srcRepo.visitAll("", null, new RepoVisitor() { //$NON-NLS-1$

            @Override
            public boolean visit(String name, JsonContent content, boolean isFolder) {
                try {
                    log.info(adminMessageCatalog.getMessage("Copying", name).toString()); //$NON-NLS-1$
                    targRepo.addDocument(name, content.getContent(), "$copy", //$NON-NLS-1$
                            adminMessageCatalog.getMessage("CopyRepo", name).toString(), wipe); //$NON-NLS-1$
                } catch (RaptureException e) {
                    log.info(adminMessageCatalog.getMessage("NoAddDoc", name)); //$NON-NLS-1$
                }
                return true;
            }

        });
    }

    /**
     * Some type specific calls
     */
    @Override
    public void deleteUser(CallingContext context, String userName) {
        checkParameter("User", userName); //$NON-NLS-1$
        // Assuming the userName is not "you", mark the user as inactive
        if (userName.equals(context.getUser())) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    adminMessageCatalog.getMessage("NoDeleteYourself")); //$NON-NLS-1$
        }
        log.info(adminMessageCatalog.getMessage("RemovingUser", userName)); //$NON-NLS-1$
        RaptureUser usr = getUser(context, userName);
        if (!usr.getInactive()) {
            if (usr.getHasRoot()) {
                throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                        adminMessageCatalog.getMessage("NoDeleteRoot")); //$NON-NLS-1$
            }
            usr.setInactive(true);
            RaptureUserStorage.add(usr, context.getUser(),
                    adminMessageCatalog.getMessage("Inactive", userName).toString()); //$NON-NLS-1$ //$NON-NLS-2$
        }
    }

    @Override
    public void restoreUser(CallingContext context, String userName) {
        checkParameter("User", userName); //$NON-NLS-1$
        log.info(adminMessageCatalog.getMessage("RestoringUser", userName)); //$NON-NLS-1$
        RaptureUser usr = getUser(context, userName);
        if (usr.getInactive()) {
            usr.setInactive(false);
            RaptureUserStorage.add(usr, context.getUser(),
                    adminMessageCatalog.getMessage("Active", userName).toString()); //$NON-NLS-1$ //$NON-NLS-2$
        }
    }

    @Override
    public Boolean doesUserExist(CallingContext context, String userName) {
        RaptureUser usr = getUser(context, userName);
        return usr != null;
    }

    @Override
    public RaptureUser getUser(CallingContext context, String userName) {
        return RaptureUserStorage.readByFields(userName);
    }

    @Override
    public RaptureUser generateApiUser(CallingContext context, String prefix, String description) {
        // Special treatment of prefix "debug"
        checkParameter("Prefix", prefix); //$NON-NLS-1$

        String userId = "zz-" + prefix; //$NON-NLS-1$

        if (!prefix.equals("debug")) { //$NON-NLS-1$
            userId = prefix + "-" + IDGenerator.getUUID(); //$NON-NLS-1$
        }
        RaptureUser usr = new RaptureUser();
        usr.setUsername(userId);
        usr.setDescription(description);
        usr.setHashPassword(""); //$NON-NLS-1$
        usr.setInactive(false);
        usr.setApiKey(true);
        RaptureUserStorage.add(usr, context.getUser(), adminMessageCatalog.getMessage("CreatedApi").toString()); //$NON-NLS-1$
        return usr;
    }

    @Override
    public List<String> getIPWhiteList(CallingContext context) {
        RaptureIPWhiteList wlist = RaptureIPWhiteListStorage.readByFields();
        return wlist.getIpWhiteList();
    }

    /**
     * @return @
     */
    @Override
    public List<RepoConfig> getRepoConfig(CallingContext context) {
        return RepoConfigStorage.readAll();
    }

    @Override
    public List<CallingContext> getSessionsForUser(CallingContext context, final String user) {
        // Get the sessions for this user. Visit and test the content before
        // adding it
        checkParameter("User", user); //$NON-NLS-1$
        final List<CallingContext> ret = new ArrayList<>();
        getEphemeralRepo().visitAll("session", //$NON-NLS-1$
                null, new RepoVisitor() {

                    @Override
                    public boolean visit(String name, JsonContent content, boolean isFolder) {
                        if (!isFolder) {
                            CallingContext ctx;
                            try {
                                ctx = CallingContextStorage.readFromJson(content);
                                if (ctx.getUser().equals(user)) {
                                    ret.add(ctx);
                                }
                            } catch (RaptureException e) {
                                logError(name);
                            }
                        }
                        return true;
                    }

                });
        return ret;
    }

    @Override
    public Map<String, String> getSystemProperties(CallingContext context, List<String> keys) {
        Map<String, String> ret = new TreeMap<>();
        if (keys.isEmpty()) {
            ret.putAll(System.getenv());
            Properties p = System.getProperties();
            for (Map.Entry<Object, Object> prop : p.entrySet()) {
                ret.put(prop.getKey().toString(), prop.getValue().toString());
            }
        } else {
            for (String k : keys) {
                String val = System.getenv(k);
                if (val != null) {
                    ret.put(k, System.getenv(k));
                } else {
                    String prop = System.getProperty(k);
                    if (prop != null) {
                        ret.put(k, prop);
                    }
                }
            }
        }
        return ret;
    }

    @Override
    public String getTemplate(CallingContext context, String name) {
        return templates.get(name);
    }

    private void logError(String name) {
        log.error(adminMessageCatalog.getMessage("CouldNotLoadDoc", name).toString());
    }

    @Override
    public void removeIPFromWhiteList(CallingContext context, String ipAddress) {
        RaptureIPWhiteList wlist = RaptureIPWhiteListStorage.readByFields();
        wlist.getIpWhiteList().remove(ipAddress);
        RaptureIPWhiteListStorage.add(wlist, context.getUser(),
                adminMessageCatalog.getMessage("RemoveWhiteList", ipAddress).toString()); //$NON-NLS-1$
    }

    @Override
    public void resetUserPassword(CallingContext context, String userName, String newHashPassword) {
        checkParameter("User", userName); //$NON-NLS-1$
        checkParameter("Password", newHashPassword); //$NON-NLS-1$
        // Set a new password for this user
        RaptureUser usr = getUser(context, userName);
        if (usr != null) {
            usr.setInactive(false);
            usr.setHashPassword(newHashPassword);
            RaptureUserStorage.add(usr, context.getUser(),
                    adminMessageCatalog.getMessage("PasswordChange", userName).toString()); //$NON-NLS-1$
        } else {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    adminMessageCatalog.getMessage("NoExistUser", userName)); //$NON-NLS-1$
        }
    }

    @Override
    public String createPasswordResetToken(CallingContext context, String userName) {
        checkParameter("User", userName);
        RaptureUser user = getUser(context, userName);
        if (user == null) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    adminMessageCatalog.getMessage("NoExistUser", userName)); //$NON-NLS-1$
        }
        String token = generateSecureToken();
        user.setPasswordResetToken(token);
        user.setTokenExpirationTime(DateTime.now().plusDays(1).getMillis());

        RaptureUserStorage.add(user, context.getUser(),
                adminMessageCatalog.getMessage("GenReset", userName).toString()); //$NON-NLS-1$
        return token;
    }

    @Override
    public String createRegistrationToken(CallingContext context, String userName) {
        checkParameter("User", userName);
        RaptureUser user = getUser(context, userName);
        if (user == null) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    adminMessageCatalog.getMessage("NoExistUser", userName)); //$NON-NLS-1$
        }
        String token = generateSecureToken();
        user.setRegistrationToken(token);
        user.setVerified(false);
        // Registration Token doesn't
        RaptureUserStorage.add(user, context.getUser(),
                adminMessageCatalog.getMessage("GenReg", userName).toString()); //$NON-NLS-1$
        return token;
    }

    @Override
    public Boolean verifyUser(CallingContext context, String userName, String token) {
        checkParameter("User", userName);
        checkParameter("Token", token);
        RaptureUser user = getUser(context, userName);
        boolean match = user.getVerified();

        if (user == null) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    adminMessageCatalog.getMessage("NoExistUser", userName)); //$NON-NLS-1$
        }
        if (!match) {
            match = token.equals(user.getRegistrationToken());
            if (match) {
                user.setRegistrationToken("");
                user.setVerified(true);
                RaptureUserStorage.add(user, context.getUser(),
                        adminMessageCatalog.getMessage("CreatedApi").toString()); //$NON-NLS-1$
            }
        }
        return match;
    }

    private String generateSecureToken() {
        try {
            // get secure random
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            byte bytes[] = new byte[128];
            random.nextBytes(bytes);
            // get its digest
            MessageDigest sha = MessageDigest.getInstance("SHA-1");
            byte[] result = sha.digest(bytes);
            // encode to hex
            return (new Hex()).encodeHexString(result);
        } catch (NoSuchAlgorithmException e) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST, e.getMessage());
        }
    }

    @Override
    public void cancelPasswordResetToken(CallingContext context, String userName) {
        checkParameter("User", userName);
        RaptureUser user = getUser(context, userName);
        if (user == null) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    adminMessageCatalog.getMessage("NoExistUser", userName)); //$NON-NLS-1$
        }
        // expire token now
        user.setTokenExpirationTime(System.currentTimeMillis());
        RaptureUserStorage.add(user, context.getUser(), "Cancel password reset token for user " + userName); //$NON-NLS-1$
    }

    @Override
    public void updateUserEmail(CallingContext context, String userName, String newEmail) {
        checkParameter("User", userName); //$NON-NLS-1$
        RaptureUser user = getUser(context, userName);
        if (user != null) {
            user.setEmailAddress(newEmail);
            RaptureUserStorage.add(user, context.getUser(),
                    adminMessageCatalog.getMessage("UpdateEmail") + userName);
        } else {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    adminMessageCatalog.getMessage("NoExistUser", userName)); //$NON-NLS-1$
        }
    }

    @Override
    public void emailUser(CallingContext context, String userName, String emailTemplate,
            Map<String, Object> templateValues) {
        checkParameter("User", userName); //$NON-NLS-1$
        RaptureUser user = getUser(context, userName);
        if (user != null) {
            templateValues.put("user", user);
            Mailer.email(context, emailTemplate, templateValues);
        } else {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    adminMessageCatalog.getMessage("NoExistUser", userName)); //$NON-NLS-1$ }
        }
    }

    @Override
    public String runTemplate(CallingContext context, String name, String params) {
        String template = templates.get(name);
        if (template == null) {
            log.info(adminMessageCatalog.getMessage("NoExist", name)); //$NON-NLS-1$ //$NON-NLS-2$
            return null;
        }
        String args[] = { template, params };
        log.info(adminMessageCatalog.getMessage("RunTemplate", args).toString()); //$NON-NLS-1$ //$NON-NLS-2$
        String ret = TemplateF.parseTemplate(template, params);
        log.info(adminMessageCatalog.getMessage("TemplateOutput", ret).toString()); //$NON-NLS-1$
        return ret;
    }

    @Override
    public List<RaptureUser> getAllUsers(CallingContext context) {
        return RaptureUserStorage.readAll();
    }

    TypeConversionExecutor tExecutor = new TypeConversionExecutor();

    @Override
    public void initiateTypeConversion(CallingContext context, String raptureURIString, String newConfig,
            int versionsToKeep) {
        RaptureURI internalURI = new RaptureURI(raptureURIString, Scheme.DOCUMENT);
        checkParameter(NAME, internalURI.getDocPath());
        tExecutor.runRebuildFor(internalURI.getAuthority(), internalURI.getDocPath(), newConfig, versionsToKeep);
    }

    @Override
    public void putArchiveConfig(CallingContext context, String raptureURIString, TypeArchiveConfig config) {
        RaptureURI internalURI = new RaptureURI(raptureURIString, Scheme.DOCUMENT);
        checkParameter(NAME, internalURI.getDocPath());
        config.setAuthority(internalURI.getAuthority());
        config.setTypeName(internalURI.getDocPath());
        TypeArchiveConfigStorage.add(config, context.getUser(), "Created type archive config");
    }

    @Override
    public TypeArchiveConfig getArchiveConfig(CallingContext context, String raptureURIString) {
        RaptureURI addressURI = new RaptureURI(raptureURIString, Scheme.DOCUMENT);
        checkParameter(NAME, addressURI.getDocPath());
        return TypeArchiveConfigStorage.readByAddress(addressURI);
    }

    @Override
    public void deleteArchiveConfig(CallingContext context, String raptureURIString) {
        RaptureURI addressURI = new RaptureURI(raptureURIString, Scheme.DOCUMENT);
        checkParameter(NAME, addressURI.getDocPath());
        TypeArchiveConfigStorage.deleteByAddress(addressURI, context.getUser(), "Removed archive config");
    }

    /**
     * A call to simply test that the system is working.
     */
    @Override
    public Boolean ping(CallingContext context) {
        return true;
    }

    @Override
    public void addMetadata(CallingContext context, Map<String, String> values, Boolean overwrite) {
        if ((values == null) || values.isEmpty())
            return;

        Map<String, String> metadata = context.getMetadata();
        if (metadata == null)
            metadata = new HashMap<>();
        for (String key : values.keySet()) {
            if (!overwrite && metadata.containsKey(key)) {
                throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                        key + " exists and overwrite was disallowed");
            }
            metadata.put(key, values.get(key));
        }
        context.setMetadata(metadata);
        getEphemeralRepo().addToStage(RaptureConstants.OFFICIAL_STAGE, "session/" + context.getContext(),
                JacksonUtil.jsonFromObject(context), false);
    }

    // Message of the day and environment, these are all in the same document.

    private EnvironmentInfo getEnvInfo(CallingContext context) {
        EnvironmentInfo info = EnvironmentInfoStorage.readByFields();
        if (info == null) {
            info = new EnvironmentInfo();
            info.setMotd("Welcome to Rapture");
            info.setName("Rapture");
            info.getProperties().put("BANNER_COLOR", "blue");
        }
        return info;
    }

    private void putEnvInfo(CallingContext context, EnvironmentInfo info) {
        EnvironmentInfoStorage.add(info, context.getUser(), "Updated environment info");
    }

    @Override
    public void setMOTD(CallingContext context, String message) {
        EnvironmentInfo info = getEnvInfo(context);
        info.setMotd(message);
        putEnvInfo(context, info);
    }

    @Override
    public String getMOTD(CallingContext context) {
        EnvironmentInfo info = getEnvInfo(context);
        return info.getMotd();
    }

    @Override
    public void setEnvironmentName(CallingContext context, String name) {
        EnvironmentInfo info = getEnvInfo(context);
        info.setName(name);
        putEnvInfo(context, info);
    }

    @Override
    public void setEnvironmentProperties(CallingContext context, Map<String, String> properties) {
        EnvironmentInfo info = getEnvInfo(context);
        info.getProperties().putAll(properties);
        putEnvInfo(context, info);
    }

    @Override
    public String getEnvironmentName(CallingContext context) {
        EnvironmentInfo info = getEnvInfo(context);
        return info.getName();
    }

    @Override
    public Map<String, String> getEnvironmentProperties(CallingContext context) {
        EnvironmentInfo info = getEnvInfo(context);
        return info.getProperties();

    }

    @Override
    public void destroyUser(CallingContext context, String userName) {
        checkParameter("User", userName); //$NON-NLS-1$
        log.info("Destroying user: " + userName);
        RaptureUser usr = getUser(context, userName);
        if (usr == null) {
            MessageFormat error = adminMessageCatalog.getMessage("NoExistUser", userName);
            log.error(error.toString());
            throw RaptureExceptionFactory.create(error);
        }
        if (usr.getInactive()) {
            MessageFormat error = adminMessageCatalog.getMessage("UserNotDestroyed", userName);
            log.error(error.toString());
            throw RaptureExceptionFactory.create(error);
        }
        RaptureUserStorage.deleteByFields(userName, context.getUser(),
                adminMessageCatalog.getMessage("UserDestroyed", userName).toString());
    }

    @Override
    public String encode(CallingContext context, String toEncode) {
        return RaptureURLCoder.encode(toEncode);
    }

    private static final RaptureURLCoderFilter allowDotSlash = new RaptureURLCoderFilter("./");

    @Override
    public String createURI(CallingContext context, String path, String leaf) {
        return RaptureURLCoder.encode(path, allowDotSlash) + "/" + RaptureURLCoder.encode(leaf);
    }

    @Override
    public String createMultipartURI(CallingContext context, List<String> elements) {
        if (elements == null)
            return null;

        StringBuilder sb = new StringBuilder();

        sb.append("/");
        for (String element : elements)
            sb.append("/").append(RaptureURLCoder.encode(element));
        return sb.toString();
    }

    @Override
    public String decode(CallingContext context, String encoded) {
        return RaptureURLCoder.encode(encoded);
    }

    @Override
    public List<String> findGroupNamesByUser(CallingContext context, String userName) {
        List<String> result = Lists.newArrayList();
        FindGroupsByUserFilter filter = new FindGroupsByUserFilter(userName);
        List<RaptureEntitlementGroup> groups = RaptureEntitlementGroupStorage.filterAll(filter);
        for (RaptureEntitlementGroup group : groups) {
            result.add(group.getName());
        }
        return result;
    }

    private class FindGroupsByUserFilter implements ObjectFilter<RaptureEntitlementGroup> {
        private String userName;

        private FindGroupsByUserFilter(String userName) {
            this.userName = userName;
        }

        @Override
        public boolean shouldInclude(RaptureEntitlementGroup obj) {
            return obj.getUsers().contains(userName);
        }
    }
}