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 * Portions Copyrighted 2011-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 com.sun.identity.shared.encode.URLEncDec; 036import java.security.Principal; 037 038import org.forgerock.i18n.LocalizedIllegalArgumentException; 039import org.forgerock.opendj.ldap.DN; 040import org.forgerock.opendj.ldap.Filter; 041import org.forgerock.opendj.ldap.LDAPUrl; 042import org.forgerock.opendj.ldap.ModificationType; 043import org.forgerock.opendj.ldap.SearchScope; 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.valueOf(filter), SearchScope.valueOf(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 SearchScope scope = url.getScope(); 177 178 Guid baseGuid = new Guid(url.getName().toString()); 179 try { 180 setUrl(baseGuid, Filter.valueOf(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().toString(); 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 SearchScope scope = url.getScope(); 211 Filter 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().getName().toString()); 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.getName().toString()); 244 Filter filter = url.getFilter(); 245 try { 246 setUrl(baseGuid, filter, SearchScope.valueOf(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().intValue(); 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, Filter filter, SearchScope scope) { 276 StringBuilder urlBuf = new StringBuilder(); 277 urlBuf.append("ldap:///").append(base).append("?"); 278 279 if (SearchScope.BASE_OBJECT.equals(scope)) { 280 urlBuf.append("?base"); 281 } else if (SearchScope.SINGLE_LEVEL.equals(scope)) { 282 urlBuf.append("?one"); 283 } else { 284 urlBuf.append("?sub"); 285 } 286 287 if (filter != null && !filter.toString().isEmpty()) { 288 urlBuf.append("?").append(filter); 289 } else { 290 urlBuf.append("?"); 291 } 292 293 return urlBuf.toString(); 294 } 295 296 /** 297 * Creates a new search definition; the change is not persistent until 298 * save() is called. 299 * 300 * @param baseGuid Search base for evaluating members of the group. 301 * @param filter Search filter for evaluating members of the group. 302 * @param scope Search scope for evaluating members of the group. 303 */ 304 protected void setUrl(Guid baseGuid, Filter filter, SearchScope scope) { 305 // Only valid scope is "sub" and "one" 306 // 307 if (!SearchScope.SINGLE_LEVEL.equals(scope) && !SearchScope.WHOLE_SUBTREE.equals(scope)) { 308 String msg = i18n.getString(IUMSConstants.ILLEGAL_GROUP_SCOPE); 309 throw new IllegalArgumentException(msg); 310 } 311 312 String urlStr = toUrlStr(baseGuid.getDn(), filter, scope); 313 314 // Sanity check on the url 315 // 316 try { 317 LDAPUrl.valueOf(urlStr); 318 } catch (LocalizedIllegalArgumentException e) { 319 throw new IllegalArgumentException(e.getMessage()); 320 } 321 322 // TODO: Need to support multiple values of memberUrl? If so, do 323 // an ADD instead of a replace. 324 // 325 modify(new Attr(MEMBER_URL_NAME, urlStr), ModificationType.REPLACE); 326 } 327 328 /** 329 * Returns the native LDAP URL used to evaluate this dynamic group. 330 * 331 * @return LDAP URL for evaluating members of the group 332 */ 333 protected LDAPUrl getUrl() { 334 Attr attr = getAttribute(MEMBER_URL_NAME); 335 LDAPUrl url = null; 336 try { 337 // TODO: Need to support multiple values of memberUrl? 338 if (attr != null && attr.getStringValues().length > 0) { 339 340 // Converting the url string to 341 // application/x-www-form-urlencoded as expected by 342 // LDAPUrl constructor. 343 url = LDAPUrl.valueOf(URLEncDec.encodeLDAPUrl(attr.getStringValues()[0])); 344 } 345 } catch (LocalizedIllegalArgumentException ex) { 346 debug.error("DynamicGroup.setSearchFilter : Exception : " + ex.getMessage()); 347 throw new IllegalArgumentException(ex.getMessage()); 348 } 349 return url; 350 } 351 352 /** 353 * Sets the native LDAP URL used to evaluate this dynamic group. 354 * 355 * @param url LDAP URL for evaluating members of the group search scope in 356 * the url has to be <code>LDAPv2.SCOPE_ONE</code> or 357 * <code>LDAPv2.SCOPE_SUB</code>. 358 */ 359 protected void setUrl(LDAPUrl url) { 360 String ldapurl = url.toString(); 361 if (SearchScope.SINGLE_LEVEL.equals(url.getScope()) && SearchScope.WHOLE_SUBTREE.equals(url.getScope())) { 362 String msg = i18n.getString(IUMSConstants.ILLEGAL_GROUP_SCOPE); 363 throw new IllegalArgumentException(msg); 364 } 365 // TODO: Need to support multiple values of memberUrl? If so, do 366 // an ADD instead of a replace. 367 modify(new Attr(MEMBER_URL_NAME, ldapurl), ModificationType.REPLACE); 368 // modify( new Attr( MEMBER_URL_NAME, url.toString() ), ModSet.ADD ); 369 } 370 371 /** 372 * Returns the members of the group. 373 * 374 * @param attributes Attributes to return. 375 * @return Iterator for unique identifiers for members of the group. 376 * @exception UMSException if fail to search. 377 */ 378 protected SearchResults getMemberIDs(String[] attributes) 379 throws UMSException { 380 return DataLayer.getInstance().search(getPrincipal(), getSearchBase(), 381 getSearchScope(), getSearchFilter(), attributes, false, null); 382 } 383 384 /** 385 * Returns the members of the group. 386 * 387 * @return Iterator for unique identifiers for members of the group. 388 * @exception UMSException if fail to search. 389 * 390 * @supported.api 391 */ 392 public SearchResults getMemberIDs() throws UMSException { 393 String[] attributesToGet = { "objectclass" }; 394 return getMemberIDs(attributesToGet); 395 } 396 397 /** 398 * Returns the member count. 399 * 400 * @return Number of members of the group. 401 * @exception UMSException if fail to search. 402 * 403 * @supported.api 404 */ 405 public int getMemberCount() throws UMSException { 406 int count = 0; 407 String[] attributesToGet = { "dn" }; 408 SearchResults searchResults = getMemberIDs(attributesToGet); 409 while (searchResults.hasMoreElements()) { 410 searchResults.next().getDN(); 411 count++; 412 } 413 return count; 414 } 415 416 /** 417 * Returns a member given an index (zero-based). 418 * 419 * @param index Zero-based index into the group container. 420 * @return Unique identifier for a member. 421 * @exception UMSException if fail to search. 422 * 423 * @supported.api 424 */ 425 public Guid getMemberIDAt(int index) throws UMSException { 426 if (index < 0) { 427 throw new IllegalArgumentException(Integer.toString(index)); 428 } 429 String filter = getSearchFilter(); 430 if (filter == null) { 431 return null; 432 } 433 String[] attributesToGet = { "dn" }; 434 SearchResults searchResults = getMemberIDs(attributesToGet); 435 while (searchResults.hasMoreElements()) { 436 String s = searchResults.next().getDN(); 437 if (index == 0) { 438 searchResults.abandon(); 439 return new Guid(s); 440 } 441 index--; 442 } 443 throw new ArrayIndexOutOfBoundsException(Integer.toString(index)); 444 } 445 446 /** 447 * Returns <code>true</code> if a given identifier is a member of the 448 * group. 449 * 450 * @param guid Identity of member to be checked for membership. 451 * @return <code>true</code> if it is a member. 452 * @exception UMSException if fail to evaluate group 453 * 454 * @supported.api 455 */ 456 public boolean hasMember(Guid guid) throws UMSException { 457 String filter = getSearchFilter(); 458 if (filter == null) { 459 return false; 460 } 461 // Narrow the filter by using the RDN of the target 462 // TODO: Should not have to use a DN here 463 String dn = guid.getDn(); 464 String rdn = DN.valueOf(dn).rdn().toString(); 465 filter = "(&" + filter + "(" + rdn + "))"; 466 String[] attributesToGet = { "dn" }; 467 SearchResults searchResults = DataLayer.getInstance().search( 468 getPrincipal(), getSearchBase(), getSearchScope(), filter, 469 attributesToGet, false, null); 470 while (searchResults.hasMoreElements()) { 471 String s = searchResults.next().getDN(); 472 if (Guid.equals(s, dn)) { 473 searchResults.abandon(); 474 return true; 475 } 476 } 477 return false; 478 } 479 480 private static final String MEMBER_URL_NAME = "memberurl"; 481 482 private static final Class _class = com.iplanet.ums.DynamicGroup.class; 483}