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: ISPermission.java,v 1.5 2008/08/19 19:09:17 veiming Exp $
026 *
027 * Portions Copyrighted 2015 ForgeRock AS.
028 */
029
030package com.sun.identity.policy.jaas;
031
032import com.iplanet.sso.SSOToken;
033import com.iplanet.sso.SSOTokenManager;
034import com.iplanet.sso.SSOException;
035
036import com.sun.identity.authentication.service.SSOTokenPrincipal;
037import com.sun.identity.policy.client.PolicyEvaluator;
038import com.sun.identity.policy.client.PolicyEvaluatorFactory;
039import com.sun.identity.shared.debug.Debug;
040
041import java.security.Permission;
042import java.security.CodeSource;
043import java.security.ProtectionDomain;
044import java.security.PermissionCollection;
045
046import javax.security.auth.Subject;
047import java.security.Principal;
048
049import java.util.Set;
050import java.util.HashSet;
051import java.util.StringTokenizer;
052import java.util.Map;
053import java.util.Collections;
054
055/**
056 * This class provides the support for JAAS Authorization service 
057 * Its a new JAAS <code>Permission</code> which extends the
058 * {@link java.security.Permission} class. This is the only
059 * API which gets used by an application/container to evaluate policy against
060 * the OpenAM Policy framework. This class provides implementations
061 * of all the required abstract methods of <code>java.security.Permission</code>
062 * , in a way that the policy evaluation is made against the OpenAM
063 * Policy service.
064 * <p>
065 * For example, one would use this class as follows to evaluate policy
066 * permissions:
067 * <pre>
068 * ISPermission perm = new ISPermission("iPlanetAMWebAgentService",
069 *                  "http://www.example.com:80","GET");
070 * AccessController.checkPermission(perm);
071 * </pre>
072 * If OpenAM has the policy service
073 * <code>iPlanetAMWebAgentService</code> which has a <code>Rule</code> defined
074 * for resource <code>http://www.example.com:80</code>
075 * with action "GET" with allow privilege, this call will return quietly, if
076 * such a policy is not found then access is denied and Exception thrown
077 * accordingly. Also <code>ISPermission</code> co-exists with the 
078 * permissions specified in the JDK policy store ( by default file <code>
079 * sun.security.provider.PolicyFile</code> or defined on the command line 
080 * using the -D option.
081 *
082 * <p>
083 * @see java.security.Permission
084 * @see javax.security.auth.Subject
085 * @see java.security.ProtectionDomain
086 * <p>
087 *
088 * @supported.all.api
089 */
090public class ISPermission extends Permission {
091    private Subject subject;
092    private CodeSource codesource;
093    private ProtectionDomain protectionDomain;
094    private String serviceName;
095    private String resourceName;
096    private String actions;
097    private Set actionSet;
098    private Map envParams = Collections.synchronizedMap(Collections.EMPTY_MAP);
099    private PolicyEvaluatorFactory policyEvalFactory;
100    static Debug debug = Debug.getInstance("amPolicy");
101
102    /**
103     * Constructs an <code>ISPermission</code> instance, with the specified
104     * <code>ProtectionDomain</code>.
105     *
106     * @param pd <code>ProtectionDomain</code> for which this
107     *        <code>ISPermission</code> is being created.
108     */
109    protected ISPermission(ProtectionDomain pd) {
110        super("ISPermission"); 
111        if (debug.messageEnabled()) {
112            debug.message("ISPermission(protectionDomain) constructor "
113                + "called ");
114        }
115        this.protectionDomain = pd;
116    }
117
118    /**
119     * Constructs an <code>ISPermission</code> instance, with the specified
120     * <code>Subject</code> and the <code>CodeSource</code>.
121     *
122     * @param subject <code>Subject</code> for which this
123     *        <code>ISPermission</code> is being created.
124     * @param codesource <code>CodeSource</code> for which this permission is
125     *        being created.
126     */
127    public ISPermission(Subject subject,CodeSource codesource) {
128        super("ISPermission"); 
129        if (debug.messageEnabled()) {
130            debug.message("ISPermission(subject,codesource) constructor "
131                + "called ");
132        }
133        this.subject = subject;
134        this.codesource = codesource;
135    }
136
137    /**
138     * Constructs an <code>ISPermission</code> instance, with the specified
139     * <code>CodeSource</code>.
140     *
141     * @param codesource <code>CodeSource</code> for which this permission is
142     *        being created.
143     */
144    public ISPermission(CodeSource codesource) {
145        super("ISPermission"); 
146        if (debug.messageEnabled()) {
147            debug.message("ISPermission(codesource) constructor "
148                + "called ");
149        }
150        this.codesource = codesource;
151    }
152
153    /**
154     * Constructs an <code>ISPermission</code> instance, with the specified
155     * service name, resource name and action name.
156     * @param serviceName name of service for which this
157     *        <code>ISPermission</code> is being created. This name needs to be
158     *        one of the loaded services in the OpenAM's policy
159     *        engine. example: <code>iPlanetAMWegAgentService</code>
160     *
161     * @param resourceName name of the resource for which this 
162     *        <code>ISPermission</code> is being defined.
163     *
164     * @param actions name of the action that needs to be checked for. It
165     *        may be a <code>String</code> like "GET", "POST" in case of 
166     *        service name <code>iPlanetAMWebAgentService</code>.
167     */
168    public ISPermission(String serviceName,String resourceName, String 
169        actions) 
170    {
171        super("ISPermission");
172        this.serviceName = serviceName;
173        this.resourceName = resourceName;
174        this.actions = actions;
175        debug.message("ISPermission:: Constructor called");
176    }
177
178
179    /**
180     * Constructs an <code>ISPermission</code> instance, with the specified
181     * service name, resource name and action name.
182     * @param serviceName name of service for which this
183     *        <code>ISPermission</code> is being created. This name needs to be
184     *        one of the loaded policy services in the OpenSSO.
185     *        example:
186     *        <code>iPlanetAMWegAgentService</code>
187     *
188     * @param resourceName name of the resource for which this 
189     *        <code>ISPermission</code> is being defined.
190     *
191     * @param actions name of the action that needs to be checked for. It
192     *        may be a <code>String</code> like "GET", "POST" in case of 
193     *        service name <code>iPlanetAMWebAgentService</code>.
194     * @param envParams a <code>java.util.Map</code> of environment parameters
195     *        which are used by the  
196     *        <code>com.sun.identity.policy.client.PolicyEvaluator</code> 
197     *        to evaluate the <code>com.sun.identity.policy.Conditions</code> 
198     *        associated with the policy. This is a Map of attribute-value pairs
199     *        representing the environment under which the policy needs to be
200     *        evaluated.
201     */
202    public ISPermission(String serviceName,String resourceName, 
203        String actions, Map envParams) 
204    {
205        super("ISPermission");
206        this.serviceName = serviceName;
207        this.resourceName = resourceName;
208        this.actions = actions;
209        this.envParams = envParams;
210        debug.message("ISPermission:: Constructor called");
211    }
212
213    /**
214     * returns the name of the service associated with this <code>ISPermission
215     * </code>.
216     * @return <code>String</code> representing the name of the service for this
217     *         permission.
218     */
219    public String getServiceName() {
220        debug.message("ISPermission: getServiceName called");
221        return serviceName;
222    }
223
224    /**
225     * returns the name of the resource associated with this <code>ISPermission
226     * </code>.
227     * @return  <code>String</code> representing the name of the resource for 
228     *          this permission.
229     */
230    public String getResourceName() {
231        debug.message("ISPermission: getResourceName called");
232        return resourceName;
233    }
234
235    /**
236     * returns environment parameters and their values associated with this 
237     * <code>ISPermission</code>.
238     * @return  <code>Map</code> representing the environment parameters of
239     *          this permission. The <code>Map</code> consists of attribute 
240     *          value pairs.
241     */
242    public Map getEnvParams() {
243        return envParams;
244    }
245
246    /**
247     * returns a comma separated list of actions associated with this 
248     * <code>ISPermission</code>.
249     * @return  a comma separated <code>String</code> representing the name 
250     *          of the action for this object. For example for:
251     *          <pre>
252     *          ISPermission isp = new ISPermission("iPlanetAMWebAgentService, 
253     *              "http://www.sun.com:80", "GET, POST");
254     *          getActions() would return "GET,POST"
255     *          </pre>
256     */
257    public String getActions() {
258        debug.message("ISPermission: getActions called");
259        if (debug.messageEnabled()) {
260            debug.message("returning actions:"+actions);
261        }
262        return actions;
263    }
264
265    /**
266     * Returns true if two comma separated strings are equal.
267     *
268     * @param actions1 actions string.
269     * @param actions2 actions string.
270     * @return true if two comma separated strings are equal.
271     */
272    private boolean actionEquals(String actions1, String actions2) {
273            Set actionSet1 = Collections.synchronizedSet(new HashSet());
274            Set actionSet2 = Collections.synchronizedSet(new HashSet());
275        if (actions1 != null) {
276               StringTokenizer st = new StringTokenizer(actions1,",");
277            while (st.hasMoreTokens()) {
278                String action = (String)st.nextToken().trim();
279                actionSet1.add(action);
280            }
281        }
282        if (actions2 != null) {
283               StringTokenizer st = new StringTokenizer(actions2,",");
284            while (st.hasMoreTokens()) {
285                String action = (String)st.nextToken().trim();
286                actionSet2.add(action);
287            }
288        }
289        return actionSet1.equals(actionSet2);
290    }
291
292    /**
293     * Returns a <code>Set</code> of actions for this Permission.
294     * @param actions comma separated actions string.
295     * @return set of actions in this permsision.
296     *
297     */
298    private Set actionsInSet(String actions) {
299        if (actionSet == null) {
300                actionSet = Collections.synchronizedSet(new HashSet());
301        } else {
302            return actionSet;
303        }
304        if (actions != null) {
305               StringTokenizer st = new StringTokenizer(actions,",");
306            while (st.hasMoreTokens()) {
307                String action = (String)st.nextToken();
308                actionSet.add(action);
309            }
310        }
311        return actionSet;
312    }
313        
314    /**
315     * returns the <code>Subject</code>associated with this <code>ISPermission
316     * </code>.
317     * @return  <code>javax.security.auth.Subject</code> representing the 
318     *          subject of this permission.
319     */
320    public Subject getSubject() {
321        debug.message("ISPermission:: getSubject called ");
322        return subject;
323    }
324
325    /**
326     * returns the <code>CodeSource</code>associated with this 
327     * <code>ISPermission</code>.
328     * @return <code>java.security.CodeSource</code> representing the 
329     *         <code>codesource</code> of this permission. 
330     */
331    public CodeSource getCodeSource() {
332        debug.message("ISPermission:: getCodeSource called ");
333        return codesource;
334    }
335
336    /**
337     * returns the <code>ProtectionDomain</code>associated with this 
338     * <code>ISPermission</code>.
339     * @return <code>java.security.ProtectionDomain</code> representing the 
340     *         <code>protectionDomain</code> of this permission. 
341     */
342    public ProtectionDomain getProtectionDomain() {
343        debug.message("ISPermission:: getProtectionDomain called ");
344        return protectionDomain;
345    }
346
347    /**
348     * Returns true if two <code>ISPermission</code> objects for equality.
349     *
350     * @param obj <code>ISPermission</code> object.
351     * @return true if subject, <code>codesource</code>, service name, resource
352     *         name actions and environment parameters of both objects are 
353     *         equal.
354     */
355    public boolean equals(Object obj){
356        boolean result = true;
357        debug.message("ISPermission:: equals(Object) called ");
358        if (obj == this) {
359            if (debug.messageEnabled()) {
360                debug.message("ISPermission::equals::this " +result);
361            }
362            return true;
363        }
364        if (obj instanceof ISPermission) {
365            ISPermission perm = (ISPermission) obj;
366            Subject subject = perm.getSubject();
367            if (subject != null) {
368                result = subject.equals(this.subject);
369            } else {
370                if (this.subject != null) {
371                    result = false; // subject is null, while this.subject is 
372                                    // not null.
373                }
374            }
375            if (debug.messageEnabled()) {
376                debug.message("ISPermission::subject equals:"+result);
377            }
378            if (result) {
379                CodeSource codesource = perm.getCodeSource();
380                if (codesource != null) {
381                    result = codesource.equals(this.codesource);
382                    if (debug.messageEnabled()) {
383                          debug.message("ISPermission::codesource equals:"+
384                            codesource.equals(this.codesource));
385                    }
386                } else {
387                    if (this.codesource != null) {
388                        result = false;
389                    }
390                }
391            }
392            if (result) {
393                ProtectionDomain protectionDomain = perm.getProtectionDomain();
394                if (protectionDomain != null) {
395                    result = protectionDomain.equals(this.protectionDomain);
396                    if (debug.messageEnabled()) {
397                          debug.message("ISPermission::protectionDomain equals:"
398                            + protectionDomain.equals(this.protectionDomain));
399                    }
400                } else {
401                    if (this.protectionDomain != null) {
402                        result = false;
403                    }
404                }
405            }
406            if (result) {
407                String serviceName = perm.getServiceName();
408                if (serviceName != null) {
409                    result = serviceName.equals(this.serviceName); 
410                    if (debug.messageEnabled()) {
411                          debug.message("ISPermission::servicename equals:"+
412                            serviceName.equals(this.serviceName));
413                    }
414                } else {
415                    if (this.serviceName != null) {
416                        result = false;
417                    }
418                }
419            }
420            if (result) {
421                String resourceName = perm.getResourceName();
422               if (resourceName != null) {
423                   result = resourceName.equals(this.resourceName);
424                    if (debug.messageEnabled()) {
425                          debug.message("ISPermission::resourceName equals:"+
426                            resourceName.equals(this.resourceName));
427                    }
428                } else {
429                    if (this.resourceName != null) {
430                        result = false;
431                    }
432                }
433            }
434            if (result) {
435                String actions = perm.getActions();
436                if (actions != null) {
437                    result = actionEquals(actions,this.actions);
438                    if (debug.messageEnabled()) {
439                          debug.message("ISPermission::Actions equals:"+
440                            actionEquals(actions,this.actions));
441                    }
442                } else {
443                    if (this.actions != null) {
444                        result = false;
445                    }
446                }
447            }
448            if (result) {
449                Map envParams = perm.getEnvParams();
450                if (envParams != null  && !envParams.isEmpty())  {
451                    result = envParams.equals(this.envParams);
452                    if (debug.messageEnabled()) {
453                        debug.message("ISPermission::equals::envMap"
454                            + envParams.equals(this.envParams));
455                    }
456                } else {
457                    if (this.envParams != null && !this.envParams.isEmpty()) {
458                        result = false;
459                    }
460                }
461            }
462        }
463        if (debug.messageEnabled()) {
464            debug.message("ISPermission::equals::returning " +result);
465        }
466        return result;
467    }
468
469    /**
470     * Returns the hash code value for this Permission object.
471     * <P>
472     * The required <code>hashCode</code> behavior for Permission Objects is
473     * the following: <p>
474     * <ul>
475     * <li>Whenever it is invoked on the same Permission object more than 
476     *     once during an execution of a Java application, the 
477     *     <code>hashCode</code> method
478     *     must consistently return the same integer. This integer need not 
479     *     remain consistent from one execution of an application to another 
480     *     execution of the same application. <p>
481     * <li>If two Permission objects are equal according to the 
482     *     <code>equals</code> 
483     *     method, then calling the <code>hashCode</code> method on each of the
484     *     two Permission objects must produce the same integer result. 
485     * </ul>
486     *
487     * @return a hash code value for this object.
488     */
489    public int hashCode() {
490        int hash = 0;
491        if (subject != null) {
492            hash = hash + this.subject.hashCode();
493        }
494        if (codesource != null) {
495            hash = hash + this.codesource.hashCode();
496        }
497        if (protectionDomain != null) {
498            hash = hash + this.protectionDomain.hashCode();
499        }
500        if (serviceName != null) {
501            hash = hash + this.serviceName.hashCode();
502        }
503        if (resourceName != null) {
504            hash = hash + this.resourceName.hashCode();
505        }
506        if (actions != null) {
507            Set actionSet = actionsInSet(actions);
508            hash = hash + actionSet.hashCode();
509        }
510        if (envParams != null) {
511            hash = hash + this.envParams.hashCode();
512        }
513        if (debug.messageEnabled()) {
514            debug.message("ISPermission::hashCode::"+hash);
515        }
516        return hash;
517    }
518
519    /**
520     * Checks if the specified permission's actions are "implied by" 
521     * this object's actions.
522     * <P>
523     * The <code>implies</code> method is used by the
524     * <code>AccessController</code> to determine whether or not a requested
525     * permission is implied by another permission that is known to be valid
526     * in the current execution context.
527     *
528     * @param perm the permission to check against.
529     *
530     * @return true if the specified permission is implied by this object,
531     *         false if not. The check is made against the OpenAM's
532     *         policy service to determine this evaluation.
533     */
534    public boolean implies(Permission perm) {
535        debug.message("ISPermission: implies called");
536        boolean allowed = false;
537        if (perm instanceof ISPermission) {
538            debug.message("ISPermission:passed perm is of type ISPermission");
539            if (protectionDomain != null) {
540                debug.message("ISPermission:implies:protectionDomain not null");
541                if (debug.messageEnabled()) {
542                    debug.message("ISPermission::implies: protectionDomain:"
543                    +protectionDomain.toString());
544                }
545                final String serviceName =((ISPermission)perm).getServiceName();
546                final String resourceName =
547                    ((ISPermission)perm).getResourceName();
548                final String actions = ((ISPermission)perm).getActions();
549                final Map envParams = ((ISPermission)perm).getEnvParams();
550                if (debug.messageEnabled()) {
551                    debug.message("ISPermission: resourceName="
552                        +resourceName);
553                    debug.message("ISPermission: serviceName="
554                        +serviceName);
555                    debug.message("ISPermission: actions="+actions);
556                }
557                SSOTokenPrincipal tokenPrincipal = null;
558                try {
559                    Principal[] principals = protectionDomain.getPrincipals();
560                    // principals should have only one entry
561                    Principal principal = (Principal)principals[0];
562                    if (principal.getName().equals("com.sun.identity."
563                        +"authentication.service.SSOTokenPrincipal")) {
564                        if (debug.messageEnabled()) {
565                            debug.message("ISPermission::implies:principals:"
566                                +principal.toString());
567                        }
568                        tokenPrincipal = (SSOTokenPrincipal) principal;
569                    }
570                    if (tokenPrincipal == null) {
571                        if (debug.messageEnabled()) {
572                            debug.error("ISPermission::implies:"
573                                + " Principal is null");
574                        }
575                    } else {
576                        SSOTokenManager ssomgr = SSOTokenManager.getInstance();
577                        final SSOToken token =
578                            ssomgr.createSSOToken(tokenPrincipal.getName());
579                        /* TODO currently ISPermission uses remote policy 
580                        client API so if this class gets used from server side
581                        , will always make remote call, need to make changes 
582                        in this code to to make a local/remote call accordingly.
583                        */
584                            if (policyEvalFactory == null) { 
585                             policyEvalFactory = 
586                                PolicyEvaluatorFactory.getInstance(); 
587                            } 
588                            PolicyEvaluator policyEvaluator = 
589                        policyEvalFactory.
590                            getPolicyEvaluator(serviceName);
591                        if (debug.messageEnabled()) {
592                            debug.message("ISPermission::implies::created "
593                                + "PolicyEvaluator for "+serviceName);
594                        }
595                        if (actions != null) {
596                                   StringTokenizer st = 
597                                new StringTokenizer(actions,",");
598                                while (st.hasMoreTokens()) {
599                                String action = (String)st.nextToken();
600                                allowed  =  policyEvaluator.isAllowed(token, 
601                                    resourceName , action ,envParams);
602                                    if (!allowed) {
603                                    break; // the final result is not allowwed
604                                }
605                                if (debug.messageEnabled()) {
606                                    debug.message("ISPermission::result for "
607                                        + action+" is :"+allowed);
608                                }
609                            }
610                            if (debug.messageEnabled()) {
611                                debug.message("ISPermission::result for "
612                                    + actions+" is :"+allowed);
613                            }
614                        } else {
615                            if (debug.messageEnabled()) {
616                                debug.message("ISPermission:: actions is null");
617                            }
618                        }
619                    }
620                } catch (SSOException ssoe ) {
621                    if (debug.messageEnabled()) {
622                        debug.error("ISPermission::SSOException:"
623                            +ssoe.getMessage());
624                        ssoe.printStackTrace();
625                    }
626                } catch (Exception  e ) {
627                    if (debug.messageEnabled()) {
628                        debug.error("ISPermission::Exception:"
629                            +e.getMessage());
630                        e.printStackTrace();
631                    }
632                }
633            } else {
634                debug.message("ISPermission:: subject was null");
635            }
636        }
637        if (debug.messageEnabled()) {
638            debug.message("ISPermission: allowed::"+allowed);
639        }
640        return allowed;
641    }
642
643    /**
644     * Returns a <code>java.security.PermissionCollection</code> to store this 
645     * kind of Permission.
646     *
647     * @return an instance of <code>ISPermissionCollection</code>
648     */
649    public PermissionCollection newPermissionCollection() {
650        debug.message("ISPermission:: newISPermissionCollection() called");
651        return new ISPermissionCollection();
652    }
653
654
655    /**
656     * Returns a string describing this Permission. 
657     * @return <code>String</code> containing information about this Permission.
658     */
659    public String toString() {
660        StringBuffer str = new StringBuffer(200);
661        str = str.append("(").append(getClass().getName()).append("\n");
662        String actions = getActions();
663        if (subject != null) {
664            str = str.append(subject.toString()).append("\n");
665        }
666        if (codesource != null) {
667            str = str.append(codesource.toString()).append("\n");
668        }
669        if ((serviceName != null) && (serviceName.length() != 0)) { 
670            str = str.append("serviceName=").append(serviceName).append("\n");
671        }
672        if ((resourceName != null) && (resourceName.length() != 0)) { 
673            str = str.append("resourceName=").append(resourceName).append("\n");
674        }
675        if ((actions != null) && (actions.length() != 0)) { 
676            str = str.append("actions=").append(actions).append("\n");
677        }
678        if ((envParams != null) && !(envParams.isEmpty())) { 
679            str = str.append("envParams=").append(envParams.values())
680                     .append("\n");
681        }
682        str.append(")");
683        return str.toString();
684    }
685}