001package jmri.server.json;
002
003import java.util.HashMap;
004import java.util.Map;
005import java.util.UUID;
006
007import javax.annotation.Nonnull;
008import javax.annotation.CheckForNull;
009
010import jmri.InstanceManager;
011import jmri.InstanceManagerAutoDefault;
012
013/**
014 * Manager for deletion tokens in the JSON protocols. This is a separate manager
015 * to be able to support the RESTful API where the connection where a token is
016 * generated may be broken before the client's deletion request containing the
017 * token is sent.
018 * <p>
019 * Note this is <em>package private</em> and is not part of a committed to API.
020 * 
021 * @author Randall Wood Copyright 2019
022 * @since 4.15.6
023 */
024class JsonDeleteTokenManager {
025
026    private final Map<String, String> tokens = new HashMap<>();
027
028    /**
029     * Use this method to access the default instance. This ensures that public
030     * API does not need to be exposed for {@link InstanceManagerAutoDefault} to
031     * function.
032     * 
033     * @return the default instance
034     */
035    static JsonDeleteTokenManager getDefault() {
036        if (InstanceManager.getNullableDefault(JsonDeleteTokenManager.class) == null) {
037            InstanceManager.setDefault(JsonDeleteTokenManager.class, new JsonDeleteTokenManager());
038        }
039        return InstanceManager.getDefault(JsonDeleteTokenManager.class);
040    }
041
042    JsonDeleteTokenManager() {
043        // nothing to do
044    }
045
046    /**
047     * Accept a token. If the token is not valid, any valid token is also invalidated.
048     * 
049     * @param type the type of the object to delete
050     * @param name the name of the object to delete
051     * @param token the token to test
052     * @return true if the token was accepted; false otherwise
053     */
054    boolean acceptToken(@Nonnull String type, @Nonnull String name, @CheckForNull String token) {
055        // generate a random token so that a fixed string cannot be discovered and used to
056        // bypass this check
057        String value = tokens.getOrDefault(getKey(type, name), UUID.randomUUID().toString());
058        return value.equals(token);
059    }
060
061    /**
062     * Generate a token to allow deletion following the rejection of a deletion
063     * request.
064     * 
065     * @param type the type of the object to delete
066     * @param name the name of the object to delete
067     * @return the token to use to confirm a deletion should be accepted
068     */
069    String getToken(@Nonnull String type, @Nonnull String name) {
070        String key = getKey(type, name);
071        tokens.put(key, UUID.randomUUID().toString());
072        return tokens.get(key);
073    }
074
075    private String getKey(@Nonnull String type, @Nonnull String name) {
076        return type + name;
077    }
078}