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