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.command.ActiveMQDestination;
037import org.apache.activemq.jaas.GroupPrincipal;
038import org.apache.activemq.jaas.LDAPLoginModule;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042/**
043 * An {@link AuthorizationMap} which uses LDAP
044 * 
045 * @org.apache.xbean.XBean
046 * @author ngcutura
047 */
048public class LDAPAuthorizationMap implements AuthorizationMap {
049
050    public static final String INITIAL_CONTEXT_FACTORY = "initialContextFactory";
051    public static final String CONNECTION_URL = "connectionURL";
052    public static final String CONNECTION_USERNAME = "connectionUsername";
053    public static final String CONNECTION_PASSWORD = "connectionPassword";
054    public static final String CONNECTION_PROTOCOL = "connectionProtocol";
055    public static final String AUTHENTICATION = "authentication";
056
057    public static final String TOPIC_SEARCH_MATCHING = "topicSearchMatching";
058    public static final String TOPIC_SEARCH_SUBTREE = "topicSearchSubtree";
059    public static final String QUEUE_SEARCH_MATCHING = "queueSearchMatching";
060    public static final String QUEUE_SEARCH_SUBTREE = "queueSearchSubtree";
061
062    public static final String ADMIN_BASE = "adminBase";
063    public static final String ADMIN_ATTRIBUTE = "adminAttribute";
064    public static final String READ_BASE = "readBase";
065    public static final String READ_ATTRIBUTE = "readAttribute";
066    public static final String WRITE_BASE = "writeBAse";
067    public static final String WRITE_ATTRIBUTE = "writeAttribute";
068
069    private static final Logger LOG = LoggerFactory.getLogger(LDAPLoginModule.class);
070
071    private String initialContextFactory;
072    private String connectionURL;
073    private String connectionUsername;
074    private String connectionPassword;
075    private String connectionProtocol;
076    private String authentication;
077
078    private DirContext context;
079
080    private MessageFormat topicSearchMatchingFormat;
081    private MessageFormat queueSearchMatchingFormat;
082
083    private boolean topicSearchSubtreeBool = true;
084    private boolean queueSearchSubtreeBool = true;
085
086    private String adminBase;
087    private String adminAttribute;
088    private String readBase;
089    private String readAttribute;
090    private String writeBase;
091    private String writeAttribute;
092
093    public LDAPAuthorizationMap() {
094        // lets setup some sensible defaults
095        initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
096        connectionURL = "ldap://localhost:10389";
097        connectionUsername = "uid=admin,ou=system";
098        connectionPassword = "secret";
099        connectionProtocol = "s";
100        authentication = "simple";
101
102        topicSearchMatchingFormat = new MessageFormat("uid={0},ou=topics,ou=destinations,o=ActiveMQ,dc=example,dc=com");
103        queueSearchMatchingFormat = new MessageFormat("uid={0},ou=queues,ou=destinations,o=ActiveMQ,dc=example,dc=com");
104
105        adminBase = "(cn=admin)";
106        adminAttribute = "uniqueMember";
107        readBase = "(cn=read)";
108        readAttribute = "uniqueMember";
109        writeBase = "(cn=write)";
110        writeAttribute = "uniqueMember";
111    }
112
113    public LDAPAuthorizationMap(Map options) {
114        initialContextFactory = (String)options.get(INITIAL_CONTEXT_FACTORY);
115        connectionURL = (String)options.get(CONNECTION_URL);
116        connectionUsername = (String)options.get(CONNECTION_USERNAME);
117        connectionPassword = (String)options.get(CONNECTION_PASSWORD);
118        connectionProtocol = (String)options.get(CONNECTION_PROTOCOL);
119        authentication = (String)options.get(AUTHENTICATION);
120
121        adminBase = (String)options.get(ADMIN_BASE);
122        adminAttribute = (String)options.get(ADMIN_ATTRIBUTE);
123        readBase = (String)options.get(READ_BASE);
124        readAttribute = (String)options.get(READ_ATTRIBUTE);
125        writeBase = (String)options.get(WRITE_BASE);
126        writeAttribute = (String)options.get(WRITE_ATTRIBUTE);
127
128        String topicSearchMatching = (String)options.get(TOPIC_SEARCH_MATCHING);
129        String topicSearchSubtree = (String)options.get(TOPIC_SEARCH_SUBTREE);
130        String queueSearchMatching = (String)options.get(QUEUE_SEARCH_MATCHING);
131        String queueSearchSubtree = (String)options.get(QUEUE_SEARCH_SUBTREE);
132        topicSearchMatchingFormat = new MessageFormat(topicSearchMatching);
133        queueSearchMatchingFormat = new MessageFormat(queueSearchMatching);
134        topicSearchSubtreeBool = Boolean.valueOf(topicSearchSubtree).booleanValue();
135        queueSearchSubtreeBool = Boolean.valueOf(queueSearchSubtree).booleanValue();
136    }
137
138    public Set<GroupPrincipal> getTempDestinationAdminACLs() {
139        // TODO insert implementation
140        return null;
141    }
142
143    public Set<GroupPrincipal> getTempDestinationReadACLs() {
144        // TODO insert implementation
145        return null;
146    }
147
148    public Set<GroupPrincipal> getTempDestinationWriteACLs() {
149        // TODO insert implementation
150        return null;
151    }
152
153    public Set<GroupPrincipal> getAdminACLs(ActiveMQDestination destination) {
154        return getACLs(destination, adminBase, adminAttribute);
155    }
156
157    public Set<GroupPrincipal> getReadACLs(ActiveMQDestination destination) {
158        return getACLs(destination, readBase, readAttribute);
159    }
160
161    public Set<GroupPrincipal> getWriteACLs(ActiveMQDestination destination) {
162        return getACLs(destination, writeBase, writeAttribute);
163    }
164
165    // Properties
166    // -------------------------------------------------------------------------
167
168    public String getAdminAttribute() {
169        return adminAttribute;
170    }
171
172    public void setAdminAttribute(String adminAttribute) {
173        this.adminAttribute = adminAttribute;
174    }
175
176    public String getAdminBase() {
177        return adminBase;
178    }
179
180    public void setAdminBase(String adminBase) {
181        this.adminBase = adminBase;
182    }
183
184    public String getAuthentication() {
185        return authentication;
186    }
187
188    public void setAuthentication(String authentication) {
189        this.authentication = authentication;
190    }
191
192    public String getConnectionPassword() {
193        return connectionPassword;
194    }
195
196    public void setConnectionPassword(String connectionPassword) {
197        this.connectionPassword = connectionPassword;
198    }
199
200    public String getConnectionProtocol() {
201        return connectionProtocol;
202    }
203
204    public void setConnectionProtocol(String connectionProtocol) {
205        this.connectionProtocol = connectionProtocol;
206    }
207
208    public String getConnectionURL() {
209        return connectionURL;
210    }
211
212    public void setConnectionURL(String connectionURL) {
213        this.connectionURL = connectionURL;
214    }
215
216    public String getConnectionUsername() {
217        return connectionUsername;
218    }
219
220    public void setConnectionUsername(String connectionUsername) {
221        this.connectionUsername = connectionUsername;
222    }
223
224    public DirContext getContext() {
225        return context;
226    }
227
228    public void setContext(DirContext context) {
229        this.context = context;
230    }
231
232    public String getInitialContextFactory() {
233        return initialContextFactory;
234    }
235
236    public void setInitialContextFactory(String initialContextFactory) {
237        this.initialContextFactory = initialContextFactory;
238    }
239
240    public MessageFormat getQueueSearchMatchingFormat() {
241        return queueSearchMatchingFormat;
242    }
243
244    public void setQueueSearchMatchingFormat(MessageFormat queueSearchMatchingFormat) {
245        this.queueSearchMatchingFormat = queueSearchMatchingFormat;
246    }
247
248    public boolean isQueueSearchSubtreeBool() {
249        return queueSearchSubtreeBool;
250    }
251
252    public void setQueueSearchSubtreeBool(boolean queueSearchSubtreeBool) {
253        this.queueSearchSubtreeBool = queueSearchSubtreeBool;
254    }
255
256    public String getReadAttribute() {
257        return readAttribute;
258    }
259
260    public void setReadAttribute(String readAttribute) {
261        this.readAttribute = readAttribute;
262    }
263
264    public String getReadBase() {
265        return readBase;
266    }
267
268    public void setReadBase(String readBase) {
269        this.readBase = readBase;
270    }
271
272    public MessageFormat getTopicSearchMatchingFormat() {
273        return topicSearchMatchingFormat;
274    }
275
276    public void setTopicSearchMatchingFormat(MessageFormat topicSearchMatchingFormat) {
277        this.topicSearchMatchingFormat = topicSearchMatchingFormat;
278    }
279
280    public boolean isTopicSearchSubtreeBool() {
281        return topicSearchSubtreeBool;
282    }
283
284    public void setTopicSearchSubtreeBool(boolean topicSearchSubtreeBool) {
285        this.topicSearchSubtreeBool = topicSearchSubtreeBool;
286    }
287
288    public String getWriteAttribute() {
289        return writeAttribute;
290    }
291
292    public void setWriteAttribute(String writeAttribute) {
293        this.writeAttribute = writeAttribute;
294    }
295
296    public String getWriteBase() {
297        return writeBase;
298    }
299
300    public void setWriteBase(String writeBase) {
301        this.writeBase = writeBase;
302    }
303
304    // Implementation methods
305    // -------------------------------------------------------------------------
306    protected Set<GroupPrincipal> getACLs(ActiveMQDestination destination, String roleBase, String roleAttribute) {
307        try {
308            context = open();
309        } catch (NamingException e) {
310            LOG.error(e.toString());
311            return new HashSet<GroupPrincipal>();
312        }
313
314        // if ((destination.getDestinationType() &
315        // (ActiveMQDestination.QUEUE_TYPE | ActiveMQDestination.TOPIC_TYPE)) !=
316        // 0)
317        // return new HashSet();
318
319        String destinationBase = "";
320        SearchControls constraints = new SearchControls();
321
322        if ((destination.getDestinationType() & ActiveMQDestination.QUEUE_TYPE) == ActiveMQDestination.QUEUE_TYPE) {
323            destinationBase = queueSearchMatchingFormat.format(new String[] {destination.getPhysicalName()});
324            if (queueSearchSubtreeBool) {
325                constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
326            } else {
327                constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
328            }
329        }
330        if ((destination.getDestinationType() & ActiveMQDestination.TOPIC_TYPE) == ActiveMQDestination.TOPIC_TYPE) {
331            destinationBase = topicSearchMatchingFormat.format(new String[] {destination.getPhysicalName()});
332            if (topicSearchSubtreeBool) {
333                constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
334            } else {
335                constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
336            }
337        }
338
339        constraints.setReturningAttributes(new String[] {roleAttribute});
340
341        try {
342            Set<GroupPrincipal> roles = new HashSet<GroupPrincipal>();
343            Set<String> acls = new HashSet<String>();
344            NamingEnumeration results = context.search(destinationBase, roleBase, constraints);
345            while (results.hasMore()) {
346                SearchResult result = (SearchResult)results.next();
347                Attributes attrs = result.getAttributes();
348                if (attrs == null) {
349                    continue;
350                }
351                acls = addAttributeValues(roleAttribute, attrs, acls);
352            }
353            for (Iterator<String> iter = acls.iterator(); iter.hasNext();) {
354                String roleName = iter.next();
355                roles.add(new GroupPrincipal(roleName));
356            }
357            return roles;
358        } catch (NamingException e) {
359            LOG.error(e.toString());
360            return new HashSet<GroupPrincipal>();
361        }
362    }
363
364    protected Set<String> addAttributeValues(String attrId, Attributes attrs, Set<String> values) throws NamingException {
365        if (attrId == null || attrs == null) {
366            return values;
367        }
368        if (values == null) {
369            values = new HashSet<String>();
370        }
371        Attribute attr = attrs.get(attrId);
372        if (attr == null) {
373            return values;
374        }
375        NamingEnumeration e = attr.getAll();
376        while (e.hasMore()) {
377            String value = (String)e.next();
378            values.add(value);
379        }
380        return values;
381    }
382
383    protected DirContext open() throws NamingException {
384        if (context != null) {
385            return context;
386        }
387
388        try {
389            Hashtable<String, String> env = new Hashtable<String, String>();
390            env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
391            if (connectionUsername != null || !"".equals(connectionUsername)) {
392                env.put(Context.SECURITY_PRINCIPAL, connectionUsername);
393            }
394            if (connectionPassword != null || !"".equals(connectionPassword)) {
395                env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
396            }
397            env.put(Context.SECURITY_PROTOCOL, connectionProtocol);
398            env.put(Context.PROVIDER_URL, connectionURL);
399            env.put(Context.SECURITY_AUTHENTICATION, authentication);
400            context = new InitialDirContext(env);
401
402        } catch (NamingException e) {
403            LOG.error(e.toString());
404            throw e;
405        }
406        return context;
407    }
408
409}