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: IdUtils.java,v 1.34 2009/11/20 23:52:54 ww203982 Exp $
026 *
027 * Portions Copyrighted 2011-2017 ForgeRock AS.
028 * Portions Copyrighted 2014 Nomura Research Institute, Ltd
029 */
030
031package com.sun.identity.idm;
032
033import java.security.AccessController;
034import java.util.Collections;
035import java.util.HashSet;
036import java.util.Iterator;
037import java.util.Map;
038import java.util.Set;
039import java.util.StringTokenizer;
040
041import com.iplanet.am.sdk.AMConstants;
042import com.iplanet.am.sdk.AMDirectoryAccessFactory;
043import com.iplanet.am.sdk.AMException;
044import com.iplanet.am.sdk.AMObject;
045import com.iplanet.am.sdk.AMOrganization;
046import com.iplanet.am.sdk.AMStoreConnection;
047import com.iplanet.am.sdk.common.IDirectoryServices;
048import com.iplanet.am.util.SystemProperties;
049import com.iplanet.sso.SSOException;
050import com.iplanet.sso.SSOToken;
051import com.sun.identity.authentication.service.AuthD;
052import com.sun.identity.common.CaseInsensitiveHashMap;
053import com.sun.identity.common.CaseInsensitiveHashSet;
054import com.sun.identity.common.DNUtils;
055import com.sun.identity.security.AdminTokenAction;
056import com.sun.identity.shared.Constants;
057import com.sun.identity.shared.debug.Debug;
058import com.sun.identity.sm.DNMapper;
059import com.sun.identity.sm.OrgConfigViaAMSDK;
060import com.sun.identity.sm.OrganizationConfigManager;
061import com.sun.identity.sm.SMSEntry;
062import com.sun.identity.sm.SMSException;
063import com.sun.identity.sm.ServiceConfig;
064import com.sun.identity.sm.ServiceConfigManager;
065import com.sun.identity.sm.ServiceManager;
066import org.forgerock.openam.ldap.LDAPUtils;
067import org.forgerock.openam.utils.CollectionUtils;
068import org.forgerock.openam.utils.StringUtils;
069import org.forgerock.opendj.ldap.DN;
070
071/**
072 * The class defines some static utilities used by other components like policy
073 * and auth
074 *
075 * @supported.api
076 */
077public final class IdUtils {
078    private static Debug debug = AMIdentityRepository.debug;
079
080    private static Map mapSupportedTypes = new CaseInsensitiveHashMap(10);
081
082    public static Set supportedTypes = new HashSet();
083
084    private static Map mapTypesToServiceNames = new CaseInsensitiveHashMap();
085
086    protected static Map typesCanBeMemberOf = new CaseInsensitiveHashMap();
087
088    protected static Map typesCanHaveMembers = new CaseInsensitiveHashMap();
089
090    protected static Map typesCanAddMembers = new CaseInsensitiveHashMap();
091
092    // Static map to cache "orgIdentifier" and organization DN
093    private static Map<String, String> orgIdentifierToOrgName = Collections.synchronizedMap(
094        new CaseInsensitiveHashMap<String, String>());
095    private static Map<String, Boolean> orgStatusCache = Collections.synchronizedMap(
096        new CaseInsensitiveHashMap<String, Boolean>());
097
098    // Static Set to cache unknown organisation lookups
099    private static Set<String> unknownOrgLookupCache =
100            Collections.synchronizedSet(new CaseInsensitiveHashSet<String>());
101
102    // ServiceConfigManager for sunidentityrepository service
103    private static String notificationId;
104
105    private static ServiceConfigManager serviceConfigManager;
106    
107    // User naming attribute for AMSDK
108    private static String USER_NAMING_ATTR;
109    
110    // Organization naming attribute for AMSDK
111    private static String ORG_NAMING_ATTR;
112    
113    // SMS Root Suffix
114    private static String ROOT_SUFFIX;
115    
116    // DN pointing to the services node
117    private static String SERVICES_SUFFIX;
118    
119    // Special Users
120    private static Set specialUsers = new HashSet();
121    
122    static {
123        initialize();
124    }
125
126    protected static void initialize() {
127        if (ServiceManager.isConfigMigratedTo70()) {
128            // IdRepo service schema exists. Read the supported
129            // entities from there
130            try {
131                SSOToken adminToken = (SSOToken) AccessController
132                        .doPrivileged(AdminTokenAction.getInstance());
133                serviceConfigManager = new ServiceConfigManager(adminToken,
134                        IdConstants.REPO_SERVICE, "1.0");
135                ServiceConfig ss = serviceConfigManager.getGlobalConfig(null);
136                Set typeSchemaNames = ss.getSubConfigNames("*",
137                        IdConstants.SUPPORTED_TYPES);
138                if (typeSchemaNames == null || typeSchemaNames.isEmpty()) {
139                    loadDefaultTypes();
140                } else {
141                    Iterator it = typeSchemaNames.iterator();
142                    while (it.hasNext()) {
143                        String typeSchema = (String) it.next();
144                        IdType idType = new IdType(typeSchema);
145                        supportedTypes.add(idType);
146                        mapSupportedTypes.put(idType.getName(), idType);
147                        ServiceConfig tsc = ss.getSubConfig(typeSchema);
148                        Map attributes = tsc.getAttributes();
149                        Set serviceNameSet = (Set) attributes
150                                .get(IdConstants.SERVICE_NAME);
151                        Set canBeMembersOf = (Set) attributes
152                                .get(IdConstants.ATTR_MEMBER_OF);
153                        Set canHaveMembers = (Set) attributes
154                                .get(IdConstants.ATTR_HAVE_MEMBERS);
155                        Set canAddMembers = (Set) attributes
156                                .get(IdConstants.ATTR_ADD_MEMBERS);
157                        if (serviceNameSet != null && 
158                                !serviceNameSet.isEmpty()) {
159                            mapTypesToServiceNames.put(typeSchema,
160                                    (String) serviceNameSet.iterator().next());
161                        }
162                        if (canBeMembersOf != null && 
163                                !canBeMembersOf.isEmpty()) {
164                            Set memberOfSet = getMemberSet(canBeMembersOf);
165                            typesCanBeMemberOf.put(typeSchema, memberOfSet);
166                        }
167                        if (canHaveMembers != null && 
168                                !canHaveMembers.isEmpty()) {
169                            Set memberSet = getMemberSet(canHaveMembers);
170                            typesCanHaveMembers.put(typeSchema, memberSet);
171                        }
172                        if (canAddMembers != null && 
173                                !canAddMembers.isEmpty()) 
174                        {
175                            Set memberSet = getMemberSet(canAddMembers);
176                            typesCanAddMembers.put(typeSchema, memberSet);
177                        }
178                    }
179                }
180            } catch (SMSException e) {
181                String installTime = SystemProperties.get(
182                    Constants.SYS_PROPERTY_INSTALL_TIME, "false");
183                if (!installTime.equals("true")) {
184                    debug.error(
185                        "IdUtils.initialize: Loading default types.", e);
186                }
187                loadDefaultTypes();
188            } catch  (SSOException ssoe) {
189                debug.error("dUtils.initialize: Loading default types", ssoe);
190                loadDefaultTypes();
191            }
192        } else {
193            loadDefaultTypes();
194        }
195
196        // Register for SMS notifications to root realm
197        if (notificationId == null) {
198            try {
199                SSOToken adminToken = (SSOToken) AccessController
200                        .doPrivileged(AdminTokenAction.getInstance());
201                if (serviceConfigManager == null) {
202                    serviceConfigManager = new ServiceConfigManager(adminToken,
203                        IdConstants.REPO_SERVICE, "1.0");
204                }
205                notificationId = serviceConfigManager.addListener(
206                    new IdUtilsListener());
207            } catch (SMSException e) {
208                String installTime = SystemProperties.get(
209                    Constants.SYS_PROPERTY_INSTALL_TIME, "false");
210                if (!installTime.equals("true")) {
211                    debug.error(
212                        "IdUtils.initialize: Register notification", e);
213                }
214            } catch (SSOException ssoe) {
215                String installTime = SystemProperties.get(
216                    Constants.SYS_PROPERTY_INSTALL_TIME, "false");
217                if (!installTime.equals("true")) {
218                    debug.error(
219                        "IdUtils.initialize: Register notification", ssoe);
220                }
221            }
222        }
223    }
224
225    /**
226     * @supported.api 
227     * Returns a handle of the Identity object based on
228     * the SSO Token passed in (<code>AMIdentity</code> object of the user
229     * who is authenticated).
230     * 
231     * @param token    Single sign on token of user.
232     * @return Identity object.
233     * @throws IdRepoException if there are repository related error conditions.
234     * @throws SSOException if user's single sign on token is invalid.
235     */
236    public static AMIdentity getIdentity(SSOToken token)
237        throws IdRepoException, SSOException {
238        String principal = token.getProperty(Constants.UNIVERSAL_IDENTIFIER);
239        if (principal == null) {
240            // This could happen during co-existence with AM 6.x
241            // and SSOToken created by AM 6.x server. In this case
242            // the principal name would be the DN
243            principal = token.getPrincipal().getName();
244        }
245        return (getIdentity(token, principal));
246    }
247
248    /**
249     * @supported.api
250     * 
251     * Returns a string which uniquely represents this identity object.
252     * 
253     * @param id
254     *            <code>AMIdentity</code> object whose string represenation is
255     *            needed.
256     * @return universal identifier of <code>id</code>.
257     */
258    public static String getUniversalId(AMIdentity id) {
259        return id.getUniversalId();
260    }
261
262    /**
263     * @supported.api 
264     * 
265     * Returns an <code>AMIdentity</code> object, if provided with a string 
266     * identifier for the object.
267     * 
268     * @param token SSOToken of the administrator
269     * @param univId String represenation of the identity.
270     * @return Identity object
271     * @throws IdRepoException if the identifier provided is wrong.
272     */
273    public static AMIdentity getIdentity(SSOToken token, String univId)
274            throws IdRepoException {
275        return (getIdentity(token, univId, null));
276    }
277
278    /**
279     * Returns an <code>AMIdentity</code> object, given the
280     * DN of an authenticated identity, realm name and identity type.
281     * This interface is mainly for authentication component to get
282     * back the identity of the user.
283     * 
284     * @param token SSOToken of the administrator
285     * @param amsdkdn DN of the authenticated user
286     * @param realm  realm name where the user was authenticated
287     * @return Identity object or <code>null</code> 
288     * @throws IdRepoException if the underly components throws
289     * exception while obtaining the identity object
290     */
291    public static AMIdentity getIdentity(SSOToken token, String amsdkdn,
292        String realm) throws IdRepoException {
293        if (amsdkdn == null || !LDAPUtils.isDN(amsdkdn)) {
294            Object[] args = { amsdkdn };
295            throw (new IdRepoException(IdRepoBundle.BUNDLE_NAME,
296                    IdRepoErrorCode.ILLEGAL_UNIVERSAL_IDENTIFIER, args));
297        }
298        DN amsdkdnObject = LDAPUtils.newDN(amsdkdn);
299
300        // Try constructing the identity object
301        if (amsdkdn.toLowerCase().startsWith("id=")) {
302            try {
303                return (new AMIdentity(amsdkdnObject, token));
304            } catch (IdRepoException ide) {
305                // this could be a AMSDK DN. Follow the AMSDK rules
306                if (debug.messageEnabled()) {
307                    debug.message("IdUtils:getIdentity(token, " +
308                        amsdkdn + ") got exception: " + ide.getMessage() +
309                        "\n\tContinuing with AMSDK DN check");
310                }
311            }
312        }
313
314        // Check for Special Users
315        initializeSpecialUsers();
316        if (specialUsers.contains(DNUtils.normalizeDN(amsdkdn))) {
317            return new AMIdentity(amsdkdnObject, token, LDAPUtils.rdnValueFromDn(
318                    amsdkdnObject), IdType.USER, ROOT_SUFFIX);
319        }
320
321        // Since "amsdkdn" is not a UUID, check if realm has AMSDK configured
322        // This change is to avoid the issue of IdUtils always checking the 
323        // users in AMSDK as IdUtils does not check if AMSDK is configured in 
324        // any of the realms. 
325        try {
326            if (!ServiceManager.isAMSDKEnabled() || ((realm != null) && 
327                !OrgConfigViaAMSDK.isAMSDKConfigured(realm)) ||
328                    (!ServiceManager.isAMSDKConfigured())) { 
329                // Not configured for AMSDK, return
330                return (null);
331            }
332        } catch (SMSException smse) {
333            // Ignore the exception and continue
334        } 
335
336        // Initialize root realm suffix, org and user naming attributes
337        initializeForGetIdentity();
338
339        // Determine if the amsdkdn is valid. Obtain name & type
340        String name = null;
341        IdType type = null;
342        try {
343            // Since we would using AMSDK, get AMDirectoryManager preload
344            // all the attributes and check if it exists
345            IDirectoryServices dsServices = 
346                AMDirectoryAccessFactory.getDirectoryServices();
347            // Preload/cache all the attributes assuming it is a user
348            // Mainly for performance reasons, since getObjectType would
349            // force multiple another directory lookup
350            try {
351                if (amsdkdn.startsWith(USER_NAMING_ATTR)) {
352                    dsServices.getAttributes(token, amsdkdn, AMObject.USER);
353                }
354            } catch (Exception e) {
355                // Ignore the exception and continue since this for cache
356            }
357
358            // Getting object type would use the cached attributes
359            int sdkType = dsServices.getObjectType(token, amsdkdn);
360
361            // Convert the sdkType to IdRepo type
362            type = getType(AMStoreConnection.getObjectName(sdkType));
363            name = AMConstants.CONTAINER_DEFAULT_TEMPLATE_ROLE;
364            if (!type.equals(IdType.REALM)) {
365                name = LDAPUtils.rdnValueFromDn(amsdkdnObject);
366            }
367        } catch (AMException ame) {
368            // Debug the message and return null
369            if (debug.messageEnabled()) {
370                debug.message("IdUtils.getIdentity: Unable to resolve " +
371                    "AMSDK DN: " + amsdkdn, ame);
372            }
373            return (null);
374        } catch (SSOException ssoe) {
375            // Debug the message and return null
376            if (debug.messageEnabled()) {
377                debug.message("IdUtils.getIdentity: Unable to resolve " +
378                    "AMSDK DN. Got SSOException", ssoe);
379            }
380            return (null);
381        }
382
383        // Need to determine realm for amsdkdn
384        String srealm = ROOT_SUFFIX;
385        if (!amsdkdn.equals(ROOT_SUFFIX) &&
386            !amsdkdn.equals(SERVICES_SUFFIX)) {
387            // Need to get the object type and walk up the tree
388            int index = amsdkdn.indexOf(ORG_NAMING_ATTR);
389            if (index == 0) {
390                srealm = OrgConfigViaAMSDK.getRealmForAMSDK(amsdkdn, realm);
391            } else if (index > 0) {
392                srealm = OrgConfigViaAMSDK.getRealmForAMSDK(
393                    amsdkdn.substring(index), realm);
394            }
395            if (debug.messageEnabled()) {
396                debug.message("IdUtils.getIdentity:: amsdkdn=" +
397                    amsdkdn + " maps to realm=" + srealm);
398            }
399        } else if (amsdkdn.equals(SERVICES_SUFFIX)) {
400            // Since amsdkdn points to services node,
401            // it should be reset to root suffix
402            amsdkdn = ROOT_SUFFIX;
403        }
404
405        return (new AMIdentity(amsdkdnObject, token, name, type, srealm));
406    }
407
408    /**
409     * Returns the name of service which defines the profile information for
410     * this type. Returns null, if nothing is defined.
411     * 
412     * @param type IdType whose service name is needed.
413     * @return Name of the service.
414     */
415    public static String getServiceName(IdType type) {
416        return (String) mapTypesToServiceNames.get(type.getName());
417    }
418
419    /**
420     * Returns corresponding <code>IdType</code> object given a type.
421     * 
422     * @param type of object to return.
423     * @return Idtype of type.
424     * @throws IdRepoException if there are no corresponding types.
425     */
426    public static IdType getType(String type) throws IdRepoException {
427        if (type.equalsIgnoreCase("managedrole")) {
428            type = "role";
429        } else if (type.equalsIgnoreCase("organization")
430                || type.equalsIgnoreCase("organizationalunit")) {
431            type = "realm";
432        }
433
434        IdType returnType = (IdType) mapSupportedTypes.get(type);
435        if (returnType == null) {
436            Object args[] = { type };
437            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
438                    IdRepoErrorCode.NOT_SUPPORTED_TYPE, args);
439        }
440        return returnType;
441    }
442
443    /**
444     * Returns the matching DN from the AM SDK for this entry. This utility is
445     * required by auth.
446     * 
447     * @param id  <code>AMIdentity</code> object.
448     * @return <code>DN</code> of the object, as represented in the datastore.
449     */
450    public static String getDN(AMIdentity id) {
451        if (id.getDN() != null) {
452            return id.getDN();
453        } else {
454            return id.getUniversalId();
455        }
456    }
457
458    /**
459     * Returns an organization which maps to the identifier used by application
460     * 
461     * @param orgIdentifier  Organization identifier
462     * @return Organization mapping to that identifier.
463     */
464    public static String getOrganization(SSOToken token, String orgIdentifier)
465            throws IdRepoException, SSOException {
466        // Check in cache first
467        String id = orgIdentifierToOrgName.get(orgIdentifier);
468        if (id != null) {
469            return (id);
470        }
471
472        String[] args = {orgIdentifier};
473        // Don't go to the expense of an organisation search if we have failed to look this up already.
474        if (unknownOrgLookupCache.contains(orgIdentifier)) {
475            if (debug.messageEnabled()) {
476                debug.message("IdUtils.getOrganization: orgIdentifier {} found in unknown org lookup cache.",
477                        orgIdentifier);
478            }
479            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
480        }
481
482        // Compute the organization name
483        if (debug.messageEnabled()) {
484            debug.message("IdUtils:getOrganization Input orgname: "
485                    + orgIdentifier);
486        }
487        if (orgIdentifier == null || orgIdentifier.length() == 0
488                || orgIdentifier.equals("/")) {
489            // Return base DN
490            id = DNMapper.orgNameToDN("/");
491        } else if (orgIdentifier.startsWith("/")) {
492            // If orgIdentifier is in "/" format covert to DN and return
493            id = DNMapper.orgNameToDN(orgIdentifier);
494            try {
495                new OrganizationConfigManager(token, orgIdentifier);
496            } catch (SMSException e) {
497                if (debug.messageEnabled()) {
498                    debug.message("IdUtils.getOrganization Exception in getting org name from SMS", e);
499                }
500                unknownOrgLookupCache.add(orgIdentifier);
501                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
502            }
503        } else if (LDAPUtils.isDN(orgIdentifier)) {
504            id = orgIdentifier;
505            try {
506                // Search for realms with orgIdentifier name
507                OrganizationConfigManager ocm = 
508                    new OrganizationConfigManager(token, orgIdentifier);
509            } catch (SMSException smse) {
510                if (debug.messageEnabled()) {
511                    debug.message("IdUtils.getOrganization Exception in getting org name from SMS", smse);
512                }
513                unknownOrgLookupCache.add(orgIdentifier);
514                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
515            }
516        } else if (ServiceManager.isCoexistenceMode()) {
517            // Return the org DN as determined by AMStoreConnection
518            if (debug.messageEnabled()) {
519                debug.message("IdUtils.getOrganization: getting from AMSDK");
520            }
521            try {
522                AMStoreConnection amsc = new AMStoreConnection(token);
523                id = amsc.getOrganizationDN(orgIdentifier, null);
524            } catch (AMException ame) {
525                if (debug.messageEnabled()) {
526                    debug.message("IdUtils.getOrganization Exception in "
527                            + "getting org name from AMSDK", ame);
528                }
529                throw convertAMException(ame);
530            }
531        } else {
532            // Get the realm name from SMS
533            if (debug.messageEnabled()) {
534                debug.message("IdUtils.getOrganization: getting from " +
535                    "SMS realms");
536            }
537            try {
538                boolean foundOrg = false;
539                ServiceManager sm = new ServiceManager(token);
540                // First search for realms with orgIdentifier name
541                OrganizationConfigManager ocm = sm.getOrganizationConfigManager("/");
542                Set<String> subOrgNames = ocm.getSubOrganizationNames(orgIdentifier, true);
543                if (CollectionUtils.isNotEmpty(subOrgNames)) {
544                    if (subOrgNames.size() == 1) {
545                        id = DNMapper.orgNameToDN(subOrgNames.iterator().next());
546                        foundOrg = true;
547                    } else {
548                        for (String subRealmName : subOrgNames) {
549                            // check for orgIdentifier
550                            StringTokenizer st = new StringTokenizer(subRealmName, "/");
551                            // Need to handle the scenario where multiple
552                            // sub-realm with the same name should not be
553                            // allowed
554                            while (st.hasMoreTokens()) {
555                                if (st.nextToken().equalsIgnoreCase(orgIdentifier)) {
556                                    if (!foundOrg) {
557                                        id = DNMapper.orgNameToDN(subRealmName);
558                                        foundOrg = true;
559                                    } else {
560                                        throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
561                                                IdRepoErrorCode.MULTIPLE_MAPPINGS_FOUND, args);
562                                    }
563                                }
564                            }
565                        }
566                    }
567                }
568
569                // Check if organization name has been determined
570                if (debug.messageEnabled()) {
571                    debug.message("IdUtils.getOrganization: getting from " +
572                        "SMS realms aliases");
573                }
574                // perform organization alias search
575                Set<String> vals = new HashSet<>(1);
576                vals.add(orgIdentifier);
577                Set orgAliases = sm.searchOrganizationNames(
578                        IdConstants.REPO_SERVICE,
579                        IdConstants.ORGANIZATION_ALIAS_ATTR, vals);
580                if (!foundOrg && CollectionUtils.isEmpty(orgAliases)) {
581                    if (debug.warningEnabled()) {
582                        debug.warning("IdUtils.getOrganization Unable to find Org name for: " + orgIdentifier);
583                    }
584                    unknownOrgLookupCache.add(orgIdentifier);
585                    throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
586                            IdRepoErrorCode.NO_MAPPING_FOUND, args);
587                } else if (CollectionUtils.isNotEmpty(orgAliases) && foundOrg) {
588                    // Check to see if there is one alias, which points to the same realm as the one previously found
589                    boolean sameRealm = false;
590                    if (orgAliases.size() == 1) {
591                        String aliasId = DNMapper.orgNameToDN((String)orgAliases.iterator().next());
592                        if (StringUtils.isEqualTo(aliasId, id)) {
593                            // The realm has an alias that is equivalent to the realm name
594                            sameRealm = true;
595                        }
596                    }
597                    if (!sameRealm) {
598                        // Multiple realms should not have the same alias
599                        if (debug.warningEnabled()) {
600                            debug.warning("IdUtils.getOrganization Multiple " +
601                                    " matching Orgs found for: " + orgIdentifier);
602                        }
603                        throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
604                                IdRepoErrorCode.MULTIPLE_MAPPINGS_FOUND, args);
605                    }
606                } else if (CollectionUtils.isNotEmpty(orgAliases) && (orgAliases.size() > 1)) {
607                    // Multiple realms should not have the same alias
608                    if (debug.warningEnabled()) {
609                        debug.warning("IdUtils.getOrganization Multiple " +
610                                " matching Orgs found for: " + orgIdentifier);
611                    }
612                    throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
613                            IdRepoErrorCode.MULTIPLE_MAPPINGS_FOUND, args);
614                }
615                if (!foundOrg) {
616                    String tmpS = (String) orgAliases.iterator().next();
617                    id = DNMapper.orgNameToDN(tmpS);
618                }
619            } catch (SMSException smse) {
620                if (debug.messageEnabled()) {
621                    debug.message("IdUtils.getOrganization Exception in "
622                            + "getting org name from SMS", smse);
623                }
624                unknownOrgLookupCache.add(orgIdentifier);
625                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
626            }
627        }
628
629        if (debug.messageEnabled()) {
630            debug.message("IdUtils:getOrganization Search for OrgIdentifier:" +
631                orgIdentifier + " returning realm DN: " + id);
632        }
633
634        // Add to cache and return id
635        orgIdentifierToOrgName.put(orgIdentifier, id);
636        return id;
637    }
638
639    /**
640     * Clears the cache containing orgIdentifiers to organization names
641     */
642    protected static void clearOrganizationNamesCache() {
643        orgIdentifierToOrgName.clear();
644        orgStatusCache.clear();
645        unknownOrgLookupCache.clear();
646        if (debug.messageEnabled()) {
647            debug.message("IdUtils.clearOrganizationNamesCache called");
648        }
649    }
650
651    /**
652     * Returs true or false, depending on if this organization is enabled or
653     * not. The organization string passed to this method should be an
654     * identifier returned from the method
655     * <code> IdUtils.getOrganization </code>. In the default mode, where
656     * relams are enabled but backward comaptibility is required, this checks
657     * for organization status in the AM enabled Sun DS. Otherwise, it checks
658     * for organization status from the realms tree.
659     * 
660     * @param token token SSOToken a valid SSOToken.
661     * @param org name of the organization of interest.
662     * @return <code>true</code> if org is active; 
663     *    otherwise <code>false</code>
664     * @throws IdRepoException if there are repository related error conditions.
665     * @throws SSOException If user's single sign on token is invalid.
666     */
667    public static boolean isOrganizationActive(SSOToken token, String org)
668            throws IdRepoException, SSOException {
669        // Check the cache
670        if (orgStatusCache.containsKey(org)) {
671            return orgStatusCache.get(org);
672        }
673        boolean isActive = true;
674        // Need to initialize ServiceManager by creating the constructor
675        if (!ServiceManager.isCoexistenceMode()) {
676            // Pick it up from the realms tree.
677            try {
678                OrganizationConfigManager ocm = new OrganizationConfigManager(
679                        token, org);
680                if (ocm == null) {
681                    Object[] args = { org };
682                    throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
683                }
684                Map attributes = ocm.getAttributes(IdConstants.REPO_SERVICE);
685                Set vals = (Set) attributes
686                        .get(IdConstants.ORGANIZATION_STATUS_ATTR);
687                if (vals == null || vals.isEmpty()) {
688                    isActive = true;
689                } else {
690                    String stringActive = (String) vals.iterator().next();
691                    isActive = stringActive.equalsIgnoreCase("Active");
692                }
693            } catch (SMSException smse) {
694                Object args[] = { org };
695                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.NO_MAPPING_FOUND, args);
696            }
697        } else if (ServiceManager.isAMSDKEnabled()) {
698            // Return the org DN as determined by AMStoreConnection.
699            try {
700                AMStoreConnection amsc = new AMStoreConnection(token);
701                AMOrganization orgObj = amsc.getOrganization(org);
702                isActive = orgObj.isActivated();
703            } catch (AMException ame) {
704                throw convertAMException(ame);
705            }
706        }
707        // Add to cache
708        orgStatusCache.put(org, isActive);
709        return isActive;
710    }
711
712    private static void initializeForGetIdentity() {
713        // Initialize root realm, if not already initalized
714        if (ROOT_SUFFIX == null) {
715            ROOT_SUFFIX = SMSEntry.getRootSuffix();
716            StringBuilder sb = new StringBuilder(100);
717            sb.append(SMSEntry.SERVICES_RDN)
718                .append(SMSEntry.COMMA).append(ROOT_SUFFIX);
719            SERVICES_SUFFIX = DNUtils.normalizeDN(sb.toString());
720        }
721
722        // Initialize organization and user naming attributes
723        if ((ORG_NAMING_ATTR == null) || (USER_NAMING_ATTR == null)) {
724            try {
725                ORG_NAMING_ATTR = AMStoreConnection.getNamingAttribute(
726                    AMObject.ORGANIZATION).toLowerCase() + "=";
727                USER_NAMING_ATTR = AMStoreConnection.getNamingAttribute(
728                    AMObject.USER).toLowerCase() + "=";
729            } catch (AMException ame) {
730                if (debug.warningEnabled()) {
731                    debug.warning("IdUtils: unable to get naming " +
732                        "attribute for org/user. Using \"o\"/\"uid\"");
733                }
734                ORG_NAMING_ATTR = "o=";
735                USER_NAMING_ATTR = "uid=";
736            }
737        }
738    }
739
740    private static void initializeSpecialUsers() {
741        // Populate special users
742        if (specialUsers.isEmpty()) {
743            String susers = SystemProperties.get(
744                Constants.AUTHENTICATION_SPECIAL_USERS, "");
745            StringTokenizer st = new StringTokenizer(
746                susers, "|");
747            while (st.hasMoreTokens()) {
748                specialUsers.add(DNUtils.normalizeDN(st.nextToken()));
749            }
750            susers = SystemProperties.get(
751                "com.sun.identity.authentication.super.user", "");
752            specialUsers.add(DNUtils.normalizeDN(susers));
753        }
754    }
755    
756    /**
757     * Returns an IdRepoException based on an <code>AMException</code>
758     * 
759     * @param ame
760     * @return IdRepoException based on ame.
761     */
762    public static IdRepoException convertAMException(AMException ame) {
763        Object[] args = ame.getMessageArgs();
764        String eCode = ame.getErrorCode();
765        IdRepoException ide = null;
766        if (args == null) {
767            ide = new IdRepoException("amProfile", eCode, null);
768        } else { 
769            ide = new IdRepoException("amProfile", ame.getErrorCode(), args);
770        }
771        ide.setLDAPErrorCode(ame.getLDAPErrorCode());
772        return ide;
773    }
774
775    private static void loadDefaultTypes() {
776        supportedTypes.add(IdType.REALM);
777        supportedTypes.add(IdType.AGENT);
778        supportedTypes.add(IdType.USER);
779        supportedTypes.add(IdType.ROLE);
780        supportedTypes.add(IdType.GROUP);
781        supportedTypes.add(IdType.FILTEREDROLE);
782        mapSupportedTypes.put(IdType.REALM.getName(), IdType.REALM);        
783        mapSupportedTypes.put(IdType.USER.getName(), IdType.USER);
784        mapSupportedTypes.put(IdType.ROLE.getName(), IdType.ROLE);
785        mapSupportedTypes.put(IdType.FILTEREDROLE.getName(),
786                IdType.FILTEREDROLE);
787        mapSupportedTypes.put(IdType.AGENT.getName(), IdType.AGENT);
788        mapSupportedTypes.put(IdType.GROUP.getName(), IdType.GROUP);
789        Set memberSet = new HashSet();
790        memberSet.add(IdType.ROLE);
791        memberSet.add(IdType.GROUP);
792        memberSet.add(IdType.FILTEREDROLE);
793        typesCanBeMemberOf.put(IdType.USER.getName(), memberSet);
794        Set memberShipSet = new HashSet();
795        memberShipSet.add(IdType.USER);
796        typesCanHaveMembers.put(IdType.ROLE.getName(), memberShipSet);
797        typesCanHaveMembers.put(IdType.GROUP.getName(), memberShipSet);
798        typesCanHaveMembers.put(IdType.FILTEREDROLE.getName(), memberShipSet);
799        typesCanAddMembers.put(IdType.GROUP.getName(), memberShipSet);
800        typesCanAddMembers.put(IdType.ROLE.getName(), memberShipSet);
801    }
802
803    private static Set getMemberSet(Set members) {
804        Set memberSet = new HashSet(members.size() * 2);
805        for (Iterator iter = members.iterator(); iter.hasNext();) {
806            String currType = (String) iter.next();
807            memberSet.add(new IdType(currType));
808        }
809        return memberSet;
810    }
811
812    /**
813     * Returns the user name extracted from the uuid
814     * if the orgName supplied in the parameter is 
815     * not same realm name in uuid then <code>IdRepoException</code>
816     * is thrown
817     * 
818     * @param uuid uuid of the user
819     * @param orgName the org user is trying to login to
820     * @return user name
821     * @throws IdRepoException 
822     */
823    public static String getIdentityName(String uuid, String orgName)
824        throws IdRepoException {
825        String username = uuid;
826        // Check uuid
827        if ((uuid != null) && uuid.toLowerCase().startsWith("id=")) {
828            // Could be universal id, get the identity object
829            AMIdentity id = new AMIdentity(null, uuid);
830            username = id.getName();
831            // Check the realm names
832            String realm = DNUtils.normalizeDN(id.getRealm());
833            if (!DNUtils.normalizeDN(orgName).equals(realm)) {
834                Object[] args = {uuid, orgName};
835                throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, IdRepoErrorCode.REALM_NAME_NOT_MATCH_AUTHENTICATION_REALM,
836                args);
837            }
838        }
839        return (username);
840    }
841
842    /**
843     * Gets the AMIdentity of a user with username equal to uName that exists in realm
844     *
845     * @param uName username of the user to get.
846     * @param realm realm the user belongs to.
847     * @return The AMIdentity of user with username equal to uName.
848     */
849    public static AMIdentity getIdentity(String uName, String realm) {
850        return getIdentity(uName, realm, null);
851    }
852
853    /**
854     * Gets the AMIdentity of a user with username equal to uName that exists in realm.
855     * If no AMIdentity found using username it will fall back to using userSearchAttributes if supplied.
856     *
857     * @param uName username of the user to get.
858     * @param realm realm the user belongs to.
859     * @param userSearchAttributes Alias Search Attribute Name.
860     * @return The AMIdentity of user with username equal to uName.
861     */
862    public static AMIdentity getIdentity(String uName, String realm, Set<String> userSearchAttributes) {
863        AMIdentity theID = null;
864
865        AMIdentityRepository amIdRepo = getAMIdentityRepository(DNMapper.orgNameToDN(realm));
866
867        IdSearchControl idsc = new IdSearchControl();
868        idsc.setRecursive(true);
869        idsc.setAllReturnAttributes(true);
870        // search for the identity
871        Set<AMIdentity> results = Collections.EMPTY_SET;
872        try {
873            idsc.setMaxResults(0);
874            IdSearchResults searchResults = amIdRepo.searchIdentities(IdType.USER, uName, idsc);
875
876            if (searchResults.getSearchResults().isEmpty() && CollectionUtils.isNotEmpty(userSearchAttributes)) {
877                debug.message("IdUtils.getIdentity: searching user identity with alternative attributes {} ",
878                        userSearchAttributes);
879                final Map<String, Set<String>> searchAVP = CollectionUtils.toAvPairMap(userSearchAttributes, uName);
880                idsc.setSearchModifiers(IdSearchOpModifier.OR, searchAVP);
881                //workaround as data store always adds 'user-naming-attribute' to searchfilter
882                searchResults = amIdRepo.searchIdentities(IdType.USER, "*", idsc);
883            }
884
885            if (searchResults != null) {
886                results = searchResults.getSearchResults();
887            }
888
889            if (results == null || results.size() != 1) {
890                throw new IdRepoException("IdUtils" +
891                        ".getIdentity : " +
892                        "More than one user found");
893            }
894            theID = results.iterator().next();
895        } catch (IdRepoException e) {
896            debug.warning("Error searching for user identity");
897        } catch (SSOException e) {
898            debug.warning("User's ssoToken has expired");
899        }
900        return theID;
901    }
902
903    /**
904     * Returns <code>AMIdentityRepostiory</code> handle for an organization.
905     *
906     * @param orgDN the organization name.
907     * @return <code>AMIdentityRepostiory</code> object
908     */
909    public static AMIdentityRepository getAMIdentityRepository(String orgDN) {
910        return AuthD.getAuth().getAMIdentityRepository(orgDN);
911    }
912    
913    // SMS service listener to reinitialize if IdRepo service changes
914    static class IdUtilsListener implements com.sun.identity.sm.ServiceListener 
915    {
916        public void schemaChanged(String serviceName, String version) {
917            initialize();
918        }
919
920        public void globalConfigChanged(String serviceName, String version,
921            String groupName, String serviceComponent, int type) {
922            initialize();
923            clearOrganizationNamesCache();
924        }
925
926        public void organizationConfigChanged(String serviceName,
927            String version, String orgName, String groupName,
928            String serviceComponent, int type) {
929            clearOrganizationNamesCache();
930        }
931    }
932
933}