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