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    import javax.naming.ldap.LdapName;
036    import javax.naming.ldap.Rdn;
037    
038    import org.apache.activemq.advisory.AdvisorySupport;
039    import org.apache.activemq.command.ActiveMQDestination;
040    import org.apache.activemq.filter.DestinationMap;
041    import org.apache.activemq.jaas.GroupPrincipal;
042    import org.apache.activemq.jaas.LDAPLoginModule;
043    import org.slf4j.Logger;
044    import org.slf4j.LoggerFactory;
045    
046    /**
047     * An {@link AuthorizationMap} which uses LDAP
048     *
049     * @org.apache.xbean.XBean
050     * @author ngcutura
051     */
052    public 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                }
475                if (connectionPassword != null || !"".equals(connectionPassword)) {
476                    env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
477                }
478                env.put(Context.SECURITY_PROTOCOL, connectionProtocol);
479                env.put(Context.PROVIDER_URL, connectionURL);
480                env.put(Context.SECURITY_AUTHENTICATION, authentication);
481                context = new InitialDirContext(env);
482    
483            } catch (NamingException e) {
484                LOG.error(e.toString());
485                throw e;
486            }
487            return context;
488        }
489    
490    }