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 |
} |