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: AssignableDynamicGroup.java,v 1.6 2009/01/28 05:34:50 ww203982 Exp $
026 *
027 * Portions Copyrighted 2013-2015 ForgeRock AS.
028 */
029package com.iplanet.ums;
030
031import com.iplanet.services.ldap.Attr;
032import com.iplanet.services.ldap.AttrSet;
033import com.iplanet.services.util.I18n;
034import com.sun.identity.shared.debug.Debug;
035import org.forgerock.opendj.ldap.DN;
036import org.forgerock.opendj.ldap.Filter;
037import org.forgerock.opendj.ldap.LDAPUrl;
038import org.forgerock.opendj.ldap.ModificationType;
039import org.forgerock.opendj.ldap.SearchScope;
040
041/**
042 * Represents a dynamic group entry that uses memberOf as its filter. It checks
043 * whether the user is the member of the specified group
044 *
045 * @supported.api
046 */
047public class AssignableDynamicGroup extends DynamicGroup implements
048        IAssignableMembership {
049
050    private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
051
052    private static Debug debug;
053    static {
054        debug = Debug.getInstance(IUMSConstants.UMS_DEBUG);
055    }
056
057    /**
058     * Default constructor
059     *
060     * @supported.api
061     */
062    public AssignableDynamicGroup() {
063    }
064
065    /**
066     * Constructs an in memory AssignableDynamicGroup object. Default registered
067     * template will be used. This is an in memory Group object and one needs to
068     * call <code>save</code> method to save this newly created object to
069     * persistent storage.
070     * 
071     * @param attrSet Attribute/value set.
072     * @exception UMSException if fail to instantiate from persistent storage.
073     */
074    AssignableDynamicGroup(AttrSet attrSet) throws UMSException {
075        this(TemplateManager.getTemplateManager().getCreationTemplate(_class,
076                null), attrSet);
077    }
078
079    /**
080     * Constructs an in memory <code>AssignableDynamicGroup</code> object with
081     * a given template. This is an in memory Group object and one needs to
082     * call save method to <code>save</code> this newly created object to
083     * persistent storage.
084     * 
085     * @param template Template for creating a group.
086     * @param attrSet Attribute/value set.
087     * @exception UMSException if fail to instantiate from persistent storage.
088     *
089     * @supported.api
090     */
091    public AssignableDynamicGroup(CreationTemplate template, AttrSet attrSet)
092            throws UMSException {
093        super(template, attrSet);
094    }
095
096    /**
097     * Constructs an in memory <code>AssignableDynamicGroup</code> object using
098     * default registered for <code>AssignableDynamicGroup</code>. This is an
099     * in memory Group object and one needs to call <code>save</code> method to
100     * save this newly created object to persistent storage.
101     * 
102     * @param attrSet Attribute/value set, which should not contain
103     *        <code>memberUrl</code>; any values of <code>memberUrl</code> will
104     *        be overwritten by the explicit search criteria arguments.
105     * @param base Search base for evaluating members of the group.
106     * @param scope Search scope for evaluating members of the group the value
107     *        has to be <code>LDAPv2.SCOPE_ONE</code> or
108     *        <code>LDAPv2.SCOPE_SUB</code>.
109     * @exception UMSException if fail to instantiate from persistent storage.
110     */
111    AssignableDynamicGroup(AttrSet attrSet, Guid baseGuid, int scope)
112            throws UMSException {
113        this(TemplateManager.getTemplateManager().getCreationTemplate(_class,
114                null), attrSet, baseGuid, scope);
115    }
116
117    /**
118     * Constructs an <code>AssignableDynamicGroup</code> object with a given
119     * template. This is an in memory Group object and one needs to call
120     * <code>save</code> method to save this newly created object to
121     * persistent storage.
122     * 
123     * @param template Template for creating a group.
124     * @param attrSet Attribute-value set which should not contain member URL;
125     *        any values of member URL will be overwritten by the explicit
126     *        search criteria arguments.
127     * @param baseGuid Search base for evaluating members of the group
128     * @param scope Search scope for evaluating members of the group has to be
129     *        <code>LDAPv2.SCOPE_ONE</code> or <code>LDAPv2.SCOPE_SUB</code>.
130     * @exception UMSException if fail to instantiate from persistent storage
131     *
132     * @supported.api
133     */
134    public AssignableDynamicGroup(CreationTemplate template, AttrSet attrSet,
135            Guid baseGuid, int scope) throws UMSException {
136        super(template, attrSet);
137        // No host, port, or attributes in the URL
138        // setUrl( new LDAPUrl( null, 0, base, (String[])null, scope, "" ) );
139        setUrl(baseGuid, null, SearchScope.valueOf(scope));
140    }
141
142    /**
143     * Sets the search filter used to evaluate this dynamic group. For an 
144     * <code>AssignableDynamicGroup</code>, the filter is always
145     * <code>"memberof=THIS_DN"</code>, so this method should not generally be
146     * called outside the package.
147     * 
148     * @param filter Search filter for evaluating members of the group the
149     *        scope in the filter has to be <code>LDAPv2.SCOPE_ONE</code> or
150     *        <code>LDAPv2.SCOPE_SUB</code>.
151     *
152     * @supported.api
153     */
154    public void setSearchFilter(String filter) {
155        LDAPUrl url = getUrl();
156        SearchScope scope = url.getScope();
157        if (SearchScope.SINGLE_LEVEL.equals(scope) && SearchScope.WHOLE_SUBTREE.equals(scope)) {
158            String msg = i18n.getString(IUMSConstants.ILLEGAL_ADGROUP_SCOPE);
159            throw new IllegalArgumentException(msg);
160        }
161        Guid baseGuid = new Guid(url.getName().toString());
162        setUrl(baseGuid, Filter.valueOf(filter), scope);
163    }
164
165    /**
166     * Sets the GUID of the entity; used within the package.
167     * 
168     * @param guid GUID <code>REVIEW</code>: This method overloads the
169     *        <code>PersistentObject.setGuid()</code> method. Hence the
170     *        signature has to match, and we can't throw the
171     *        <code>UMSException</code> that could be thrown from
172     *        <code>"setSearchFilter"</code>. Is it enough to log such an
173     *        error ???
174     */
175    protected void setGuid(Guid guid) {
176        super.setGuid(guid);
177        // setSearchFilter( "(" + "memberof=" + getDN() + ")" );
178        try {
179            setSearchFilter("memberof=" + getDN());
180        } catch (Exception e) {
181            // TODO - Log Exception
182            if (debug.messageEnabled()) {
183                debug.message("AssignableDynamicGroup.setGuid() : "
184                        + "Exception : " + e.getMessage());
185            }
186        }
187    }
188
189    /**
190     * Adds a member to the group. The change is saved to persistent storage.
191     * 
192     * @param userGuid Globally unique identifier for the member to be added.
193     * @exception UMSException if fail to save to persistent storage or if the
194     *            user is not within the scope of the group.
195     *
196     * @supported.api
197     */
198    public void addMember(Guid userGuid) throws UMSException {
199        // UMSSession session = getUMSSession();
200        if (getPrincipal() == null) {
201            throw new IllegalArgumentException(i18n
202                    .getString(IUMSConstants.NULL_PRINCIPAL));
203        }
204
205        addMember(UMSObject.getObject(getPrincipal(), userGuid));
206    }
207
208    /**
209     * Adds a member to the group. The change is saved to persistent storage.
210     * 
211     * @param member Object to be added as member.
212     * @exception UMSException if fail to save to persistent storage or if the
213     *            user is not within the scope of the group.
214     *
215     * @supported.api
216     */
217    public void addMember(PersistentObject member) throws UMSException {
218        // check whether the userGuid is within the scope of memberUrl
219        DN userDN = DN.valueOf(member.getGuid().getDn());
220        LDAPUrl memberUrl = getUrl();
221        DN memberDN = memberUrl.getName();
222
223        if (!userDN.isInScopeOf(memberDN, SearchScope.WHOLE_SUBTREE)) {
224            String args[] = new String[2];
225            args[0] = userDN.toString();
226            args[1] = memberUrl.toString();
227            throw new UMSException(i18n.getString(IUMSConstants.USER_NOT_IN_GROUP_SCOPE, args));
228        } else if ((userDN.size() - memberDN.size()) > 1 && SearchScope.SINGLE_LEVEL.equals(memberUrl.getScope())) {
229            String args[] = new String[2];
230            args[0] = userDN.toString();
231            args[1] = memberUrl.toString();
232            throw new UMSException(i18n.getString(IUMSConstants.USER_NOT_IN_GROUP_SCOPE, args));
233        }
234        member.modify(new Attr(MEMBER_ATTR_NAME, this.getDN()), ModificationType.ADD);
235        member.save();
236    }
237
238    /**
239     * Adds a list of members to the group. The change is saved to persistent
240     * storage.
241     * 
242     * @param guids Array of member GUIDs to be added as members to the group.
243     * @exception UMSException if fail to save to persistent storage.
244     *
245     * @supported.api
246     */
247    public void addMembers(Guid[] guids) throws UMSException {
248        if (guids == null) {
249            throw new IllegalArgumentException(i18n
250                    .getString(IUMSConstants.NULL_GUIDS));
251        }
252        for (int i = 0; i < guids.length; i++) {
253            addMember(guids[i]);
254        }
255    }
256
257    /**
258     * Removes a member from the group. The change is saved to persistent
259     * storage.
260     * 
261     * @param guid Unique identifier for the member to be removed.
262     * @exception UMSException if fail to save to persistent storage.
263     *
264     * @supported.api
265     */
266    public void removeMember(Guid guid) throws UMSException {
267        PersistentObject member = UMSObject.getObject(getPrincipal(), guid);
268        removeMember(member);
269    }
270
271    /**
272     * Removes a member from the group. The change is saved to persistent
273     * storage.
274     * 
275     * @param member Object to be removed.
276     * @exception UMSException if fail to save to persistent storage.
277     *
278     * @supported.api
279     */
280    public void removeMember(PersistentObject member) throws UMSException {
281        member.modify(new Attr(MEMBER_ATTR_NAME, this.getDN()), ModificationType.DELETE);
282        member.save();
283    }
284
285    /**
286     * Removes all members of the group.
287     * 
288     * @exception UMSException if fail to save to persistent storage.
289     *
290     * @supported.api
291     */
292    public void removeAllMembers() throws UMSException {
293
294        String filter = getSearchFilter();
295        if (filter == null) {
296            return;
297        }
298        String[] attributesToGet = { "dn" };
299        SearchResults searchResults = getMemberIDs(attributesToGet);
300        while (searchResults.hasMoreElements()) {
301            PersistentObject member = searchResults.next();
302            member.setPrincipal(getPrincipal());
303            removeMember(member);
304        }
305    }
306
307    /**
308     * Returns <code>true</code> if a given identifier is a member of the
309     * group.
310     * 
311     * @param guid Identity of member to be checked for membership.
312     * @return <code>true</code> if it is a member.
313     * @exception UMSException if fail to read object for guid.
314     *
315     * @supported.api
316     */
317    public boolean hasMember(Guid guid) throws UMSException {
318        if (getPrincipal() == null) {
319            throw new IllegalArgumentException(i18n
320                    .getString(IUMSConstants.NULL_PRINCIPAL));
321        }
322        PersistentObject object = UMSObject.getObject(getPrincipal(), guid);
323        Attr attr = object.getAttribute(MEMBER_ATTR_NAME);
324        if (attr == null) {
325            if (debug.messageEnabled()) {
326                debug.message("AssignableDynamicGroup.hasMember: no "
327                        + "attribute " + MEMBER_ATTR_NAME + " in "
328                        + guid.getDn());
329            }
330            return false;
331        }
332
333        // need to normalize DN to escape spaces and such
334        // for accurate checking of membership
335        // TODO: This ties guids to DNS. The methods to normalize and compare
336        // should be managed separately.
337        // TODO: The members should have been normalized before adding to
338        // the group (i.e. when creating or modifying it), so it should not
339        // be necessary to have normalizing code spread out in the classes
340        // and methods.
341        String normalized = getGuid().getDn();
342        String[] members = attr.getStringValues();
343        for (int i = 0; i < members.length; i++) {
344            String target = members[i];
345            if (debug.messageEnabled()) {
346                debug.message("AssignableDynamicGroup.hasMember: comparing "
347                        + normalized + " to " + target);
348            }
349            if (Guid.equals(normalized, target)) {
350                return true;
351            }
352        }
353
354        return false;
355    }
356
357    /**
358     * Saves the modification(s) to the object to persistent storage.
359     * 
360     * @return UMSException on failure to save to persistent storage.
361     */
362    /*
363     * public void save () throws UMSException { String filter =
364     * getSearchFilter(); if ( (filter == null) || (filter.length() < 1) ) {
365     * setSearchFilter( "memberof=" + getDN() ); } super.save(); }
366     */
367
368    private static final String MEMBER_ATTR_NAME = "memberof";
369
370    private static final Class _class = new AssignableDynamicGroup().getClass();
371}