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