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: COSManager.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.cos;
031
032import com.iplanet.services.ldap.Attr;
033import com.iplanet.services.ldap.AttrSet;
034import com.iplanet.services.util.I18n;
035import com.iplanet.sso.SSOException;
036import com.iplanet.sso.SSOToken;
037import com.iplanet.sso.SSOTokenManager;
038import com.iplanet.ums.Guid;
039import com.iplanet.ums.IUMSConstants;
040import com.iplanet.ums.PersistentObject;
041import com.iplanet.ums.SchemaManager;
042import com.iplanet.ums.SearchResults;
043import com.iplanet.ums.UMSException;
044import com.iplanet.ums.UMSObject;
045import java.security.Principal;
046import java.util.AbstractCollection;
047import java.util.ArrayList;
048import java.util.Arrays;
049import java.util.Collection;
050import java.util.Enumeration;
051import java.util.HashSet;
052import java.util.StringTokenizer;
053import org.forgerock.opendj.ldap.DN;
054import org.forgerock.opendj.ldap.LdapException;
055import org.forgerock.opendj.ldap.Modification;
056import org.forgerock.opendj.ldap.ModificationType;
057import org.forgerock.opendj.ldap.ResultCode;
058import org.forgerock.opendj.ldap.SearchScope;
059
060/**
061 * This class has the responsibility of adding, removing and replacing COS
062 * definitions. It also provides search capabilities for COS definitions.
063 * @supported.api
064 */
065public class COSManager {
066
067    /**
068     * This constructor sets the parent Directory entry which identifies the
069     * location of COS definitions which will be managed. It also gets an
070     * instance of a SchemaManager which will be used to update schema entries
071     * for COS assignments.
072     * 
073     * @param token Authenticated principal's single sign on token.
074     * @param guid The unique identifier specifying where COS definitions will
075     *        be managed.
076     * @throws UMSException if the token authentication fails, or if
077     *         the guid for the parent entry is not valid.
078     */
079    protected COSManager(SSOToken token, Guid guid) throws UMSException {
080        try {
081            SSOTokenManager.getInstance().validateToken(token);
082        } catch (SSOException se) {
083            throw new UMSException(i18n.getString(IUMSConstants.INVALID_TOKEN),
084                    se);
085        }
086        _parentObject = UMSObject.getObject(token, guid);
087        try {
088            _schemaManager = SchemaManager.getSchemaManager(token
089                    .getPrincipal());
090        } catch (SSOException se) {
091            throw new UMSException("Bad Authentication Token "
092                    + se.getMessage());
093        }
094    }
095
096    /**
097     * This constructor sets the parent Directory entry which identifies the
098     * location of COS definitions which will be managed. It also gets an
099     * instance of a SchemaManager which will be used to update schema entries
100     * for COS assignments.
101     * 
102     * @param principal
103     *            Authenticated principal
104     * @param guid
105     *            The unique identifier specifying where COS definitions will be
106     *            managed.
107     * 
108     * @throws UMSException
109     *             The exception thrown if there is a problem determining the
110     *             parent entry, or getting the SchemaManager instance.
111     */
112    protected COSManager(Principal principal, Guid guid) throws UMSException {
113        _parentObject = UMSObject.getObject(principal, guid);
114        _schemaManager = SchemaManager.getSchemaManager(principal);
115    }
116
117    /**
118     * This method returns an instance of a COS Manager.
119     * 
120     * @param token Authenticated principal's single sign on token.
121     * @param guid COS definitions will be managed under the level identified by
122     *        this guid.
123     * @throws UMSException
124     *             The exception thrown from the COSManager constructor.
125     * @supported.api
126     */
127    public static COSManager getCOSManager(SSOToken token, Guid guid)
128            throws UMSException {
129        return new COSManager(token, guid);
130    }
131
132    /**
133     * This method returns an instance of a COS Manager.
134     * 
135     * @param principal Authenticated principal.
136     * @param guid COS definitions will be managed under the level identified by
137     *        this guid.
138     * @throws UMSException
139     *             The exception thrown from the data layer.
140     */
141    public static COSManager getCOSManager(Principal principal, Guid guid)
142            throws UMSException {
143        return new COSManager(principal, guid);
144    }
145
146    /**
147     * This method adds a COS definition to the persistent store. The definition
148     * is added under the specified "guid" parameter.
149     * 
150     * @param cosDef
151     *            The COS definition to be added.
152     * 
153     * @throws UMSException
154     *             The exception thrown from the data layer.
155     * @supported.api
156     */
157    public void addDefinition(ICOSDefinition cosDef) throws UMSException {
158        if (!(cosDef instanceof DirectCOSDefinition)) {
159            String msg = i18n.getString(IUMSConstants.INVALID_COSDEFINITION);
160            throw new UMSException(msg);
161        }
162        String[] cosAttributes = cosDef.getCOSAttributes();
163        AbstractCollection aList = (AbstractCollection) Arrays
164                .asList(ICOSDefinition.qualifiers);
165        for (int i = 0; i < cosAttributes.length; i++) {
166            String cosAttribute = null;
167            String qualifier = null;
168            StringTokenizer st = new StringTokenizer(cosAttributes[i]);
169            if (st.hasMoreTokens()) {
170                cosAttribute = st.nextToken();
171            }
172            if (cosAttribute == null) {
173                String msg = i18n.getString(
174                        IUMSConstants.INVALID_COS_ATTRIBUTE_QUALIFIER);
175                throw new UMSException(msg);
176            }
177            if (st.hasMoreTokens())
178                qualifier = st.nextToken();
179            if (qualifier == null) {
180                qualifier = ICOSDefinition.qualifiers[ICOSDefinition.DEFAULT];
181                cosDef.removeCOSAttribute(cosAttribute);
182                cosDef.addCOSAttribute(cosAttribute, ICOSDefinition.DEFAULT);
183            }
184            if (!aList.contains(qualifier)) {
185                String msg = i18n.getString(
186                        IUMSConstants.INVALID_COS_ATTRIBUTE_QUALIFIER);
187                throw new UMSException(msg);
188            }
189        }
190        PersistentObject po = (PersistentObject) cosDef;
191        _parentObject.addChild(po);
192    }
193
194    /**
195     * Removes the COS definition.
196     * 
197     * @param name
198     *            The name of the definition to be removed.
199     * 
200     * @throws UMSException
201     *             The exception thrown from the data layer.
202     * @supported.api
203     */
204    public void removeDefinition(String name) throws UMSException {
205        Guid guid = new Guid(ICOSDefinition.DEFAULT_NAMING_ATTR + "=" + name
206                + "," + _parentObject.getGuid().getDn());
207        _parentObject.removeChild(guid);
208    }
209
210    /**
211     * Updates the contents of a COS definition with the new contents. The COS
212     * definition must already exist in the persistent layer, before its
213     * contents can be replaced.
214     * 
215     * @param cosDef
216     *            The COS definition containing new contents, which will replace
217     *            the same definition in the persistent layer.
218     * 
219     * @throws UMSException
220     *             The exception thrown from the data layer.
221     * @supported.api
222     */
223    public void updateDefinition(ICOSDefinition cosDef) throws UMSException {
224        PersistentObject pObject = (PersistentObject) cosDef;
225        if (pObject.getGuid() == null) {
226            String msg = i18n
227                    .getString(IUMSConstants.REPLACE_DEFINITION_NOT_PERSISTENT);
228            throw new UMSException(msg);
229        }
230        pObject.save();
231    }
232
233    /**
234     * Returns COS definition given the name.
235     * 
236     * @param name Name of the COS definition.
237     * @return A COS definition with the specified name.
238     * @throws UMSException if exception occurred at the data layer.
239     * @throws COSNotFoundException if the COS object is not found.
240     * @supported.api
241     */
242    public ICOSDefinition getDefinition(String name) throws UMSException,
243            COSNotFoundException {
244        ICOSDefinition cosDef = null;
245        SearchResults sr = _parentObject.getChildren(
246                ICOSDefinition.COSSUPERDEF_NAME_SEARCH + name + ")",
247                DEF_ATTRIBUTE_NAMES, null);
248        while (sr.hasMoreElements()) {
249            cosDef = (ICOSDefinition) sr.next();
250            if (cosDef.getName().equals(name)) {
251                break;
252            } else {
253                cosDef = null;
254            }
255        }
256        if (cosDef == null) {
257            String msg = i18n.getString(IUMSConstants.COS_DEFINITION_NOT_FOUND);
258            throw new COSNotFoundException(msg);
259        }
260        sr.abandon();
261        return cosDef;
262    }
263
264    /**
265     * Retrieves all COS definitions for the current organization. This
266     * COSManager instance applies to an organization.
267     * 
268     * @return A collection of COS definition objects.
269     * 
270     * @throws UMSException
271     *             The exception thrown from the data layer.
272     * @supported.api
273     */
274    public Collection getDefinitions() throws UMSException {
275        Collection cosDefinitions = new ArrayList();
276        SearchResults sr = _parentObject.search(
277                ICOSDefinition.COSSUPERDEF_SEARCH, DEF_ATTRIBUTE_NAMES, null);
278        while (sr.hasMoreElements()) {
279            cosDefinitions.add(sr.next());
280        }
281        return cosDefinitions;
282    }
283
284    /**
285     * Assigns a COS (as defined by a COS definition) to the persistent object.
286     * The COS target persistent object could be a user, group, organization,
287     * organizationalunit, etc. The COS target object must be persistent before
288     * this method can be used.
289     * 
290     * @param pObject
291     *            The COS target persistent object.
292     * @param cosDef
293     *            A COS definition.
294     * @param cosTemplate
295     *            A COS template. This only applies for COS and Indirect COS
296     *            definitions. For pointer COS definitions, this parameter can
297     *            be null.
298     * 
299     * @throws UMSException
300     *             If a data layer exception occurs.
301     * @supported.api
302     */
303    public void assignCOSDef(PersistentObject pObject, ICOSDefinition cosDef,
304            COSTemplate cosTemplate) throws UMSException {
305        if (pObject == null || cosDef == null) {
306            String msg = i18n
307                    .getString(IUMSConstants.COS_DEF_OR_TARGET_OBJECT_NULL);
308            throw new UMSException(msg);
309        }
310
311        // Do validation....
312        //
313        if (pObject.getGuid() == null) {
314            String msg = i18n
315                    .getString(IUMSConstants.COS_TARGET_OBJECT_NOT_PERSISTENT);
316            throw new UMSException(msg);
317        }
318
319        if (!(cosDef instanceof DirectCOSDefinition)) {
320            String msg = i18n.getString(IUMSConstants.INVALID_COSDEFINITION);
321            throw new UMSException(msg);
322        }
323
324        if (cosDef instanceof DirectCOSDefinition) {
325            assignDirectCOSDef(pObject, (DirectCOSDefinition) cosDef,
326                    cosTemplate, _schemaManager);
327        }
328    }
329
330    /**
331     * Removes COS assignment from the persistent object. The COS target
332     * persistent object could be a user, group, organization,
333     * organizationalunit, etc. The COS target object must be persistent before
334     * this method can be used.
335     * 
336     * @param pObject
337     *            The COS target persistent object.
338     * @param cosDef
339     *            A COS definition.
340     * @param cosTemplate
341     *            A COS template.
342     * 
343     * @throws UMSException
344     *             The exception thrown if any of the following occur: o the
345     *             target persistent object or COS definition parameter is null.
346     *             o the target object is not persistent. o the COS definition
347     *             is not one of the valid COS definitions. o an exception is
348     *             propagated from any of the "remove" methods.
349     * @supported.api
350     */
351    public void removeCOSAssignment(PersistentObject pObject,
352            ICOSDefinition cosDef, COSTemplate cosTemplate) throws UMSException 
353            {
354        if (pObject == null || cosDef == null) {
355            String msg = i18n
356                    .getString(IUMSConstants.COS_DEF_OR_TARGET_OBJECT_NULL);
357            throw new UMSException(msg);
358        }
359
360        // Do validation....
361        //
362        if (pObject.getGuid() == null) {
363            String msg = i18n
364                    .getString(IUMSConstants.COS_TARGET_OBJECT_NOT_PERSISTENT);
365            throw new UMSException(msg);
366        }
367
368        if (!(cosDef instanceof DirectCOSDefinition)) {
369            String msg = i18n.getString(IUMSConstants.INVALID_COSDEFINITION);
370            throw new UMSException(msg);
371        }
372
373        if (cosDef instanceof DirectCOSDefinition) {
374            removeDirectCOSAssignment(pObject, (DirectCOSDefinition) cosDef,
375                    cosTemplate, _schemaManager);
376        }
377    }
378
379    /**
380     * Removes a Direct COS assignment from a target persistent object. The COS
381     * target persistent object could be a user, group, organization,
382     * organizationalunit, etc. The COS target object must be persistent before
383     * this method can be used.
384     * 
385     * @param pObject
386     *            The COS target persistent object.
387     * @param cosDef
388     *            A COS definition.
389     * @param sMgr
390     *            A SchemaManager object, which is used to determine object
391     *            classes for attributes.
392     * 
393     * @throws UMSException
394     *             The exception thrown if any of the following occur: o an
395     *             exception occurs determining the object class for the COS
396     *             specifier. o an exception occurs determining the object class
397     *             for the COS attributes. o there is an exception thrown rom
398     *             the data layer.
399     */
400    private void removeDirectCOSAssignment(PersistentObject pObject,
401            DirectCOSDefinition cosDef, COSTemplate cosTemplate,
402            SchemaManager sMgr) throws UMSException {
403        ArrayList aList;
404        AttrSet attrSet = new AttrSet();
405
406        try {
407            // Include the attribute (whose name is the cosSpecifier)
408            // in the attribute set for removal (only if it exists).
409            //
410            if (pObject.getAttribute(cosDef.getCOSSpecifier()) != null)
411                attrSet.add(new Attr(cosDef.getCOSSpecifier(), cosTemplate
412                        .getName()));
413
414            // Get cosSpecifier object class - should only be one.
415            // Include the cosSpecifier object class in the attribute
416            // set for removal (only if itt exists).
417            //
418            aList = (ArrayList) sMgr.getObjectClasses(cosDef.getCOSSpecifier());
419            String cosSpecObjectClass = (String) aList.get(0);
420            if (objectClassExists(cosSpecObjectClass, pObject)) {
421                attrSet.add(new Attr("objectclass", cosSpecObjectClass));
422            }
423
424            // Get the cos attributes from the definition (ex. mailquota).
425            // For each of the attributes, get the objectclass. Include the
426            // object classes in the attribute set for removal (if they exist).
427            //
428            String[] cosAttributes = cosDef.getCOSAttributes();
429            String cosAttribute = null;
430            for (int i = 0; i < cosAttributes.length; i++) {
431                // Only get the attribute - not the qualifier
432                //
433                StringTokenizer st = new StringTokenizer(cosAttributes[i]);
434                cosAttribute = st.nextToken();
435                aList = (ArrayList) sMgr.getObjectClasses(cosAttribute);
436                String cosAttributeObjectClass = (String) aList.get(0);
437                if (objectClassExists(cosAttributeObjectClass, pObject)) {
438                    attrSet
439                            .add(new Attr("objectclass",
440                                    cosAttributeObjectClass));
441                }
442            }
443
444            if (attrSet.size() > 0) {
445                pObject.modify(toModifications(ModificationType.DELETE, attrSet));
446                pObject.save();
447            }
448        } catch (UMSException e) {
449            LdapException le = (LdapException) e.getRootCause();
450            // Ignore anything that is not a COS generated attribute's object class
451            if (!ResultCode.OBJECTCLASS_VIOLATION.equals(le.getResult().getResultCode())) {
452                throw e;
453            }
454        }
455    }
456
457    /**
458     * Assigns a direct (Classic) COS definition to a persistent object.
459     * 
460     * @param pObject
461     *            The target persistent object.
462     * @param cosDef
463     *            The direct (Classic) COS definition.
464     * @param cosTemplate
465     *            A COS template belonging to the definition.
466     * @param sMgr
467     *            A SchemaManager instance.
468     * 
469     * @throws UMSException
470     *             if an exception occurs
471     */
472    private void assignDirectCOSDef(PersistentObject pObject,
473            DirectCOSDefinition cosDef, COSTemplate cosTemplate,
474            SchemaManager sMgr) throws UMSException {
475
476        // Do validation....
477        //
478        if (cosDef.getGuid() == null) {
479            String msg = i18n
480                    .getString(IUMSConstants.COS_DEFINITION_NOT_PERSISTENT);
481            throw new UMSException(msg);
482        }
483
484        // Make sure target entry is in same tree as COS Def parent.
485        //
486        DN targetDN = DN.valueOf(pObject.getGuid().getDn());
487        DN cosParentDN = DN.valueOf(cosDef.getParentGuid().getDn());
488        if (!(targetDN.isInScopeOf(cosParentDN, SearchScope.SUBORDINATES))) {
489            String msg = i18n
490                    .getString(IUMSConstants.COS_TARGET_OBJECT_DIFFERENT_TREE);
491            throw new UMSException(msg);
492        }
493
494        // If cosSpecifier is "nsRole", then we don't need to go
495        // any further (we don't need to update target entries).
496        //
497        if (cosDef.getCOSSpecifier().equalsIgnoreCase("nsrole"))
498            return;
499
500        ArrayList aList;
501        AttrSet attrSet = new AttrSet();
502
503        // Get cosSpecifier object class - should only be one.
504        // Update the target entry with cosSpecifier object class.
505        // Only add it if it doesn't already exist.
506        //
507        aList = (ArrayList) sMgr.getObjectClasses(cosDef.getCOSSpecifier());
508        String cosSpecObjectClass = (String) aList.get(0);
509        if (!objectClassExists(cosSpecObjectClass, pObject)) {
510            attrSet.add(new Attr("objectclass", cosSpecObjectClass));
511        }
512
513        // Get the cos attributes from the definition (ex. mailquota).
514        // For each of the attributes, get the objectclass. These
515        // will be used to attach to the target entry. This is only
516        // done if the cos attribute qualifier is not "operational"
517        // (you don't need to add cos attribute object classes for
518        // "operational" cos attribute qualifier.
519        //
520        String[] cosAttributes = cosDef.getCOSAttributes();
521        String qualifier = null;
522        Arrays.asList(ICOSDefinition.qualifiers);
523        Attr attr = cosTemplate.getAttribute("objectclass");
524        String[] cosTempObjClasses = attr.getStringValues();
525
526        for (int i = 0; i < cosAttributes.length; i++) {
527            StringTokenizer st = new StringTokenizer(cosAttributes[i]);
528            st.nextToken();
529            qualifier = st.nextToken();
530            if ((!qualifier.equals(
531                    ICOSDefinition.qualifiers[ICOSDefinition.OPERATIONAL]))) {
532                for (int j = 0; j < cosTempObjClasses.length; j++) {
533                    if (!cosTempObjClasses[j].equalsIgnoreCase("top")
534                       && !cosTempObjClasses[j].equalsIgnoreCase("costemplate")
535                       && !objectClassExists(cosTempObjClasses[j], pObject)) 
536                    {
537                        if (!attrSet.contains("objectclass",
538                                cosTempObjClasses[j])) {
539                            attrSet.add(new Attr("objectclass",
540                                    cosTempObjClasses[j]));
541                        }
542                    }
543                }
544            }
545        }
546
547        // Add the attribute name (cosSpecifier value) and attribute
548        // value (cosTemplate name) only if it doesn't exist.
549        //
550        if (pObject.getAttribute(cosDef.getCOSSpecifier()) == null)
551            attrSet.add(new Attr(cosDef.getCOSSpecifier(), cosTemplate
552                    .getName()));
553
554        if (attrSet.size() > 0) {
555            pObject.modify(toModifications(ModificationType.ADD, attrSet));
556            pObject.save();
557        }
558    }
559
560    private Collection<Modification> toModifications(ModificationType type, AttrSet attrSet) {
561        Collection<Modification> modifications = new HashSet<>();
562        Enumeration<Attr> attributes = attrSet.getAttributes();
563        while (attributes.hasMoreElements()) {
564            modifications.add(new Modification(type, attributes.nextElement().toLDAPAttribute()));
565        }
566        return modifications;
567    }
568
569    /**
570     * Utility method to check if an object class exists in a persistent object.
571     * 
572     * @param objectClass
573     *            The object class.
574     * @param pObject
575     *            The persistent object.
576     */
577    private boolean objectClassExists(String objectClass,
578            PersistentObject pObject) {
579        Attr attr = pObject.getAttribute("objectclass");
580        String[] vals = attr.getStringValues();
581        for (int i = 0; i < vals.length; i++) {
582            if (objectClass.equalsIgnoreCase(vals[i])) {
583                return true;
584            }
585        }
586        return false;
587    }
588
589    //
590    // Definition Search Attributes
591    //
592    private static final String[] DEF_ATTRIBUTE_NAMES = { "objectclass",
593            ICOSDefinition.DEFAULT_NAMING_ATTR, ICOSDefinition.COSTEMPLATEDN,
594            ICOSDefinition.COSSPECIFIER, ICOSDefinition.COSATTRIBUTE,
595            ICOSDefinition.ICOSSPECIFIER };
596
597    private PersistentObject _parentObject;
598
599    private SchemaManager _schemaManager;
600
601    private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
602}