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