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.rest.server.auth.saml;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022
023/**
024 * Per-process, in-memory implementation of {@link AuthnRequestStore}.
025 *
026 * <p>
027 * Holds at most {@link #DEFAULT_MAX_ENTRIES} unexpired request IDs; each ID is accepted once.  Not shared
028 * between nodes.
029 *
030 * @since 10.0.0
031 */
032public class InMemoryAuthnRequestStore implements AuthnRequestStore {
033
034   /** Default upper bound on outstanding request IDs. */
035   public static final int DEFAULT_MAX_ENTRIES = 10_000;
036
037   private final int maxEntries;
038   private final Map<String,Long> pending = new HashMap<>();
039
040   /** Constructor using {@link #DEFAULT_MAX_ENTRIES}. */
041   public InMemoryAuthnRequestStore() {
042      this(DEFAULT_MAX_ENTRIES);
043   }
044
045   /**
046    * Constructor.
047    *
048    * @param maxEntries The maximum number of outstanding request IDs.  Must be positive.
049    */
050   public InMemoryAuthnRequestStore(int maxEntries) {
051      if (maxEntries <= 0)
052         throw iaex("maxEntries must be positive (was %s)", maxEntries);
053      this.maxEntries = maxEntries;
054   }
055
056   @Override /* Overridden from AuthnRequestStore */
057   public void remember(String id, long expiresAtMs, long nowMs) {
058      reqnb("id", id);
059      synchronized (pending) {
060         if (pending.size() >= maxEntries)
061            pending.values().removeIf(exp -> exp <= nowMs);
062         if (pending.size() >= maxEntries)
063            throw new IllegalStateException("Too many outstanding SAML authentication requests");
064         pending.put(id, expiresAtMs);
065      }
066   }
067
068   @Override /* Overridden from AuthnRequestStore */
069   public boolean consume(String id, long nowMs) {
070      reqnb("id", id);
071      synchronized (pending) {
072         var exp = pending.remove(id);
073         return exp != null && exp > nowMs;
074      }
075   }
076}