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