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: StaticGroup.java,v 1.4 2009/01/28 05:34:51 ww203982 Exp $
026 *
027 * Portions Copyright 2015 ForgeRock AS.
028 */
029
030package com.iplanet.ums;
031
032import java.security.Principal;
033import java.util.Collections;
034import java.util.Iterator;
035
036import com.iplanet.services.ldap.Attr;
037import com.iplanet.services.ldap.AttrSet;
038import com.iplanet.services.util.I18n;
039import org.forgerock.opendj.ldap.Attribute;
040import org.forgerock.opendj.ldap.Attributes;
041import org.forgerock.opendj.ldap.ByteString;
042import org.forgerock.opendj.ldap.DN;
043import org.forgerock.opendj.ldap.Modification;
044import org.forgerock.opendj.ldap.ModificationType;
045
046/**
047 * Represents a static group entry.
048 * @supported.api
049 */
050public class StaticGroup extends PersistentObject implements
051        IAssignableMembership {
052
053    /**
054     * Level indicator for no nesting of group membership. Use this level
055     * indicator for getting direct membership in a group.
056     * 
057     * @supported.api
058     */
059    public static final int LEVEL_DIRECT = 0;
060
061    /**
062     * Level indicator for expanding nested membership to the fullest. Use this
063     * level indicator in getting all direct and indirect members through nested
064     * group behavior.
065     * 
066     * @supported.api
067     */
068    public static final int LEVEL_ALL = -1;
069
070    /**
071     * Internal maximum level used if no default is found in configuration.
072     */
073    static final int DEFAULT_MAX = 5;
074
075    private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
076
077    /**
078     * Default constructor
079     */
080    protected StaticGroup() {
081    }
082
083    /**
084     * Constructs a group object from an ID by reading from persistent storage.
085     * 
086     * @param session
087     *            Authenticated session
088     * @param guid
089     *            Globally unique identifier for the group entry
090     * @exception UMSException
091     *                on failure to instantiate from persistent storage
092     * @deprecated
093     */
094    StaticGroup(Principal principal, Guid guid) throws UMSException {
095        super(principal, guid);
096        verifyClass();
097    }
098
099    /**
100     * Constructs a group object in memory using the default registered template
101     * for StaticGroup. This is an in-memory representation of a new StaticGroup
102     * object; the save method must be called to save the new object to
103     * persistent storage.
104     * 
105     * @param attrSet
106     *            Attribute/value set
107     * @exception UMSException
108     *                on failure to instantiate from persistent storage
109     */
110    StaticGroup(AttrSet attrSet) throws UMSException {
111        this(TemplateManager.getTemplateManager().getCreationTemplate(_class,
112                null), attrSet);
113    }
114
115    /**
116     * Constructs a StaticGroup object in memory with
117     * a given template. This one simply creates a Group object in memory; the
118     * save method must be called to save the new object to persistent storage.
119     * 
120     * @param template
121     *            Template for creating a group
122     * @param attrSet
123     *            Attribute/value set
124     * @exception UMSException
125     *                on failure to instantiate from persistent storage
126     * @supported.api
127     */
128    public StaticGroup(CreationTemplate template, AttrSet attrSet)
129            throws UMSException {
130        super(template, attrSet);
131    }
132
133    /**
134     * Adds a member to the group. The change is saved to
135     * persistent storage.
136     * 
137     * @param guid
138     *            Globally unique identifier for the member to be added
139     * @exception UMSException
140     *                on failure to save to persistent storage
141     * @supported.api
142     */
143    public void addMember(Guid guid) throws UMSException {
144
145        String id = guid.getDn();
146
147        PersistentObject entry = null;
148
149        try {
150            entry = UMSObject.getObject(getPrincipal(), guid);
151        } catch (UMSException ignore) {
152        }
153
154        if (entry != null && entry instanceof StaticGroup) {
155            StaticGroup g = (StaticGroup) entry;
156            if (id.equalsIgnoreCase(getDN())
157                    || g.hasMember(getGuid(), LEVEL_ALL)) {
158                throw new UMSException(i18n
159                        .getString(IUMSConstants.NO_RECURSION_ALLOW));
160            }
161        }
162
163        modify(new Attr(MEMBER_ATTR_NAME, id), ModificationType.ADD);
164        save();
165    }
166
167    /**
168     * Adds a member to the group. The change is saved to
169     * persistent storage.
170     * 
171     * @param member
172     *            Object to be added as member
173     * @exception UMSException
174     *                on failure to save to persistent storage
175     * @supported.api
176     */
177    public void addMember(PersistentObject member) throws UMSException {
178        addMember(member.getGuid());
179    }
180
181    /**
182     * Adds a list of members to the group. The change is
183     * saved to persistent storage.
184     * 
185     * @param guids
186     *            Array of member guids to be added as members to the group
187     * @exception UMSException
188     *                on failure to save to persistent storage
189     * @supported.api
190     */
191    public void addMembers(Guid[] guids) throws UMSException {
192        if (guids == null) {
193            String msg = i18n.getString(IUMSConstants.BAD_GUID);
194            throw new IllegalArgumentException(msg);
195        }
196
197        for (int i = 0; i < guids.length; i++) {
198            addMember(guids[i]);
199        }
200    }
201
202    /**
203     * Gets the members of the group.
204     * 
205     * @return SearchResults for members of the group
206     * @exception Not
207     *                thrown by this class
208     * @supported.api
209     */
210    public SearchResults getMemberIDs() throws UMSException {
211        return getMembers(LEVEL_DIRECT);
212    }
213
214    static int getMaxNestingLevel() {
215        // PKB: Get it from the dsConfig manager
216        // TO FIX
217        return DEFAULT_MAX;
218    }
219
220    /**
221     * Get members of the group.
222     * 
223     * @param level
224     *            Nesting level
225     * @return SearchResults for members of the group
226     * @exception Not
227     *                thrown by this class
228     * @supported.api
229     * 
230     */
231    public SearchResults getMembers(int level) throws UMSException {
232        Attr attr = getAttribute(MEMBER_ATTR_NAME);
233        if (attr == null) {
234            return null;
235        }
236
237        if (level == LEVEL_ALL) {
238            level = getMaxNestingLevel();
239        }
240
241        if (level == LEVEL_DIRECT) {
242            return new SearchResults(getAttribute(MEMBER_ATTR_NAME));
243        }
244
245        Attr nestedMembers = new Attr(MEMBER_ATTR_NAME);
246        Attribute la = attr.toLDAPAttribute();
247        Iterator<ByteString> iterator = la.iterator();
248
249        while (iterator.hasNext()) {
250            String memberdn = iterator.next().toString();
251            PersistentObject entry = null;
252
253            try {
254                // entry = getUMSSession().getObject(new Guid(memberdn));
255                entry = UMSObject.getObject(getPrincipal(), new Guid(memberdn));
256            } catch (UMSException ignore) {
257            }
258
259            if (entry != null && entry instanceof StaticGroup) {
260                SearchResults r = ((StaticGroup) entry).getMembers(level - 1);
261
262                while (r.hasMoreElements()) {
263                    PersistentObject member = null;
264
265                    try {
266                        member = r.next();
267                        nestedMembers.addValue(member.getDN());
268                    } catch (UMSException ignore) {
269                    }
270                }
271            } else {
272                nestedMembers.addValue(memberdn);
273            }
274
275            entry = null;
276        }
277
278        return new SearchResults(nestedMembers);
279    }
280
281    /**
282     * Gets the member count.
283     * 
284     * @return Number of members of the group
285     * @exception Not
286     *                thrown by this class
287     * @supported.api
288     */
289    public int getMemberCount() throws UMSException {
290        return getMemberCount(LEVEL_DIRECT);
291    }
292
293    /**
294     * Gets the member count.
295     * 
296     * @param level
297     *            Nesting level
298     * @return Number of members of the group
299     * @exception Not
300     *                thrown by this class
301     * @supported.api
302     */
303    public int getMemberCount(int level) throws UMSException {
304
305        if (level == LEVEL_ALL) {
306            level = getMaxNestingLevel();
307        }
308
309        if (level == LEVEL_DIRECT) {
310            Attr attr = getAttribute(MEMBER_ATTR_NAME);
311            return (attr != null) ? attr.size() : 0;
312        }
313
314        SearchResults allMembers = getMembers(level);
315
316        if (allMembers == null)
317            return 0;
318        int count = 0;
319        while (allMembers.hasMoreElements()) {
320            allMembers.next();
321            count++;
322        }
323        return count;
324    }
325
326    /**
327     * Gets a member given an index (zero-based).
328     * 
329     * @param index
330     *            Zero-based index into the group container
331     * @return The unique identifier for a member
332     * @exception Not
333     *                thrown by this class
334     * @supported.api
335     */
336    public Guid getMemberIDAt(int index) throws UMSException {
337        Attr attr = getAttribute(MEMBER_ATTR_NAME);
338        String value = attr.getStringValues()[index];
339        return (value != null) ? new Guid(value) : null;
340    }
341
342    /**
343     * Gets a member given an index (zero-based).
344     * 
345     * @param index
346     *            Zero-based index into the group container
347     * @param level
348     *            Nesting level
349     * @return The unique identifier for a member
350     * @exception Not
351     *                thrown by this class
352     * @supported.api
353     */
354    public Guid getMemberIDAt(int index, int level) throws UMSException {
355        SearchResults allMembers = getMembers(level);
356        if (allMembers == null) {
357            return null;
358        }
359
360        int i = 0;
361        while (allMembers.hasMoreElements()) {
362            PersistentObject entry = allMembers.next();
363            if (i++ == index) {
364                return new Guid(entry.getDN());
365            }
366        }
367        return null;
368    }
369
370    /**
371     * Removes a member from the group. The change is saved to persistent
372     * storage.
373     * 
374     * @param guid
375     *            Unique identifier for the member to be removed
376     * @exception UMSException
377     *                on failure to save to persistent storage
378     * @supported.api
379     */
380    public void removeMember(Guid guid) throws UMSException {
381        String dn = guid.getDn();
382        super.modify(new Attr(MEMBER_ATTR_NAME, dn), ModificationType.DELETE);
383        save();
384    }
385
386    /**
387     * Removes a member from the group. The change is saved to persistent
388     * storage.
389     * 
390     * @param member
391     *            Object to be removed
392     * @exception UMSException
393     *                on failure to save to persistent storage
394     * @supported.api
395     */
396    public void removeMember(PersistentObject member) throws UMSException {
397        removeMember(member.getGuid());
398    }
399
400    /**
401     * Removes all members of the group.
402     * 
403     * @exception UMSException
404     *                on failure to save to persistent storage
405     * @supported.api
406     */
407    public void removeAllMembers() throws UMSException {
408
409        if (getMemberCount() == 0) {
410            return;
411        }
412
413        // TODO: this should probably be REPLACE instead of DELETE, so it works even if there are no members
414        modify(Collections.singleton(
415                new Modification(ModificationType.DELETE, Attributes.emptyAttribute(MEMBER_ATTR_NAME))));
416        save();
417    }
418
419    /**
420     * Checks if a given identifier is a member of the group.
421     * 
422     * @param guid
423     *            Identity of member to be checked for membership
424     * @return <code>true if it is a member
425     * @exception   Not thrown by this class
426     * @supported.api
427     */
428    public boolean hasMember(Guid guid) throws UMSException {
429        return isMemberAtLevel(guid.getDn(), LEVEL_DIRECT);
430    }
431
432    private boolean isMemberAtLevel(String normalizedID, int level)
433            throws UMSException {
434
435        if (level == LEVEL_ALL) {
436            level = getMaxNestingLevel();
437        }
438
439        SearchResults members = getMembers(level);
440
441        while (members.hasMoreElements()) {
442            PersistentObject entry = members.next();
443            String entryDN = entry.getDN();
444            if (Guid.equals(normalizedID, entryDN)) {
445                return true;
446            }
447        }
448
449        return false;
450    }
451
452    /**
453     * Checks if a given identifier is a member of the group.
454     * 
455     * @param guid
456     *            Identity of member to be checked for membership
457     * @param level
458     *            Nesting level
459     * @return <code>true</code> if it is a member
460     * @exception Not
461     *                thrown by this class
462     * @supported.api
463     */
464    public boolean hasMember(Guid guid, int level) throws UMSException {
465
466        if (level == LEVEL_ALL) {
467            level = getMaxNestingLevel();
468        }
469
470        String id = guid.getDn();
471
472        for (int i = LEVEL_DIRECT; i <= level; i++) {
473            if (isMemberAtLevel(id, i)) {
474                return true;
475            }
476        }
477        return false;
478    }
479
480    private static final String MEMBER_ATTR_NAME = "uniquemember";
481
482    private static final Class _class = new StaticGroup().getClass();
483}