001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.extensions; 028 029import static org.forgerock.util.Reject.*; 030import static org.opends.messages.ExtensionMessages.*; 031 032import java.util.Iterator; 033import java.util.Set; 034 035import org.forgerock.i18n.LocalizableMessage; 036import org.forgerock.i18n.LocalizedIllegalArgumentException; 037import org.forgerock.i18n.slf4j.LocalizedLogger; 038import org.forgerock.opendj.ldap.DN.CompactDn; 039import org.forgerock.opendj.ldap.SearchScope; 040import org.opends.server.types.DN; 041import org.opends.server.types.DirectoryConfig; 042import org.opends.server.types.DirectoryException; 043import org.opends.server.types.Entry; 044import org.opends.server.types.MemberList; 045import org.opends.server.types.MembershipException; 046import org.opends.server.types.SearchFilter; 047 048/** 049 * This class provides an implementation of the {@code MemberList} class that 050 * may be used in conjunction when static groups when additional criteria is to 051 * be used to select a subset of the group members. 052 */ 053public class FilteredStaticGroupMemberList extends MemberList 054{ 055 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 056 057 /** The base DN below which all returned members should exist. */ 058 private DN baseDN; 059 060 /** The DN of the static group with which this member list is associated. */ 061 private DN groupDN; 062 063 /** The entry of the next entry that matches the member list criteria. */ 064 private Entry nextMatchingEntry; 065 066 /** The iterator used to traverse the set of member DNs. */ 067 private Iterator<CompactDn> memberDNIterator; 068 069 /** 070 * The membership exception that should be thrown the next time a member is 071 * requested. 072 */ 073 private MembershipException nextMembershipException; 074 075 /** The search filter that all returned members should match. */ 076 private SearchFilter filter; 077 078 /** The search scope to apply against the base DN for the member subset. */ 079 private SearchScope scope; 080 081 /** 082 * Creates a new filtered static group member list with the provided 083 * information. 084 * 085 * @param groupDN The DN of the static group with which this member list 086 * is associated. 087 * @param memberDNs The set of DNs for the users that are members of the 088 * associated static group. 089 * @param baseDN The base DN below which all returned members should 090 * exist. If this is {@code null}, then all members will 091 * be considered to match the base and scope criteria. 092 * @param scope The search scope to apply against the base DN when 093 * selecting eligible members. 094 * @param filter The search filter which all returned members should 095 * match. If this is {@code null}, then all members will 096 * be considered eligible. 097 */ 098 public FilteredStaticGroupMemberList(DN groupDN, Set<CompactDn> memberDNs, DN baseDN, SearchScope scope, 099 SearchFilter filter) 100 { 101 ifNull(groupDN, memberDNs); 102 103 this.groupDN = groupDN; 104 this.memberDNIterator = memberDNs.iterator(); 105 this.baseDN = baseDN; 106 this.filter = filter; 107 this.scope = scope != null ? scope : SearchScope.WHOLE_SUBTREE; 108 109 nextMemberInternal(); 110 } 111 112 /** 113 * Attempts to find the next member that matches the associated criteria. 114 * When this method returns, if {@code nextMembershipException} is 115 * non-{@code null}, then that exception should be thrown on the next attempt 116 * to retrieve a member. If {@code nextMatchingEntry} is non-{@code null}, 117 * then that entry should be returned on the next attempt to retrieve a 118 * member. If both are {@code null}, then there are no more members to 119 * return. 120 */ 121 private void nextMemberInternal() 122 { 123 while (memberDNIterator.hasNext()) 124 { 125 DN nextDN = null; 126 try 127 { 128 nextDN = StaticGroup.fromCompactDn(memberDNIterator.next()); 129 } 130 catch (LocalizedIllegalArgumentException e) 131 { 132 logger.traceException(e); 133 nextMembershipException = new MembershipException(ERR_STATICMEMBERS_CANNOT_DECODE_DN.get(nextDN, groupDN, 134 e.getMessageObject()), true, e); 135 return; 136 } 137 138 // Check to see if we can eliminate the entry as a possible match purely 139 // based on base DN and scope. 140 if (baseDN != null) 141 { 142 switch (scope.asEnum()) 143 { 144 case BASE_OBJECT: 145 if (! baseDN.equals(nextDN)) 146 { 147 continue; 148 } 149 break; 150 151 case SINGLE_LEVEL: 152 if (! baseDN.equals(nextDN.parent())) 153 { 154 continue; 155 } 156 break; 157 158 case SUBORDINATES: 159 if (baseDN.equals(nextDN) || !baseDN.isAncestorOf(nextDN)) 160 { 161 continue; 162 } 163 break; 164 165 default: 166 if (!baseDN.isAncestorOf(nextDN)) 167 { 168 continue; 169 } 170 break; 171 } 172 } 173 174 // Get the entry for the potential member. If we can't, then populate 175 // the next membership exception. 176 try 177 { 178 Entry memberEntry = DirectoryConfig.getEntry(nextDN); 179 if (memberEntry == null) 180 { 181 nextMembershipException = new MembershipException(ERR_STATICMEMBERS_NO_SUCH_ENTRY.get(nextDN, groupDN), true); 182 return; 183 } 184 185 if (filter == null) 186 { 187 nextMatchingEntry = memberEntry; 188 return; 189 } 190 else if (filter.matchesEntry(memberEntry)) 191 { 192 nextMatchingEntry = memberEntry; 193 return; 194 } 195 else 196 { 197 continue; 198 } 199 } 200 catch (DirectoryException de) 201 { 202 logger.traceException(de); 203 204 LocalizableMessage message = ERR_STATICMEMBERS_CANNOT_GET_ENTRY. 205 get(nextDN, groupDN, de.getMessageObject()); 206 nextMembershipException = 207 new MembershipException(message, true, de); 208 return; 209 } 210 } 211 212 // If we've gotten here, then there are no more members. 213 nextMatchingEntry = null; 214 nextMembershipException = null; 215 } 216 217 /** {@inheritDoc} */ 218 @Override 219 public boolean hasMoreMembers() 220 { 221 return memberDNIterator.hasNext() 222 && (nextMatchingEntry != null || nextMembershipException != null); 223 } 224 225 /** {@inheritDoc} */ 226 @Override 227 public DN nextMemberDN() throws MembershipException 228 { 229 if (! memberDNIterator.hasNext()) 230 { 231 return null; 232 } 233 234 Entry entry = nextMemberEntry(); 235 return entry != null ? entry.getName() : null; 236 } 237 238 /** {@inheritDoc} */ 239 @Override 240 public Entry nextMemberEntry() throws MembershipException 241 { 242 if (! memberDNIterator.hasNext()) 243 { 244 return null; 245 } 246 if (nextMembershipException != null) 247 { 248 MembershipException me = nextMembershipException; 249 nextMembershipException = null; 250 nextMemberInternal(); 251 throw me; 252 } 253 254 Entry e = nextMatchingEntry; 255 nextMatchingEntry = null; 256 nextMemberInternal(); 257 return e; 258 } 259 260 /** {@inheritDoc} */ 261 @Override 262 public void close() 263 { 264 // No implementation is required. 265 } 266} 267