Return-Path: Delivered-To: apmail-incubator-river-dev-archive@minotaur.apache.org Received: (qmail 23217 invoked from network); 13 Aug 2010 12:05:48 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 13 Aug 2010 12:05:48 -0000 Received: (qmail 42587 invoked by uid 500); 13 Aug 2010 12:05:48 -0000 Delivered-To: apmail-incubator-river-dev-archive@incubator.apache.org Received: (qmail 42439 invoked by uid 500); 13 Aug 2010 12:05:45 -0000 Mailing-List: contact river-dev-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: river-dev@incubator.apache.org Delivered-To: mailing list river-dev@incubator.apache.org Received: (qmail 42431 invoked by uid 99); 13 Aug 2010 12:05:44 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 13 Aug 2010 12:05:44 +0000 X-ASF-Spam-Status: No, hits=2.7 required=10.0 tests=RCVD_IN_BL_SPAMCOP_NET,RCVD_IN_DNSWL_NONE,SPF_NEUTRAL X-Spam-Check-By: apache.org Received-SPF: neutral (athena.apache.org: local policy) Received: from [61.9.168.149] (HELO nskntmtas05p.mx.bigpond.com) (61.9.168.149) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 13 Aug 2010 12:05:34 +0000 Received: from nskntotgx03p.mx.bigpond.com ([61.9.223.241]) by nskntmtas05p.mx.bigpond.com with ESMTP id <20100813120512.UOBG18865.nskntmtas05p.mx.bigpond.com@nskntotgx03p.mx.bigpond.com> for ; Fri, 13 Aug 2010 12:05:12 +0000 Received: from [10.1.1.2] (really [61.9.223.241]) by nskntotgx03p.mx.bigpond.com with ESMTP id <20100813120511.OTXU13584.nskntotgx03p.mx.bigpond.com@[10.1.1.2]> for ; Fri, 13 Aug 2010 12:05:11 +0000 Message-ID: <4C653413.80401@zeus.net.au> Date: Fri, 13 Aug 2010 22:01:23 +1000 From: Peter Firmstone User-Agent: Thunderbird 2.0.0.14 (X11/20080531) MIME-Version: 1.0 To: river-dev@incubator.apache.org Subject: Re: Learnings from a RevokeableDynamicPolicy & A Future Roadmap References: <4C54EFBA.30907@zeus.net.au> <4C5BEAC7.1020400@zeus.net.au> <4C5CF8DF.8040302@zeus.net.au> <4C5FE2A7.1010804@zeus.net.au> <4C607389.20109@zeus.net.au> <4C6249B6.9070601@zeus.net.au> <4C644C92.50005@zeus.net.au> <4C648250.8050201@zeus.net.au> <4C64CFDC.1090403@zeus.net.au> In-Reply-To: <4C64CFDC.1090403@zeus.net.au> Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit X-RPD-ScanID: Class unknown; VirusThreatLevel unknown, RefID str=0001.0A090208.4C6534F8.01A0,ss=1,fgs=0 Fred, Does this look more like it? Multiple policy, multiple Permission checks, and an execution cache to minimise re checking during revocation. it's used like this: ecm.begin(reaper); try{ ecm.checkPermission(p); ecm.checkPermission(q); // Do something briefly return something; } finally { ecm.end(); } /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.river.imp.security.policy.se; import java.security.AccessControlContext; import java.security.AccessControlException; import java.security.AccessController; import java.security.Permission; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.river.api.security.ExecutionContextManager; import org.apache.river.api.security.Reaper; import org.apache.river.imp.util.ConcurrentWeakIdentityMap; /** * Only a Single instance of ECM is required per policy, it is threadsafe. * Threads will wait until revoke is complete. * * @author Peter Firmstone */ public class ECM implements ExecutionContextManager{ private final ConcurrentMap> checkedCache; private final ConcurrentMap> executionCache; private final ConcurrentMap> threadAssociation; private final ConcurrentMap association; private final ReadWriteLock blockLock; private final Lock rl; // This lock is held briefly by callers of begin and end. private final Lock wl; // This lock is held by revocation. ECM(){ checkedCache = new ConcurrentWeakIdentityMap>(); executionCache = new ConcurrentHashMap>(); threadAssociation = new ConcurrentHashMap>(); association = new ConcurrentHashMap(); blockLock = new ReentrantReadWriteLock(); rl = blockLock.readLock(); wl = blockLock.writeLock(); } Set revoke(Set perms){ wl.lock(); try { /* This is where we determine what needs to be revoked, we first * get all the AccessControlContexts for the Permission class, * these are removed from the checkedCache, then we narrow * down the AccessControlContext's to only those in the * execution cache, each AccessControlContext in the execution cache * has checkPermission(Permission) called for each revoked * permission. Any that throw AccessControlException will be * caught and have their Reaper run, for the Threads referenced * from that AccessControlContext. * * The RevokeableDynamicPolicy will have updated the current * Permission's after revocation, so the ProtectionDomain's in * the AccessControlContext will now throw an AccessControlException * if the revocation applied to them. * * The wl lock will be released, any threads that were interrupted * will exit through the finally block and remove themselves * from the execution cache. Those that aren't may throw * an exception and bubble up the stack, due to closing sockets * etc. * * The execution cache is comprised of the following fields: * executionCache * threadAssociation */ // Identify Permission's with matching class files to those revoked. Set permClasses = new HashSet(); Iterator itp = perms.iterator(); while (itp.hasNext()){ permClasses.add(itp.next().getClass()); } // Remove Permission's and AccessControlContexts from the checked cache. Map> removed = new HashMap>(); Iterator keysIt = checkedCache.keySet().iterator(); while (keysIt.hasNext()){ Permission p = keysIt.next(); if (permClasses.contains(p.getClass())){ Set a = checkedCache.get(p); keysIt.remove(); removed.put(p, a); } } // Match the AccessControlContexts with the execution cache; Set exCache = executionCache.keySet(); // Get the AccessControlContext's in the execution cache that fail. Set accFails = new HashSet(); Iterator retests = removed.keySet().iterator(); while (retests.hasNext()){ Permission p = retests.next(); Set rechecks = removed.get(p); Iterator recheck = rechecks.iterator(); while (recheck.hasNext()){ AccessControlContext a = recheck.next(); if (accFails.contains(a)) continue; // This really narrows down the checks. if (exCache.contains(a)){ try { a.checkPermission(p); } catch (AccessControlException e){ accFails.add(a); } } } } // Identify the threads and prepare reapers. Set reapers = new HashSet(); Iterator failedAcc = accFails.iterator(); while (failedAcc.hasNext()){ AccessControlContext fail = failedAcc.next(); Set threads = executionCache.get(fail); Iterator i = threads.iterator(); while (i.hasNext()) { Thread t = i.next(); Reaper r = association.get(t); r.put(t); reapers.add(r); } } return reapers; } finally { wl.unlock(); } } public void begin(Reaper r) { Thread currentThread = Thread.currentThread(); association.put(currentThread, r); } public void checkPermission(Permission p) throws AccessControlException { Thread currentThread = Thread.currentThread(); AccessControlContext executionContext = AccessController.getContext(); rl.lock(); try { // execution cache. Set exCacheThreadSet = executionCache.get(executionContext); if ( exCacheThreadSet == null ){ exCacheThreadSet = Collections.synchronizedSet(new HashSet()); Set existed = executionCache.putIfAbsent(executionContext, exCacheThreadSet); if (existed != null){ exCacheThreadSet = existed; } } exCacheThreadSet.add(currentThread);// end execution cache. // thread association Set thAssocSet = threadAssociation.get(currentThread); if ( thAssocSet == null ){ thAssocSet = Collections.synchronizedSet(new HashSet()); Set existed = threadAssociation.putIfAbsent(currentThread, thAssocSet); if (existed != null){ thAssocSet = existed; } } thAssocSet.add(executionContext); // end thread association. // checkedCache - the permission check. Set checked = checkedCache.get(p); if (checked == null ){ checked = Collections.synchronizedSet(new HashSet()); Set existed = checkedCache.putIfAbsent(p, checked); if (existed != null){ checked = existed; } } if ( checked.contains(executionContext)) return; // it's passed before. executionContext.checkPermission(p); // Throws AccessControlException // If we get here cache the AccessControlContext. checked.add(executionContext); // end checkedCache. } finally { rl.unlock(); } } public void end() { // Teardown. Thread t = Thread.currentThread(); rl.lock(); try { association.remove(t); Set accSet = threadAssociation.remove(t); Iterator it = accSet.iterator(); while (it.hasNext()){ AccessControlContext acc = it.next(); executionCache.get(acc).remove(t); } }finally { rl.unlock(); } } }