1 /**
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */

17 package org.apache.catalina.authenticator.jaspic;
18
19 import java.io.File;
20 import java.lang.reflect.Constructor;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.CopyOnWriteArrayList;
29
30 import javax.security.auth.message.config.AuthConfigFactory;
31 import javax.security.auth.message.config.AuthConfigProvider;
32 import javax.security.auth.message.config.RegistrationListener;
33
34 import org.apache.catalina.Globals;
35 import org.apache.catalina.authenticator.jaspic.PersistentProviderRegistrations.Provider;
36 import org.apache.catalina.authenticator.jaspic.PersistentProviderRegistrations.Providers;
37 import org.apache.juli.logging.Log;
38 import org.apache.juli.logging.LogFactory;
39 import org.apache.tomcat.util.res.StringManager;
40
41 public class AuthConfigFactoryImpl extends AuthConfigFactory {
42
43     private final Log log = LogFactory.getLog(AuthConfigFactoryImpl.class); // must not be static
44     private static final StringManager sm = StringManager.getManager(AuthConfigFactoryImpl.class);
45
46     private static final String CONFIG_PATH = "conf/jaspic-providers.xml";
47     private static final File CONFIG_FILE =
48             new File(System.getProperty(Globals.CATALINA_BASE_PROP), CONFIG_PATH);
49     private static final Object CONFIG_FILE_LOCK = new Object();
50
51     private static final String[] EMPTY_STRING_ARRAY = new String[0];
52
53     private static String DEFAULT_REGISTRATION_ID = getRegistrationID(nullnull);
54
55     private final Map<String,RegistrationContextImpl> layerAppContextRegistrations =
56             new ConcurrentHashMap<>();
57     private final Map<String,RegistrationContextImpl> appContextRegistrations =
58             new ConcurrentHashMap<>();
59     private final Map<String,RegistrationContextImpl> layerRegistrations =
60             new ConcurrentHashMap<>();
61     // Note: Although there will only ever be a maximum of one entry in this
62     //       Map, use a ConcurrentHashMap for consistency
63     private final Map<String,RegistrationContextImpl> defaultRegistration =
64             new ConcurrentHashMap<>(1);
65
66
67     public AuthConfigFactoryImpl() {
68         loadPersistentRegistrations();
69     }
70
71
72     @Override
73     public AuthConfigProvider getConfigProvider(String layer, String appContext,
74             RegistrationListener listener) {
75         RegistrationContextImpl registrationContext =
76                 findRegistrationContextImpl(layer, appContext);
77         if (registrationContext != null) {
78             if (listener != null) {
79                 RegistrationListenerWrapper wrapper = new RegistrationListenerWrapper(
80                         layer, appContext, listener);
81                 registrationContext.addListener(wrapper);
82             }
83             return registrationContext.getProvider();
84         }
85         return null;
86     }
87
88
89     @Override
90     public String registerConfigProvider(String className,
91             @SuppressWarnings("rawtypes") Map properties, String layer, String appContext,
92             String description) {
93         String registrationID =
94                 doRegisterConfigProvider(className, properties, layer, appContext, description);
95         savePersistentRegistrations();
96         return registrationID;
97     }
98
99
100     @SuppressWarnings("unchecked")
101     private String doRegisterConfigProvider(String className,
102             @SuppressWarnings("rawtypes") Map properties, String layer, String appContext,
103             String description) {
104         if (log.isDebugEnabled()) {
105             log.debug(sm.getString("authConfigFactoryImpl.registerClass",
106                     className, layer, appContext));
107         }
108
109         AuthConfigProvider provider = null;
110         if (className != null) {
111             provider = createAuthConfigProvider(className, properties);
112         }
113
114         String registrationID = getRegistrationID(layer, appContext);
115         RegistrationContextImpl registrationContextImpl = new RegistrationContextImpl(
116                 layer, appContext, description, true, provider, properties);
117         addRegistrationContextImpl(layer, appContext, registrationID, registrationContextImpl);
118         return registrationID;
119     }
120
121
122     private AuthConfigProvider createAuthConfigProvider(String className,
123             @SuppressWarnings("rawtypes") Map properties) throws SecurityException {
124         Class<?> clazz = null;
125         AuthConfigProvider provider = null;
126         try {
127             clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
128         } catch (ClassNotFoundException e) {
129             // Ignore so the re-try below can proceed
130         }
131         try {
132             if (clazz == null) {
133                 clazz = Class.forName(className);
134             }
135             Constructor<?> constructor = clazz.getConstructor(Map.class, AuthConfigFactory.class);
136             provider = (AuthConfigProvider) constructor.newInstance(properties, null);
137         } catch (ReflectiveOperationException | IllegalArgumentException e) {
138             throw new SecurityException(e);
139         }
140         return provider;
141     }
142
143
144     @Override
145     public String registerConfigProvider(AuthConfigProvider provider, String layer,
146             String appContext, String description) {
147         if (log.isDebugEnabled()) {
148             log.debug(sm.getString("authConfigFactoryImpl.registerInstance",
149                     provider.getClass().getName(), layer, appContext));
150         }
151         String registrationID = getRegistrationID(layer, appContext);
152         RegistrationContextImpl registrationContextImpl = new RegistrationContextImpl(
153                 layer, appContext, description, false, provider, null);
154         addRegistrationContextImpl(layer, appContext, registrationID, registrationContextImpl);
155         return registrationID;
156     }
157
158
159     private void addRegistrationContextImpl(String layer, String appContext,
160             String registrationID, RegistrationContextImpl registrationContextImpl) {
161         RegistrationContextImpl previous = null;
162
163         // Add the registration, noting any registration it replaces
164         if (layer != null && appContext != null) {
165             previous = layerAppContextRegistrations.put(registrationID, registrationContextImpl);
166         } else if (layer == null && appContext != null) {
167             previous = appContextRegistrations.put(registrationID, registrationContextImpl);
168         } else if (layer != null && appContext == null) {
169             previous = layerRegistrations.put(registrationID, registrationContextImpl);
170         } else {
171             previous = defaultRegistration.put(registrationID, registrationContextImpl);
172         }
173
174         if (previous == null) {
175             // No match with previous registration so need to check listeners
176             // for all less specific registrations to see if they need to be
177             // notified of this new registration. That there is no exact match
178             // with a previous registration allows a few short-cuts to be taken
179             if (layer != null && appContext != null) {
180                 // Need to check existing appContext registrations
181                 // (and layer and default)
182                 // appContext must match
183                 RegistrationContextImpl registration =
184                         appContextRegistrations.get(getRegistrationID(null, appContext));
185                 if (registration != null) {
186                     for (RegistrationListenerWrapper wrapper : registration.listeners) {
187                         if (layer.equals(wrapper.getMessageLayer()) &&
188                                 appContext.equals(wrapper.getAppContext())) {
189                             registration.listeners.remove(wrapper);
190                             wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
191                         }
192                     }
193                 }
194             }
195             if (appContext != null) {
196                 // Need to check existing layer registrations
197                 // (and default)
198                 // Need to check registrations for all layers
199                 for (RegistrationContextImpl registration : layerRegistrations.values()) {
200                     for (RegistrationListenerWrapper wrapper : registration.listeners) {
201                         if (appContext.equals(wrapper.getAppContext())) {
202                             registration.listeners.remove(wrapper);
203                             wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
204                         }
205                     }
206                 }
207             }
208             if (layer != null || appContext != null) {
209                 // Need to check default
210                 for (RegistrationContextImpl registration : defaultRegistration.values()) {
211                     for (RegistrationListenerWrapper wrapper : registration.listeners) {
212                         if (appContext != null && appContext.equals(wrapper.getAppContext()) ||
213                                 layer != null && layer.equals(wrapper.getMessageLayer())) {
214                             registration.listeners.remove(wrapper);
215                             wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
216                         }
217                     }
218                 }
219             }
220         } else {
221             // Replaced an existing registration so need to notify those listeners
222             for (RegistrationListenerWrapper wrapper : previous.listeners) {
223                 previous.listeners.remove(wrapper);
224                 wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
225             }
226         }
227     }
228
229
230     @Override
231     public boolean removeRegistration(String registrationID) {
232         RegistrationContextImpl registration = null;
233         if (DEFAULT_REGISTRATION_ID.equals(registrationID)) {
234             registration = defaultRegistration.remove(registrationID);
235         }
236         if (registration == null) {
237             registration = layerAppContextRegistrations.remove(registrationID);
238         }
239         if (registration == null) {
240             registration =  appContextRegistrations.remove(registrationID);
241         }
242         if (registration == null) {
243             registration = layerRegistrations.remove(registrationID);
244         }
245
246         if (registration == null) {
247             return false;
248         } else {
249             for (RegistrationListenerWrapper wrapper : registration.listeners) {
250                 wrapper.getListener().notify(wrapper.getMessageLayer(), wrapper.getAppContext());
251             }
252             if (registration.isPersistent()) {
253                 savePersistentRegistrations();
254             }
255             return true;
256         }
257     }
258
259
260     @Override
261     public String[] detachListener(RegistrationListener listener, String layer, String appContext) {
262         String registrationID = getRegistrationID(layer, appContext);
263         RegistrationContextImpl registrationContext = findRegistrationContextImpl(layer, appContext);
264         if (registrationContext != null && registrationContext.removeListener(listener)) {
265             return new String[] { registrationID };
266         }
267         return EMPTY_STRING_ARRAY;
268     }
269
270
271     @Override
272     public String[] getRegistrationIDs(AuthConfigProvider provider) {
273         List<String> result = new ArrayList<>();
274         if (provider == null) {
275             result.addAll(layerAppContextRegistrations.keySet());
276             result.addAll(appContextRegistrations.keySet());
277             result.addAll(layerRegistrations.keySet());
278             if (!defaultRegistration.isEmpty()) {
279                 result.add(DEFAULT_REGISTRATION_ID);
280             }
281         } else {
282             findProvider(provider, layerAppContextRegistrations, result);
283             findProvider(provider, appContextRegistrations, result);
284             findProvider(provider, layerRegistrations, result);
285             findProvider(provider, defaultRegistration, result);
286         }
287         return result.toArray(EMPTY_STRING_ARRAY);
288     }
289
290
291     private void findProvider(AuthConfigProvider provider,
292             Map<String,RegistrationContextImpl> registrations, List<String> result) {
293         for (Entry<String,RegistrationContextImpl> entry : registrations.entrySet()) {
294             if (provider.equals(entry.getValue().getProvider())) {
295                 result.add(entry.getKey());
296             }
297         }
298     }
299
300
301     @Override
302     public RegistrationContext getRegistrationContext(String registrationID) {
303         RegistrationContext result = defaultRegistration.get(registrationID);
304         if (result == null) {
305             result = layerAppContextRegistrations.get(registrationID);
306         }
307         if (result == null) {
308             result = appContextRegistrations.get(registrationID);
309         }
310         if (result == null) {
311             result = layerRegistrations.get(registrationID);
312         }
313         return result;
314     }
315
316
317     @Override
318     public void refresh() {
319         loadPersistentRegistrations();
320     }
321
322
323     private static String getRegistrationID(String layer, String appContext) {
324         if (layer != null && layer.length() == 0) {
325             throw new IllegalArgumentException(
326                     sm.getString("authConfigFactoryImpl.zeroLengthMessageLayer"));
327         }
328         if (appContext != null && appContext.length() == 0) {
329             throw new IllegalArgumentException(
330                     sm.getString("authConfigFactoryImpl.zeroLengthAppContext"));
331         }
332         return (layer == null ? "" : layer) + ":" + (appContext == null ? "" : appContext);
333     }
334
335
336     private void loadPersistentRegistrations() {
337         synchronized (CONFIG_FILE_LOCK) {
338             if (log.isDebugEnabled()) {
339                 log.debug(sm.getString("authConfigFactoryImpl.load",
340                         CONFIG_FILE.getAbsolutePath()));
341             }
342             if (!CONFIG_FILE.isFile()) {
343                 return;
344             }
345             Providers providers = PersistentProviderRegistrations.loadProviders(CONFIG_FILE);
346             for (Provider provider : providers.getProviders()) {
347                 doRegisterConfigProvider(provider.getClassName(), provider.getProperties(),
348                         provider.getLayer(), provider.getAppContext(), provider.getDescription());
349             }
350         }
351     }
352
353
354     private void savePersistentRegistrations() {
355         synchronized (CONFIG_FILE_LOCK) {
356             Providers providers = new Providers();
357             savePersistentProviders(providers, layerAppContextRegistrations);
358             savePersistentProviders(providers, appContextRegistrations);
359             savePersistentProviders(providers, layerRegistrations);
360             savePersistentProviders(providers, defaultRegistration);
361             PersistentProviderRegistrations.writeProviders(providers, CONFIG_FILE);
362         }
363     }
364
365
366     private void savePersistentProviders(Providers providers,
367             Map<String,RegistrationContextImpl> registrations) {
368         for (Entry<String,RegistrationContextImpl> entry : registrations.entrySet()) {
369             savePersistentProvider(providers, entry.getValue());
370         }
371     }
372
373
374     private void savePersistentProvider(Providers providers,
375             RegistrationContextImpl registrationContextImpl) {
376         if (registrationContextImpl != null && registrationContextImpl.isPersistent()) {
377             Provider provider = new Provider();
378             provider.setAppContext(registrationContextImpl.getAppContext());
379             if (registrationContextImpl.getProvider() != null) {
380                 provider.setClassName(registrationContextImpl.getProvider().getClass().getName());
381             }
382             provider.setDescription(registrationContextImpl.getDescription());
383             provider.setLayer(registrationContextImpl.getMessageLayer());
384             for (Entry<String,String> property : registrationContextImpl.getProperties().entrySet()) {
385                 provider.addProperty(property.getKey(), property.getValue());
386             }
387             providers.addProvider(provider);
388         }
389     }
390
391
392     private RegistrationContextImpl findRegistrationContextImpl(String layer, String appContext) {
393         RegistrationContextImpl result;
394         result = layerAppContextRegistrations.get(getRegistrationID(layer, appContext));
395         if (result == null) {
396             result = appContextRegistrations.get(getRegistrationID(null, appContext));
397         }
398         if (result == null) {
399             result = layerRegistrations.get(getRegistrationID(layer, null));
400         }
401         if (result == null) {
402             result = defaultRegistration.get(DEFAULT_REGISTRATION_ID);
403         }
404         return result;
405     }
406
407
408     private static class RegistrationContextImpl implements RegistrationContext {
409
410         private RegistrationContextImpl(String messageLayer, String appContext, String description,
411                 boolean persistent, AuthConfigProvider provider, Map<String,String> properties) {
412             this.messageLayer = messageLayer;
413             this.appContext = appContext;
414             this.description = description;
415             this.persistent = persistent;
416             this.provider = provider;
417             Map<String,String> propertiesCopy = new HashMap<>();
418             if (properties != null) {
419                 propertiesCopy.putAll(properties);
420             }
421             this.properties = Collections.unmodifiableMap(propertiesCopy);
422         }
423
424         private final String messageLayer;
425         private final String appContext;
426         private final String description;
427         private final boolean persistent;
428         private final AuthConfigProvider provider;
429         private final Map<String,String> properties;
430         private final List<RegistrationListenerWrapper> listeners = new CopyOnWriteArrayList<>();
431
432         @Override
433         public String getMessageLayer() {
434             return messageLayer;
435         }
436
437
438         @Override
439         public String getAppContext() {
440             return appContext;
441         }
442
443         @Override
444         public String getDescription() {
445             return description;
446         }
447
448
449         @Override
450         public boolean isPersistent() {
451             return persistent;
452         }
453
454
455         private AuthConfigProvider getProvider() {
456             return provider;
457         }
458
459
460         private void addListener(RegistrationListenerWrapper listener) {
461             if (listener != null) {
462                 listeners.add(listener);
463             }
464         }
465
466
467         private Map<String,String> getProperties() {
468             return properties;
469         }
470
471
472         private boolean removeListener(RegistrationListener listener) {
473             boolean result = false;
474             for (RegistrationListenerWrapper wrapper : listeners) {
475                 if (wrapper.getListener().equals(listener)) {
476                     listeners.remove(wrapper);
477                     result = true;
478                 }
479             }
480             return result;
481         }
482     }
483
484
485     private static class RegistrationListenerWrapper {
486
487         private final String messageLayer;
488         private final String appContext;
489         private final RegistrationListener listener;
490
491
492         public RegistrationListenerWrapper(String messageLayer, String appContext,
493                 RegistrationListener listener) {
494             this.messageLayer = messageLayer;
495             this.appContext = appContext;
496             this.listener = listener;
497         }
498
499
500         public String getMessageLayer() {
501             return messageLayer;
502         }
503
504
505         public String getAppContext() {
506             return appContext;
507         }
508
509
510         public RegistrationListener getListener() {
511             return listener;
512         }
513     }
514 }
515