001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.jwk.source;
019
020
021import java.util.Date;
022import java.util.concurrent.TimeUnit;
023
024import net.jcip.annotations.ThreadSafe;
025
026import com.nimbusds.jose.jwk.JWKSet;
027
028
029/**
030 * JSON Web Key (JWK) set cache implementation.
031 *
032 * @author Vladimir Dzhuvinov
033 * @author Sarvesh Sharma
034 * @version 2021-01-08
035 */
036@ThreadSafe
037public class DefaultJWKSetCache implements JWKSetCache {
038        
039        
040        /**
041         * The default lifespan for cached JWK sets (15 minutes).
042         */
043        public static final long DEFAULT_LIFESPAN_MINUTES = 15;
044
045
046        /**
047         * The default refresh time for cached JWK sets (5 minutes).
048         */
049        public static final long DEFAULT_REFRESH_TIME_MINUTES = 5;
050
051        
052        /**
053         * The lifespan of the cached JWK set, in {@link #timeUnit}s, negative
054         * means no expiration.
055         */
056        private final long lifespan;
057
058
059        /**
060         * The refresh time of the cached JWK set, in {@link #timeUnit}s,
061         * negative means no refresh time.
062         */
063        private final long refreshTime;
064
065        
066        /**
067         * The time unit, may be {@code null} if no expiration / refresh time.
068         */
069        private final TimeUnit timeUnit;
070        
071        
072        /**
073         * The cached JWK set, {@code null} if none.
074         */
075        private volatile JWKSetWithTimestamp jwkSetWithTimestamp;
076        
077        
078        /**
079         * Creates a new JWK set, the default lifespan of the cached JWK set is
080         * set to 15 minutes, the refresh time to 5 minutes.
081         */
082        public DefaultJWKSetCache() {
083                
084                this(DEFAULT_LIFESPAN_MINUTES, DEFAULT_REFRESH_TIME_MINUTES, TimeUnit.MINUTES);
085        }
086        
087        
088        /**
089         * Creates a new JWK set cache.
090         *
091         * @param lifespan    The lifespan of the cached JWK set before it
092         *                    expires, negative means no expiration.
093         * @param refreshTime The time after which the cached JWK set is marked
094         *                    for refresh, negative if not specified. Should be
095         *                    shorter or equal to the lifespan.
096         * @param timeUnit    The lifespan time unit, may be {@code null} if no
097         *                    expiration or refresh time.
098         */
099        public DefaultJWKSetCache(final long lifespan, final long refreshTime, final TimeUnit timeUnit) {
100                
101                this.lifespan = lifespan;
102                this.refreshTime = refreshTime;
103
104                if ((lifespan > -1 || refreshTime > -1) && timeUnit == null) {
105                        throw new IllegalArgumentException("A time unit must be specified for non-negative lifespans or refresh times");
106                }
107                
108                this.timeUnit = timeUnit;
109        }
110        
111        
112        @Override
113        public void put(final JWKSet jwkSet) {
114                
115                final JWKSetWithTimestamp updatedJWKSetWithTs;
116                if (jwkSet != null) {
117                        updatedJWKSetWithTs = new JWKSetWithTimestamp(jwkSet);
118                } else {
119                        // clear cache
120                        updatedJWKSetWithTs = null;
121                }
122                
123                jwkSetWithTimestamp = updatedJWKSetWithTs;
124        }
125        
126        
127        @Override
128        public JWKSet get() {
129                
130                if (jwkSetWithTimestamp == null || isExpired()) {
131                        return null;
132                }
133                
134                return jwkSetWithTimestamp.getJWKSet();
135        }
136
137
138        @Override
139        public boolean requiresRefresh() {
140
141                return jwkSetWithTimestamp != null &&
142                        refreshTime > -1 &&
143                        new Date().getTime() > jwkSetWithTimestamp.getDate().getTime() + TimeUnit.MILLISECONDS.convert(refreshTime, timeUnit);
144        }
145
146        
147        /**
148         * Returns the cache put timestamp.
149         *
150         * @return The cache put timestamp, negative if not specified.
151         */
152        public long getPutTimestamp() {
153                
154                return jwkSetWithTimestamp != null ? jwkSetWithTimestamp.getDate().getTime() : -1L;
155        }
156        
157        
158        /**
159         * Returns {@code true} if the cached JWK set is expired.
160         *
161         * @return {@code true} if expired.
162         */
163        public boolean isExpired() {
164        
165                return jwkSetWithTimestamp != null &&
166                        lifespan > -1 &&
167                        new Date().getTime() > jwkSetWithTimestamp.getDate().getTime() + TimeUnit.MILLISECONDS.convert(lifespan, timeUnit);
168        }
169        
170        
171        /**
172         * Returns the configured lifespan of the cached JWK.
173         *
174         * @param timeUnit The time unit to use.
175         *
176         * @return The configured lifespan, negative means no expiration.
177         */
178        public long getLifespan(final TimeUnit timeUnit) {
179                
180                if (lifespan < 0) {
181                        return lifespan;
182                }
183
184                return timeUnit.convert(lifespan, this.timeUnit);
185        }
186
187
188        /**
189         * Returns the configured refresh time of the cached JWK.
190         *
191         * @param timeUnit The time unit to use.
192         *
193         * @return The configured refresh time, negative means no expiration.
194         */
195        public long getRefreshTime(final TimeUnit timeUnit) {
196
197                if (refreshTime < 0) {
198                        return refreshTime;
199                }
200
201                return timeUnit.convert(refreshTime, this.timeUnit);
202        }
203}