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.SearchScope; 029import org.forgerock.opendj.ldap.DN; 030import org.opends.server.core.ServerContext; 031import org.opends.server.extensions.StaticGroup.CompactDn; 032import org.opends.server.types.DirectoryConfig; 033import org.opends.server.types.DirectoryException; 034import org.opends.server.types.Entry; 035import org.opends.server.types.MemberList; 036import org.opends.server.types.MembershipException; 037import org.opends.server.types.SearchFilter; 038 039/** 040 * This class provides an implementation of the {@code MemberList} class that 041 * may be used in conjunction when static groups when additional criteria is to 042 * be used to select a subset of the group members. 043 */ 044public class FilteredStaticGroupMemberList extends MemberList 045{ 046 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 047 048 /** The base DN below which all returned members should exist. */ 049 private DN baseDN; 050 051 /** The DN of the static group with which this member list is associated. */ 052 private DN groupDN; 053 054 /** The entry of the next entry that matches the member list criteria. */ 055 private Entry nextMatchingEntry; 056 057 /** The iterator used to traverse the set of member DNs. */ 058 private Iterator<CompactDn> memberDNIterator; 059 060 /** The membership exception that should be thrown the next time a member is requested. */ 061 private MembershipException nextMembershipException; 062 063 /** The search filter that all returned members should match. */ 064 private SearchFilter filter; 065 066 /** The search scope to apply against the base DN for the member subset. */ 067 private SearchScope scope; 068 069 private final ServerContext serverContext; 070 071 /** 072 * Creates a new filtered static group member list with the provided 073 * information. 074 * 075 * @param serverContext 076 * The server context. 077 * @param groupDN The DN of the static group with which this member list 078 * is associated. 079 * @param memberDNs The set of DNs for the users that are members of the 080 * associated static group. 081 * @param baseDN The base DN below which all returned members should 082 * exist. If this is {@code null}, then all members will 083 * be considered to match the base and scope criteria. 084 * @param scope The search scope to apply against the base DN when 085 * selecting eligible members. 086 * @param filter The search filter which all returned members should 087 * match. If this is {@code null}, then all members will 088 * be considered eligible. 089 */ 090 public FilteredStaticGroupMemberList(ServerContext serverContext, DN groupDN, Set<CompactDn> memberDNs, DN baseDN, 091 SearchScope scope, SearchFilter filter) 092 { 093 ifNull(groupDN, memberDNs); 094 095 this.serverContext = serverContext; 096 this.groupDN = groupDN; 097 this.memberDNIterator = memberDNs.iterator(); 098 this.baseDN = baseDN; 099 this.filter = filter; 100 this.scope = scope != null ? scope : SearchScope.WHOLE_SUBTREE; 101 102 nextMemberInternal(); 103 } 104 105 /** 106 * Attempts to find the next member that matches the associated criteria. 107 * When this method returns, if {@code nextMembershipException} is 108 * non-{@code null}, then that exception should be thrown on the next attempt 109 * to retrieve a member. If {@code nextMatchingEntry} is non-{@code null}, 110 * then that entry should be returned on the next attempt to retrieve a 111 * member. If both are {@code null}, then there are no more members to 112 * return. 113 */ 114 private void nextMemberInternal() 115 { 116 while (memberDNIterator.hasNext()) 117 { 118 DN nextDN = null; 119 try 120 { 121 nextDN = memberDNIterator.next().toDn(serverContext); 122 } 123 catch (LocalizedIllegalArgumentException e) 124 { 125 logger.traceException(e); 126 nextMembershipException = new MembershipException(ERR_STATICMEMBERS_CANNOT_DECODE_DN.get(nextDN, groupDN, 127 e.getMessageObject()), true, e); 128 return; 129 } 130 131 // Check to see if we can eliminate the entry as a possible match purely 132 // based on base DN and scope. 133 if (baseDN != null) 134 { 135 switch (scope.asEnum()) 136 { 137 case BASE_OBJECT: 138 if (! baseDN.equals(nextDN)) 139 { 140 continue; 141 } 142 break; 143 144 case SINGLE_LEVEL: 145 if (! baseDN.equals(nextDN.parent())) 146 { 147 continue; 148 } 149 break; 150 151 case SUBORDINATES: 152 if (baseDN.equals(nextDN) || !baseDN.isSuperiorOrEqualTo(nextDN)) 153 { 154 continue; 155 } 156 break; 157 158 default: 159 if (!baseDN.isSuperiorOrEqualTo(nextDN)) 160 { 161 continue; 162 } 163 break; 164 } 165 } 166 167 // Get the entry for the potential member. If we can't, then populate 168 // the next membership exception. 169 try 170 { 171 Entry memberEntry = DirectoryConfig.getEntry(nextDN); 172 if (memberEntry == null) 173 { 174 nextMembershipException = new MembershipException(ERR_STATICMEMBERS_NO_SUCH_ENTRY.get(nextDN, groupDN), true); 175 return; 176 } 177 178 if (filter == null) 179 { 180 nextMatchingEntry = memberEntry; 181 return; 182 } 183 else if (filter.matchesEntry(memberEntry)) 184 { 185 nextMatchingEntry = memberEntry; 186 return; 187 } 188 else 189 { 190 continue; 191 } 192 } 193 catch (DirectoryException de) 194 { 195 logger.traceException(de); 196 197 LocalizableMessage message = ERR_STATICMEMBERS_CANNOT_GET_ENTRY. 198 get(nextDN, groupDN, de.getMessageObject()); 199 nextMembershipException = 200 new MembershipException(message, true, de); 201 return; 202 } 203 } 204 205 // If we've gotten here, then there are no more members. 206 nextMatchingEntry = null; 207 nextMembershipException = null; 208 } 209 210 @Override 211 public boolean hasMoreMembers() 212 { 213 return memberDNIterator.hasNext() 214 && (nextMatchingEntry != null || nextMembershipException != null); 215 } 216 217 @Override 218 public DN nextMemberDN() throws MembershipException 219 { 220 if (! memberDNIterator.hasNext()) 221 { 222 return null; 223 } 224 225 Entry entry = nextMemberEntry(); 226 return entry != null ? entry.getName() : null; 227 } 228 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 @Override 251 public void close() 252 { 253 // No implementation is required. 254 } 255}