001/**
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2006 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: SubjectTypeManager.java,v 1.5 2009/01/28 05:35:01 ww203982 Exp $
026 *
027 */
028
029
030
031
032package com.sun.identity.policy;
033
034import java.util.*;
035
036import com.iplanet.sso.SSOToken;
037import com.iplanet.sso.SSOException;
038import com.sun.identity.sm.*;
039import com.sun.identity.policy.interfaces.Subject;
040import com.sun.identity.shared.locale.AMResourceBundleCache;
041import com.sun.identity.shared.debug.Debug;
042import com.sun.identity.shared.locale.Locale;
043
044import com.sun.identity.shared.ldap.util.DN;
045
046/**
047 * The class <code>SubjectTypeManager</code> provides
048 * methods to get a list of configured <code>Subject
049 * </code> objects, and to obtain a factory object for it.
050 *
051 * @supported.all.api
052 */
053public class SubjectTypeManager {
054
055    private static String SUBJECT = "Subject";
056
057    private SSOToken token;
058    private PolicyManager pm;
059
060    private ResourceBundle rb;
061    private Subjects realmSubjects = null;
062    private Map sharedSubjects = Collections.synchronizedMap(new HashMap());
063    private static AMResourceBundleCache amCache = 
064            AMResourceBundleCache.getInstance();
065    private String pmRealmName;
066
067    static Debug debug = PolicyManager.debug;
068
069    /**
070     * Constructs a <code>SubjectTypeManager</code> object
071     */
072    protected SubjectTypeManager() throws SSOException {
073        token = ServiceTypeManager.getSSOToken();
074        String lstr = token.getProperty("Locale");
075        java.util.Locale loc = com.sun.identity.shared.locale.Locale.getLocale(
076            lstr);
077        rb = amCache.getResBundle(ResBundleUtils.rbName, loc);
078    }
079
080    /**
081     * Constructs a <code>SubjectTypeManager</code> object
082     * @param pm <code>PolicyManager</code> to initialize
083     * <code>SubjectTypeManager</code> with
084     */
085    protected SubjectTypeManager(PolicyManager pm)  {
086        this.pm = pm;
087        pmRealmName = new DN(pm.getOrganizationDN()).toRFCString()
088                .toLowerCase();
089        token = pm.token;
090        java.util.Locale loc;
091        try {
092            String lstr = token.getProperty("Locale");
093            loc = com.sun.identity.shared.locale.Locale.getLocale(lstr);
094        } catch (SSOException ex) {
095            debug.error(
096                "SubjectTypeManager:Unable to retreive locale from SSOToken",
097                ex);
098            loc = Locale.getDefaultLocale();
099        }
100
101         if (debug.messageEnabled()) {
102            debug.message("SubjectManager locale="+loc+"\tI18nFileName = "+
103                     ResBundleUtils.rbName);
104        }
105        rb = amCache.getResBundle(ResBundleUtils.rbName, loc);
106    }
107
108    /**
109     * Returns a set of all valid subject type names defined by the policy
110     * service.
111     * Examples are <code>LDAPRole</code>, <code>LDAPGroup</code>, etc.
112     *
113     * @return a set of all valid subject type names defined by the policy
114     *         service.
115     * @throws SSOException if the <code>SSOToken</code> used to create 
116     *                      the <code>PolicyManager</code> has become invalid
117     * @throws PolicyException for any other abnormal condition
118     */
119    public Set getSubjectTypeNames() throws SSOException,
120            PolicyException {
121        return (PolicyManager.getPluginSchemaNames(SUBJECT));
122    }
123
124    /**
125     * Returns a set of valid subject type names configured for the
126     * organization.
127     * Examples are <code>LDAPRole</code>, <code>LDAPGroup</code>, etc.
128     *
129     * @return a set of valid subject type names configured for the
130     *         organization.
131     * @throws SSOException if the <code>SSOToken</code> used to create 
132     *                      the <code>PolicyManager</code> has become invalid
133     * @throws PolicyException for any other abnormal condition
134     */
135    public Set getSelectedSubjectTypeNames() throws SSOException,
136            PolicyException {
137        Map policyConfig = pm.getPolicyConfig();
138        Set selectedSubjects = null;
139        if (policyConfig != null) {
140            selectedSubjects = 
141                    (Set)policyConfig.get(PolicyConfig.SELECTED_SUBJECTS); 
142        }
143        if ( selectedSubjects == null) {
144            selectedSubjects = Collections.EMPTY_SET;
145        }
146        return selectedSubjects;
147    }
148
149    /**
150     * Returns the type of the <code>Subject</code> implementation.
151     * For example <code>LDAPRoles</code>, <code>LDAPGroups</code> etc.
152     *
153     * @param subject <code>Subject</code> for which this method will
154     * return its associated type
155     *
156     * @return type of the <code>Subject</code>, e.g., <code>LDAPRoles</code>,
157     *         <code>LDAPGroups</code>, etc. Returns <code>null</code> if
158     *         not present.
159     */
160    public String getSubjectTypeName(Subject subject) {
161        return (subjectTypeName(subject));
162    }
163
164    /**
165     * Returns the I18N properties file name that should be
166     * used to localize display names for the given
167     * subject type.
168     *
169     * @param subjectType subject type name
170     *
171     * @return i18n properties file name
172     */
173    protected String getI18NPropertiesFileName(String subjectType) {
174        // %%% Need to get the file name from plugin schema
175        return (null);
176    }
177
178    /**
179     * Returns the I18N key to be used to localize the
180     * display name for the subject type name.
181     *
182     * @param subjectType subject type name
183     *
184     * @return i18n key to obtain the display name
185     */
186    public String getI18NKey(String subjectType) {
187        PluginSchema ps = PolicyManager.getPluginSchema(SUBJECT, subjectType);
188        if (ps != null) {
189            return (ps.getI18NKey());
190        }
191        return (null);
192    }
193
194    /**
195     * Returns the display name for the subject type
196     * @param subjectType subject type
197     * @return display name for the subject type
198     */
199    public String getDisplayName(String subjectType) {
200        String displayName = null;
201        String i18nKey = getI18NKey(subjectType);
202        if (i18nKey == null || i18nKey.length() == 0) {
203            displayName = subjectType;
204        } else {
205            displayName = Locale.getString(rb,i18nKey,debug);
206        }
207        return displayName;
208    }
209
210    /**
211     * Returns an instance of the <code>Subject</code> given the subject type
212     * name.
213     *
214     * @param subjectType subject type.
215     * @return an instance of the <code>Subject</code> given the subject type
216     * name.
217     * @throws NameNotFoundException if the <code>Subject</code> for the
218     *            <code>subjectType</code> name is not found
219     * @throws PolicyException for any other abnormal condition
220     */
221    public Subject getSubject(String subjectType)
222        throws NameNotFoundException, PolicyException {
223        PluginSchema ps = PolicyManager.getPluginSchema(SUBJECT, subjectType);
224        if (ps == null) {
225            throw (new NameNotFoundException(ResBundleUtils.rbName,
226                "invalid_subject", null,
227                subjectType, PolicyException.USER_COLLECTION));
228        }
229
230        // Construct the object
231        Subject answer = null;
232        try {
233            String className = ps.getClassName();
234            answer = (Subject) Class.forName(className).newInstance();
235        } catch (Exception e) {
236            throw (new PolicyException(e));
237        }
238
239        //initialize with policy config
240        answer.initialize(pm.getPolicyConfig());
241        return (answer);
242    }
243
244    /**
245     * Adds a policy subject at realm. 
246     *
247     * @param subjectName name of the Subject instance 
248     * @param subject Subject object to be added 
249     *
250     * @throws NameAlreadyExistsException if a Subject with the given name
251     *          already exists at the realm
252     * @throws InvalidNameException if the subject name is invalid
253     *
254     * @throws PolicyException if can not add the Subject 
255     */
256    public void addSubject(String subjectName, Subject subject) 
257            throws NameAlreadyExistsException, InvalidNameException,
258            PolicyException, SSOException {
259
260        //we  really do not use the exclusive flag at realm level
261        addSubject(subjectName, subject, false);
262    }
263
264    /**
265     * Adds a policy subject at realm. 
266     *
267     * @param subjectName name of the Subject instance 
268     * @param subject Subject object to be added 
269     *
270     * @param exclusive boolean flag indicating whether the subject 
271     *        is to be exclusive subject. If subject is exclusive, 
272     *        policy applies to users who are not members of the 
273     *        subject. Otherwise, policy applies to members of the subject.
274     *
275     * @throws NameAlreadyExistsException if a Subject with the given name
276     *          already exists at the realm
277     * @throws InvalidNameException if the subject name is invalid
278     *
279     * @throws PolicyException if can not add the Subject 
280     *
281     *
282     */
283    private void addSubject(String subjectName, Subject subject, 
284            boolean exclusive) 
285            throws NameAlreadyExistsException, InvalidNameException,
286            PolicyException, SSOException {
287        if (debug.messageEnabled()) {
288            debug.message("Adding realm subject : " + subjectName
289                    + ", in realm:" + pmRealmName);
290        }
291        if (realmSubjects == null) {
292            initRealmSubjects();
293        }
294        realmSubjects.addSubject(subjectName, subject, exclusive);
295        saveSubjects();
296        if (debug.messageEnabled()) {
297            debug.message("Added realm subject : " + subjectName
298                    + ", in realm:" + pmRealmName);
299        }
300    }
301
302    /**
303     * Removes the subject with the given name  from the realm.
304     * This method would throw PolicyException if the subject 
305     * is being used by any policy.
306     *
307     * @param subjectName name of the Subject
308     *
309     * @return returns the Subject object being removed,
310     *         returns <code>null</code> if Subject with 
311     *         the given subjectName is not present 
312     *
313     * @throws PolicyException if can not remove the Subject 
314     */
315    public Subject removeSubject(String subjectName) 
316            throws ObjectInUseException, PolicyException, SSOException {
317        return removeSubject(subjectName, false);
318    }
319
320    /**
321     * Removes the subject with the given name  from the realm.
322     * This method would throw PolicyException if the subject 
323     * is being used by any policy unless <code>forcedRemove</code> 
324     * argument  is set to <code>true</code>. 
325     * If the <code>forcedRemove</code> argument is set to 
326     * <code>true</code> policies that are using the subject would 
327     * be modified to  remove the references to the subject
328     *
329     * @param subjectName name of the Subject
330     * @param forcedRemove if set to <code>true</code>, policies that
331     *    use the subject would be modifed to remove the references
332     *    to the subject. Otherwise, <code>ObjectInUseException</code>
333     *    would be thrown if there is any policy using the subject
334     *
335     * @return returns the Subject object being removed,
336     *         returns <code>null</code> if Subject with 
337     *         the given subjectName is not present 
338     *
339     * @throws PolicyException if can not remove the Subject 
340     */
341    public Subject removeSubject(String subjectName, boolean forcedRemove) 
342            throws ObjectInUseException, PolicyException, SSOException {
343        if (debug.messageEnabled()) {
344            debug.message("Removing realm subject : " + subjectName
345                    + ", in realm:" + pmRealmName);
346        }
347        if (realmSubjects == null) {
348            initRealmSubjects();
349        }
350        if (forcedRemove) {
351            Set userPolicies = pm.getPoliciesUsingRealmSubject(subjectName);
352            for (Iterator policyIter = userPolicies.iterator();
353                    policyIter.hasNext();) {
354                Policy policy = (Policy)policyIter.next();
355                policy.removeSubject(subjectName);
356            }
357        } else {
358            Policy p = pm.getPolicyUsingRealmSubject(subjectName);
359            if ( p != null) {
360                //ObjectInUseException(String rbName, String errCode,         
361                //Object[] args, String name, Object user) 
362                throw new ObjectInUseException(null, null, null, null, null);
363            }
364        }
365        Subject subject = realmSubjects.removeSubject(subjectName);
366        saveSubjects();
367        if (debug.messageEnabled()) {
368            debug.message("Removed realm subject : " + subjectName
369                    + ", in realm:" + pmRealmName);
370        }
371        return subject;
372    }
373
374    /**
375     * Replaces an existing subject with the same name by the
376     * current one at the realm. If a subject with the same name does 
377     * not exist, it will be added.
378     *
379     * @param subjectName name of the Subject instance 
380     * @param subject Subject that will replace an existing Subject
381     *         with the same name
382     *
383     * @throws NameNotFoundException if a Subject instance
384     *         with the given name is not present
385     *
386     * @throws PolicyException if can not replace the Subject 
387     */
388    public void replaceSubject(String subjectName, Subject subject) 
389            throws NameNotFoundException, PolicyException, SSOException {
390
391        //we  really do not use the exclusive flag at realm level
392        replaceSubject(subjectName, subject, false);
393    }
394
395    /**
396     * Replaces an existing subject with the same name by the
397     * current one at the realm. If a subject with the same name does 
398     * not exist, it will be added.
399     *
400     * @param subjectName name of the Subject instance 
401     * @param subject Subject that will replace an existing Subject
402     *         with the same name
403     *
404     * @param exclusive boolean flag indicating whether the subject 
405     *        is to be exclusive subject. If subject is exclusive, 
406     *        policy applies to users who are not members of the 
407     *        subject. Otherwise, policy applies to members of the subject.
408     *
409     * @throws NameNotFoundException if a Subject instance
410     *         with the given name is not present
411     *
412     * @throws PolicyException if can not replace the Subject 
413     *
414     *
415     */
416    private void replaceSubject(String subjectName, Subject subject, 
417            boolean exclusive) 
418            throws NameNotFoundException, PolicyException, SSOException {
419        if (debug.messageEnabled()) {
420            debug.message("Replacing realm subject : " + subjectName
421                    + ", in realm:" + pmRealmName);
422        }
423        if (realmSubjects == null) {
424            initRealmSubjects();
425        }
426        realmSubjects.replaceSubject(subjectName, subject, exclusive);
427        saveSubjects();
428        if (debug.messageEnabled()) {
429            debug.message("Replaced realm subject : " + subjectName
430                    + ", in realm:" + pmRealmName);
431        }
432    }
433
434    /**
435     * Get the set of names of Subject(s) defined at the realm
436     *
437     * @return set of subject names
438     */
439    public Set getSubjectNames() throws PolicyException, SSOException {
440        if (debug.messageEnabled()) {
441            debug.message("Getting subject names from realm: " 
442                    +  pmRealmName);
443        }
444        if (realmSubjects == null) {
445            initRealmSubjects();
446        }
447        Set subjectNames = realmSubjects.getSubjectNames();
448        if (debug.messageEnabled()) {
449            debug.message("Returning subject names from realm: " 
450                    +  pmRealmName + ",subjectNames=" + subjectNames);
451        }
452        return subjectNames;
453    }
454
455    /**
456     * Returns the Subject object identified by subjectName defined at 
457     * the realm
458     *
459     * @param subjectName name of subject.
460     *
461     * @return Subject object
462     *
463     * @throws NameNotFoundException if a Subject with the given name
464     * does not exist
465     *
466     * @throws PolicyException if can not get the Subject
467     */
468    public Subject getSubjectByName(String subjectName) 
469            throws NameNotFoundException, PolicyException {
470        if (debug.messageEnabled()) {
471            debug.message("Getting subject by name from realm: " 
472                    +  pmRealmName + ", subjectName=" + subjectName);
473        }
474        if (realmSubjects == null) {
475            initRealmSubjects();
476        }
477        if (debug.messageEnabled()) {
478            debug.message("Returning subject by name from realm: " 
479                    +  pmRealmName + ", subjectName=" + subjectName);
480        }
481        return (Subject)realmSubjects.getSubject(subjectName).clone();
482    }
483
484    synchronized Subject getCachedSubjectByName(String subjectName) 
485            throws  PolicyException {
486        if (debug.messageEnabled()) {
487            debug.message("Getting cached subject by name from realm: " 
488                    +  pmRealmName + ", subjectName=" + subjectName);
489        }
490        if (realmSubjects == null) {
491            initRealmSubjects();
492        }
493        if (debug.messageEnabled()) {
494            debug.message("Returning cached subject by name from realm: " 
495                    +  pmRealmName + ", subjectName=" + subjectName);
496        }
497        return (Subject)realmSubjects.fetchSubject(subjectName);
498    }
499
500    /**
501     * Returns a handle to the Subject object identified by subjectName 
502     * defined at the realm, to add to a policy. 
503     * Returned Subject is backed by 
504     * the Subject at the realm. However, you can not change the values
505     * using the returned Subject. 
506     *
507     * @param subjectName name of subject.
508     *
509     * @return Subject object
510     *
511     * @throws NameNotFoundException if a Subject with the given name
512     * does not exist
513     *
514     * @throws PolicyException if can not get the Subject
515     *
516     */
517    Subject getSharedSubject(String subjectName) 
518            throws  PolicyException {
519        if (debug.messageEnabled()) {
520            debug.message("Getting shared subject from realm: " 
521                    +  pmRealmName + ", subjectName=" + subjectName);
522        }
523        Subject subject = (Subject)sharedSubjects.get(subjectName);
524        if (subject == null) {
525            subject = new SharedSubject(subjectName, this);
526            sharedSubjects.put(subjectName, subject);
527        }
528        if (debug.messageEnabled()) {
529            debug.message("Returning shared subject from realm: " 
530                    +  pmRealmName + ", subjectName=" + subjectName);
531        }
532        return subject;
533    }
534
535    /**
536     * Returns subject type name for the given <code>subject</code>
537     * @return subject type name for the given <code>subject</code>
538     */
539    static String subjectTypeName(Subject subject) {
540        if (subject == null) {
541            return (null);
542        }
543        String answer = null;
544        String className = subject.getClass().getName();
545        Iterator items = PolicyManager.getPluginSchemaNames(SUBJECT).iterator();
546        while (items.hasNext()) {
547            String pluginName = (String) items.next();
548            PluginSchema ps = PolicyManager.getPluginSchema(SUBJECT, 
549                pluginName);
550            if (className.equals(ps.getClassName())) {
551                answer = pluginName;
552                break;
553            }
554        }
555        return (answer);
556    }
557
558    /**
559     * Returns the view bean URL given the Subject
560     *
561     * @param subject subject for which to get the view bean URL
562     *
563     * @return view bean URL defined for the subject plugin in the policy
564     *         service <code>PluginSchema</code>.
565     */
566    public String getViewBeanURL(Subject subject) {
567        return PolicyManager.getViewBeanURL(SUBJECT, 
568            subject.getClass().getName());
569    }
570
571    /**
572     * Returns <code>PolicyManager</code> used by this object
573     */
574    PolicyManager getPolicyManager() {
575        return pm;
576    }
577
578    /**
579     * Saves the realm scoped <code>Subject</code> objects to persistent store
580     */
581    private void saveSubjects() throws PolicyException, SSOException {
582        if (realmSubjects != null) {
583            pm.saveRealmSubjects(realmSubjects);
584        }
585    }
586
587    /**
588     * Initializes the realm scoped <code>Subject</code> objects reading from
589     * persistent store
590     */
591    private void initRealmSubjects() throws PolicyException {
592        if (debug.messageEnabled()) {
593            debug.message("Initializing realm subjects in realm : " 
594                    +  pmRealmName); 
595        }
596        try {
597            realmSubjects = pm.readRealmSubjects();
598        } catch (SSOException ssoe){
599            throw new PolicyException(ResBundleUtils.rbName,
600                "could_not_initialize_realm_subjects", null, ssoe);
601        }
602        if (debug.messageEnabled()) {
603            debug.message("Initialized realm subjects in realm : " 
604                    +  pmRealmName); 
605        }
606    }
607
608    /**
609     * Resets the cached realm scoped <code>Subject</code> objects. 
610     * Would read from persistent store on next access to realm scoped
611     * <code>Subject</code> object
612     */
613    void resetRealmSubjects() {
614        if (debug.messageEnabled()) {
615            debug.message("Resetting realm subjects in realm : " 
616                    +  pmRealmName); 
617        }
618        synchronized(this) {
619            realmSubjects = null;
620        }
621        if (debug.messageEnabled()) {
622            debug.message("Reset realm subjects in realm : " 
623                    +  pmRealmName); 
624        }
625    }
626
627}