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