001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.secret;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.concurrent.*;
023
024/**
025 * Built-in default {@link SecretStore}: a per-process, {@link ConcurrentHashMap}-backed key-to-secret map.
026 *
027 * <p>
028 * This is the zero-config store {@link SecretStores#resolve(org.apache.juneau.commons.inject.BeanStore)} falls back
029 * to when no store has been contributed.  It is honest about its limits:
030 *
031 * <p>
032 * <b>Process-local only.</b> Secrets live in this JVM's heap and are gone when the process exits &mdash; this store is
033 * <b>not</b> persistent.  Two separate JVMs (or two instances behind a load balancer) each hold their own map and do
034 * <b>not</b> see each other's secrets, so it is <b>not</b> cross-node.  It is a convenience default and a test double,
035 * not a production secret backend; deployments that need durability or sharing must contribute a store backed by an
036 * external secret manager.
037 *
038 * <p>
039 * <b>Defensive copies.</b> {@link #store} copies the supplied array and {@link #find} returns a fresh copy, so the
040 * caller may zero its own arrays without disturbing the stored value and vice-versa.  {@link #delete} zeroes the
041 * retained array before dropping it.  The value is never {@code toString()}'d, logged, or dumped.
042 *
043 * <p>
044 * <b>Thread-safety.</b> All operations are safe for concurrent invocation.
045 *
046 * @since 10.0.0
047 */
048public class InMemorySecretStore implements SecretStore {
049
050   private final ConcurrentHashMap<String,char[]> secrets = new ConcurrentHashMap<>();
051
052   @Override /* SecretStore */
053   public void store(String key, char[] secret) {
054      reqnn("key", key);
055      reqnn("secret", secret);
056      secrets.put(key, secret.clone());
057   }
058
059   @Override /* SecretStore */
060   public Optional<char[]> find(String key) {
061      reqnn("key", key);
062      var v = secrets.get(key);
063      return v == null ? oe() : o(v.clone());
064   }
065
066   @Override /* SecretStore */
067   public boolean exists(String key) {
068      reqnn("key", key);
069      return secrets.containsKey(key);
070   }
071
072   @Override /* SecretStore */
073   public boolean delete(String key) {
074      reqnn("key", key);
075      var v = secrets.remove(key);
076      if (v == null)
077         return false;
078      Arrays.fill(v, '\0');
079      return true;
080   }
081}