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 org.apache.activemq.command.ActiveMQDestination;
020 import org.apache.activemq.command.ActiveMQQueue;
021 import org.apache.activemq.command.ActiveMQTopic;
022 import org.apache.activemq.filter.DestinationMapEntry;
023 import org.apache.activemq.jaas.GroupPrincipal;
024 import org.apache.activemq.jaas.UserPrincipal;
025 import org.slf4j.Logger;
026 import org.slf4j.LoggerFactory;
027 import org.springframework.beans.factory.DisposableBean;
028 import org.springframework.beans.factory.InitializingBean;
029
030 import javax.naming.Binding;
031 import javax.naming.Context;
032 import javax.naming.InvalidNameException;
033 import javax.naming.NamingEnumeration;
034 import javax.naming.NamingException;
035 import javax.naming.directory.*;
036 import javax.naming.event.*;
037 import javax.naming.ldap.LdapName;
038 import javax.naming.ldap.Rdn;
039
040 import java.util.*;
041
042 /**
043 * A {@link DefaultAuthorizationMap} implementation which uses LDAP to initialize and update authorization
044 * policy.
045 *
046 * @org.apache.xbean.XBean
047 */
048 public class CachedLDAPAuthorizationMap extends DefaultAuthorizationMap implements InitializingBean, DisposableBean {
049
050 private static final Logger LOG = LoggerFactory.getLogger(CachedLDAPAuthorizationMap.class);
051
052 // Configuration Options
053 private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
054 private String connectionURL = "ldap://localhost:1024";
055 private String connectionUsername = "uid=admin,ou=system";
056 private String connectionPassword = "secret";
057 private String connectionProtocol = "s";
058 private String authentication = "simple";
059
060
061 private int queuePrefixLength = 4;
062 private int topicPrefixLength = 4;
063 private int tempPrefixLength = 4;
064
065 private String queueSearchBase = "ou=Queue,ou=Destination,ou=ActiveMQ,ou=system";
066 private String topicSearchBase = "ou=Topic,ou=Destination,ou=ActiveMQ,ou=system";
067 private String tempSearchBase = "ou=Temp,ou=Destination,ou=ActiveMQ,ou=system";
068
069
070 private String permissionGroupMemberAttribute = "member";
071
072 private String adminPermissionGroupSearchFilter = "(cn=Admin)";
073 private String readPermissionGroupSearchFilter = "(cn=Read)";
074 private String writePermissionGroupSearchFilter = "(cn=Write)";
075
076 private boolean legacyGroupMapping = true;
077 private String groupObjectClass = "groupOfNames";
078 private String userObjectClass = "person";
079 private String groupNameAttribute = "cn";
080 private String userNameAttribute = "uid";
081
082
083 private int refreshInterval = -1;
084 private boolean refreshDisabled = false;
085
086 // Internal State
087 private long lastUpdated;
088
089 private static String ANY_DESCENDANT = "\\$";
090
091 protected DirContext context;
092 private EventDirContext eventContext;
093
094 protected HashMap<ActiveMQDestination, AuthorizationEntry> entries =
095 new HashMap<ActiveMQDestination, AuthorizationEntry>();
096
097 protected DirContext createContext() throws NamingException {
098 Hashtable<String, String> env = new Hashtable<String, String>();
099 env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
100 if (connectionUsername != null || !"".equals(connectionUsername)) {
101 env.put(Context.SECURITY_PRINCIPAL, connectionUsername);
102 }
103 if (connectionPassword != null || !"".equals(connectionPassword)) {
104 env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
105 }
106 env.put(Context.SECURITY_PROTOCOL, connectionProtocol);
107 env.put(Context.PROVIDER_URL, connectionURL);
108 env.put(Context.SECURITY_AUTHENTICATION, authentication);
109 return new InitialDirContext(env);
110 }
111
112 protected boolean isContextAlive() {
113 boolean alive = false;
114 if (context != null) {
115 try {
116 context.getAttributes("");
117 alive = true;
118 } catch (Exception e) {}
119 }
120 return alive;
121 }
122
123 /**
124 * Returns the existing open context or creates a new one and registers listeners for
125 * push notifications if such an update style is enabled. This implementation should not
126 * be invoked concurrently.
127 *
128 * @return the current context
129 *
130 * @throws NamingException if there is an error setting things up
131 */
132 protected DirContext open() throws NamingException {
133 if (isContextAlive()) {
134 return context;
135 }
136
137 try {
138 context = createContext();
139 if (refreshInterval == -1 && !refreshDisabled) {
140 eventContext = ((EventDirContext)context.lookup(""));
141
142 final SearchControls constraints = new SearchControls();
143 constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
144
145 // Listeners for Queue policy //
146
147 // Listeners for each type of permission
148 for (PermissionType permissionType : PermissionType.values()) {
149 eventContext.addNamingListener(queueSearchBase, getFilterForPermissionType(permissionType), constraints,
150 this.new CachedLDAPAuthorizationMapNamespaceChangeListener(DestinationType.QUEUE, permissionType));
151 }
152 // Listener for changes to the destination pattern entry itself and not a permission entry.
153 eventContext.addNamingListener(queueSearchBase, "cn=*", new SearchControls(),
154 this.new CachedLDAPAuthorizationMapNamespaceChangeListener(DestinationType.QUEUE, null));
155
156 // Listeners for Topic policy //
157
158 // Listeners for each type of permission
159 for (PermissionType permissionType : PermissionType.values()) {
160 eventContext.addNamingListener(topicSearchBase, getFilterForPermissionType(permissionType), constraints,
161 this.new CachedLDAPAuthorizationMapNamespaceChangeListener(DestinationType.TOPIC, permissionType));
162 }
163 // Listener for changes to the destination pattern entry itself and not a permission entry.
164 eventContext.addNamingListener(topicSearchBase, "cn=*", new SearchControls(),
165 this.new CachedLDAPAuthorizationMapNamespaceChangeListener(DestinationType.TOPIC, null));
166
167 // Listeners for Temp policy //
168
169 // Listeners for each type of permission
170 for (PermissionType permissionType : PermissionType.values()) {
171 eventContext.addNamingListener(tempSearchBase, getFilterForPermissionType(permissionType), constraints,
172 this.new CachedLDAPAuthorizationMapNamespaceChangeListener(DestinationType.TEMP, permissionType));
173 }
174
175 }
176 } catch (NamingException e) {
177 context = null;
178 throw e;
179 }
180
181 return context;
182 }
183
184 /**
185 * Queries the directory and initializes the policy based on the data in the directory.
186 * This implementation should not be invoked concurrently.
187 *
188 * @throws Exception if there is an unrecoverable error processing the directory contents
189 */
190 @SuppressWarnings("rawtypes")
191 protected void query() throws Exception {
192 DirContext currentContext = open();
193
194 final SearchControls constraints = new SearchControls();
195 constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
196
197 for (PermissionType permissionType : PermissionType.values()) {
198 try {
199 processQueryResults(
200 currentContext.search(queueSearchBase, getFilterForPermissionType(permissionType), constraints),
201 DestinationType.QUEUE, permissionType);
202 } catch (Exception e) {
203 LOG.error("Policy not applied!. Error processing policy under '" + queueSearchBase + "' with filter '"
204 + getFilterForPermissionType(permissionType) + "'", e);
205 }
206 }
207
208 for (PermissionType permissionType : PermissionType.values()) {
209 try {
210 processQueryResults(
211 currentContext.search(topicSearchBase, getFilterForPermissionType(permissionType), constraints),
212 DestinationType.TOPIC, permissionType);
213 } catch (Exception e) {
214 LOG.error("Policy not applied!. Error processing policy under '" + topicSearchBase + "' with filter '"
215 + getFilterForPermissionType(permissionType) + "'", e);
216 }
217 }
218
219 for (PermissionType permissionType : PermissionType.values()) {
220 try {
221 processQueryResults(
222 currentContext.search(tempSearchBase, getFilterForPermissionType(permissionType), constraints),
223 DestinationType.TEMP, permissionType);
224 } catch (Exception e) {
225 LOG.error("Policy not applied!. Error processing policy under '" + tempSearchBase + "' with filter '"
226 + getFilterForPermissionType(permissionType) + "'", e);
227 }
228 }
229
230 setEntries(new ArrayList<DestinationMapEntry>(entries.values()));
231 updated();
232 }
233
234 /**
235 * Processes results from a directory query in the context of a given destination type and permission type.
236 * This implementation should not be invoked concurrently.
237 *
238 * @param results the results to process
239 * @param destinationType the type of the destination for which the directory results apply
240 * @param permissionType the type of the permission for which the directory results apply
241 *
242 * @throws Exception if there is an error processing the results
243 */
244 protected void processQueryResults(NamingEnumeration<SearchResult> results,
245 DestinationType destinationType, PermissionType permissionType) throws Exception {
246
247 while (results.hasMore()) {
248 SearchResult result = results.next();
249 AuthorizationEntry entry = null;
250
251 try {
252 entry = getEntry(new LdapName(result.getNameInNamespace()), destinationType);
253 } catch (Exception e) {
254 LOG.error("Policy not applied! Error parsing authorization policy entry under "
255 + result.getNameInNamespace(), e);
256 continue;
257 }
258
259 applyACL(entry, result, permissionType);
260 }
261 }
262
263 /**
264 * Marks the time at which the authorization state was last refreshed. Relevant for synchronous policy updates.
265 * This implementation should not be invoked concurrently.
266 */
267 protected void updated() {
268 lastUpdated = System.currentTimeMillis();
269 }
270
271 /**
272 * Retrieves or creates the {@link AuthorizationEntry} that corresponds to
273 * the DN in {@code dn}. This implementation should not be invoked concurrently.
274 *
275 * @param dn
276 * the DN representing the policy entry in the directory
277 * @param destinationType the type of the destination to get/create the entry for
278 *
279 * @return the corresponding authorization entry for the DN
280 *
281 * @throws IllegalArgumentException
282 * if destination type is not one of {@link DestinationType#QUEUE}, {@link DestinationType#TOPIC},
283 * {@link DestinationType#TEMP} or if the policy entry DN is malformed
284 */
285 protected AuthorizationEntry getEntry(LdapName dn, DestinationType destinationType) {
286
287 AuthorizationEntry entry = null;
288
289
290 switch (destinationType) {
291 case TEMP:
292 // handle temp entry
293 if (dn.size() != getPrefixLengthForDestinationType(destinationType) + 1) {
294 // handle unknown entry
295 throw new IllegalArgumentException("Malformed policy structure for a temporary destination "
296 + "policy entry. The permission group entries should be immediately below the "
297 + "temporary policy base DN.");
298 }
299 entry = getTempDestinationAuthorizationEntry();
300 if (entry == null) {
301 entry = new TempDestinationAuthorizationEntry();
302 setTempDestinationAuthorizationEntry((TempDestinationAuthorizationEntry) entry);
303 }
304
305 break;
306
307 case QUEUE:
308 case TOPIC:
309 // handle regular destinations
310 if (dn.size() != getPrefixLengthForDestinationType(destinationType) + 2) {
311 throw new IllegalArgumentException("Malformed policy structure for a queue or topic destination "
312 + "policy entry. The destination pattern and permission group entries should be "
313 + "nested below the queue or topic policy base DN.");
314 }
315
316 ActiveMQDestination dest = formatDestination(dn, destinationType);
317
318 if (dest != null) {
319 entry = entries.get(dest);
320 if (entry == null) {
321 entry = new AuthorizationEntry();
322 entry.setDestination(dest);
323 entries.put(dest, entry);
324 }
325 }
326
327 break;
328 default:
329 // handle unknown entry
330 throw new IllegalArgumentException("Unknown destination type " + destinationType);
331 }
332
333 return entry;
334 }
335
336 /**
337 * Applies the policy from the directory to the given entry within the context of the provided
338 * permission type.
339 *
340 * @param entry the policy entry to apply the policy to
341 * @param result the results from the directory to apply to the policy entry
342 * @param permissionType the permission type of the data in the directory
343 *
344 * @throws NamingException if there is an error applying the ACL
345 */
346 protected void applyACL(AuthorizationEntry entry, SearchResult result,
347 PermissionType permissionType) throws NamingException {
348
349 // Find members
350 Attribute memberAttribute = result.getAttributes().get(permissionGroupMemberAttribute);
351 NamingEnumeration<?> memberAttributeEnum = memberAttribute.getAll();
352
353 HashSet<Object> members = new HashSet<Object>();
354
355 while (memberAttributeEnum.hasMoreElements()) {
356 String memberDn = (String) memberAttributeEnum.nextElement();
357 boolean group = false;
358 boolean user = false;
359 String principalName = null;
360
361 if (!legacyGroupMapping) {
362 // Lookup of member to determine principal type (group or user) and name.
363 Attributes memberAttributes;
364 try {
365 memberAttributes = context.getAttributes(memberDn,
366 new String[] {"objectClass", groupNameAttribute, userNameAttribute});
367 } catch (NamingException e) {
368 LOG.error(
369 "Policy not applied! Unknown member " + memberDn
370 + " in policy entry "
371 + result.getNameInNamespace(), e);
372 continue;
373 }
374
375 Attribute memberEntryObjectClassAttribute = memberAttributes.get("objectClass");
376 NamingEnumeration<?> memberEntryObjectClassAttributeEnum = memberEntryObjectClassAttribute.getAll();
377
378 while (memberEntryObjectClassAttributeEnum.hasMoreElements()) {
379 String objectClass = (String) memberEntryObjectClassAttributeEnum.nextElement();
380
381 if (objectClass.equalsIgnoreCase(groupObjectClass)) {
382 group = true;
383 Attribute name = memberAttributes.get(groupNameAttribute);
384 if (name == null) {
385 LOG.error("Policy not applied! Group "
386 + memberDn
387 + "does not have name attribute "
388 + groupNameAttribute + " under entry " + result.getNameInNamespace());
389 break;
390 }
391
392 principalName = (String) name.get();
393 }
394
395 if (objectClass.equalsIgnoreCase(userObjectClass)) {
396 user = true;
397 Attribute name = memberAttributes.get(userNameAttribute);
398 if (name == null) {
399 LOG.error("Policy not applied! User "
400 + memberDn + " does not have name attribute "
401 + userNameAttribute + " under entry " + result.getNameInNamespace());
402 break;
403 }
404
405 principalName = (String) name.get();
406 }
407 }
408
409 } else {
410 group = true;
411 principalName = memberDn.replaceAll("(cn|CN)=", "");
412 }
413
414 if ((!group && !user) || (group && user)) {
415 LOG.error("Policy not applied! Can't determine type of member "
416 + memberDn + " under entry " + result.getNameInNamespace());
417 } else if (principalName != null){
418 if (group && !user) {
419 members.add(new GroupPrincipal(principalName));
420 } else if (!group && user) {
421 members.add(new UserPrincipal(principalName));
422 }
423 }
424 }
425
426 try {
427 applyAcl(entry, permissionType, members);
428 } catch (Exception e) {
429 LOG.error(
430 "Policy not applied! Error adding principals to ACL under "
431 + result.getNameInNamespace(), e);
432 }
433 }
434
435 /**
436 * Applies policy to the entry given the actual principals that will be applied to the policy entry.
437 *
438 * @param entry the policy entry to which the policy should be applied
439 * @param permissionType the type of the permission that the policy will be applied to
440 * @param acls the principals that represent the actual policy
441 *
442 * @throw IllegalArgumentException if {@code permissionType} is unsupported
443 */
444 protected void applyAcl(AuthorizationEntry entry, PermissionType permissionType, Set<Object> acls) {
445
446 switch (permissionType) {
447 case READ:
448 entry.setReadACLs(acls);
449 break;
450 case WRITE:
451 entry.setWriteACLs(acls);
452 break;
453 case ADMIN:
454 entry.setAdminACLs(acls);
455 break;
456 default:
457 throw new IllegalArgumentException("Unknown permission " + permissionType + ".");
458 }
459 }
460
461 /**
462 * Parses a DN into the equivalent {@link ActiveMQDestination}. The default implementation
463 * expects a format of cn=<PERMISSION_NAME>,ou=<DESTINATION_PATTERN>,.... or
464 * ou=<DESTINATION_PATTERN>,.... for permission and destination entries, respectively.
465 * For example {@code cn=admin,ou=$,ou=...} or {@code ou=$,ou=...}.
466 *
467 * @param dn the DN to parse
468 * @param destinationType the type of the destination that we are parsing
469 *
470 * @return the destination that the DN represents
471 *
472 * @throws IllegalArgumentException if {@code destinationType} is {@link DestinationType#TEMP} or
473 * if the format of {@code dn} is incorrect for for a topic or queue
474 *
475 * @see #formatDestination(Rdn, DestinationType)
476 */
477 protected ActiveMQDestination formatDestination(LdapName dn, DestinationType destinationType) {
478 ActiveMQDestination destination = null;
479
480 switch (destinationType) {
481 case QUEUE:
482 case TOPIC:
483 // There exists a need to deal with both names representing a permission or simply a
484 // destination. As such, we need to determine the proper RDN to work with based
485 // on the destination type and the DN size.
486 if (dn.size() == (getPrefixLengthForDestinationType(destinationType) + 2)) {
487 destination = formatDestination(dn.getRdn(dn.size() - 2), destinationType);
488 } else if (dn.size() == (getPrefixLengthForDestinationType(destinationType) + 1)){
489 destination = formatDestination(dn.getRdn(dn.size() - 1), destinationType);
490 } else {
491 throw new IllegalArgumentException(
492 "Malformed DN for representing a permission or destination entry.");
493 }
494 break;
495 default:
496 throw new IllegalArgumentException(
497 "Cannot format destination for destination type " + destinationType);
498 }
499
500 return destination;
501 }
502
503 /**
504 * Parses RDN values representing the destination name/pattern and
505 * destination type into the equivalent {@link ActiveMQDestination}.
506 *
507 * @param destinationName
508 * the RDN representing the name or pattern for the destination
509 * @param destinationType
510 * the type of the destination
511 *
512 * @return the destination that the RDN represent
513 *
514 * @throws IllegalArgumentException
515 * if {@code destinationType} is not one of {@link DestinationType#TOPIC} or
516 * {@link DestinationType#QUEUE}.
517 *
518 * @see #formatDestinationName(Rdn)
519 * @see #formatDestination(LdapName, DestinationType)
520 */
521 protected ActiveMQDestination formatDestination(Rdn destinationName, DestinationType destinationType) {
522 ActiveMQDestination dest = null;
523
524 switch (destinationType) {
525 case QUEUE:
526 dest = new ActiveMQQueue(formatDestinationName(destinationName));
527 break;
528 case TOPIC:
529 dest = new ActiveMQTopic(formatDestinationName(destinationName));
530 break;
531 default:
532 throw new IllegalArgumentException("Unknown destination type: "
533 + destinationType);
534 }
535
536 return dest;
537 }
538
539 /**
540 * Parses the RDN representing a destination name/pattern into the standard string representation
541 * of the name/pattern. This implementation does not care about the type of the RDN such that the RDN could
542 * be a CN or OU.
543 *
544 * @param destinationName the RDN representing the name or pattern for the destination
545 *
546 * @see #formatDestination(Rdn, Rdn)
547 */
548 protected String formatDestinationName(Rdn destinationName) {
549 return destinationName.getValue().toString().replaceAll(ANY_DESCENDANT, ">");
550 }
551
552 /**
553 * Transcribes an existing set into a new set. Used to make defensive copies
554 * for concurrent access.
555 *
556 * @param source
557 * the source set or {@code null}
558 *
559 * @return a new set containing the same elements as {@code source} or
560 * {@code null} if {@code source} is {@code null}
561 */
562 protected <T> Set<T> transcribeSet(Set<T> source) {
563 if (source != null) {
564 return new HashSet<T>(source);
565 } else {
566 return null;
567 }
568 }
569
570 /**
571 * Returns the filter string for the given permission type.
572 *
573 * @throws IllegalArgumentException if {@code permissionType} is not supported
574 *
575 * @see #setAdminPermissionGroupSearchFilter(String)
576 * @see #setReadPermissionGroupSearchFilter(String)
577 * @see #setWritePermissionGroupSearchFilter(String)
578 */
579 protected String getFilterForPermissionType(PermissionType permissionType) {
580 String filter = null;
581
582 switch (permissionType) {
583 case ADMIN:
584 filter = adminPermissionGroupSearchFilter;
585 break;
586 case READ:
587 filter = readPermissionGroupSearchFilter;
588 break;
589 case WRITE:
590 filter = writePermissionGroupSearchFilter;
591 break;
592 default:
593 throw new IllegalArgumentException("Unknown permission type " + permissionType);
594 }
595
596 return filter;
597 }
598
599 /**
600 * Returns the DN prefix size based on the given destination type.
601 *
602 * @throws IllegalArgumentException if {@code destinationType} is not supported
603 *
604 * @see #setQueueSearchBase(String)
605 * @see #setTopicSearchBase(String)
606 * @see #setTempSearchBase(String)
607 */
608 protected int getPrefixLengthForDestinationType(DestinationType destinationType) {
609 int filter = 0;
610
611 switch (destinationType) {
612 case QUEUE:
613 filter = queuePrefixLength;
614 break;
615 case TOPIC:
616 filter = topicPrefixLength;
617 break;
618 case TEMP:
619 filter = tempPrefixLength;
620 break;
621 default:
622 throw new IllegalArgumentException("Unknown permission type " + destinationType);
623 }
624
625 return filter;
626 }
627
628 /**
629 * Performs a check for updates from the server in the event that synchronous updates are enabled
630 * and are the refresh interval has elapsed.
631 */
632 protected void checkForUpdates() {
633 if (context == null || (!refreshDisabled && (refreshInterval != -1 && System.currentTimeMillis() >= lastUpdated + refreshInterval))) {
634 if (!isContextAlive()) {
635 try {
636 context = createContext();
637 } catch (NamingException ne) {
638 // LDAP is down, use already cached values
639 return;
640 }
641 }
642 reset();
643 setTempDestinationAuthorizationEntry(null);
644 entries.clear();
645
646 LOG.debug("Updating authorization map!");
647 try {
648 query();
649 } catch (Exception e) {
650 LOG.error("Error updating authorization map. Partial policy "
651 + "may be applied until the next successful update.", e);
652 }
653 }
654 }
655
656 // Authorization Map
657
658 /**
659 * Provides synchronous refresh capabilities if so configured before delegating to the super implementation,
660 * and otherwise simply delegates to the super implementation.
661 */
662 @Override
663 protected synchronized Set<AuthorizationEntry> getAllEntries(ActiveMQDestination destination) {
664 checkForUpdates();
665 return super.getAllEntries(destination);
666 }
667
668 /**
669 * Provides synchronized and defensive access to the admin ACLs for temp destinations as the super
670 * implementation returns live copies of the ACLs and {@link AuthorizationEntry} is not
671 * setup for concurrent access.
672 */
673 @Override
674 public synchronized Set<Object> getTempDestinationAdminACLs() {
675 checkForUpdates();
676 return transcribeSet(super.getTempDestinationAdminACLs());
677 }
678
679 /**
680 * Provides synchronized and defensive access to the read ACLs for temp destinations as the super
681 * implementation returns live copies of the ACLs and {@link AuthorizationEntry} is not
682 * setup for concurrent access.
683 */
684 public synchronized Set<Object> getTempDestinationReadACLs() {
685 checkForUpdates();
686 return transcribeSet(super.getTempDestinationReadACLs());
687 }
688
689 /**
690 * Provides synchronized and defensive access to the write ACLs for temp destinations as the super
691 * implementation returns live copies of the ACLs and {@link AuthorizationEntry} is not
692 * setup for concurrent access.
693 */
694 public synchronized Set<Object> getTempDestinationWriteACLs() {
695 checkForUpdates();
696 return transcribeSet(super.getTempDestinationWriteACLs());
697 }
698
699 /**
700 * Provides synchronized access to the admin ACLs for the destinations as
701 * {@link AuthorizationEntry} is not setup for concurrent access.
702 */
703 public synchronized Set<Object> getAdminACLs(ActiveMQDestination destination) {
704 return super.getAdminACLs(destination);
705 }
706
707 /**
708 * Provides synchronized access to the read ACLs for the destinations as
709 * {@link AuthorizationEntry} is not setup for concurrent access.
710 */
711 public synchronized Set<Object> getReadACLs(ActiveMQDestination destination) {
712 checkForUpdates();
713 return super.getReadACLs(destination);
714 }
715
716 /**
717 * Provides synchronized access to the write ACLs for the destinations as
718 * {@link AuthorizationEntry} is not setup for concurrent access.
719 */
720 public synchronized Set<Object> getWriteACLs(ActiveMQDestination destination) {
721 checkForUpdates();
722 return super.getWriteACLs(destination);
723 }
724
725 /**
726 * Handler for new policy entries in the directory.
727 *
728 * @param namingEvent the new entry event that occurred
729 * @param destinationType the type of the destination to which the event applies
730 * @param permissionType the permission type to which the event applies
731 */
732 public synchronized void objectAdded(NamingEvent namingEvent, DestinationType destinationType,
733 PermissionType permissionType) {
734 LOG.debug("Adding object: " + namingEvent.getNewBinding());
735 SearchResult result = (SearchResult) namingEvent.getNewBinding();
736
737 try {
738 LdapName name = new LdapName(result.getName());
739
740 AuthorizationEntry entry = getEntry(name, destinationType);
741
742 applyACL(entry, result, permissionType);
743 if (!(entry instanceof TempDestinationAuthorizationEntry)) {
744 put(entry.getDestination(), entry);
745 }
746
747 } catch (InvalidNameException e) {
748 LOG.error("Policy not applied! Error parsing DN for addition of "
749 + result.getName(), e);
750 } catch (Exception e) {
751 LOG.error("Policy not applied! Error processing object addition for addition of "
752 + result.getName(), e);
753 }
754 }
755
756 /**
757 * Handler for removed policy entries in the directory.
758 *
759 * @param namingEvent the removed entry event that occurred
760 * @param destinationType the type of the destination to which the event applies
761 * @param permissionType the permission type to which the event applies
762 */
763 public synchronized void objectRemoved(NamingEvent namingEvent, DestinationType destinationType,
764 PermissionType permissionType) {
765 LOG.debug("Removing object: " + namingEvent.getOldBinding());
766 Binding result = namingEvent.getOldBinding();
767
768 try {
769 LdapName name = new LdapName(result.getName());
770
771 AuthorizationEntry entry = getEntry(name, destinationType);
772
773 applyAcl(entry, permissionType, new HashSet<Object>());
774 } catch (InvalidNameException e) {
775 LOG.error("Policy not applied! Error parsing DN for object removal for removal of "
776 + result.getName(), e);
777 } catch (Exception e) {
778 LOG.error("Policy not applied! Error processing object removal for removal of "
779 + result.getName(), e);
780 }
781 }
782
783 /**
784 * Handler for renamed policy entries in the directory. This handler deals with the renaming
785 * of destination entries as well as permission entries. If the permission type is not null, it is
786 * assumed that we are dealing with the renaming of a permission entry. Otherwise, it is assumed
787 * that we are dealing with the renaming of a destination entry.
788 *
789 * @param namingEvent the renaming entry event that occurred
790 * @param destinationType the type of the destination to which the event applies
791 * @param permissionType the permission type to which the event applies
792 */
793 public synchronized void objectRenamed(NamingEvent namingEvent, DestinationType destinationType,
794 PermissionType permissionType) {
795 Binding oldBinding = namingEvent.getOldBinding();
796 Binding newBinding = namingEvent.getNewBinding();
797 LOG.debug("Renaming object: " + oldBinding + " to " + newBinding);
798
799 try {
800 LdapName oldName = new LdapName(oldBinding.getName());
801 ActiveMQDestination oldDest = formatDestination(oldName, destinationType);
802
803 LdapName newName = new LdapName(newBinding.getName());
804 ActiveMQDestination newDest = formatDestination(newName, destinationType);
805
806 if (permissionType != null) {
807 // Handle the case where a permission entry is being renamed.
808 objectRemoved(namingEvent, destinationType, permissionType);
809
810 SearchControls controls = new SearchControls();
811 controls.setSearchScope(SearchControls.OBJECT_SCOPE);
812
813 boolean matchedToType = false;
814
815 for (PermissionType newPermissionType : PermissionType.values()) {
816 NamingEnumeration<SearchResult> results = context.search(
817 newName,
818 getFilterForPermissionType(newPermissionType), controls);
819
820 if (results.hasMore()) {
821 objectAdded(namingEvent, destinationType, newPermissionType);
822 matchedToType = true;
823 break;
824 }
825 }
826
827 if (!matchedToType) {
828 LOG.error("Policy not applied! Error processing object rename for rename of "
829 + oldBinding.getName() + " to " + newBinding.getName()
830 + ". Could not determine permission type of new object.");
831 }
832
833 } else {
834 // Handle the case where a destination entry is being renamed.
835 if (oldDest != null && newDest != null) {
836 AuthorizationEntry entry = entries.remove(oldDest);
837 if (entry != null) {
838 entry.setDestination(newDest);
839 put(newDest, entry);
840 remove(oldDest, entry);
841 entries.put(newDest, entry);
842 } else {
843 LOG.warn("No authorization entry for " + oldDest);
844 }
845 }
846 }
847 } catch (InvalidNameException e) {
848 LOG.error("Policy not applied! Error parsing DN for object rename for rename of "
849 + oldBinding.getName() + " to " + newBinding.getName(), e);
850 } catch (Exception e) {
851 LOG.error("Policy not applied! Error processing object rename for rename of "
852 + oldBinding.getName() + " to " + newBinding.getName(), e);
853 }
854 }
855
856 /**
857 * Handler for changed policy entries in the directory.
858 *
859 * @param namingEvent the changed entry event that occurred
860 * @param destinationType the type of the destination to which the event applies
861 * @param permissionType the permission type to which the event applies
862 */
863 public synchronized void objectChanged(NamingEvent namingEvent,
864 DestinationType destinationType, PermissionType permissionType) {
865 LOG.debug("Changing object " + namingEvent.getOldBinding() + " to " + namingEvent.getNewBinding());
866 objectRemoved(namingEvent, destinationType, permissionType);
867 objectAdded(namingEvent, destinationType, permissionType);
868 }
869
870 /**
871 * Handler for exception events from the registry.
872 *
873 * @param namingExceptionEvent the exception event
874 */
875 public void namingExceptionThrown(NamingExceptionEvent namingExceptionEvent) {
876 context = null;
877 LOG.error("Caught unexpected exception.", namingExceptionEvent.getException());
878 }
879
880 // Init / Destroy
881
882 @Override
883 public void afterPropertiesSet() throws Exception {
884 query();
885 }
886
887 @Override
888 public void destroy() throws Exception {
889 if (eventContext != null) {
890 eventContext.close();
891 eventContext = null;
892 }
893
894 if (context != null) {
895 context.close();
896 context = null;
897 }
898 }
899
900 // Getters and Setters
901
902 public String getConnectionURL() {
903 return connectionURL;
904 }
905
906 public void setConnectionURL(String connectionURL) {
907 this.connectionURL = connectionURL;
908 }
909
910 public String getConnectionUsername() {
911 return connectionUsername;
912 }
913
914 public void setConnectionUsername(String connectionUsername) {
915 this.connectionUsername = connectionUsername;
916 }
917
918 public String getConnectionPassword() {
919 return connectionPassword;
920 }
921
922 public void setConnectionPassword(String connectionPassword) {
923 this.connectionPassword = connectionPassword;
924 }
925
926 public String getConnectionProtocol() {
927 return connectionProtocol;
928 }
929
930 public void setConnectionProtocol(String connectionProtocol) {
931 this.connectionProtocol = connectionProtocol;
932 }
933
934 public String getAuthentication() {
935 return authentication;
936 }
937
938 public void setAuthentication(String authentication) {
939 this.authentication = authentication;
940 }
941
942 public String getQueueSearchBase() {
943 return queueSearchBase;
944 }
945
946 public void setQueueSearchBase(String queueSearchBase) {
947 try {
948 LdapName baseName = new LdapName(queueSearchBase);
949 queuePrefixLength = baseName.size();
950 this.queueSearchBase = queueSearchBase;
951 } catch (InvalidNameException e) {
952 throw new IllegalArgumentException("Invalid base DN value " + queueSearchBase, e);
953 }
954 }
955
956 public String getTopicSearchBase() {
957 return topicSearchBase;
958 }
959
960 public void setTopicSearchBase(String topicSearchBase) {
961 try {
962 LdapName baseName = new LdapName(topicSearchBase);
963 topicPrefixLength = baseName.size();
964 this.topicSearchBase = topicSearchBase;
965 } catch (InvalidNameException e) {
966 throw new IllegalArgumentException("Invalid base DN value " + topicSearchBase, e);
967 }
968 }
969
970 public String getTempSearchBase() {
971 return tempSearchBase;
972 }
973
974 public void setTempSearchBase(String tempSearchBase) {
975 try {
976 LdapName baseName = new LdapName(tempSearchBase);
977 tempPrefixLength = baseName.size();
978 this.tempSearchBase = tempSearchBase;
979 } catch (InvalidNameException e) {
980 throw new IllegalArgumentException("Invalid base DN value " + tempSearchBase, e);
981 }
982 }
983
984 public String getPermissionGroupMemberAttribute() {
985 return permissionGroupMemberAttribute;
986 }
987
988 public void setPermissionGroupMemberAttribute(
989 String permissionGroupMemberAttribute) {
990 this.permissionGroupMemberAttribute = permissionGroupMemberAttribute;
991 }
992
993 public String getAdminPermissionGroupSearchFilter() {
994 return adminPermissionGroupSearchFilter;
995 }
996
997 public void setAdminPermissionGroupSearchFilter(
998 String adminPermissionGroupSearchFilter) {
999 this.adminPermissionGroupSearchFilter = adminPermissionGroupSearchFilter;
1000 }
1001
1002 public String getReadPermissionGroupSearchFilter() {
1003 return readPermissionGroupSearchFilter;
1004 }
1005
1006 public void setReadPermissionGroupSearchFilter(
1007 String readPermissionGroupSearchFilter) {
1008 this.readPermissionGroupSearchFilter = readPermissionGroupSearchFilter;
1009 }
1010
1011 public String getWritePermissionGroupSearchFilter() {
1012 return writePermissionGroupSearchFilter;
1013 }
1014
1015 public void setWritePermissionGroupSearchFilter(
1016 String writePermissionGroupSearchFilter) {
1017 this.writePermissionGroupSearchFilter = writePermissionGroupSearchFilter;
1018 }
1019
1020 public boolean isLegacyGroupMapping() {
1021 return legacyGroupMapping;
1022 }
1023
1024 public void setLegacyGroupMapping(boolean legacyGroupMapping) {
1025 this.legacyGroupMapping = legacyGroupMapping;
1026 }
1027
1028 public String getGroupObjectClass() {
1029 return groupObjectClass;
1030 }
1031
1032 public void setGroupObjectClass(String groupObjectClass) {
1033 this.groupObjectClass = groupObjectClass;
1034 }
1035
1036 public String getUserObjectClass() {
1037 return userObjectClass;
1038 }
1039
1040 public void setUserObjectClass(String userObjectClass) {
1041 this.userObjectClass = userObjectClass;
1042 }
1043
1044 public String getGroupNameAttribute() {
1045 return groupNameAttribute;
1046 }
1047
1048 public void setGroupNameAttribute(String groupNameAttribute) {
1049 this.groupNameAttribute = groupNameAttribute;
1050 }
1051
1052 public String getUserNameAttribute() {
1053 return userNameAttribute;
1054 }
1055
1056 public void setUserNameAttribute(String userNameAttribute) {
1057 this.userNameAttribute = userNameAttribute;
1058 }
1059
1060 public boolean isRefreshDisabled() {
1061 return refreshDisabled;
1062 }
1063
1064 public void setRefreshDisabled(boolean refreshDisabled) {
1065 this.refreshDisabled = refreshDisabled;
1066 }
1067
1068 public int getRefreshInterval() {
1069 return refreshInterval;
1070 }
1071
1072 public void setRefreshInterval(int refreshInterval) {
1073 this.refreshInterval = refreshInterval;
1074 }
1075
1076 protected static enum DestinationType {
1077 QUEUE,
1078 TOPIC,
1079 TEMP;
1080 }
1081
1082 protected static enum PermissionType {
1083 READ,
1084 WRITE,
1085 ADMIN;
1086 }
1087
1088 /**
1089 * Listener implementation for directory changes that maps change events to
1090 * destination types.
1091 */
1092 protected class CachedLDAPAuthorizationMapNamespaceChangeListener implements
1093 NamespaceChangeListener, ObjectChangeListener {
1094
1095 private final DestinationType destinationType;
1096 private final PermissionType permissionType;
1097
1098 /**
1099 * Creates a new listener. If {@code permissionType} is {@code null}, add
1100 * and remove events are ignored as they do not directly affect policy state.
1101 * This configuration is used when listening for changes on entries that represent
1102 * destination patterns and not for entries that represent permissions.
1103 *
1104 * @param destinationType the type of the destination being listened for
1105 * @param permissionType the optional permission type being listened for
1106 */
1107 public CachedLDAPAuthorizationMapNamespaceChangeListener(
1108 DestinationType destinationType, PermissionType permissionType) {
1109 this.destinationType = destinationType;
1110 this.permissionType = permissionType;
1111 }
1112
1113 @Override
1114 public void namingExceptionThrown(NamingExceptionEvent evt) {
1115 CachedLDAPAuthorizationMap.this.namingExceptionThrown(evt);
1116 }
1117
1118 @Override
1119 public void objectAdded(NamingEvent evt) {
1120 // This test is a hack to work around the fact that Apache DS 2.0 seems to trigger notifications
1121 // for the entire sub-tree even when one-level is the selected search scope.
1122 if (permissionType != null) {
1123 CachedLDAPAuthorizationMap.this.objectAdded(evt, destinationType, permissionType);
1124 }
1125 }
1126
1127 @Override
1128 public void objectRemoved(NamingEvent evt) {
1129 // This test is a hack to work around the fact that Apache DS 2.0 seems to trigger notifications
1130 // for the entire sub-tree even when one-level is the selected search scope.
1131 if (permissionType != null) {
1132 CachedLDAPAuthorizationMap.this.objectRemoved(evt, destinationType, permissionType);
1133 }
1134 }
1135
1136 @Override
1137 public void objectRenamed(NamingEvent evt) {
1138 CachedLDAPAuthorizationMap.this.objectRenamed(evt, destinationType, permissionType);
1139 }
1140
1141 @Override
1142 public void objectChanged(NamingEvent evt) {
1143 // This test is a hack to work around the fact that Apache DS 2.0 seems to trigger notifications
1144 // for the entire sub-tree even when one-level is the selected search scope.
1145 if (permissionType != null) {
1146 CachedLDAPAuthorizationMap.this.objectChanged(evt, destinationType, permissionType);
1147 }
1148 }
1149 }
1150 }