001/**
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: DomainComponentTree.java,v 1.5 2009/01/28 05:34:51 ww203982 Exp $
026 *
027 */
028
029package com.iplanet.ums.dctree;
030
031import java.util.Hashtable;
032import java.util.StringTokenizer;
033
034import com.sun.identity.shared.ldap.LDAPDN;
035
036import com.iplanet.services.util.I18n;
037import com.iplanet.sso.SSOException;
038import com.iplanet.sso.SSOToken;
039import com.iplanet.sso.SSOTokenManager;
040import com.iplanet.ums.EntryNotFoundException;
041import com.iplanet.ums.Guid;
042import com.iplanet.ums.IUMSConstants;
043import com.iplanet.ums.PersistentObject;
044import com.iplanet.ums.SearchResults;
045import com.iplanet.ums.UMSException;
046import com.iplanet.ums.UMSObject;
047import com.iplanet.ums.User;
048
049/**
050 * Represents the domain component index tree (dctree). A domain component tree
051 * is used to represent virtual domains as used in DNS. DCTree composes of a
052 * hiearchical tree of domain components (dc) and each dc node may/maynot
053 * associate with a organizational DIT (convergence tree as noted in nortel
054 * spec). Sample of a dctree that starts at dcroot of "o=internet" will look
055 * like this
056 * <p>
057 * 
058 * <pre>
059 *               o=internet
060 *                   |
061 *              ------------------------------
062 *             |             |                |
063 *           dc=com        dc=net           dc=edu
064 *              |
065 *         ---------
066 *         |        |
067 *       dc=sun    dc=iplanet
068 *         |            |
069 *       dc=eng       dc=red
070 * </pre>
071 * 
072 * DomainComponentTree allows the user to create a dc tree capturing virtual
073 * domain names in a network (hosted or enterprise) environment with each low
074 * level dc node being mapped to an organizational DIT.
075 * 
076 * @see DomainComponent
077 * @supported.api
078 */
079public class DomainComponentTree {
080
081    private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
082
083    /**
084     * Default constructor
085     */
086    public DomainComponentTree() {
087    }
088
089    /**
090     * Constructs a <code>DomainComponentTree</code> with an authenticated
091     * prinicipal and an identification of the root of the dc index tree.
092     * 
093     * @param token Single sign on token of authenticated principal with
094     *        priviledge for accessing the domain component index tree (dctree).
095     * @param dcRoot Identification of root, a DN, of the dc tree such as
096     *        <code>o=internet</code>.
097     * @throws InvalidDCRootException if invalid root specification.
098     * @throws UMSException if other read error occurred.
099     * @supported.api
100     */
101    public DomainComponentTree(SSOToken token, Guid dcRoot)
102            throws InvalidDCRootException, UMSException {
103        if (token == null)
104            throw new IllegalArgumentException(i18n
105                    .getString(IUMSConstants.NULL_TOKEN));
106        try {
107            SSOTokenManager.getInstance().validateToken(token);
108        } catch (SSOException se) {
109            throw new UMSException(i18n.getString(IUMSConstants.INVALID_TOKEN),
110                    se);
111        }
112
113        setSSOToken(token);
114
115        try {
116            setDCRoot(dcRoot);
117        } catch (EntryNotFoundException e) {
118            throw new InvalidDCRootException(dcRoot.getDn(), e.getRootCause());
119        }
120
121    }
122
123    /**
124     * Sets the authenticated principal used for access to directory server
125     * 
126     * @param ssotoken
127     *            SSO token for authenticated user
128     */
129    void setSSOToken(SSOToken token) throws UMSException {
130        try {
131            SSOTokenManager.getInstance().validateToken(token);
132        } catch (SSOException se) {
133            throw new UMSException(i18n.getString(IUMSConstants.INVALID_TOKEN),
134                    se);
135        }
136        m_token = token;
137    }
138
139    /**
140     * Gets the authenticated Principal used to contruct the dctree instance
141     * 
142     * @return authenticated principal that is associated with dctree
143     *         construction
144     * 
145     * java.security.Principal getPrincipal() { if (token) return
146     * token.getPrincipal(); else return null; }
147     */
148
149    /**
150     * Gets the SSOToken used to contruct the dctree instance
151     * 
152     * @return SSOToken that is associated with dctree construction
153     */
154    SSOToken getSSOToken() {
155        return m_token;
156    }
157
158    /**
159     * Sets the root of the domain component tree (dc
160     * tree). Needs an established authenticated principal before setting
161     * dcroot.
162     * 
163     * @param root
164     *            Identification of the root of the tree such as o=internet
165     * @supported.api
166     */
167    public void setDCRoot(Guid root) throws UMSException {
168        SSOToken token = getSSOToken();
169        try {
170            SSOTokenManager.getInstance().validateToken(token);
171        } catch (SSOException se) {
172            throw new UMSException(i18n.getString(IUMSConstants.INVALID_TOKEN),
173                    se);
174        }
175
176        if (token != null) {
177            m_dcRoot = UMSObject.getObject(token, root);
178        }
179    }
180
181    /**
182     * Gets the root of the domain component tree (dc
183     * tree)
184     * 
185     * @return PersistentObject representing the dctree root in the dctree DIT
186     * @supported.api
187     */
188    public PersistentObject getDCRoot() {
189        return m_dcRoot;
190    }
191
192    /**
193     * Add a virtual domain into the domain component
194     * tree.
195     * 
196     * @param domain
197     *            Fully qualified domain name
198     * @return Domain Componet entry just added to the dctree
199     * @throws InvalidDCRootException
200     *             if dcroot is not defined
201     * @throws UMSException
202     *             for write problem in adding domain to dctree
203     * @supported.api
204     */
205    public DomainComponent addDomain(String domain) throws UMSException {
206        if (domain == null || domain.length() == 0) {
207            throw new IllegalArgumentException();
208        }
209
210        if (m_dcRoot == null) {
211            throw new InvalidDCRootException();
212        }
213
214        StringTokenizer st = new StringTokenizer(domain, ".");
215
216        int nDoms = st.countTokens();
217        String[] doms = new String[nDoms];
218
219        int i = 0;
220        while (st.hasMoreElements()) {
221            doms[i++] = st.nextToken();
222        }
223
224        PersistentObject parent = UMSObject.getObject(getSSOToken(), m_dcRoot
225                .getGuid());
226
227        // Going from right to left on the virtual domain name
228        // to go through all the domain components (dc). Make sure that
229        // all dc entries are created in the dctree
230        // e.g. adding a domain for eng.sun.com with dcRoot being o=internet
231        // will yield
232        // <pre>
233        // o=internet (assmumed to exist)
234        // dc=com,o=internet (created)
235        // dc=sun,dc=com,o=ineternet (created)
236        // dc=eng,dc=sun,dc=com,o=internet (created)
237        // </pre>
238        // in the domain component tree
239        //
240        DomainComponent dc = null;
241        for (i = 0; i < nDoms; i++) {
242
243            SearchResults results = parent.getChildren("dc="
244                    + doms[nDoms - i - 1], null);
245
246            try {
247                dc = (DomainComponent) results.assertOneEntry();
248            } catch (EntryNotFoundException e) {
249                dc = new DomainComponent(getSSOToken(), doms[nDoms - i - 1]);
250                parent.addChild(dc);
251            }
252
253            parent = UMSObject.getObject(getSSOToken(), dc.getGuid());
254        }
255
256        return dc;
257    }
258
259    /**
260     * Remove a virtual domain in the dctree
261     * 
262     * @param domain
263     *            Virtual domain name to be removed
264     * @throws UMSException
265     *             upon failure to remove the corresponding dc entry in the
266     *             dctree
267     * 
268     * @supported.api
269     */
270    public void removeDomain(String domain) throws UMSException {
271        if (m_dcRoot == null)
272            return;
273
274        DomainComponent dc = getDomainComponent(domain);
275        m_dcRoot.removeChild(dc);
276    }
277
278    /**
279     * Set the domain mapping so that the dc entry maps to
280     * an organization in the the organization DIT hosting user data (the
281     * convergence tree in Nortel spec)
282     * 
283     * @param domain
284     *            Fully qualified domain name
285     * @param org
286     *            Organization entry to be mapped from dctree to organization
287     *            DIT (the convergence tree in nortel spec)
288     * @throws DomainNotFoundException
289     *             if domain id not defined
290     * @throws UMSException
291     *             upon write failure
292     * @supported.api
293     */
294    public void setDomainMapping(String domain, PersistentObject org)
295            throws UMSException {
296        setDomainMapping(domain, org.getGuid());
297    }
298
299    /**
300     * Set the domain mapping so that the dc entry maps to
301     * an organization in the convergence tree.
302     * 
303     * @param domain Virtual domain name.
304     * @param orgGuid Identifiication of Organization entry to be mapped from
305     *        dctree to organization DIT (the convergence tree in nortel spec).
306     * @throws UMSException if write failed.
307     * @supported.api
308     */
309    public void setDomainMapping(String domain, Guid orgGuid)
310            throws UMSException {
311        DomainComponent dc = getDomainComponent(domain);
312        dc.setAssociatedOrganization(orgGuid);
313    }
314
315    /**
316     * Sets the domain status for a given virtual domain
317     * 
318     * @param domain
319     *            Virtual domain name
320     * @param status
321     *            Domain status to be set
322     * @throws DomainNotFoundException
323     *             if domain is not found in dctree
324     * @throws UMSException
325     *             upon write failure
326     * 
327     * @supported.api
328     */
329    public void setDomainStatus(String domain, String status)
330            throws DomainNotFoundException, UMSException {
331        DomainComponent dc = getDomainComponent(domain);
332        dc.setDomainStatus(status);
333    }
334
335    /**
336     * Gets the domain status of a given virtual domain
337     * 
338     * @param domain
339     *            Virtual domain name
340     * @return Domain status for the given domain
341     * @throws DomainNotFoundException
342     *             if domain not found in dctree
343     * @throws UMSException
344     *             upon read failure
345     * @supported.api
346     */
347    public String getDomainStatus(String domain)
348            throws DomainNotFoundException, UMSException {
349        DomainComponent dc = getDomainComponent(domain);
350        return dc.getDomainStatus();
351    }
352
353    /**
354     * Given a fully qualified domain name, maps it to the
355     * corresponding DN in the DCtree
356     * 
357     * @param domain
358     *            Fully qualified domain name
359     * @return String representation of the Distinguished Name in the DC Tree
360     * @supported.api
361     */
362    public String mapDomainToDN(String domain) {
363        StringTokenizer st = new StringTokenizer(domain, ".");
364        String dn = new String();
365
366        while (st.hasMoreElements()) {
367            dn = dn + "dc=" + st.nextToken() + ",";
368        }
369
370        dn = dn + getDCRoot().getDN();
371        return dn;
372    }
373
374    /**
375     * Given a virtual domain name such as
376     * "javasoft.sun.com", returns the domain component entry in the dc index
377     * tree. This entry lives under dc index tree and one can use the dc entry
378     * to get to the organization assoicated with the dc tree
379     * 
380     * @param domain
381     *            Virtual domain name such as "javasoft.sun.com"
382     * @return Domain componet entry representing the virtual domain in the
383     *         domain component tree
384     * @throws DomainNotFoundException
385     *             if given domain is not found in the dctree
386     * @throws UMSException
387     *             upon read error
388     * @supported.api
389     */
390    public DomainComponent getDomainComponent(String domain)
391            throws DomainNotFoundException, UMSException {
392
393        String dn = mapDomainToDN(domain);
394
395        try {
396            DomainComponent dc = (DomainComponent) UMSObject.getObject(
397                    getSSOToken(), new Guid(dn));
398            dc.setSSOToken(getSSOToken());
399            return dc;
400        } catch (EntryNotFoundException e) {
401            throw new DomainNotFoundException(domain, e.getRootCause());
402        }
403
404    }
405
406    /**
407     * Given a virtual domain name such as
408     * "javasoft.sun.com", return the organization, organizationalunit or any
409     * DIT entry that is assoicated from the domain compoent tree (dctree) to
410     * the customer oranization DIT (the convergence tree as outlined in nortel
411     * spec)
412     * 
413     * @param domain
414     *            Fully qualified virtual domain name
415     * @return Entry referred in the dc tree.
416     * @throws DomainNotFoundException
417     *             if domain is not found
418     * @throws UMSException
419     *             for reading problem in instantiating the mapped organization
420     * @supported.api
421     */
422    public PersistentObject getOrganization(String domain)
423            throws DomainNotFoundException, UMSException {
424        DomainComponent dc = getDomainComponent(domain);
425        return dc.getOrganization();
426    }
427
428    /**
429     * Given a uid for a user, lookup the user under a
430     * specified virtual domain name. For example,
431     * 
432     * <pre>
433     * DomainComponentTree dctree = new DomainComponentTree(ctx, 
434     *                                      &quot;red.iplanet.com&quot;);
435     * 
436     * User user = dctree.getUser(&quot;hman&quot;, 
437     *                                      &quot;red.iplanet.com&quot;);
438     * </pre>
439     * 
440     * @param uid
441     *            User id for the entry to be searched
442     * @param domain
443     *            Fully qualified domain name such as "red.iplanet.com"
444     * @return User object found
445     * @throws DomainNotFoundException
446     *             if domain is not found
447     * @throws UMSException
448     *             upon failure in instantiating the user object
449     * @supported.api
450     */
451    public User getUser(String uid, String domain)
452            throws DomainNotFoundException, UMSException {
453        return getUser("uid", uid, domain);
454    }
455
456    /**
457     * Given identification of a user with a naming
458     * attribute and value, lookup the user under a virtual domain specified.
459     * For example,
460     * 
461     * <pre>
462     * DomainComponentTree dctree = new DomainComponentTree(ctx,
463     *                                           &quot;red.iplanet.com&quot;);
464     * 
465     * User user = dctree.getUser(&quot;cn&quot;, &quot;Hin Man&quot;, 
466     *                                             &quot;red.iplanet.com&quot;);
467     * </pre>
468     * 
469     * @param namingAttribute
470     *            Naming attribute for the user object such as "uid" or "mail".
471     *            The naming attribute has to provide a unique identifier for
472     *            the user.
473     * @param value
474     *            attribute value for the naming attribute
475     * @param domain
476     *            Fully qualified domain name such as "red.iplanet.com"
477     * @return User object if found
478     * @throws DomainNotFoundException
479     *             if domain is not found
480     * @throws UMSException
481     *             upon failure in instantiating the user object
482     * @supported.api
483     */
484    public User getUser(String namingAttribute, String value, String domain)
485            throws DomainNotFoundException, UMSException {
486        PersistentObject orgEntry = getOrganization(domain);
487
488        SearchResults result = orgEntry.search(namingAttribute + "=" + value,
489                null);
490
491        return (User) result.assertOneEntry();
492    }
493
494    /**
495     * Given a domain componet in a dctree, maps it to a
496     * virtual domain name
497     * 
498     * @param dc
499     *            A domain component that lives in the dctree
500     * @return Fully qualified domain name
501     * @supported.api
502     */
503    public String mapDCToDomainName(DomainComponent dc) {
504
505        if (m_dcRoot == null)
506            return null;
507
508        String rootDN = LDAPDN.normalize(m_dcRoot.getDN());
509        String dcDN = LDAPDN.normalize(dc.getDN());
510
511        // Skip the dcRoot part of the DN for the given domain component.
512        // Find the position of the rootDN in dcDN
513        //
514        int end = dcDN.indexOf("," + rootDN);
515
516        // TODO: Kind of kludgy, need to revisit
517        //
518        dcDN = dcDN.substring(0, end);
519
520        String[] doms = LDAPDN.explodeDN(dcDN, true);
521        String domainName = doms[0];
522
523        // Compose the fully qualified domain name with the "." character
524        //
525        for (int i = 1; i < doms.length; i++) {
526            domainName = domainName + "." + doms[i];
527        }
528
529        return domainName;
530    }
531
532    /**
533     * Get all virtual domains present in the dctree.
534     * Construct a hashtable of the found domain names and their associated
535     * organization in the customer organizational DIT (the convergence tree)
536     * <p>
537     * 
538     * This function can be used as a cache function for the complete DCTree.
539     * The returning hastable provides all the virtual domain name as keys that
540     * maps to organization mapping linked in the domain component dc nodes
541     * <p>
542     * 
543     * @return Hashtable of domain names and associated organizations. Each
544     *         domain name is associated with one organization but muliple
545     *         domain names can map to the same organization in the customer
546     *         DIT.
547     * @throws UMSException
548     *             upon failure in searching all mapped domains
549     * @supported.api
550     */
551    public Hashtable getChildDomainIDs() throws UMSException {
552
553        if (m_dcRoot == null) {
554            return null;
555        }
556
557        // Search in DCTree that have a link to a mapped organization
558        //
559        SearchResults results = m_dcRoot.search(
560                "(&(objectclass=inetDomain)(inetDomainBaseDN=*))", null);
561
562        Hashtable domains = new Hashtable();
563
564        // For each domain found, recapture the domain name from the DC hiearchy
565        // such as
566        // dc=red,dc=iplanet,dc=com -> red.iplanet.com
567        // dc=eng,dc=sun,dc=com -> eng.sun.com
568        // and put in the hashtable the corresponding organization mapped from
569        // dctree to customer organization DIT (the convergence tree in nortel
570        // spec)
571        //
572        while (results.hasMoreElements()) {
573            DomainComponent dc = (DomainComponent) results.next();
574            String domainName = mapDCToDomainName(dc);
575            domains.put(domainName, dc.getAssociatedOrganizationGuid().getDn());
576        }
577
578        return domains;
579    }
580
581    private PersistentObject m_dcRoot = null;
582
583    private SSOToken m_token = null;
584}