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}