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: DynamicGroup.java,v 1.6 2009/01/28 05:34:50 ww203982 Exp $
026 *
027 */
028
029/**
030 * Portions Copyrighted [2011] [ForgeRock AS]
031 */
032package com.iplanet.ums;
033
034import com.iplanet.services.ldap.Attr;
035import com.iplanet.services.ldap.AttrSet;
036import com.iplanet.services.ldap.ModSet;
037import com.iplanet.services.util.I18n;
038import com.sun.identity.shared.debug.Debug;
039import com.sun.identity.shared.encode.URLEncDec;
040import java.security.Principal;
041import com.sun.identity.shared.ldap.LDAPDN;
042import com.sun.identity.shared.ldap.LDAPUrl;
043import com.sun.identity.shared.ldap.LDAPv2;
044
045/**
046 * Represents a dynamic group entry.
047 *
048 * @supported.api
049 */
050public class DynamicGroup extends PersistentObject implements
051        IDynamicMembership {
052
053    private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
054
055    private static Debug debug;
056    static {
057        debug = Debug.getInstance(IUMSConstants.UMS_DEBUG);
058    }
059
060    /**
061     * Default constructor.
062     */
063    protected DynamicGroup() {
064    }
065
066    /**
067     * Constructs a group object from an ID by reading from persistent storage.
068     * 
069     * @param session Authenticated session.
070     * @param guid globally unique identifier for the group entry.
071     * @exception UMSException if fail to instantiate from persistent storage.
072     * @deprecated
073     */
074    DynamicGroup(Principal principal, Guid guid) throws UMSException {
075        super(principal, guid);
076        verifyClass();
077    }
078
079    /**
080     * Constructs a <code>DynamicGroup</code> in memory using the default
081     * registered template for <code>DynamicGroup</code>. This is an
082     * in-memory representation of a new object and one needs to call the
083     * <code>save</code> method to save this new object to persistent storage.
084     * 
085     * @param attrSet Attribute/value set, which should contain
086     *        <code>memberUrl</code>.
087     * @exception UMSException if fail to instantiate from persistent storage.
088     */
089    DynamicGroup(AttrSet attrSet) throws UMSException {
090        this(TemplateManager.getTemplateManager().getCreationTemplate(_class,
091                null), attrSet);
092    }
093
094    /**
095     * Constructs a <code>DynamicGroup</code> in memory with a given template
096     * for <code>DynamicGroup</code>. This is an in-memory representation of a
097     * new object; the <code>save</code> method must be called to save this
098     * new object to persistent storage.
099     * 
100     * @param template Template for creating a group.
101     * @param attrSet Attribute/value set, which should contain
102     *        <code>memberUrl</code>.
103     * @exception UMSException if fail to instantiate from persistent storage.
104     *
105     * @supported.api
106     */
107    public DynamicGroup(CreationTemplate template, AttrSet attrSet)
108            throws UMSException {
109        super(template, attrSet);
110    }
111
112    /**
113     * Constructs a <code>DynamicGroup</code> in memory using the default
114     * registered template for <code>DynamicGroup</code>. This is an in memory
115     * representation of a new object and the <code>save</code> method must be
116     * called to save this new object to persistent storage.
117     * 
118     * @param attrSet Attribute/value set, which should not contain
119     *        <code>memberUrl</code>; any values of <code>memberUrl</code> will
120     *        be overwritten by the explicit search criteria arguments.
121     * @param base Search base for evaluating members of the group.
122     * @param filter Search filter for evaluating members of the group.
123     * @param scope Search scope for evaluating members of the group.
124     * @exception UMSException if fail to instantiate from persistent storage.
125     */
126    DynamicGroup(AttrSet attrSet, Guid baseGuid, String filter, int scope)
127            throws UMSException {
128        this(TemplateManager.getTemplateManager().getCreationTemplate(_class,
129                null), attrSet, baseGuid, filter, scope);
130    }
131
132    /**
133     * Constructs a <code>DynamicGroup</code> in memory given a template for
134     * <code>DynamicGroup</code>. This is an in-memory representation of a new
135     * object and the <code>save</code> method must be called to save this new
136     * object to persistent storage.
137     * 
138     * @param template Template for creating a group.
139     * @param attrSet Attribute/value set, which should not contain member Url;
140     *        any values of memberUrl will be overwritten by the explicit search
141     *        criteria arguments.
142     * @param baseGuid Search base for evaluating members of the group.
143     * @param filter Search filter for evaluating members of the group.
144     * @param scope Search scope for evaluating members of the group has to be
145     *        <code>LDAPv2.SCOPE_ONE</code> or <code>LDAPv2.SCOPE_SUB</code>.
146     * 
147     * @exception UMSException if fail to instantiate from persistent storage.
148     *
149     * @supported.api
150     */
151    public DynamicGroup(
152        CreationTemplate template,
153        AttrSet attrSet,
154        Guid baseGuid,
155        String filter,
156        int scope
157    ) throws UMSException {
158        super(template, attrSet);
159        try {
160            setUrl(baseGuid, filter, scope);
161        } catch (Exception e) {
162            // TODO - Log Exception
163            debug.error("DynamicGroup : Exception : " + e.getMessage());
164        }
165    }
166
167    /**
168     * Sets the search filter used to evaluate this dynamic group.
169     * 
170     * @param filter Search filter for evaluating members of the group.
171     *
172     * @supported.api
173     */
174    public void setSearchFilter(String filter) {
175        LDAPUrl url = getUrl();
176        int scope = url.getScope();
177
178        Guid baseGuid = new Guid(url.getDN());
179        try {
180            setUrl(baseGuid, filter, scope);
181        } catch (Exception e) {
182            // TODO - Log Exception
183            debug.error("DynamicGroup.setSearchFilter : Exception : "
184                    + e.getMessage());
185        }
186    }
187
188    /**
189     * Returns the search filter used to evaluate this dynamic group.
190     * 
191     * @return Search filter for evaluating members of the group the scope in
192     *         the filter has to be <code>LDAPv2.SCOPE_ONE</code> or
193     *         <code>LDAPv2.SCOPE_SUB</code>.
194     *
195     * @supported.api
196     */
197    public String getSearchFilter() {
198        return getUrl().getFilter();
199    }
200
201    /**
202     * Sets the search base used to evaluate this dynamic group.
203     * 
204     * @param baseGuid Search base for evaluating members of the group.
205     *
206     * @supported.api
207     */
208    public void setSearchBase(Guid baseGuid) {
209        LDAPUrl url = getUrl();
210        int scope = url.getScope();
211        String filter = url.getFilter();
212        try {
213            setUrl(baseGuid, filter, scope);
214        } catch (Exception e) {
215            // TODO - Log Exception
216            debug.error("DynamicGroup.setSearchFilter : Exception : "
217                    + e.getMessage());
218        }
219    }
220
221    /**
222     * Returns the search base used to evaluate this dynamic group.
223     * 
224     * @return Search base for evaluating members of the group.
225     *
226     * @supported.api
227     */
228    public Guid getSearchBase() {
229        return new Guid(getUrl().getDN());
230    }
231
232    /**
233     * Sets the search scope used to evaluate this dynamic group.
234     * 
235     * @param scope Search scope for evaluating members of the group. Use one of
236     *        the search scope <code>SCOPE_BASE</code>,
237     *        <code>SCOPE_ONE</code>, or <code>SCOPE_SUB</code>.
238     *
239     * @supported.api
240     */
241    public void setSearchScope(int scope) {
242        LDAPUrl url = getUrl();
243        Guid baseGuid = new Guid(url.getDN());
244        String filter = url.getFilter();
245        try {
246            setUrl(baseGuid, filter, scope);
247        } catch (Exception e) {
248            // TODO - Log Exception
249            debug.error("DynamicGroup.setSearchFilter : Exception : "
250                    + e.getMessage());
251        }
252    }
253
254    /**
255     * Returns the search scope used to evaluate this dynamic group.
256     * 
257     * @return Search scope for evaluating members of the group.
258     *
259     * @supported.api
260     */
261    public int getSearchScope() {
262        return getUrl().getScope();
263    }
264
265    /**
266     * Convert the given parameters into an LDAP URL string. No LDAP host, port,
267     * and attribute to return are present in the LDAP URL. Only search base,
268     * filter and scope are given.
269     * 
270     * @param base Search Base DN in the LDAP URL.
271     * @param filter Search filter in LDAP URL.
272     * @param scope Search scope in LDAP URL.
273     * @return LDAP URL.
274     */
275    protected String toUrlStr(String base, String filter, int scope) {
276        StringBuilder urlBuf = new StringBuilder();
277        urlBuf.append("ldap:///").append(base).append("?");
278
279        switch (scope) {
280        case LDAPv2.SCOPE_BASE:
281            urlBuf.append("?base");
282            break;
283        case LDAPv2.SCOPE_ONE:
284            urlBuf.append("?one");
285            break;
286        default:
287        case LDAPv2.SCOPE_SUB:
288            urlBuf.append("?sub");
289            break;
290        }
291
292        if (filter != null && filter.length() > 0) {
293            urlBuf.append("?").append(filter);
294        } else {
295            urlBuf.append("?");
296        }
297
298        return urlBuf.toString();
299    }
300
301    /**
302     * Creates a new search definition; the change is not persistent until
303     * save() is called.
304     * 
305     * @param baseGuid Search base for evaluating members of the group.
306     * @param filter Search filter for evaluating members of the group.
307     * @param scope Search scope for evaluating members of the group.
308     */
309    protected void setUrl(Guid baseGuid, String filter, int scope) {
310        // Only valid scope is "sub" and "one"
311        //
312        if (scope != LDAPv2.SCOPE_ONE && scope != LDAPv2.SCOPE_SUB) {
313            String msg = i18n.getString(IUMSConstants.ILLEGAL_GROUP_SCOPE);
314            throw new IllegalArgumentException(msg);
315        }
316
317        String urlStr = toUrlStr(baseGuid.getDn(), filter, scope);
318
319        // Sanity check on the url
320        //
321        try {
322            new LDAPUrl(urlStr);
323        } catch (java.net.MalformedURLException e) {
324            throw new IllegalArgumentException(e.getMessage());
325        }
326
327        // TODO: Need to support multiple values of memberUrl? If so, do
328        // an ADD instead of a replace.
329        //
330        modify(new Attr(MEMBER_URL_NAME, urlStr), ModSet.REPLACE);
331    }
332
333    /**
334     * Returns the native LDAP URL used to evaluate this dynamic group.
335     * 
336     * @return LDAP URL for evaluating members of the group
337     */
338    protected LDAPUrl getUrl() {
339        Attr attr = getAttribute(MEMBER_URL_NAME);
340        LDAPUrl url = null;
341        try {
342            // TODO: Need to support multiple values of memberUrl?
343            if ((attr != null) && (attr.getStringValues().length > 0)) {
344
345                // Converting the url string to
346                // application/x-www-form-urlencoded as expected by
347                // LDAPUrl constructor.
348                url = new LDAPUrl(URLEncDec.encodeLDAPUrl(attr
349                        .getStringValues()[0]));
350            }
351            if (url == null) {
352                url = new LDAPUrl(null, 0, "", (String[]) null,
353                        LDAPv2.SCOPE_ONE, "");
354            }
355        } catch (java.net.MalformedURLException ex) {
356            debug.error("DynamicGroup.setSearchFilter : Exception : "
357                    + ex.getMessage());
358            throw new IllegalArgumentException(ex.getMessage());
359        }
360        return url;
361    }
362
363    /**
364     * Sets the native LDAP URL used to evaluate this dynamic group.
365     * 
366     * @param url LDAP URL for evaluating members of the group search scope in
367     *        the url has to be <code>LDAPv2.SCOPE_ONE</code> or
368     *        <code>LDAPv2.SCOPE_SUB</code>.
369     */
370    protected void setUrl(LDAPUrl url) {
371        String ldapurl = url.toString();
372        try {
373            ldapurl = LDAPUrl.decode(ldapurl);
374        } catch (Exception ex) {
375            if (debug.messageEnabled()) {
376                debug.message("DynamicGroup.setUrl : " +
377                        "Exception:" + ex.getMessage());
378            }
379        }
380        if (url.getScope() != LDAPv2.SCOPE_ONE
381                && url.getScope() != LDAPv2.SCOPE_SUB) {
382            String msg = i18n.getString(IUMSConstants.ILLEGAL_GROUP_SCOPE);
383            throw new IllegalArgumentException(msg);
384        }
385        // TODO: Need to support multiple values of memberUrl? If so, do
386        // an ADD instead of a replace.
387        modify(new Attr(MEMBER_URL_NAME, ldapurl), ModSet.REPLACE);
388        // modify( new Attr( MEMBER_URL_NAME, url.toString() ), ModSet.ADD );
389    }
390
391    /**
392     * Returns the members of the group.
393     * 
394     * @param attributes Attributes to return.
395     * @return Iterator for unique identifiers for members of the group.
396     * @exception UMSException if fail to search.
397     */
398    protected SearchResults getMemberIDs(String[] attributes)
399            throws UMSException {
400        return DataLayer.getInstance().search(getPrincipal(), getSearchBase(),
401                getSearchScope(), getSearchFilter(), attributes, false, null);
402    }
403
404    /**
405     * Returns the members of the group.
406     * 
407     * @return Iterator for unique identifiers for members of the group.
408     * @exception UMSException if fail to search.
409     *
410     * @supported.api
411     */
412    public SearchResults getMemberIDs() throws UMSException {
413        String[] attributesToGet = { "objectclass" };
414        return getMemberIDs(attributesToGet);
415    }
416
417    /**
418     * Returns the member count.
419     * 
420     * @return Number of members of the group.
421     * @exception UMSException if fail to search.
422     *
423     * @supported.api
424     */
425    public int getMemberCount() throws UMSException {
426        int count = 0;
427        String[] attributesToGet = { "dn" };
428        SearchResults searchResults = getMemberIDs(attributesToGet);
429        while (searchResults.hasMoreElements()) {
430            searchResults.next().getDN();
431            count++;
432        }
433        return count;
434    }
435
436    /**
437     * Returns a member given an index (zero-based).
438     * 
439     * @param index Zero-based index into the group container.
440     * @return Unique identifier for a member.
441     * @exception UMSException if fail to search.
442     *
443     * @supported.api
444     */
445    public Guid getMemberIDAt(int index) throws UMSException {
446        if (index < 0) {
447            throw new IllegalArgumentException(Integer.toString(index));
448        }
449        String filter = getSearchFilter();
450        if (filter == null) {
451            return null;
452        }
453        String[] attributesToGet = { "dn" };
454        SearchResults searchResults = getMemberIDs(attributesToGet);
455        while (searchResults.hasMoreElements()) {
456            String s = searchResults.next().getDN();
457            if (index == 0) {
458                searchResults.abandon();
459                return new Guid(s);
460            }
461            index--;
462        }
463        throw new ArrayIndexOutOfBoundsException(Integer.toString(index));
464    }
465
466    /**
467     * Returns <code>true</code> if a given identifier is a member of the
468     * group.
469     * 
470     * @param guid Identity of member to be checked for membership.
471     * @return <code>true</code> if it is a member.
472     * @exception UMSException if fail to evaluate group
473     *
474     * @supported.api
475     */
476    public boolean hasMember(Guid guid) throws UMSException {
477        String filter = getSearchFilter();
478        if (filter == null) {
479            return false;
480        }
481        // Narrow the filter by using the RDN of the target
482        // TODO: Should not have to use a DN here
483        String dn = guid.getDn();
484        String rdn = LDAPDN.explodeDN(dn, false)[0];
485        filter = "(&" + filter + "(" + rdn + "))";
486        String[] attributesToGet = { "dn" };
487        SearchResults searchResults = DataLayer.getInstance().search(
488                getPrincipal(), getSearchBase(), getSearchScope(), filter,
489                attributesToGet, false, null);
490        while (searchResults.hasMoreElements()) {
491            String s = searchResults.next().getDN();
492            if (Guid.equals(s, dn)) {
493                searchResults.abandon();
494                return true;
495            }
496        }
497        return false;
498    }
499
500    private static final String MEMBER_URL_NAME = "memberurl";
501
502    private static final Class _class = com.iplanet.ums.DynamicGroup.class;
503}