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 */ 017 package org.apache.activemq.security; 018 019 import java.text.MessageFormat; 020 import java.util.HashSet; 021 import java.util.Hashtable; 022 import java.util.Iterator; 023 import java.util.Map; 024 import java.util.Set; 025 026 import javax.naming.Context; 027 import javax.naming.NamingEnumeration; 028 import javax.naming.NamingException; 029 import javax.naming.directory.Attribute; 030 import javax.naming.directory.Attributes; 031 import javax.naming.directory.DirContext; 032 import javax.naming.directory.InitialDirContext; 033 import javax.naming.directory.SearchControls; 034 import javax.naming.directory.SearchResult; 035 036 import org.apache.activemq.command.ActiveMQDestination; 037 import org.apache.activemq.jaas.GroupPrincipal; 038 import org.apache.activemq.jaas.LDAPLoginModule; 039 import org.slf4j.Logger; 040 import org.slf4j.LoggerFactory; 041 042 /** 043 * An {@link AuthorizationMap} which uses LDAP 044 * 045 * @org.apache.xbean.XBean 046 * @author ngcutura 047 */ 048 public class LDAPAuthorizationMap implements AuthorizationMap { 049 050 public static final String INITIAL_CONTEXT_FACTORY = "initialContextFactory"; 051 public static final String CONNECTION_URL = "connectionURL"; 052 public static final String CONNECTION_USERNAME = "connectionUsername"; 053 public static final String CONNECTION_PASSWORD = "connectionPassword"; 054 public static final String CONNECTION_PROTOCOL = "connectionProtocol"; 055 public static final String AUTHENTICATION = "authentication"; 056 057 public static final String TOPIC_SEARCH_MATCHING = "topicSearchMatching"; 058 public static final String TOPIC_SEARCH_SUBTREE = "topicSearchSubtree"; 059 public static final String QUEUE_SEARCH_MATCHING = "queueSearchMatching"; 060 public static final String QUEUE_SEARCH_SUBTREE = "queueSearchSubtree"; 061 062 public static final String ADMIN_BASE = "adminBase"; 063 public static final String ADMIN_ATTRIBUTE = "adminAttribute"; 064 public static final String READ_BASE = "readBase"; 065 public static final String READ_ATTRIBUTE = "readAttribute"; 066 public static final String WRITE_BASE = "writeBAse"; 067 public static final String WRITE_ATTRIBUTE = "writeAttribute"; 068 069 private static final Logger LOG = LoggerFactory.getLogger(LDAPLoginModule.class); 070 071 private String initialContextFactory; 072 private String connectionURL; 073 private String connectionUsername; 074 private String connectionPassword; 075 private String connectionProtocol; 076 private String authentication; 077 078 private DirContext context; 079 080 private MessageFormat topicSearchMatchingFormat; 081 private MessageFormat queueSearchMatchingFormat; 082 083 private boolean topicSearchSubtreeBool = true; 084 private boolean queueSearchSubtreeBool = true; 085 086 private String adminBase; 087 private String adminAttribute; 088 private String readBase; 089 private String readAttribute; 090 private String writeBase; 091 private String writeAttribute; 092 093 public LDAPAuthorizationMap() { 094 // lets setup some sensible defaults 095 initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory"; 096 connectionURL = "ldap://localhost:10389"; 097 connectionUsername = "uid=admin,ou=system"; 098 connectionPassword = "secret"; 099 connectionProtocol = "s"; 100 authentication = "simple"; 101 102 topicSearchMatchingFormat = new MessageFormat("uid={0},ou=topics,ou=destinations,o=ActiveMQ,dc=example,dc=com"); 103 queueSearchMatchingFormat = new MessageFormat("uid={0},ou=queues,ou=destinations,o=ActiveMQ,dc=example,dc=com"); 104 105 adminBase = "(cn=admin)"; 106 adminAttribute = "uniqueMember"; 107 readBase = "(cn=read)"; 108 readAttribute = "uniqueMember"; 109 writeBase = "(cn=write)"; 110 writeAttribute = "uniqueMember"; 111 } 112 113 public LDAPAuthorizationMap(Map options) { 114 initialContextFactory = (String)options.get(INITIAL_CONTEXT_FACTORY); 115 connectionURL = (String)options.get(CONNECTION_URL); 116 connectionUsername = (String)options.get(CONNECTION_USERNAME); 117 connectionPassword = (String)options.get(CONNECTION_PASSWORD); 118 connectionProtocol = (String)options.get(CONNECTION_PROTOCOL); 119 authentication = (String)options.get(AUTHENTICATION); 120 121 adminBase = (String)options.get(ADMIN_BASE); 122 adminAttribute = (String)options.get(ADMIN_ATTRIBUTE); 123 readBase = (String)options.get(READ_BASE); 124 readAttribute = (String)options.get(READ_ATTRIBUTE); 125 writeBase = (String)options.get(WRITE_BASE); 126 writeAttribute = (String)options.get(WRITE_ATTRIBUTE); 127 128 String topicSearchMatching = (String)options.get(TOPIC_SEARCH_MATCHING); 129 String topicSearchSubtree = (String)options.get(TOPIC_SEARCH_SUBTREE); 130 String queueSearchMatching = (String)options.get(QUEUE_SEARCH_MATCHING); 131 String queueSearchSubtree = (String)options.get(QUEUE_SEARCH_SUBTREE); 132 topicSearchMatchingFormat = new MessageFormat(topicSearchMatching); 133 queueSearchMatchingFormat = new MessageFormat(queueSearchMatching); 134 topicSearchSubtreeBool = Boolean.valueOf(topicSearchSubtree).booleanValue(); 135 queueSearchSubtreeBool = Boolean.valueOf(queueSearchSubtree).booleanValue(); 136 } 137 138 public Set<GroupPrincipal> getTempDestinationAdminACLs() { 139 // TODO insert implementation 140 return null; 141 } 142 143 public Set<GroupPrincipal> getTempDestinationReadACLs() { 144 // TODO insert implementation 145 return null; 146 } 147 148 public Set<GroupPrincipal> getTempDestinationWriteACLs() { 149 // TODO insert implementation 150 return null; 151 } 152 153 public Set<GroupPrincipal> getAdminACLs(ActiveMQDestination destination) { 154 return getACLs(destination, adminBase, adminAttribute); 155 } 156 157 public Set<GroupPrincipal> getReadACLs(ActiveMQDestination destination) { 158 return getACLs(destination, readBase, readAttribute); 159 } 160 161 public Set<GroupPrincipal> getWriteACLs(ActiveMQDestination destination) { 162 return getACLs(destination, writeBase, writeAttribute); 163 } 164 165 // Properties 166 // ------------------------------------------------------------------------- 167 168 public String getAdminAttribute() { 169 return adminAttribute; 170 } 171 172 public void setAdminAttribute(String adminAttribute) { 173 this.adminAttribute = adminAttribute; 174 } 175 176 public String getAdminBase() { 177 return adminBase; 178 } 179 180 public void setAdminBase(String adminBase) { 181 this.adminBase = adminBase; 182 } 183 184 public String getAuthentication() { 185 return authentication; 186 } 187 188 public void setAuthentication(String authentication) { 189 this.authentication = authentication; 190 } 191 192 public String getConnectionPassword() { 193 return connectionPassword; 194 } 195 196 public void setConnectionPassword(String connectionPassword) { 197 this.connectionPassword = connectionPassword; 198 } 199 200 public String getConnectionProtocol() { 201 return connectionProtocol; 202 } 203 204 public void setConnectionProtocol(String connectionProtocol) { 205 this.connectionProtocol = connectionProtocol; 206 } 207 208 public String getConnectionURL() { 209 return connectionURL; 210 } 211 212 public void setConnectionURL(String connectionURL) { 213 this.connectionURL = connectionURL; 214 } 215 216 public String getConnectionUsername() { 217 return connectionUsername; 218 } 219 220 public void setConnectionUsername(String connectionUsername) { 221 this.connectionUsername = connectionUsername; 222 } 223 224 public DirContext getContext() { 225 return context; 226 } 227 228 public void setContext(DirContext context) { 229 this.context = context; 230 } 231 232 public String getInitialContextFactory() { 233 return initialContextFactory; 234 } 235 236 public void setInitialContextFactory(String initialContextFactory) { 237 this.initialContextFactory = initialContextFactory; 238 } 239 240 public MessageFormat getQueueSearchMatchingFormat() { 241 return queueSearchMatchingFormat; 242 } 243 244 public void setQueueSearchMatchingFormat(MessageFormat queueSearchMatchingFormat) { 245 this.queueSearchMatchingFormat = queueSearchMatchingFormat; 246 } 247 248 public boolean isQueueSearchSubtreeBool() { 249 return queueSearchSubtreeBool; 250 } 251 252 public void setQueueSearchSubtreeBool(boolean queueSearchSubtreeBool) { 253 this.queueSearchSubtreeBool = queueSearchSubtreeBool; 254 } 255 256 public String getReadAttribute() { 257 return readAttribute; 258 } 259 260 public void setReadAttribute(String readAttribute) { 261 this.readAttribute = readAttribute; 262 } 263 264 public String getReadBase() { 265 return readBase; 266 } 267 268 public void setReadBase(String readBase) { 269 this.readBase = readBase; 270 } 271 272 public MessageFormat getTopicSearchMatchingFormat() { 273 return topicSearchMatchingFormat; 274 } 275 276 public void setTopicSearchMatchingFormat(MessageFormat topicSearchMatchingFormat) { 277 this.topicSearchMatchingFormat = topicSearchMatchingFormat; 278 } 279 280 public boolean isTopicSearchSubtreeBool() { 281 return topicSearchSubtreeBool; 282 } 283 284 public void setTopicSearchSubtreeBool(boolean topicSearchSubtreeBool) { 285 this.topicSearchSubtreeBool = topicSearchSubtreeBool; 286 } 287 288 public String getWriteAttribute() { 289 return writeAttribute; 290 } 291 292 public void setWriteAttribute(String writeAttribute) { 293 this.writeAttribute = writeAttribute; 294 } 295 296 public String getWriteBase() { 297 return writeBase; 298 } 299 300 public void setWriteBase(String writeBase) { 301 this.writeBase = writeBase; 302 } 303 304 // Implementation methods 305 // ------------------------------------------------------------------------- 306 protected Set<GroupPrincipal> getACLs(ActiveMQDestination destination, String roleBase, String roleAttribute) { 307 try { 308 context = open(); 309 } catch (NamingException e) { 310 LOG.error(e.toString()); 311 return new HashSet<GroupPrincipal>(); 312 } 313 314 // if ((destination.getDestinationType() & 315 // (ActiveMQDestination.QUEUE_TYPE | ActiveMQDestination.TOPIC_TYPE)) != 316 // 0) 317 // return new HashSet(); 318 319 String destinationBase = ""; 320 SearchControls constraints = new SearchControls(); 321 322 if ((destination.getDestinationType() & ActiveMQDestination.QUEUE_TYPE) == ActiveMQDestination.QUEUE_TYPE) { 323 destinationBase = queueSearchMatchingFormat.format(new String[] {destination.getPhysicalName()}); 324 if (queueSearchSubtreeBool) { 325 constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); 326 } else { 327 constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE); 328 } 329 } 330 if ((destination.getDestinationType() & ActiveMQDestination.TOPIC_TYPE) == ActiveMQDestination.TOPIC_TYPE) { 331 destinationBase = topicSearchMatchingFormat.format(new String[] {destination.getPhysicalName()}); 332 if (topicSearchSubtreeBool) { 333 constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); 334 } else { 335 constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE); 336 } 337 } 338 339 constraints.setReturningAttributes(new String[] {roleAttribute}); 340 341 try { 342 Set<GroupPrincipal> roles = new HashSet<GroupPrincipal>(); 343 Set<String> acls = new HashSet<String>(); 344 NamingEnumeration results = context.search(destinationBase, roleBase, constraints); 345 while (results.hasMore()) { 346 SearchResult result = (SearchResult)results.next(); 347 Attributes attrs = result.getAttributes(); 348 if (attrs == null) { 349 continue; 350 } 351 acls = addAttributeValues(roleAttribute, attrs, acls); 352 } 353 for (Iterator<String> iter = acls.iterator(); iter.hasNext();) { 354 String roleName = iter.next(); 355 roles.add(new GroupPrincipal(roleName)); 356 } 357 return roles; 358 } catch (NamingException e) { 359 LOG.error(e.toString()); 360 return new HashSet<GroupPrincipal>(); 361 } 362 } 363 364 protected Set<String> addAttributeValues(String attrId, Attributes attrs, Set<String> values) throws NamingException { 365 if (attrId == null || attrs == null) { 366 return values; 367 } 368 if (values == null) { 369 values = new HashSet<String>(); 370 } 371 Attribute attr = attrs.get(attrId); 372 if (attr == null) { 373 return values; 374 } 375 NamingEnumeration e = attr.getAll(); 376 while (e.hasMore()) { 377 String value = (String)e.next(); 378 values.add(value); 379 } 380 return values; 381 } 382 383 protected DirContext open() throws NamingException { 384 if (context != null) { 385 return context; 386 } 387 388 try { 389 Hashtable<String, String> env = new Hashtable<String, String>(); 390 env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); 391 if (connectionUsername != null || !"".equals(connectionUsername)) { 392 env.put(Context.SECURITY_PRINCIPAL, connectionUsername); 393 } 394 if (connectionPassword != null || !"".equals(connectionPassword)) { 395 env.put(Context.SECURITY_CREDENTIALS, connectionPassword); 396 } 397 env.put(Context.SECURITY_PROTOCOL, connectionProtocol); 398 env.put(Context.PROVIDER_URL, connectionURL); 399 env.put(Context.SECURITY_AUTHENTICATION, authentication); 400 context = new InitialDirContext(env); 401 402 } catch (NamingException e) { 403 LOG.error(e.toString()); 404 throw e; 405 } 406 return context; 407 } 408 409 }