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: AMIdentity.java,v 1.37 2009/11/20 23:52:54 ww203982 Exp $
026 *
027 */
028
029/*
030 * Portions Copyrighted 2011-2014 ForgeRock AS
031 */
032package com.sun.identity.idm;
033
034import com.iplanet.am.sdk.AMCommonUtils;
035import com.iplanet.am.sdk.AMCrypt;
036import com.iplanet.am.sdk.AMHashMap;
037import com.iplanet.sso.SSOException;
038import com.iplanet.sso.SSOToken;
039import com.sun.identity.common.CaseInsensitiveHashMap;
040import com.sun.identity.common.CaseInsensitiveHashSet;
041import com.sun.identity.idm.common.IdRepoUtils;
042import com.sun.identity.shared.Constants;
043import com.sun.identity.shared.debug.Debug;
044import com.sun.identity.sm.DNMapper;
045import com.sun.identity.sm.SMSException;
046import com.sun.identity.sm.SchemaType;
047import com.sun.identity.sm.ServiceManager;
048import com.sun.identity.sm.ServiceNotFoundException;
049import com.sun.identity.sm.ServiceSchema;
050import com.sun.identity.sm.ServiceSchemaManager;
051import java.util.Collections;
052import java.util.HashMap;
053import java.util.HashSet;
054import java.util.Iterator;
055import java.util.Map;
056import java.util.Set;
057import com.sun.identity.shared.ldap.LDAPDN;
058import com.sun.identity.shared.ldap.util.DN;
059import com.sun.identity.shared.ldap.util.RDN;
060
061/**
062 * This class represents an Identity which needs to be managed by Access
063 * Manager. This identity could exist in multiple repositories, which are
064 * configured for a given realm or organization. When any operation is performed
065 * from this class, it executes all plugins that are configured for performing
066 * that operation. For eg: getAttributes. The application gets access to
067 * constructing <code> AMIdentity </code> objects by using
068 * <code> AMIdentityRepository
069 * </code> interfaces. For example:
070 * <p>
071 *
072 * <PRE>
073 *
074 * AMIdentityRepository idrepo = new AMIdentityRepository(token, org);
075 * AMIdentity id = idrepo.getRealmIdentity();
076 *
077 * </PRE>
078 *
079 * The <code>id</code> returned above is the AMIdentity object of the user's
080 * single sign-on token passed above. The results obtained from search performed
081 * using AMIdentityRepository also return AMIdentity objects. The type of an
082 * object can be determined by doing the following:
083 * <p>
084 *
085 * <PRE>
086 *
087 * IdType type = identity.getType();
088 *
089 * </PRE>
090 *
091 * The name of an object can be determined by:
092 * <p>
093 *
094 * <PRE>
095 *
096 * String name = identity.getName();
097 *
098 * </PRE>
099 *
100 * @supported.api
101 */
102
103public class AMIdentity {
104
105    private String univIdWithoutDN;
106
107    private final SSOToken token;
108
109    private final String name;
110
111    private final IdType type;
112
113    private final String orgName;
114
115    private Set fullyQualifiedNames;
116
117    private final AMHashMap modMap = new AMHashMap(false);
118
119    private final AMHashMap binaryModMap = new AMHashMap(true);
120
121    protected String univDN = null;
122
123    /**
124     * @supported.api
125     *
126     * Constructor for the <code>AMIdentity</code> object.
127     *
128     * @param ssotoken
129     *            Single sign on token of the user
130     * @throws SSOException
131     *             if user's single sign on token is invalid.
132     * @throws IdRepoException
133     *            if the single sign on token does not have a
134     *            a valid universal identifier
135     */
136    public AMIdentity(SSOToken ssotoken) throws SSOException, IdRepoException {
137        this(ssotoken, ssotoken.getProperty(Constants.UNIVERSAL_IDENTIFIER));
138    }
139
140    /**
141     * @supported.api
142     *
143     * Constructor for the <code>AMIdentity</code> object.
144     *
145     * @param ssotoken
146     *            Single sign on token to construct the identity
147     *            object. Access permission to Identity object
148     *            would be based on this user
149     * @param universalId
150     *            Universal Identifier of the identity.
151     *
152     * @throws IdRepoException
153     *            if the universal identifier is invalid
154     *
155     */
156    public AMIdentity(SSOToken ssotoken, String universalId)
157        throws IdRepoException {
158        this(new DN(universalId), ssotoken);
159    }
160
161    public AMIdentity(DN universalId, SSOToken ssotoken) throws IdRepoException {
162        this.token = ssotoken;
163        // Validate Universal ID
164        String[] array = null;
165        if (universalId != null && universalId.isDN()
166                && ("id".equals(((RDN) universalId.getRDNs().get(0)).getType().toLowerCase()))) {
167            array = universalId.explodeDN(true);
168        }
169        if (array == null || array.length < 3) {
170            // Not a valid UUID since it should have the
171            // name, type and realm components
172            Object args[] = { universalId };
173            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, "215", args);
174        }
175
176        // Valid UUID, construct rest of the parameters
177        univIdWithoutDN = universalId.toRFCString();
178
179        // Check for AMSDK DN
180        int index;
181        if ((index = univIdWithoutDN.toLowerCase().indexOf(",amsdkdn=")) != -1) {
182            // obtain DN and univIdWithoutDN
183            univDN = univIdWithoutDN.substring(index + 9);
184            univIdWithoutDN = univIdWithoutDN.substring(0, index);
185            universalId = new DN(univIdWithoutDN);
186        }
187        name = LDAPDN.unEscapeValue(array[0]);
188        type = new IdType(array[1]);
189        orgName = universalId.getParent().getParent().toRFCString();
190    }
191
192    /**
193     * Constructor for the <code>AMIdentity</code> object.
194     *
195     * @param token
196     *            Single sign on token to construct the identity
197     *            object. Access permission to Identity object
198     *            would be based on this user
199     * @param name
200     *            the name associated with this identity.
201     * @param type
202     *            the <code>IdType</code> of this identity.
203     * @param orgName
204     *            the organizaton name this identity belongs to.
205     * @param amsdkdn
206     *            the amsdk name assoicated with this identity if any.
207     */
208    public AMIdentity(SSOToken token, String name, IdType type, String orgName, String amsdkdn) {
209        this(new DN(amsdkdn), token, name, type, orgName);
210    }
211
212    public AMIdentity(DN amsdkdn, SSOToken token, String name, IdType type, String orgName) {
213        this.name = name;
214        this.type = type;
215        this.orgName = DNMapper.orgNameToDN(orgName);
216        this.token = token;
217        if (amsdkdn != null && amsdkdn.isDN()) {
218            this.univDN = amsdkdn.toRFCString();
219        }
220        StringBuilder sb = new StringBuilder(100);
221        if (name != null) {
222            DN nameDN = new DN(name);
223            if (nameDN.isDN()) {
224                name = LDAPDN.unEscapeValue(LDAPDN.explodeDN(nameDN, true)[0]);
225            }
226        }
227        sb.append("id=").append(LDAPDN.escapeValue(name)).append(",ou=").append(type.getName()).append(",")
228                .append(this.orgName);
229
230        univIdWithoutDN = sb.toString();
231    }
232
233    // General APIs
234    /**
235     *
236     * Returns the name of the identity.
237     *
238     * @return Name of the identity
239     * @supported.api
240     */
241    public String getName() {
242        String sname = name;
243        if (type.equals(IdType.REALM)) {
244            // Since '0'th location currently has ContainerDefaultTemplate
245            // the 2nd location would have the realm name
246            String[] array = (new DN(univIdWithoutDN)).explodeDN(true);
247            sname = array[2];
248        }
249        return sname;
250    }
251
252    /**
253     * Returns the Type of the Identity.
254     *
255     * @return <code>IdType</code> representing the type of this object.
256     * @supported.api
257     */
258    public IdType getType() {
259        return type;
260    }
261
262    /**
263     * Returns the realm for this identity.
264     *
265     * @return String representing realm name.
266     * @supported.api
267     */
268    public String getRealm() {
269        return orgName;
270    }
271
272    /**
273     * If there is a status attribute configured, then verifies if the identity
274     * is active and returns true. This method is only valid for AMIdentity
275     * objects of type User and Agent.
276     *
277     * @return true if the identity is active or if it is not configured for a
278     *         status attribute, false otherwise.
279     * @throws IdRepoException
280     *             If there are repository related error conditions.
281     * @throws SSOException
282     *             If user's single sign on token is invalid.
283     * @supported.api
284     */
285    public boolean isActive() throws IdRepoException, SSOException {
286        IdServices idServices = IdServicesFactory.getDataStoreServices();
287        return idServices.isActive(token, type, name, orgName, univDN);
288    }
289
290    /**
291     * If there is a status attribute configured, then set its status to
292     * true or activated state if the parameter active is true.
293     * This method is only valid for AMIdentity objects of type User and Agent.
294     *
295     * @param active The state value to assign to status attribute. The actual
296     * value assigned to the status attribute will depend on what is configured
297     * for that particular plugin.  If active is true, the status will be
298     * assigned the value corresponding to activated.
299     * @throws IdRepoException If there are repository related error conditions.
300     * @throws SSOException If user's single sign on token is invalid.
301     * @supported.api
302     */
303    public void setActiveStatus(boolean active)
304        throws IdRepoException, SSOException {
305        IdServices idServices =
306            IdServicesFactory.getDataStoreServices();
307        idServices.setActiveStatus(token, type, name, orgName, univDN, active);
308    }
309
310    /**
311     * Returns all attributes and values of this identity. This method is only
312     * valid for AMIdentity objects of type User, Agent, Group, and Role.
313     *
314     * @return Map of attribute-values
315     * @throws IdRepoException
316     *             If there are repository related error conditions.
317     * @throws SSOException
318     *             If user's single sign on token is invalid.
319     * @supported.api
320     */
321    public Map getAttributes() throws IdRepoException, SSOException {
322
323        IdServices idServices = IdServicesFactory.getDataStoreServices();
324        Map attrs = idServices
325                .getAttributes(token, type, name, orgName, univDN);
326        if (debug.messageEnabled()) {
327            debug.message("AMIdentity.getAttributes all: attrs=" +
328                IdRepoUtils.getAttrMapWithoutPasswordAttrs(attrs, null));
329        }
330        return attrs;
331    }
332
333    /**
334     * Returns requested attributes and values of this object.
335     *
336     * This method is only valid for AMIdentity object of type User, Agent,
337     * Group, and Role.
338     *
339     * @param attrNames
340     *            Set of attribute names to be read
341     * @return Map of attribute-values.
342     * @throws IdRepoException
343     *             If there are repository related error conditions.
344     * @throws SSOException
345     *             If user's single sign on token is invalid.
346     * @supported.api
347     */
348    public Map getAttributes(Set attrNames) throws IdRepoException,
349            SSOException {
350
351        IdServices idServices = IdServicesFactory.getDataStoreServices();
352        Map attrs = idServices.getAttributes(token, type, name, attrNames,
353                orgName, univDN, true);
354        CaseInsensitiveHashMap caseAttrs = new CaseInsensitiveHashMap(attrs);
355        CaseInsensitiveHashMap resultMap = new CaseInsensitiveHashMap();
356        Iterator it = attrNames.iterator();
357        while (it.hasNext()) {
358            String attrName = (String) it.next();
359            if (caseAttrs.containsKey(attrName)) {
360                resultMap.put(attrName, caseAttrs.get(attrName));
361            }
362        }
363
364        if (debug.messageEnabled()) {
365            debug.message("AMIdentity.getAttributes 6: attrNames=" + attrNames
366                    + ";  resultMap=" + resultMap + "; attrs=" + attrs);
367        }
368        return resultMap;
369    }
370
371    /**
372     * Returns requested attributes and values of this object.
373     *
374     * This method is only valid for AMIdentity objects of type User, Agent,
375     * Group, and Role.
376     *
377     * @param attrNames
378     *            Set of attribute names to be read
379     * @return Map of attribute-values.
380     * @throws IdRepoException
381     *             If there are repository related error conditions.
382     * @throws SSOException
383     *             If user's single sign on token is invalid.
384     * @supported.api
385     */
386    public Map getBinaryAttributes(Set attrNames) throws IdRepoException,
387            SSOException {
388
389        IdServices idServices = IdServicesFactory.getDataStoreServices();
390        return idServices.getAttributes(token, type, name, attrNames, orgName,
391                univDN, false);
392    }
393
394    /**
395     * Returns the values of the requested attribute. Returns an empty set, if
396     * the attribute is not set in the object.
397     *
398     * This method is only valid for AMIdentity objects of type User, Agent,
399     * Group, and Role.
400     *
401     * @param attrName
402     *            Name of attribute
403     * @return Set of attribute values.
404     * @throws IdRepoException
405     *             if there are repository related error conditions.
406     * @throws SSOException
407     *             If user's single sign on token is invalid.
408     * @supported.api
409     */
410    public Set getAttribute(String attrName) throws IdRepoException,
411            SSOException {
412
413        Set attrNames = new HashSet();
414        attrNames.add(attrName);
415        IdServices idServices = IdServicesFactory.getDataStoreServices();
416        Map valMap = idServices.getAttributes(token, type, name, attrNames,
417                orgName, univDN, true);
418        return ((Set) valMap.get(attrName));
419    }
420
421    /**
422     * Sets the values of attributes. This method should be followed by the
423     * method "store" to commit the changes to the Repository.
424     * This method is only valid for <code>AMIdentity</code> objects of
425     * type User and Agent.
426     *
427     * @param attrMap is a map of attribute name
428     *        <code>(String)</code>
429     *        to a <code>Set</code> of attribute values <code>(String)</code>.
430     *        It is arranged as:
431     *        Map::attrMap -->
432     *        Key: String::AttributeName
433     *        Value: Set::AttributeValues (Set of String)
434     * @throws IdRepoException
435     *             If there are repository related error conditions.
436     * @throws SSOException
437     *             If user's single sign on token is invalid.
438     * @supported.api
439     */
440    public void setAttributes(Map attrMap) throws IdRepoException, SSOException
441    {
442        modMap.copy(attrMap);
443    }
444
445    /**
446     * Changes password for the identity.
447     *
448     * @param oldPassword old password
449     * @param newPassword new password
450     * @throws IdRepoException If there are repository related error conditions.
451     * @throws SSOException If user's single sign on token is invalid.
452     * @supported.api
453     */
454    public void changePassword(String oldPassword, String newPassword)
455        throws IdRepoException, SSOException {
456
457        IdServices idServices = IdServicesFactory.getDataStoreServices();
458        idServices.changePassword(token, type, name, oldPassword,
459            newPassword, orgName, getDN());
460    }
461
462    /**
463     * Set the values of binary attributes. This method should be followed by
464     * the method "store" to commit the changes to the Repository
465     *
466     * This method is only valid for AMIdentity objects of type User and Agent.
467     *
468     * @param attrMap
469     *            Map of attribute-values to be set in the repository or
470     *            repositories (if multiple plugins are configured for "edit").
471     * @throws IdRepoException
472     *             If there are repository related error conditions.
473     * @throws SSOException
474     *             If user's single sign on token is invalid.
475     * @supported.api
476     */
477    public void setBinaryAttributes(Map attrMap) throws IdRepoException,
478            SSOException {
479        binaryModMap.copy(attrMap);
480    }
481
482    /**
483     * Removes the attributes from the identity entry. This method should be
484     * followed by a "store" to commit the changes to the Repository.
485     *
486     * This method is only valid for AMIdentity objects of type User and Agent.
487     *
488     * @param attrNames
489     *            Set of attribute names to be removed
490     * @throws IdRepoException
491     *             If there are repository related error conditions.
492     * @throws SSOException
493     *             If the user's single sign on token is invalid
494     * @supported.api
495     */
496    public void removeAttributes(Set attrNames) throws IdRepoException,
497            SSOException {
498        if (attrNames == null || attrNames.isEmpty()) {
499            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, "201", null);
500        }
501
502        boolean agentflg = getType().equals(IdType.AGENTONLY);
503        if (agentflg) {
504            IdServices idServices = IdServicesFactory.getDataStoreServices();
505            idServices.removeAttributes(token, type, name, attrNames,
506                orgName, null);
507            Iterator it = attrNames.iterator();
508            while (it.hasNext()) {
509                String attr = (String) it.next();
510                modMap.remove(attr);
511            }
512        } else {
513            Iterator it = attrNames.iterator();
514            while (it.hasNext()) {
515                String attr = (String) it.next();
516                modMap.put(attr, Collections.EMPTY_SET);
517            }
518        }
519    }
520
521    /**
522     * Stores the attributes of the object.
523     *
524     * This method is only valid for AMIdentity objects of type User and Agent.
525     *
526     * @throws IdRepoException
527     *             If there are repository related error conditions.
528     * @throws SSOException
529     *             If user's single sign on token is invalid.
530     * @supported.api
531     */
532    public void store() throws IdRepoException, SSOException {
533        IdServices idServices = IdServicesFactory.getDataStoreServices();
534        if (modMap != null && !modMap.isEmpty()) {
535            idServices.setAttributes(token, type, name, modMap, false, orgName,
536                    univDN, true);
537            modMap.clear();
538        }
539        if (binaryModMap != null && !binaryModMap.isEmpty()) {
540            idServices.setAttributes(token, type, name, binaryModMap, false,
541                    orgName, univDN, false);
542            binaryModMap.clear();
543        }
544    }
545
546    // SERVICE RELATED APIS
547
548    /**
549     * Returns the set of services already assigned to this identity.
550     *
551     * This method is only valid for AMIdentity object of type User.
552     *
553     * @return Set of serviceNames
554     * @throws IdRepoException
555     *             If there are repository related error conditions.
556     * @throws SSOException
557     *             If user's single sign on token is invalid.
558     * @supported.api
559     */
560    public Set getAssignedServices() throws IdRepoException, SSOException {
561        // Get all service names for the type from SMS
562        ServiceManager sm;
563        try {
564            sm = new ServiceManager(token);
565        } catch (SMSException smse) {
566            debug.error("Error while creating Service manager:", smse);
567            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, "106", null);
568        }
569        Map sMap = sm.getServiceNamesAndOCs(type.getName());
570
571        // Get the list of assigned services
572        IdServices idServices = IdServicesFactory.getDataStoreServices();
573        Set assigned = Collections.EMPTY_SET;
574        try {
575            assigned = idServices.getAssignedServices(token, type, name, sMap,
576                    orgName, univDN);
577        } catch (IdRepoException ide) {
578            // Check if this is permission denied exception
579            if (!ide.getErrorCode().equals("402")) {
580                throw (ide);
581            }
582        }
583        return (assigned);
584    }
585
586    /**
587     * Returns all services which can be assigned to this entity.
588     *
589     * This method is only valid for AMIdentity object of type User.
590     *
591     * @return Set of service names
592     * @throws IdRepoException
593     *             if there are repository related error conditions.
594     * @throws SSOException
595     *             If user's single sign on token is invalid.
596     * @supported.api
597     */
598    public Set getAssignableServices() throws IdRepoException, SSOException {
599        // Get all service names for the type from SMS
600        ServiceManager sm;
601        try {
602            sm = new ServiceManager(token);
603        } catch (SMSException smse) {
604            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, "106", null);
605        }
606        Map sMap = sm.getServiceNamesAndOCs(type.getName());
607
608        // Get the list of assigned services
609        IdServices idServices = IdServicesFactory.getDataStoreServices();
610        Set assigned = Collections.EMPTY_SET;
611        try {
612            assigned = idServices.getAssignedServices(token, type, name, sMap,
613                    orgName, univDN);
614        } catch (IdRepoException ide) {
615            // Check if this is permission denied exception
616            if (!ide.getErrorCode().equals("402")) {
617                throw (ide);
618            } else {
619                // Return the empty set
620                return (assigned);
621            }
622        }
623
624        // Return the difference
625        Set keys = sMap.keySet();
626        keys.removeAll(assigned);
627        return (keys);
628
629    }
630
631    /**
632     * Assigns the service and service related attributes to the identity.
633     *
634     * This method is only valid for AMIdentity object of type User.
635     *
636     * @param serviceName
637     *            Name of service to be assigned.
638     * @param attributes
639     *            Map of attribute-values
640     * @throws IdRepoException
641     *             If there are repository related error conditions.
642     * @throws SSOException
643     *             If user's single sign on token is invalid.
644     * @supported.api
645     */
646    public void assignService(String serviceName, Map attributes)
647            throws IdRepoException, SSOException {
648
649        IdServices idServices = IdServicesFactory.getDataStoreServices();
650        Set OCs = getServiceOCs(token, serviceName);
651        SchemaType stype;
652        Map tMap = new HashMap();
653        tMap.put(serviceName, OCs);
654        Set assignedServices = idServices.getAssignedServices(token, type,
655                name, tMap, orgName, univDN);
656
657        if (assignedServices.contains(serviceName)) {
658            Object args[] = { serviceName, type.getName() };
659            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, "105", args);
660        }
661
662        // Validate the service attributes
663        try {
664            ServiceSchemaManager ssm = new ServiceSchemaManager(serviceName,
665                    token);
666            ServiceSchema ss = ssm.getSchema(type.getName());
667
668            if (ss != null) {
669                // Check if attrMap has cos priority attribute
670                // If present, remove it for validating the attributes
671                Set cosPriority = (attributes != null) ?
672                    (Set)attributes.remove(COS_PRIORITY) : null;
673                attributes = ss.validateAndInheritDefaults(attributes, orgName,
674                        true);
675                if (cosPriority != null) {
676                    attributes.put(COS_PRIORITY, cosPriority);
677                }
678                attributes = AMCommonUtils.removeEmptyValues(attributes);
679                stype = ss.getServiceType();
680            } else {
681                ss = ssm.getSchema(SchemaType.DYNAMIC);
682                if (ss == null) {
683                    Object args[] = { serviceName };
684                    throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, "102",
685                            args);
686                }
687                if (attributes == null) {
688                    try {
689                        attributes = getServiceConfig(token, serviceName,
690                                SchemaType.DYNAMIC);
691                    } catch (SMSException smsex) {
692                        Object args[] = { serviceName, type.getName() };
693                        throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
694                                "451", args);
695                    }
696                } else {
697                    attributes = ss.validateAndInheritDefaults(attributes,
698                            orgName, true);
699                }
700                attributes = AMCommonUtils.removeEmptyValues(attributes);
701                stype = SchemaType.DYNAMIC;
702            }
703
704            // TODO: Remove this dependency of AMCrypt
705            attributes = AMCrypt.encryptPasswords(attributes, ss);
706        } catch (SMSException smse) {
707            // debug.error here
708            Object[] args = { serviceName };
709            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, "101", args);
710        }
711
712        attributes.put("objectclass", OCs);
713        // The protocol for params is to pass the
714        // name of the service, and attribute Map containing the
715        // OCs to be set and validated attribute map
716        idServices.assignService(token, type, name, serviceName, stype,
717                attributes, orgName, univDN);
718    }
719
720    /**
721     * Removes a service from the identity.
722     *
723     * This method is only valid for AMIdentity object of type User.
724     *
725     * @param serviceName
726     *            Name of service to be removed.
727     * @throws IdRepoException
728     *             If there are repository related error conditions.
729     * @throws SSOException
730     *             If user's single sign on token is invalid.
731     * @supported.api
732     */
733    public void unassignService(String serviceName) throws IdRepoException,
734            SSOException {
735        IdServices idServices = IdServicesFactory.getDataStoreServices();
736        Set OCs = getServiceOCs(token, serviceName);
737
738        Map tMap = new HashMap();
739        tMap.put(serviceName, OCs);
740        Set assignedServices = idServices.getAssignedServices(token, type,
741                name, tMap, orgName, univDN);
742
743        if (!assignedServices.contains(serviceName)) {
744            Object args[] = { serviceName };
745            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, "101", args);
746        }
747
748        Map attrMap = new HashMap();
749        Set objectclasses = getAttribute("objectclass");
750        if (objectclasses != null && !objectclasses.isEmpty()) {
751            Set removeOCs = AMCommonUtils.updateAndGetRemovableOCs(
752                    objectclasses, OCs);
753
754            try {
755                // Get attribute names for USER type only, so plugin knows
756                // what attributes to remove.
757                Set attrNames = new HashSet();
758                ServiceSchemaManager ssm = new ServiceSchemaManager(
759                        serviceName, token);
760                ServiceSchema uss = ssm.getSchema(type.getName());
761
762                if (uss != null) {
763                    attrNames = uss.getAttributeSchemaNames();
764                }
765
766                Iterator it = attrNames.iterator();
767                while (it.hasNext()) {
768                    String a = (String) it.next();
769                    attrMap.put(a, Collections.EMPTY_SET);
770                }
771            } catch (SMSException smse) {
772                /*
773                 * debug.error( "AMIdentity.unassignService: Caught SM
774                 * exception", smse); do nothing
775                 */
776            }
777
778            attrMap.put("objectclass", removeOCs);
779            // The protocol is to pass service Name and Map of objectclasses
780            // to be removed from entry.
781        }
782
783        idServices.unassignService(token, type, name, serviceName, attrMap,
784                orgName, univDN);
785    }
786
787    /**
788     * Returns attributes related to a service, if the service is assigned to
789     * the identity.
790     *
791     * This method is only valid for AMIdentity object of type User.
792     *
793     * @param serviceName
794     *            Name of the service.
795     * @return Map of attribute-values.
796     * @throws IdRepoException
797     *             if there are repository related error conditions.
798     * @throws SSOException
799     *             If user's single sign on token is invalid.
800     * @supported.api
801     */
802    public Map getServiceAttributes(String serviceName)
803        throws IdRepoException, SSOException {
804        Set attrNames = getServiceAttributesName(serviceName);
805
806        IdServices idServices =
807            IdServicesFactory.getDataStoreServices();
808        if (debug.messageEnabled()) {
809            debug.message("AMIdentity.getServiceAttributes: attrNames="
810                + attrNames + ";  orgName=" + orgName + ";  univDN=" + univDN);
811       }
812        return idServices.getServiceAttributes(token, type, name, serviceName,
813            attrNames, orgName, univDN);
814    }
815
816
817    /**
818     * Returns attributes related to a service, if the service is assigned
819     * to the identity.
820     *
821     * This method is only valid for AMIdentity object of type User.
822     *
823     * @param serviceName Name of the service.
824     * @return Map of attribute-values in array of byte.
825     * @throws IdRepoException if there are repository related error conditions.
826     * @throws SSOException If user's single sign on token is invalid.
827     * iPlanet-PUBLIC-METHOD
828     */
829    public Map getBinaryServiceAttributes(String serviceName)
830        throws IdRepoException, SSOException {
831        Set attrNames = getServiceAttributesName(serviceName);
832
833        IdServices idServices =
834            IdServicesFactory.getDataStoreServices();
835        if (debug.messageEnabled()) {
836            debug.message("AMIdentity.getBinaryServiceAttributes: attrNames="
837                + attrNames + ";  orgName=" + orgName + ";  univDN=" + univDN);
838        }
839        return idServices.getBinaryServiceAttributes(token, type, name,
840            serviceName, attrNames, orgName, univDN);
841    }
842
843
844    /**
845     * Returns attributes related to a service, if the service is assigned
846     * to the identity.
847     *
848     * This method is only valid for AMIdentity object of type User.
849     *
850     * @param serviceName Name of the service.
851     * @return Map of attribute-values.
852     * @throws IdRepoException if there are repository related error conditions.
853     * @throws SSOException If user's single sign on token is invalid.
854     * @supported.api
855     */
856    public Map getServiceAttributesAscending(String serviceName)
857        throws IdRepoException, SSOException {
858        Set attrNames = getServiceAttributesName(serviceName);
859
860        IdServices idServices =
861            IdServicesFactory.getDataStoreServices();
862        if (debug.messageEnabled()) {
863            debug.message("AMIdentity.getServiceAttributesAscending: "
864                + "attrNames=" + attrNames + ";  orgName=" + orgName
865                + ";  univDN=" + univDN);
866        }
867        return idServices.getServiceAttributesAscending(token, type, name,
868            serviceName, attrNames, orgName, univDN);
869    }
870
871
872    /**
873     * Set attributes related to a specific service. The assumption is that the
874     * service is already assigned to the identity. The attributes for the
875     * service are validated against the service schema.
876     *
877     * This method is only valid for AMIdentity object of type User.
878     *
879     * @param serviceName
880     *            Name of the service.
881     * @param attrMap
882     *            Map of attribute-values.
883     * @throws IdRepoException
884     *             If there are repository related error conditions.
885     * @throws SSOException
886     *             If user's single sign on token is invalid.
887     * @supported.api
888     */
889    public void modifyService(String serviceName, Map attrMap)
890            throws IdRepoException, SSOException {
891        IdServices idServices = IdServicesFactory.getDataStoreServices();
892        Set OCs = getServiceOCs(token, serviceName);
893        SchemaType stype;
894        Map tMap = new HashMap();
895        tMap.put(serviceName, OCs);
896        Set assignedServices = idServices.getAssignedServices(token, type,
897                name, tMap, orgName, univDN);
898        if (!assignedServices.contains(serviceName)) {
899            Object args[] = { serviceName };
900            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, "101", args);
901        }
902
903        // Check if attrMap has cos priority attribute
904        // If present, remove it for validating the attributes
905        boolean hasCosPriority = (new CaseInsensitiveHashSet(
906            attrMap.keySet()).contains(COS_PRIORITY));
907        Object values = null;
908        if (hasCosPriority) {
909             attrMap = new CaseInsensitiveHashMap(attrMap);
910             values = attrMap.remove(COS_PRIORITY);
911        }
912
913        // Validate the attributes
914        try {
915            ServiceSchemaManager ssm = new ServiceSchemaManager(serviceName,
916                    token);
917            ServiceSchema ss = ssm.getSchema(type.getName());
918            if (ss != null) {
919                attrMap = ss.validateAndInheritDefaults(attrMap, false);
920                stype = ss.getServiceType();
921            } else if ((ss = ssm.getSchema(SchemaType.DYNAMIC)) != null) {
922                 attrMap = ss.validateAndInheritDefaults(attrMap, false);
923                 stype = SchemaType.DYNAMIC;
924            } else {
925                 Object args[] = { serviceName };
926                 throw new IdRepoException(IdRepoBundle.BUNDLE_NAME,
927                     "102", args);
928            }
929        } catch (SMSException smse) {
930            // debug.error
931            Object args[] = { serviceName };
932            throw new IdRepoException(IdRepoBundle.BUNDLE_NAME, "103", args);
933        }
934
935        // Add COS priority if present
936        if (hasCosPriority) {
937            attrMap.put(COS_PRIORITY, values);
938        }
939
940        // modify service attrs
941        if (debug.messageEnabled()) {
942            debug.message("AMIdentity.modifyService befre idService " +
943                "serviceName=" + serviceName + ";  attrMap=" + attrMap);
944        }
945        idServices.modifyService(token, type, name, serviceName, stype,
946            attrMap, orgName, univDN);
947    }
948
949
950    /**
951
952     * Removes attributes value related to a specific service by
953     * setting it to empty.
954     * The assumption is that the service is already assigned to
955     * the identity. The attributes for the service are validated
956     * against the service schema.
957     *
958     * This method is only valid for <AMIdentity> object of type User.
959     *
960     * @param serviceName Name of the service.
961     * @param attrNames Set of attributes name.
962     * @throws IdRepoException If there are repository related error conditions.
963     * @throws SSOException If user's single sign on token is invalid.
964     * @supported.api
965     */
966    public void removeServiceAttributes(String serviceName, Set attrNames)
967        throws IdRepoException, SSOException {
968        Map attrMap = new HashMap(attrNames.size() *2);
969        Iterator it = attrNames.iterator();
970        while (it.hasNext()) {
971            String attrName = (String) it.next();
972            attrMap.put(attrName, Collections.EMPTY_SET);
973        }
974        modifyService(serviceName, attrMap);
975    }
976
977
978    // MEMBERSHIP RELATED APIS
979    /**
980     * Verifies if this identity is a member of the identity being passed.
981     *
982     * This method is only valid for AMIdentity objects of type Role, Group and
983     * User.
984     *
985     * @param identity
986     *            <code>AMIdentity</code> to check membership with
987     * @return true if this Identity is a member of the given Identity
988     * @throws IdRepoException
989     *             if there are repository related error conditions.
990     * @throws SSOException
991     *             if user's single sign on token is invalid.
992     * @supported.api
993     */
994    public boolean isMember(AMIdentity identity) throws IdRepoException,
995            SSOException {
996        boolean ismember = false;
997        IdRepoException idException = null;
998        IdServices idServices = IdServicesFactory.getDataStoreServices();
999        try {
1000            //This method should always retrieve all the membership information a user could possibly have (either
1001            //through the user when memberOf attribute is defined, or through the group using uniquemember attribute),
1002            //hence there is no need to try to look up the group and query its members to see if this given identity
1003            //is in that list.
1004            //Generally speaking, this should be the case for every IdRepo implementation -> when we ask for the user
1005            //memberships, we should always get all of them for the sake of consistency.
1006            Set members = idServices.getMemberships(token, getType(),
1007                    getName(), identity.getType(), orgName, getDN());
1008            if (members != null && members.contains(identity)) {
1009                ismember = true;
1010            } else if (members != null) {
1011                // Check for fully qualified names or
1012                // if AM SDK DNs for these identities match
1013                String dn = identity.getDN();
1014                Iterator it = members.iterator();
1015                while (it.hasNext()) {
1016                    AMIdentity id = (AMIdentity) it.next();
1017                    if (identity.equals(id)) {
1018                        ismember = true;
1019                        break;
1020                    } else if (dn != null) {
1021                        String mdn = id.getDN();
1022                        if ((mdn != null) && mdn.equalsIgnoreCase(dn)) {
1023                            ismember = true;
1024                            break;
1025                        }
1026                    }
1027                }
1028            }
1029
1030            // If membership is still false, check only the UUID
1031            // without the amsdkdn
1032            if (!ismember && members != null && !members.isEmpty()) {
1033                // Get UUID without amsdkdn for "membership" identity
1034                String identityDN = identity.getUniversalId();
1035                String amsdkdn = identity.getDN();
1036                if ((amsdkdn != null) &&
1037                    (identityDN.toLowerCase().indexOf(",amsdkdn=") != -1)) {
1038                    identityDN = identityDN.substring(0, identityDN
1039                            .indexOf(amsdkdn) - 9);
1040                }
1041                // Get UUID without amsdkdn for users memberships
1042                Iterator it = members.iterator();
1043                while (it.hasNext()) {
1044                    AMIdentity id = (AMIdentity) it.next();
1045                    String idDN = id.getUniversalId();
1046                    String mdn = id.getDN();
1047                    if (mdn != null) {
1048                        int endIdx = idDN.indexOf(mdn) - 9;
1049                        if (endIdx >= 0) {
1050                            idDN = idDN.substring(0, endIdx);
1051                        }
1052                    }
1053                    if (idDN.equalsIgnoreCase(identityDN)) {
1054                        ismember = true;
1055                        break;
1056                    }
1057                }
1058            }
1059
1060        } catch (IdRepoException ide) {
1061            // Save the exception to be used later
1062            idException = ide;
1063        }
1064
1065        if (idException != null) {
1066            throw (idException);
1067        }
1068        return ismember;
1069    }
1070
1071    /**
1072     * @supported.api
1073     *
1074     * If membership is supported then add the new identity as a member.
1075     *
1076     * @param identity
1077     *            AMIdentity to be added
1078     * @throws IdRepoException
1079     *             if there are repository related error conditions.
1080     * @throws SSOException
1081     *             if user's single sign on token is invalid. non-public methods
1082     */
1083    public void addMember(AMIdentity identity) throws IdRepoException,
1084            SSOException {
1085        IdServices idServices = IdServicesFactory.getDataStoreServices();
1086        Set members = new HashSet();
1087        members.add(identity.getName());
1088        idServices.modifyMemberShip(token, type, name, members, identity
1089                .getType(), IdRepo.ADDMEMBER, orgName);
1090    }
1091
1092    /**
1093     * @supported.api
1094     *
1095     * Removes the identity from this identity's membership.
1096     *
1097     * @param identity
1098     *            AMIdentity to be removed from membership.
1099     * @throws IdRepoException
1100     *             if there are repository related error conditions.
1101     * @throws SSOException
1102     *             if user's single sign on token is invalid. non-public methods
1103     */
1104    public void removeMember(AMIdentity identity) throws IdRepoException,
1105            SSOException {
1106        IdServices idServices = IdServicesFactory.getDataStoreServices();
1107        Set members = new HashSet();
1108        members.add(identity.getName());
1109        idServices.modifyMemberShip(token, type, name, members, identity
1110                .getType(), IdRepo.REMOVEMEMBER, orgName);
1111    }
1112
1113    /**
1114     * @supported.api
1115     *
1116     * Removes the identities from this identity's membership.
1117     *
1118     * @param identityObjects
1119     *            Set of AMIdentity objects
1120     * @throws IdRepoException
1121     *             if there are repository related error conditions.
1122     * @throws SSOException
1123     *             if user's single sign on token is invalid. non-public methods
1124     */
1125    public void removeMembers(Set identityObjects) throws IdRepoException,
1126            SSOException {
1127        IdServices idServices = IdServicesFactory.getDataStoreServices();
1128        Set members = new HashSet();
1129        Iterator it = identityObjects.iterator();
1130
1131        while (it.hasNext()) {
1132            AMIdentity identity = (AMIdentity) it.next();
1133            members.add(identity.getName());
1134            idServices.modifyMemberShip(token, type, name, members, identity
1135                    .getType(), IdRepo.REMOVEMEMBER, orgName);
1136            members = new HashSet();
1137        }
1138    }
1139
1140    /**
1141     * Return all members of a given identity type of this identity as a Set of
1142     * AMIdentity objects.
1143     *
1144     * This method is only valid for AMIdentity objects of type Group and User.
1145     *
1146     * @param mtype
1147     *            Type of identity objects
1148     * @return Set of AMIdentity objects that are members of this object.
1149     * @throws IdRepoException
1150     *             if there are repository related error conditions.
1151     * @throws SSOException
1152     *             if user's single sign on token is invalid.
1153     * @supported.api
1154     */
1155    public Set getMembers(IdType mtype) throws IdRepoException, SSOException {
1156        IdServices idServices = IdServicesFactory.getDataStoreServices();
1157        return idServices
1158                .getMembers(token, type, name, orgName, mtype, getDN());
1159    }
1160
1161    /**
1162     * Returns the set of identities that this identity belongs to.
1163     *
1164     * This method is only valid for AMIdentity objects of type User and Role.
1165     *
1166     * @param mtype
1167     *            Type of member identity.
1168     * @return Set of AMIdentity objects of the given type that this identity
1169     *         belongs to.
1170     * @throws IdRepoException
1171     *             if there are repository related error conditions.
1172     * @throws SSOException
1173     *             if user's single sign on token is invalid.
1174     * @supported.api
1175     */
1176    public Set getMemberships(IdType mtype) throws IdRepoException,
1177            SSOException {
1178        IdServices idServices = IdServicesFactory.getDataStoreServices();
1179        return idServices.getMemberships(token, type, name, mtype, orgName,
1180                getDN());
1181    }
1182
1183    /**
1184     * This method determines if the identity exists and returns true or false.
1185     *
1186     * This method is only valid for AMIdentity objects of type User and Agent.
1187     *
1188     * @return true if the identity exists or false otherwise.
1189     * @throws IdRepoException
1190     *             If there are repository related error conditions.
1191     * @throws SSOException
1192     *             If user's single sign on token is invalid.
1193     * @supported.api
1194     */
1195    public boolean isExists() throws IdRepoException, SSOException {
1196        IdServices idServices = IdServicesFactory.getDataStoreServices();
1197        return idServices.isExists(token, type, name, orgName);
1198    }
1199
1200    /**
1201     * Returns <code>true</code> if the given object is equal to this object.
1202     *
1203     * @param o Object for comparison.
1204     * @return <code>true</code> if the given object is equal to this object.
1205     * @supported.api
1206     */
1207    @Override
1208    public boolean equals(Object o) {
1209        boolean isEqual = false;
1210        if (o instanceof AMIdentity) {
1211            AMIdentity compareTo = (AMIdentity) o;
1212            if (univIdWithoutDN.equalsIgnoreCase(
1213                compareTo.univIdWithoutDN)) {
1214                isEqual = true;
1215            } else if (univDN != null) {
1216                // check if the amsdkdn match
1217                String dn = compareTo.getDN();
1218                if (dn != null && dn.equalsIgnoreCase(univDN)) {
1219                    isEqual = true;
1220                }
1221            }
1222
1223            if (!isEqual && !type.equals(IdType.REALM) &&
1224                type.equals(compareTo.getType())) {
1225                // Check fully qualified names
1226                Set sfqn = getFullyQualifiedNames();
1227                Set cfqn = compareTo.getFullyQualifiedNames();
1228                if ((sfqn != null) && (cfqn != null) &&
1229                    !sfqn.isEmpty() && !cfqn.isEmpty()) {
1230                    for (Iterator items = sfqn.iterator();
1231                        items.hasNext();) {
1232                        String next = (String)items.next();
1233                        if (next != null && cfqn.contains(next)) {
1234                            isEqual = true;
1235                            break;
1236                        }
1237                    }
1238                }
1239            }
1240        }
1241        return (isEqual);
1242    }
1243
1244    /**
1245     * Non-javadoc, non-public methods
1246     */
1247    @Override
1248    public int hashCode() {
1249        return (univIdWithoutDN.toLowerCase().hashCode());
1250    }
1251
1252    /**
1253     * Nonjavadoc, non-public methods
1254     *
1255     */
1256    public void setDN(String dn) {
1257        univDN = dn;
1258    }
1259
1260    /**
1261     * Returns universal distinguished name of this object.
1262     *
1263     * @return universal distinguished name of this object.
1264     */
1265    public String getDN() {
1266        return univDN;
1267    }
1268
1269    /**
1270     * Returns the universal identifier of this object.
1271     *
1272     * @return String representing the universal identifier of this object.
1273     * @supported.api
1274     */
1275    public String getUniversalId() {
1276        return univIdWithoutDN;
1277    }
1278
1279    /**
1280     * Returns String representation of the <code>AMIdentity</code>
1281     * object. It returns universal identifier, orgname, type, etc.
1282     *
1283     * @return String representation of the <code>ServiceConfig</code> object.
1284     */
1285    @Override
1286    public String toString() {
1287        StringBuilder sb = new StringBuilder(100);
1288        sb.append("AMIdentity object: ").append(univIdWithoutDN);
1289        if (univDN != null) {
1290            sb.append("AMSDKDN=").append(univDN);
1291        }
1292        return (sb.toString());
1293    }
1294
1295    // Returns a set of fully qulified names, as returned by DataStores
1296    protected Set getFullyQualifiedNames() {
1297        if (fullyQualifiedNames == null) {
1298            try {
1299                IdServices idServices =
1300                    IdServicesFactory.getDataStoreServices();
1301                fullyQualifiedNames = idServices.getFullyQualifiedNames(
1302                    token, type, name, orgName);
1303            } catch (IdRepoException ire) {
1304                if (debug.messageEnabled()) {
1305                    debug.message("AMIdentity:getFullyQualifiedNames: " +
1306                        "got exception: ", ire);
1307                }
1308            } catch (SSOException ssoe) {
1309                if (debug.messageEnabled()) {
1310                    debug.message("AMIdentity:getFullyQualifiedNames: " +
1311                        "got exception: ", ssoe);
1312                }
1313            }
1314        }
1315        return (fullyQualifiedNames);
1316    }
1317
1318    private Set getServiceOCs(SSOToken token, String serviceName)
1319            throws SSOException {
1320        Set result = new HashSet();
1321        try {
1322            if (serviceHasSubSchema(token, serviceName, SchemaType.GLOBAL)) {
1323                Map attrs = getServiceConfig(token, serviceName,
1324                        SchemaType.GLOBAL);
1325                Set vals = (Set) attrs.get("serviceObjectClasses");
1326
1327                if (vals != null) {
1328                    result.addAll(vals);
1329                }
1330            }
1331        } catch (SMSException smsex) {
1332        }
1333
1334        return result;
1335    }
1336
1337    /**
1338     * Get service default config from SMS
1339     *
1340     * @param token
1341     *            SSOToken a valid SSOToken
1342     * @param serviceName
1343     *            the service name
1344     * @param schemaType
1345     *            service schema type (Dynamic, Policy etc)
1346     * @return returns a Map of Default Configuration values for the specified
1347     *         service.
1348     */
1349    private Map getServiceConfig(SSOToken token, String serviceName,
1350            SchemaType type) throws SMSException, SSOException {
1351        Map attrMap = null; // Map of attribute/value pairs
1352        if (type != SchemaType.POLICY) {
1353            ServiceSchemaManager scm = new ServiceSchemaManager(serviceName,
1354                    token);
1355            ServiceSchema gsc = scm.getSchema(type);
1356            attrMap = gsc.getAttributeDefaults();
1357        }
1358        return attrMap;
1359    }
1360
1361    /**
1362     * Returns true if the service has the subSchema. False otherwise.
1363     *
1364     * @param token
1365     *            SSOToken a valid SSOToken
1366     * @param serviceName
1367     *            the service name
1368     * @param schemaType
1369     *            service schema type (Dynamic, Policy etc)
1370     * @return true if the service has the subSchema.
1371     */
1372    private boolean serviceHasSubSchema(SSOToken token, String serviceName,
1373            SchemaType schemaType) throws SMSException, SSOException {
1374        boolean schemaTypeFlg = false;
1375        try {
1376            ServiceSchemaManager ssm = new ServiceSchemaManager(serviceName,
1377                    token);
1378            Set types = ssm.getSchemaTypes();
1379            if (debug.messageEnabled()) {
1380                debug.message("AMServiceUtils.serviceHasSubSchema() "
1381                        + "SchemaTypes types for " + serviceName + " are: "
1382                        + types);
1383            }
1384            schemaTypeFlg = types.contains(schemaType);
1385        } catch (ServiceNotFoundException ex) {
1386            if (debug.warningEnabled()) {
1387                debug.warning("AMServiceUtils.serviceHasSubSchema() "
1388                        + "Service does not exist : " + serviceName);
1389            }
1390        }
1391        return (schemaTypeFlg);
1392    }
1393
1394    private Set getServiceAttributesName(String serviceName)
1395        throws IdRepoException, SSOException {
1396        Set attrNames = Collections.EMPTY_SET;
1397
1398        try {
1399            // Get attribute names for USER type only, so plugin knows
1400            // what attributes to remove.
1401            attrNames = new HashSet();
1402            ServiceSchemaManager ssm = new ServiceSchemaManager(
1403                serviceName, token);
1404            ServiceSchema uss = ssm.getSchema(type.getName());
1405
1406            if (uss != null) {
1407                attrNames = uss.getAttributeSchemaNames();
1408            }
1409            // If the identity type is not of role, filteredrole or
1410            // realm, need to add dynamic attributes also
1411            if (!(type.equals(IdType.ROLE) || type.equals(IdType.REALM) ||
1412                type.equals(IdType.FILTEREDROLE))) {
1413                uss = ssm.getDynamicSchema();
1414                if (uss != null) {
1415                    if (attrNames == Collections.EMPTY_SET) {
1416                        attrNames = uss.getAttributeSchemaNames();
1417                    } else {
1418                        attrNames.addAll(uss.getAttributeSchemaNames());
1419                    }
1420                }
1421            } else {
1422                // Add COS priority attribute
1423                attrNames.add(COS_PRIORITY);
1424            }
1425        } catch (SMSException smse) {
1426            if (debug.messageEnabled()) {
1427                debug.message(
1428                    "AMIdentity.getServiceAttributes: Caught SM exception",
1429                    smse);
1430            }
1431            // just returned whatever we find or empty set
1432            // if services is not found.
1433        }
1434
1435        return attrNames;
1436    }
1437
1438    private static Debug debug = Debug.getInstance("amIdm");
1439
1440    public static String COS_PRIORITY = "cospriority";
1441}




























































Copyright © 2010-2017, ForgeRock All Rights Reserved.