View | Details | Raw Unified | Return to bug 49785
Collapse All | Expand All

(-)a/java/org/apache/catalina/realm/LdapTlsContextFactory.java (+177 lines)
Line 0 Link Here
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
18
package org.apache.catalina.realm;
19
20
import java.io.IOException;
21
import java.lang.reflect.InvocationHandler;
22
import java.lang.reflect.InvocationTargetException;
23
import java.lang.reflect.Method;
24
import java.lang.reflect.Proxy;
25
import java.util.Arrays;
26
import java.util.HashMap;
27
import java.util.Hashtable;
28
import java.util.Map;
29
import java.util.logging.Logger;
30
31
import javax.naming.Context;
32
import javax.naming.NamingException;
33
import javax.naming.directory.DirContext;
34
import javax.naming.ldap.InitialLdapContext;
35
import javax.naming.ldap.LdapContext;
36
import javax.naming.ldap.StartTlsRequest;
37
import javax.naming.ldap.StartTlsResponse;
38
import javax.naming.spi.InitialContextFactory;
39
import javax.net.ssl.SSLSession;
40
41
import org.apache.catalina.util.StringManager;
42
43
/**
44
 * Implements an {@link InitialContextFactory} which will create
45
 * {@link LdapContext} instances which have tls enabled.<br>
46
 * 
47
 * Pooling will be disabled as recommended by sun.
48
 * 
49
 * @author Felix Schumacher
50
 * 
51
 */
52
public class LdapTlsContextFactory implements InitialContextFactory {
53
54
    /**
55
     * The string manager for this package.
56
     */
57
    protected static StringManager sm = StringManager
58
            .getManager(Constants.Package);
59
60
    /**
61
     * Proxies a {@link LdapContext} and handles instantiation and close
62
     * specially to start TLS and end it.
63
     */
64
    private static final class ProxyLdapContext implements InvocationHandler {
65
        private final LdapContext delegate;
66
        private final StartTlsResponse tls;
67
68
        @SuppressWarnings("unchecked")
69
        private ProxyLdapContext(Hashtable origEnv) throws NamingException {
70
            Hashtable env = new Hashtable(origEnv);
71
            /*
72
             * We want to have login to happen after TLS was established, so
73
             * save credentials for later usage and remove them from env.
74
             */
75
            Map<String, Object> credentials = new HashMap<String, Object>();
76
            for (String key : Arrays.asList(Context.SECURITY_AUTHENTICATION,
77
                    Context.SECURITY_CREDENTIALS, Context.SECURITY_PRINCIPAL,
78
                    Context.SECURITY_PROTOCOL)) {
79
                Object entry = env.remove(key);
80
                if (entry != null) {
81
                    credentials.put(key, entry);
82
                }
83
            }
84
            delegate = new InitialLdapContext(env, null);
85
            tls = (StartTlsResponse) delegate
86
                    .extendedOperation(new StartTlsRequest());
87
            try {
88
                SSLSession negotiate = tls.negotiate();
89
                Logger.getLogger(this.getClass().getCanonicalName()).fine(
90
                        sm.getString("LDAP connection protocol is {0}",
91
                                negotiate.getProtocol()));
92
            } catch (IOException e) {
93
                throw new NamingException(e.getMessage());
94
            }
95
            /*
96
             * now is the time to reinstate the credentials into env
97
             */
98
            for (Map.Entry<String, Object> savedEntry : credentials.entrySet()) {
99
                delegate.addToEnvironment(savedEntry.getKey(), savedEntry
100
                        .getValue());
101
            }
102
        }
103
104
        @Override
105
        public Object invoke(Object proxy, Method method, Object[] args)
106
                throws Throwable {
107
            if ("close".equals(method.getName())) {
108
                return doClose(delegate);
109
            }
110
            return method.invoke(delegate, args);
111
        }
112
113
        /**
114
         * Wrapper to the original close method. It will try to end tls before
115
         * closing the underlying connection.
116
         * 
117
         * @param delegate
118
         *            underlying connection
119
         * @return always <code>null</code>
120
         * @throws InvocationTargetException
121
         *             if an {@link IOException} or a {@link NamingException}
122
         *             was catched while closing the connection
123
         */
124
        private Object doClose(LdapContext delegate)
125
                throws InvocationTargetException {
126
            try {
127
                if (tls != null) {
128
                    try {
129
                        tls.close();
130
                    } catch (IOException e) {
131
                        throw new InvocationTargetException(e);
132
                    }
133
                }
134
            } finally {
135
                try {
136
                    if (delegate != null) {
137
                        delegate.close();
138
                    }
139
                } catch (NamingException e) {
140
                    throw new InvocationTargetException(e);
141
                }
142
            }
143
            return null;
144
        }
145
    }
146
147
    /**
148
     * Environment key under which the JNDI context factory is stored which
149
     * shall be used inside the proxy.
150
     */
151
    public static final String REAL_INITIAL_CONTEXT_FACTORY = "REAL_INITIAL_CONTEXT_FACTORY";
152
153
    /**
154
     * The JNDI context factory used to acquire our InitialContext. By default,
155
     * assumes use of an LDAP server using the standard JNDI LDAP provider.
156
     */
157
    private static final String DEFAULT_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
158
159
    @SuppressWarnings("unchecked")
160
    @Override
161
    public Context getInitialContext(final Hashtable environment)
162
            throws NamingException {
163
        final Hashtable proxyEnv = new Hashtable(environment);
164
        final Object realFactory;
165
        if (environment.contains(REAL_INITIAL_CONTEXT_FACTORY)) {
166
            realFactory = environment.get(REAL_INITIAL_CONTEXT_FACTORY);
167
        } else {
168
            realFactory = DEFAULT_CONTEXT_FACTORY;
169
        }
170
        proxyEnv.put(Context.INITIAL_CONTEXT_FACTORY, realFactory);
171
        proxyEnv.put("com.sun.jndi.ldap.connect.pool", "false");
172
        return (Context) Proxy.newProxyInstance(this.getClass()
173
                .getClassLoader(), new Class<?>[] { DirContext.class },
174
                new ProxyLdapContext(proxyEnv));
175
    }
176
177
}
(-)a/java/org/apache/catalina/realm/LocalStrings.properties (+1 lines)
Lines 98-100 combinedRealm.addRealm=Add "{0}" realm, making a total of "{1}" realms Link Here
98
combinedRealm.realmStartFail=Failed to start "{0}" realm
98
combinedRealm.realmStartFail=Failed to start "{0}" realm
99
lockOutRealm.authLockedUser=An attempt was made to authenticate the locked user "{0}"
99
lockOutRealm.authLockedUser=An attempt was made to authenticate the locked user "{0}"
100
lockOutRealm.removeWarning=User "{0}" was removed from the failed users cache after {1} seconds to keep the cache size within the limit set
100
lockOutRealm.removeWarning=User "{0}" was removed from the failed users cache after {1} seconds to keep the cache size within the limit set
101
tlsContextFactory.protocolInfo=connection protocol is {0}

Return to bug 49785