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