You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by pa...@apache.org on 2008/03/07 01:37:47 UTC
svn commit: r634480 [1/3] - in /felix/trunk/framework/security: ./ src/
src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/
src/main/java/org/apache/felix/ src/main/java/org/apache/felix/framework/
src/main/java/org/apache/felix/frame...
Author: pauls
Date: Thu Mar 6 16:37:30 2008
New Revision: 634480
URL: http://svn.apache.org/viewvc?rev=634480&view=rev
Log:
Initial version of the OSGi security layer. (FELIX-116, FELIX-115, FELIX-22)
Added:
felix/trunk/framework/security/
felix/trunk/framework/security/pom.xml
felix/trunk/framework/security/src/
felix/trunk/framework/security/src/main/
felix/trunk/framework/security/src/main/java/
felix/trunk/framework/security/src/main/java/org/
felix/trunk/framework/security/src/main/java/org/apache/
felix/trunk/framework/security/src/main/java/org/apache/felix/
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/LocalPermissions.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Permissions.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/PropertiesCache.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/TrustManager.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/BundleDNParser.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SignerMatcher.java
felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SubjectDNParser.java
felix/trunk/framework/security/src/main/java/org/osgi/
felix/trunk/framework/security/src/main/java/org/osgi/service/
felix/trunk/framework/security/src/main/java/org/osgi/service/condpermadmin/
felix/trunk/framework/security/src/main/java/org/osgi/service/condpermadmin/BundleLocationCondition.java
felix/trunk/framework/security/src/main/java/org/osgi/service/condpermadmin/BundleSignerCondition.java
Added: felix/trunk/framework/security/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/pom.xml?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/pom.xml (added)
+++ felix/trunk/framework/security/pom.xml Thu Mar 6 16:37:30 2008
@@ -0,0 +1,66 @@
+<!--
+ 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.
+-->
+<project>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix</artifactId>
+ <version>1.0.2</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <name>Apache Felix Security Provider</name>
+ <artifactId>org.apache.felix.framework.security</artifactId>
+ <version>0.9.0-SNAPSHOT</version>
+ <description>
+ This bundle provides an implementation of the OSGi security for Apache Felix.
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>1.4.0</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
+ <Export-Package>org.osgi.service.permissionadmin;-split-package:=merge-first, org.osgi.service.condpermadmin;-split-package:=merge-first</Export-Package>
+ <Private-Package>org.apache.felix.framework.*</Private-Package>
+ <Import-Package>!*</Import-Package>
+ <Fragment-Host>system.bundle; extension:=framework</Fragment-Host>
+ <Felix-Activator>org.apache.felix.framework.SecurityActivator</Felix-Activator>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java Thu Mar 6 16:37:30 2008
@@ -0,0 +1,320 @@
+/*
+ * 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.felix.framework;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Map.Entry;
+
+import org.apache.felix.framework.security.SecurityConstants;
+import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
+import org.apache.felix.framework.security.permissionadmin.PermissionAdminImpl;
+import org.apache.felix.framework.security.util.Conditions;
+import org.apache.felix.framework.security.util.LocalPermissions;
+import org.apache.felix.framework.security.util.Permissions;
+import org.apache.felix.framework.security.util.PropertiesCache;
+import org.apache.felix.framework.security.verifier.BundleDNParser;
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.permissionadmin.PermissionAdmin;
+
+/**
+ * <p>This Felix specific activator installs a security provider with the Felix
+ * framework. The security settings can be changed via the
+ * {@link PermissionAdmin} and/or the
+ * {@link ConditionalPermissionAdmin} services that may be published by
+ * this class.
+ * </p>
+ * <p>
+ * Permission informations as well as caching data will be stored in several
+ * files in a directory called <tt>security</tt> obtained by a call to
+ * {@link BundleContext#getDataFile(String))}.
+ * </p>
+ * <p>
+ * The following properties are recognized:
+ * <p>
+ * {@link SecurityConstants#ENABLE_PERMISSIONADMIN_PROP} - Whether or not
+ * (<tt>true</tt>|<tt>false</tt>) to
+ * publish a{@link ConditionalPermissionAdmin} service. The default is
+ * {@link SecurityConstants#ENABLE_PERMISSIONADMIN_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#ENABLE_CONDPERMADMIN_PROP} - Whether or not
+ * (<tt>true</tt>|<tt>false</tt>) to
+ * publish a{@link ConditionalPermissionAdmin} service. The default is
+ * {@link SecurityConstants#ENABLE_CONDPERMADMIN_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#KEYSTORE_FILE_PROP} - The keystore URL(s) to use as
+ * trusted CA stores. The urls must be separated by a guard (i.e., <tt>|</tt>).
+ * The default is
+ * {@link SecurityConstants#KEYSTORE_FILE_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#KEYSTORE_PASS_PROP} - The keystore password(s) to
+ * use for the given keystores. The passwords must be separated by a guard
+ * (i.e., <tt>|</tt>).The default is
+ * {@link SecurityConstants#KEYSTORE_PASS_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#KEYSTORE_TYPE_PROP} - The keystore type(s) to use
+ * for the given keystores. The types must be separated by a guard
+ * (i.e., <tt>|</tt>).The default is
+ * {@link SecurityConstants#KEYSTORE_TYPE_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#CRL_FILE_PROP} - The CRL URL(s) to use for revoked
+ * certificates. The urls must be separated by a guard (i.e., <tt>|</tt>).
+ * The default is {@link SecurityConstants#CRL_FILE_VALUE}.
+ * </p>
+ * </p>
+ */
+/*
+ * TODO: using a string for passwords is bad. We need to investigate
+ * alternatives.
+ *
+ * TODO: we might want to allow for the recognized properties to
+ * change without a restart. This is trick because we can not publish a managed
+ * service due to not being able to import as we are an extension bundle.
+ */
+public final class SecurityActivator implements BundleActivator
+{
+ private SecurityProviderImpl m_provider = null;
+ private PropertiesCache m_dnsCache = null;
+ private PropertiesCache m_localCache = null;
+ private LocalPermissions m_localPermissions = null;
+
+ public synchronized void start(BundleContext context) throws Exception
+ {
+ PermissionAdminImpl pai = null;
+
+ SecureAction action = new SecureAction();
+
+ Permissions permissions = new Permissions(context, action);
+
+ File tmp = context.getDataFile("security" + File.separator + "tmp");
+ if ((tmp == null) || (!tmp.isDirectory() && !tmp.mkdirs()))
+ {
+ throw new IOException("Can't create tmp dir.");
+ }
+ // TODO: log something if we can not clean-up the tmp dir
+ File[] old = tmp.listFiles();
+ if (old != null)
+ {
+ for (int i = 0; i < old.length; i++)
+ {
+ old[i].delete();
+ }
+ }
+
+ if ("TRUE".equalsIgnoreCase(getProperty(context,
+ SecurityConstants.ENABLE_PERMISSIONADMIN_PROP,
+ SecurityConstants.ENABLE_PERMISSIONADMIN_VALUE)))
+ {
+ File cache =
+ context.getDataFile("security" + File.separator + "pa.txt");
+ if ((cache == null) || (!cache.isFile() && !cache.createNewFile()))
+ {
+ throw new IOException("Can't create cache file");
+ }
+ pai =
+ new PermissionAdminImpl(permissions, new PropertiesCache(cache,
+ tmp, action));
+ }
+
+ ConditionalPermissionAdminImpl cpai = null;
+
+ if ("TRUE".equalsIgnoreCase(getProperty(context,
+ SecurityConstants.ENABLE_CONDPERMADMIN_PROP,
+ SecurityConstants.ENABLE_CONDPERMADMIN_VALUE)))
+ {
+ File cpaCache =
+ context.getDataFile("security" + File.separator + "cpa.txt");
+ if ((cpaCache == null) || (!cpaCache.isFile() && !cpaCache.createNewFile()))
+ {
+ throw new IOException("Can't create cache file");
+ }
+ File localCache =
+ context.getDataFile("security" + File.separator + "local.txt");
+ if ((localCache == null) || (!localCache.isFile() && !localCache.createNewFile()))
+ {
+ throw new IOException("Can't create cache file");
+ }
+
+ m_localCache = new PropertiesCache(localCache, tmp, action);
+ m_localPermissions = new LocalPermissions(permissions, m_localCache);
+
+ cpai =
+ new ConditionalPermissionAdminImpl(permissions, new Conditions(
+ action), m_localPermissions,
+ new PropertiesCache(cpaCache, tmp, action));
+ }
+
+ if ((pai != null) || (cpai != null))
+ {
+ String crlList =
+ getProperty(context, SecurityConstants.CRL_FILE_PROP,
+ SecurityConstants.CRL_FILE_VALUE);
+ String storeList =
+ getProperty(context, SecurityConstants.KEYSTORE_FILE_PROP,
+ SecurityConstants.KEYSTORE_FILE_VALUE);
+ String passwdList =
+ getProperty(context, SecurityConstants.KEYSTORE_PASS_PROP,
+ SecurityConstants.KEYSTORE_PASS_VALUE);
+ String typeList =
+ getProperty(context, SecurityConstants.KEYSTORE_TYPE_PROP,
+ SecurityConstants.KEYSTORE_TYPE_VALUE);
+
+ StringTokenizer storeTok = new StringTokenizer(storeList, "|");
+ StringTokenizer passwdTok = new StringTokenizer(passwdList, "|");
+ StringTokenizer typeTok = new StringTokenizer(typeList, "|");
+
+ if ((storeTok.countTokens() != passwdTok.countTokens())
+ || (passwdTok.countTokens() != typeTok.countTokens()))
+ {
+ throw new BundleException(
+ "Each CACerts keystore must have one type and one passwd entry and vice versa.");
+ }
+
+ m_provider =
+ new SecurityProviderImpl(crlList, typeList, passwdList,
+ storeList, pai, cpai, action);
+
+ File cache =
+ context.getDataFile("security" + File.separator + "dns.txt");
+ if ((cache == null) || (!cache.isFile() && !cache.createNewFile()))
+ {
+ throw new IOException("Can't create cache file");
+ }
+ m_dnsCache = new PropertiesCache(cache, tmp, action);
+
+ Map store = m_dnsCache.read(String[].class);
+
+ if (store != null)
+ {
+ BundleDNParser parser = m_provider.getParser();
+
+ for (Iterator iter = store.entrySet().iterator(); iter
+ .hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+ String[] value = (String[]) entry.getValue();
+ if ("none".equals(value[0]))
+ {
+ parser.put((String) entry.getKey(), null);
+ }
+ else if ("invalid".equals(value[0]))
+ {
+ parser.put((String) entry.getKey(), new String[0]);
+ }
+ else
+ {
+ parser.put((String) entry.getKey(), value);
+ }
+ }
+ }
+ ((Felix) context.getBundle(0)).setSecurityProvider(m_provider);
+ }
+
+ if (pai != null)
+ {
+ context.registerService(PermissionAdmin.class.getName(), pai, null);
+ }
+
+ if (cpai != null)
+ {
+ context.registerService(ConditionalPermissionAdmin.class.getName(),
+ cpai, null);
+ }
+ }
+
+ public synchronized void stop(BundleContext context) throws Exception
+ {
+ if (m_provider != null)
+ {
+ m_dnsCache.write(write(m_provider.getParser().getCache(), context));
+ }
+ if (m_localPermissions != null)
+ {
+ m_localCache.write(write(m_localPermissions.getStore(), context));
+ }
+ m_provider = null;
+ m_dnsCache = null;
+ m_localPermissions = null;
+ }
+
+ private Map write(Map cache, BundleContext context)
+ {
+ // Filter the cached dn chains and only store the latest for each
+ // bundle. This is ok because the framework will prune old revisions
+ // after a restart. The format is <id>-<timestamp>
+ Map store = new HashMap();
+ Map index = new HashMap();
+ for (Iterator iter = cache.entrySet().iterator(); iter.hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+
+ String key = (String) entry.getKey();
+ String id = key.substring(0, key.indexOf("-"));
+ String time = key.substring(key.indexOf("-") + 1);
+ Bundle bundle = context.getBundle(Long.parseLong(id));
+ long timeLong = Long.parseLong(time);
+ if ((bundle == null) ||
+ ((FelixBundle) bundle).getInfo().getLastModified() > timeLong)
+ {
+ continue;
+ }
+ String last = (String) index.get(id);
+
+ if ((last == null)
+ || (Long.parseLong(last) < timeLong))
+ {
+ index.put(id, time);
+ Object[] dns = (Object[]) entry.getValue();
+ store.remove(id + "-" + last);
+ if ((dns != null) && (dns.length > 0))
+ {
+ store.put(key, dns);
+ }
+ else
+ {
+ store.put(key, (dns == null) ? new String[] {"none"} : new String[] {"invalid"});
+ }
+ }
+ }
+ return store;
+ }
+
+ private String getProperty(BundleContext context, String key,
+ String defaultValue)
+ {
+ String result = context.getProperty(key);
+
+ return ((result != null) && (result.trim().length() > 0)) ? result
+ : defaultValue;
+ }
+}
Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java Thu Mar 6 16:37:30 2008
@@ -0,0 +1,136 @@
+/*
+ * 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.felix.framework;
+
+import java.security.Permission;
+import java.security.ProtectionDomain;
+
+import org.apache.felix.framework.ext.SecurityProvider;
+import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
+import org.apache.felix.framework.security.permissionadmin.PermissionAdminImpl;
+import org.apache.felix.framework.security.util.TrustManager;
+import org.apache.felix.framework.security.verifier.BundleDNParser;
+import org.apache.felix.framework.security.verifier.SignerMatcher;
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.framework.Bundle;
+
+/**
+ * This class is the entry point to the security. It is used to determine whether
+ * a given bundle is signed correctely and has permissions based on
+ * PermissionAdmin or ConditionalPermissionAdmin.
+ */
+public final class SecurityProviderImpl implements SecurityProvider
+{
+ private final BundleDNParser m_parser;
+ private final PermissionAdminImpl m_pai;
+ private final ConditionalPermissionAdminImpl m_cpai;
+ private final SecureAction m_action;
+
+ SecurityProviderImpl(String crlList, String typeList,
+ String passwdList, String storeList, PermissionAdminImpl pai,
+ ConditionalPermissionAdminImpl cpai, SecureAction action)
+ {
+ m_pai = pai;
+ m_cpai = cpai;
+ m_action = action;
+ m_parser =
+ new BundleDNParser(new TrustManager(crlList, typeList, passwdList,
+ storeList, m_action));
+ }
+
+ BundleDNParser getParser()
+ {
+ return m_parser;
+ }
+
+ /**
+ * If the given bundle is signed but can not be verified (e.g., missing files)
+ * then throw an exception.
+ */
+ public void checkBundle(Bundle bundle) throws Exception
+ {
+ BundleInfo info = ((FelixBundle) bundle).getInfo();
+
+ m_parser.checkDNChains(
+ (Long.toString(bundle.getBundleId()) + "-" + info.getArchive()
+ .getLastModified()), info.getCurrentModule().getContentLoader());
+ }
+
+ /**
+ * Get a signer matcher that can be used to match digital signed bundles.
+ */
+ public Object getSignerMatcher(final Bundle bundle)
+ {
+ return new SignerMatcher(Long.toString(bundle.getBundleId()),
+ ((FelixBundle) bundle).getInfo().getArchive(), m_parser);
+ }
+
+ /**
+ * If we have a permissionadmin then ask that one first and have it
+ * decide in case there is a location bound. If not then either use its
+ * default permission in case there is no conditional permission admin
+ * or else ask that one.
+ */
+ public boolean hasBundlePermission(ProtectionDomain bundleProtectionDomain,
+ Permission permission, boolean direct)
+ {
+ BundleProtectionDomain pd =
+ (BundleProtectionDomain) bundleProtectionDomain;
+ FelixBundle bundle = pd.getBundle();
+ BundleInfo info = bundle.getInfo();
+
+ if (info.getBundleId() == 0)
+ {
+ return true;
+ }
+
+ // TODO: using true, false, or null seems a bit awkward. Improve this.
+ Boolean result = null;
+ if (m_pai != null)
+ {
+ result =
+ m_pai.hasPermission(info.getLocation(), pd.getBundle(),
+ permission, m_cpai, pd);
+ }
+
+ if (result != null)
+ {
+ return result.booleanValue();
+ }
+
+ if (m_cpai != null)
+ {
+ try
+ {
+ return m_cpai.hasPermission(bundle,
+ info.getCurrentModule().getContentLoader(),
+ bundle.getBundleId() + "-" +
+ info.getArchive().getLastModified(),null, pd,
+ permission, direct);
+ }
+ catch (Exception e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java Thu Mar 6 16:37:30 2008
@@ -0,0 +1,55 @@
+/*
+ * 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.felix.framework.security;
+
+import java.io.File;
+
+public interface SecurityConstants
+{
+ public static final String KEYSTORE_FILE_PROP = "felix.keystore";
+
+ public static final String KEYSTORE_FILE_VALUE = "file:" +
+ System.getProperty("java.home") + File.separatorChar + "lib"
+ + File.separatorChar + "security" + File.separatorChar + "cacerts"
+ + "|file:" + System.getProperty("user.home") + File.separatorChar
+ + ".keystore";
+
+ public static final String KEYSTORE_TYPE_PROP = "felix.keystore.type";
+
+ public static final String KEYSTORE_TYPE_VALUE = "JKS" + "|" + "JKS";
+
+ public static final String KEYSTORE_PASS_PROP = "felix.keystore.pass";
+
+ public static final String KEYSTORE_PASS_VALUE =
+ "changeit" + "|" + "changeit";
+
+ public static final String CRL_FILE_PROP = "felix.crl";
+
+ public static final String CRL_FILE_VALUE = "";
+
+ public static final String ENABLE_CONDPERMADMIN_PROP =
+ "felix.security.conpermadmin";
+
+ public static final String ENABLE_CONDPERMADMIN_VALUE = "true";
+
+ public static final String ENABLE_PERMISSIONADMIN_PROP =
+ "felix.security.permissionadmin";
+
+ public static final String ENABLE_PERMISSIONADMIN_VALUE = "true";
+}
Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java Thu Mar 6 16:37:30 2008
@@ -0,0 +1,458 @@
+/*
+ * 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.felix.framework.security.condpermadmin;
+
+import java.io.IOException;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.DomainCombiner;
+import java.security.Permission;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.felix.framework.security.util.Conditions;
+import org.apache.felix.framework.security.util.LocalPermissions;
+import org.apache.felix.framework.security.util.Permissions;
+import org.apache.felix.framework.security.util.PropertiesCache;
+import org.apache.felix.framework.util.IteratorToEnumeration;
+import org.apache.felix.moduleloader.IContentLoader;
+import org.osgi.framework.Bundle;
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * An implementation of the ConditionalPermissionAdmin service that doesn't need
+ * to have a framework specific security manager set. It use the DomainGripper
+ * to know what bundleprotectiondomains are expected.
+ */
+public final class ConditionalPermissionAdminImpl implements
+ ConditionalPermissionAdmin
+{
+ private static final ConditionInfo[] EMPTY_CONDITION_INFO = new ConditionInfo[0];
+ private static final PermissionInfo[] EMPTY_PERMISSION_INFO = new PermissionInfo[0];
+ private final Map m_condPermInfos = new HashMap();
+ private final PropertiesCache m_propertiesCache;
+ private final Permissions m_permissions;
+ private final Conditions m_conditions;
+ private final LocalPermissions m_localPermissions;
+
+ public ConditionalPermissionAdminImpl(Permissions permissions,
+ Conditions condtions, LocalPermissions localPermissions,
+ PropertiesCache cache) throws IOException
+ {
+ m_propertiesCache = cache;
+ m_permissions = permissions;
+ m_conditions = condtions;
+ m_localPermissions = localPermissions;
+ // Now try to restore the cache.
+ Map old = m_propertiesCache.read(ConditionalPermissionInfoImpl.class);
+ if (old != null)
+ {
+ for (Iterator iter = old.entrySet().iterator(); iter.hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+ String name = (String) entry.getKey();
+ ConditionalPermissionInfoImpl cpi =
+ ((ConditionalPermissionInfoImpl) entry.getValue());
+ m_condPermInfos.put(name, new ConditionalPermissionInfoImpl(
+ name, cpi._getConditionInfos(), cpi._getPermissionInfos(),
+ this));
+ }
+ }
+ }
+
+ public ConditionalPermissionInfo addConditionalPermissionInfo(
+ ConditionInfo[] conditions, PermissionInfo[] permissions)
+ {
+ Object sm = System.getSecurityManager();
+ if (sm != null)
+ {
+ ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+ }
+ ConditionalPermissionInfoImpl result =
+ new ConditionalPermissionInfoImpl(notNull(conditions),
+ notNull(permissions), this);
+
+ return write(result.getName(), result);
+ }
+
+ ConditionalPermissionInfoImpl write(String name,
+ ConditionalPermissionInfoImpl cpi)
+ {
+ synchronized (m_propertiesCache)
+ {
+ Map tmp = null;
+
+ synchronized (m_condPermInfos)
+ {
+ tmp = new HashMap(m_condPermInfos);
+
+ if ((name != null) && (cpi != null))
+ {
+ m_condPermInfos.put(name, cpi);
+ }
+ else if (name != null)
+ {
+ m_condPermInfos.remove(name);
+ }
+ else
+ {
+ tmp = null;
+ }
+ }
+
+ try
+ {
+ m_propertiesCache.write(m_condPermInfos);
+ }
+ catch (IOException ex)
+ {
+ synchronized (m_condPermInfos)
+ {
+ m_condPermInfos.clear();
+ m_condPermInfos.putAll(tmp);
+ }
+ ex.printStackTrace();
+ throw new IllegalStateException(ex.getMessage());
+ }
+ }
+ synchronized (m_condPermInfos)
+ {
+ return (ConditionalPermissionInfoImpl) m_condPermInfos.get(name);
+ }
+ }
+
+ // TODO: this is pretty much untested so it might not work like this
+ public AccessControlContext getAccessControlContext(String[] signers)
+ {
+ final String[] finalSigners =
+ (String[]) notNull(signers).toArray(new String[0]);
+ return new AccessControlContext(AccessController.getContext(),
+ new DomainCombiner()
+ {
+ public ProtectionDomain[] combine(ProtectionDomain[] arg0,
+ ProtectionDomain[] arg1)
+ {
+ return new ProtectionDomain[] { new ProtectionDomain(null,
+ null)
+ {
+ public boolean implies(Permission permission)
+ {
+ return hasPermission(null, null, null, finalSigners,
+ this, permission, true);
+ }
+ } };
+ }
+ });
+ }
+
+ public ConditionalPermissionInfo getConditionalPermissionInfo(String name)
+ {
+ if (name == null)
+ {
+ throw new IllegalArgumentException("Name may not be null");
+ }
+ ConditionalPermissionInfoImpl result = null;
+
+ synchronized (m_condPermInfos)
+ {
+ result = (ConditionalPermissionInfoImpl) m_condPermInfos.get(name);
+ }
+
+ if (result == null)
+ {
+ result = new ConditionalPermissionInfoImpl(this, name);
+
+ result = write(result.getName(), result);
+ }
+
+ return result;
+ }
+
+ public Enumeration getConditionalPermissionInfos()
+ {
+ synchronized (m_condPermInfos)
+ {
+ return new IteratorToEnumeration((new ArrayList(m_condPermInfos
+ .values())).iterator());
+ }
+ }
+
+ public ConditionalPermissionInfo setConditionalPermissionInfo(String name,
+ ConditionInfo[] conditions, PermissionInfo[] permissions)
+ {
+ Object sm = System.getSecurityManager();
+ if (sm != null)
+ {
+ ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+ }
+
+ ConditionalPermissionInfoImpl result = null;
+ conditions = notNull(conditions);
+ permissions = notNull(permissions);
+
+ if (name != null)
+ {
+ synchronized (m_condPermInfos)
+ {
+ result =
+ (ConditionalPermissionInfoImpl) m_condPermInfos.get(name);
+
+ if (result == null)
+ {
+ result =
+ new ConditionalPermissionInfoImpl(name, conditions,
+ permissions, this);
+ }
+ else
+ {
+ result.setConditionsAndPermissions(conditions, permissions);
+ }
+ }
+ }
+ else
+ {
+ result =
+ new ConditionalPermissionInfoImpl(conditions, permissions, this);
+ }
+
+ return write(result.getName(), result);
+ }
+
+ private PermissionInfo[] notNull(PermissionInfo[] permissions)
+ {
+ if (permissions == null)
+ {
+ return ConditionalPermissionInfoImpl.PERMISSION_INFO;
+ }
+ return (PermissionInfo[]) notNull((Object[]) permissions).toArray(
+ EMPTY_PERMISSION_INFO);
+ }
+
+ private ConditionInfo[] notNull(ConditionInfo[] conditions)
+ {
+ if (conditions == null)
+ {
+ return ConditionalPermissionInfoImpl.CONDITION_INFO;
+ }
+ return (ConditionInfo[]) notNull((Object[]) conditions).toArray(
+ EMPTY_CONDITION_INFO);
+ }
+
+ private List notNull(Object[] elements)
+ {
+ List result = new ArrayList();
+
+ for (int i = 0; i < elements.length; i++)
+ {
+ if (elements[i] != null)
+ {
+ result.add(elements[i]);
+ }
+ }
+
+ return result;
+ }
+
+ // The thread local stack used to keep track of bundle protection domains we
+ // still expect to see.
+ private final ThreadLocal m_stack = new ThreadLocal();
+
+ /**
+ * This method does the actual permission check. If it is not a direct check
+ * it will try to determine the other bundle domains that will follow
+ * automatically in case this is the first check in one permission check.
+ * If not then it will keep track of which domains we have already see.
+ * While it keeps track it builds up a list of postponed tuples which
+ * it will evaluate at the last domain. See the core spec 9.5.1 and following
+ * for a general description.
+ *
+ * @param felixBundle the bundle in question.
+ * @param loader the content loader of the bundle to get access to the jar
+ * to check for local permissions.
+ * @param root the bundle id.
+ * @param signers the signers (this is to support the ACC based on signers)
+ * @param pd the bundle protection domain
+ * @param permission the permission currently checked
+ * @param direct whether this is a direct check or not. direct check will not
+ * expect any further bundle domains on the stack
+ * @return true in case the permission is granted or there are postponed tuples
+ * false if not. Again, see the spec for more explanations.
+ */
+ public boolean hasPermission(Bundle felixBundle, IContentLoader loader, String root,
+ String[] signers, ProtectionDomain pd, Permission permission,
+ boolean direct)
+ {
+ // System.out.println(felixBundle + "-" + permission);
+ List domains = null;
+ List tuples = null;
+ Object[] entry = null;
+ // first see whether this is the normal case (the special case is for
+ // the ACC based on signers).
+ if (signers == null)
+ {
+ // In case of a direct call we don't need to look for other pds
+ if (direct)
+ {
+ domains = new ArrayList();
+ tuples = new ArrayList();
+ domains.add(pd);
+ }
+ else
+ {
+ // Get the other pds from the stck
+ entry = (Object[]) m_stack.get();
+
+ // if there are none then get them from the gripper
+ if (entry == null)
+ {
+ entry =
+ new Object[] { new ArrayList(DomainGripper.grep()),
+ new ArrayList() };
+ }
+ else
+ {
+ m_stack.set(null);
+ }
+
+ domains = (List) entry[0];
+ tuples = (List) entry[1];
+ if (!domains.contains(pd))
+ {
+ // We have been called directly without the direct flag
+ domains.clear();
+ domains.add(pd);
+ }
+ }
+ }
+
+ // check the local permissions. they need to all the permission if there
+ // are any
+ if (!m_localPermissions.implies(root, loader, felixBundle, permission))
+ {
+ return false;
+ }
+
+ List posts = new ArrayList();
+
+ boolean result = eval(posts, felixBundle, signers, permission);
+
+ if (signers != null)
+ {
+ return result;
+ }
+
+ domains.remove(pd);
+
+ // We postponed tuples
+ if (!posts.isEmpty())
+ {
+ tuples.add(posts);
+ }
+
+ // Are we at the end or this was a direct call?
+ if (domains.isEmpty())
+ {
+ m_stack.set(null);
+ // Now eval the postponed tupels. if the previous eval did return false
+ // tuples will be empty so we don't return from here.
+ if (!tuples.isEmpty())
+ {
+ return m_conditions.evalRecursive(tuples);
+ }
+ }
+ else
+ {
+ // this is to support recursive permission checks. In case we trigger
+ // a permission check while eval the stack is null until this point
+ m_stack.set(entry);
+ }
+
+ return result;
+ }
+
+ // we need to find all conditions that apply and then check whether they
+ // de note the permission in question unless the conditions are postponed
+ // then we make sure their permissions imply the permission and add them
+ // to the list of posts. Return true in case we pass or have posts
+ // else falls and clear the posts first.
+ private boolean eval(List posts, Bundle bundle, String[] signers,
+ Permission permission)
+ {
+ List condPermInfos = null;
+
+ synchronized (m_condPermInfos)
+ {
+ if (m_condPermInfos.isEmpty())
+ {
+ return true;
+ }
+ condPermInfos = new ArrayList(m_condPermInfos.values());
+ }
+
+ // Check for implicit permissions like access to file area
+ if ((bundle != null)
+ && m_permissions.getPermissions(m_permissions.getImplicit(bundle))
+ .implies(permission, bundle))
+ {
+ return true;
+ }
+
+ // now do the real thing
+ for (Iterator iter = condPermInfos.iterator(); iter.hasNext();)
+ {
+ ConditionalPermissionInfoImpl cpi =
+ (ConditionalPermissionInfoImpl) iter.next();
+
+ ConditionInfo[] conditions = cpi._getConditionInfos();
+
+ List currentPosts = new ArrayList();
+
+ if (!m_conditions.getConditions(bundle, signers, conditions)
+ .isSatisfied(currentPosts))
+ {
+ continue;
+ }
+
+ if (!m_permissions.getPermissions(cpi._getPermissionInfos())
+ .implies(permission, null))
+ {
+ continue;
+ }
+
+ if (currentPosts.isEmpty())
+ {
+ posts.clear();
+ return true;
+ }
+
+ posts.add(currentPosts);
+ }
+
+ return !posts.isEmpty();
+ }
+}
Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java Thu Mar 6 16:37:30 2008
@@ -0,0 +1,212 @@
+/*
+ * 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.felix.framework.security.condpermadmin;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.StringTokenizer;
+
+import org.apache.felix.framework.security.util.Permissions;
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * Simple storage class for condperminfos. Additionally, this class can be used
+ * to encode and decode infos.
+ */
+public final class ConditionalPermissionInfoImpl implements ConditionalPermissionInfo
+{
+ private static final Random RANDOM = new Random();
+ static final ConditionInfo[] CONDITION_INFO = new ConditionInfo[0];
+ static final PermissionInfo[] PERMISSION_INFO = new PermissionInfo[0];
+ private final Object m_lock = new Object();
+ private final String m_name;
+ private volatile ConditionalPermissionAdminImpl m_cpai;
+ private ConditionInfo[] m_conditions;
+ private PermissionInfo[] m_permissions;
+
+ public ConditionalPermissionInfoImpl(String encoded)
+ {
+ StringTokenizer tok = new StringTokenizer(encoded, "\n");
+ if (!tok.nextToken().trim().equals("{"))
+ {
+ throw new IllegalArgumentException();
+ }
+ m_cpai = null;
+ m_name = tok.nextToken().trim().substring(1);
+ List conditions = new ArrayList();
+ List permissions = new ArrayList();
+ for (String current = tok.nextToken().trim();; current =
+ tok.nextToken().trim())
+ {
+ if (current.equals("}"))
+ {
+ break;
+ }
+ else if (current.startsWith("["))
+ {
+ conditions.add(new ConditionInfo(current));
+ }
+ else if (current.startsWith("("))
+ {
+ permissions.add(new PermissionInfo(current));
+ }
+ else
+ {
+ if (!current.startsWith("#"))
+ {
+ throw new IllegalArgumentException();
+ }
+ }
+ }
+
+ m_conditions =
+ conditions.isEmpty() ? CONDITION_INFO
+ : (ConditionInfo[]) conditions
+ .toArray(new ConditionInfo[conditions.size()]);
+ m_permissions =
+ permissions.isEmpty() ? PERMISSION_INFO
+ : (PermissionInfo[]) permissions
+ .toArray(new PermissionInfo[permissions.size()]);
+ }
+
+ public ConditionalPermissionInfoImpl(ConditionalPermissionAdminImpl cpai,
+ String name)
+ {
+ m_name = name;
+ m_cpai = cpai;
+ m_conditions = CONDITION_INFO;
+ m_permissions = PERMISSION_INFO;
+ }
+
+ public ConditionalPermissionInfoImpl(ConditionInfo[] conditions,
+ PermissionInfo[] permisions, ConditionalPermissionAdminImpl cpai)
+ {
+ m_name = Long.toString(RANDOM.nextLong() ^ System.currentTimeMillis());
+ m_cpai = cpai;
+ m_conditions = conditions;
+ m_permissions = permisions;
+ }
+
+ public ConditionalPermissionInfoImpl(String name,
+ ConditionInfo[] conditions, PermissionInfo[] permisions,
+ ConditionalPermissionAdminImpl cpai)
+ {
+ m_name = name;
+ m_conditions = conditions;
+ m_permissions = permisions;
+ m_cpai = cpai;
+ }
+
+ public void delete()
+ {
+ Object sm = System.getSecurityManager();
+ if (sm != null)
+ {
+ ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+ }
+
+ synchronized (m_lock)
+ {
+ m_cpai.write(m_name, null);
+ m_conditions = CONDITION_INFO;
+ m_permissions = PERMISSION_INFO;
+ }
+ }
+
+ public ConditionInfo[] getConditionInfos()
+ {
+ synchronized (m_lock)
+ {
+ return (ConditionInfo[]) m_conditions.clone();
+ }
+ }
+
+ ConditionInfo[] _getConditionInfos()
+ {
+ synchronized (m_lock)
+ {
+ return m_conditions;
+ }
+ }
+
+ void setConditionsAndPermissions(ConditionInfo[] conditions,
+ PermissionInfo[] permissions)
+ {
+ synchronized (m_lock)
+ {
+ m_conditions = conditions;
+ m_permissions = permissions;
+ }
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public PermissionInfo[] getPermissionInfos()
+ {
+ synchronized (m_lock)
+ {
+ return (PermissionInfo[]) m_permissions.clone();
+ }
+ }
+
+ PermissionInfo[] _getPermissionInfos()
+ {
+ synchronized (m_lock)
+ {
+ return m_permissions;
+ }
+ }
+
+ public String getEncoded()
+ {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append('{');
+ buffer.append('\n');
+ buffer.append('#');
+ buffer.append(m_name);
+ buffer.append('\n');
+ synchronized (m_lock)
+ {
+ writeTo(m_conditions, buffer);
+ writeTo(m_permissions, buffer);
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+
+ private void writeTo(Object[] elements, StringBuffer buffer)
+ {
+ for (int i = 0; i < elements.length; i++)
+ {
+ buffer.append(elements[i]);
+ buffer.append('\n');
+ }
+ }
+
+ public String toString()
+ {
+ return getEncoded();
+ }
+}
Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java Thu Mar 6 16:37:30 2008
@@ -0,0 +1,134 @@
+/*
+ * 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.felix.framework.security.condpermadmin;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.AllPermission;
+import java.security.DomainCombiner;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.framework.BundleProtectionDomain;
+
+/**
+ * This class is a hack to get all BundleProtectionDomains currently on the
+ * security stack. This way we don't need to have our own security manager set.
+ */
+final class DomainGripper implements DomainCombiner, PrivilegedAction
+{
+ private static final ProtectionDomain[] ALL_PERMISSION_PD =
+ new ProtectionDomain[] { new ProtectionDomain(null, null)
+ {
+ public boolean implies(Permission perm)
+ {
+ return true;
+ }
+ } };
+
+ // A per thread cache of DomainGripper objects. We might want to wrap them
+ // in a softreference eventually
+ private static final ThreadLocal m_cache = new ThreadLocal();
+
+ private static final Permission ALL_PERMISSION = new AllPermission();
+
+ private final List m_domains = new ArrayList();
+
+ private AccessControlContext m_system = null;
+
+ /**
+ * Get all bundle protection domains and add them to the m_domains. Then
+ * return the ALL_PERMISSION_PD.
+ */
+ public ProtectionDomain[] combine(ProtectionDomain[] current,
+ ProtectionDomain[] assigned)
+ {
+ filter(current, m_domains);
+ filter(assigned, m_domains);
+
+ return ALL_PERMISSION_PD;
+ }
+
+ private void filter(ProtectionDomain[] assigned, List domains)
+ {
+ if (assigned != null)
+ {
+ for (int i = 0; i < assigned.length; i++)
+ {
+ if ((assigned[i].getClass() == BundleProtectionDomain.class)
+ && !domains.contains(assigned[i]))
+ {
+ domains.add(assigned[i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the current bundle protection domains on the stack up to the last
+ * privileged call.
+ */
+ public static List grep()
+ {
+ // First try to get a cached version. We cache by thread.
+ DomainGripper gripper = (DomainGripper) m_cache.get();
+ if (gripper == null)
+ {
+ // there is none so create one and cache it
+ gripper = new DomainGripper();
+ m_cache.set(gripper);
+ }
+ else
+ {
+ // This thread has a cached version so prepare it
+ gripper.m_domains.clear();
+ }
+
+ // Get the current context.
+ gripper.m_system = AccessController.getContext();
+
+ // and merge it with the current combiner (i.e., gripper)
+ AccessControlContext context =
+ (AccessControlContext) AccessController.doPrivileged(gripper);
+
+ gripper.m_system = null;
+
+ // now get the protection domains
+ AccessController.doPrivileged(gripper, context);
+
+ // and return them
+ return gripper.m_domains;
+ }
+
+ public Object run()
+ {
+ // this is a call to merge with the current context.
+ if (m_system != null)
+ {
+ return new AccessControlContext(m_system, this);
+ }
+
+ // this is a call to get the protection domains.
+ AccessController.checkPermission(ALL_PERMISSION);
+ return null;
+ }
+}
Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java Thu Mar 6 16:37:30 2008
@@ -0,0 +1,291 @@
+/*
+ * 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.felix.framework.security.permissionadmin;
+
+import java.io.IOException;
+import java.security.AllPermission;
+import java.security.Permission;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
+import org.apache.felix.framework.security.util.Permissions;
+import org.apache.felix.framework.security.util.PropertiesCache;
+import org.osgi.framework.Bundle;
+import org.osgi.service.permissionadmin.PermissionAdmin;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * This class is a relatively straight forward implementation of the PermissionAdmin service.
+ * The only somewhat involved thing is that it respects the presents of a
+ * conditionalpermissionadmin service as per spec.
+ */
+// TODO: Do we need this class at all or can we just emulate it using the condpermadmin?
+public final class PermissionAdminImpl implements PermissionAdmin
+{
+ private static final PermissionInfo[] ALL_PERMISSION =
+ new PermissionInfo[] { new PermissionInfo(
+ AllPermission.class.getName(), "", "") };
+
+ private final Map m_store = new HashMap();
+
+ private final PropertiesCache m_cache;
+
+ private final Permissions m_permissions;
+
+ private PermissionInfo[] m_default = null;
+
+ public PermissionAdminImpl(Permissions permissions, PropertiesCache cache)
+ throws IOException
+ {
+ m_permissions = permissions;
+ m_cache = cache;
+ Map old = m_cache.read(PermissionInfo[].class);
+ if (old != null)
+ {
+ m_store.putAll(old);
+ }
+ }
+
+ public PermissionInfo[] getDefaultPermissions()
+ {
+ synchronized (m_store)
+ {
+ if (m_default == null)
+ {
+ return null;
+ }
+ return (PermissionInfo[]) m_default.clone();
+ }
+ }
+
+ public synchronized String[] getLocations()
+ {
+ synchronized (m_store)
+ {
+ if (m_store.isEmpty())
+ {
+ return null;
+ }
+
+ return (String[]) m_store.keySet().toArray(
+ new String[m_store.size()]);
+ }
+ }
+
+ public PermissionInfo[] getPermissions(String location)
+ {
+ synchronized (m_store)
+ {
+ if (m_store.containsKey(location))
+ {
+ return (PermissionInfo[]) ((PermissionInfo[]) m_store
+ .get(location)).clone();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * This will do the actual permission check as described in the core spec 10.2
+ * It will respect a present condpermadmin service as described in 9.10.
+ *
+ * @param location the location of the bundle.
+ * @param bundle the bundle in question.
+ * @param permission the permission to check.
+ * @param cpai A condpermadmin if one is present else null.
+ * @param pd the protectiondomain
+ * @return Boolean.TRUE if the location is bound and the permission is
+ * granted or if there is no cpa and the default permissions imply the
+ * permission Boolean.FALSE otherwise unless the location is not bound and
+ * their is a cpa in which case null is returned.
+ */
+ public Boolean hasPermission(String location, Bundle bundle,
+ Permission permission, ConditionalPermissionAdminImpl cpai,
+ ProtectionDomain pd)
+ {
+ PermissionInfo[] permissions = null;
+ boolean file = false;
+ synchronized (m_store)
+ {
+ if (m_store.containsKey(location))
+ {
+ permissions = (PermissionInfo[]) m_store.get(location);
+ file = true;
+ }
+ else if (cpai == null)
+ {
+ if (m_default != null)
+ {
+ permissions = m_default;
+ }
+ else
+ {
+ permissions = ALL_PERMISSION;
+ }
+ }
+ }
+ if ((cpai == null) || file)
+ {
+ if (check(permissions, permission, file ? bundle : null))
+ {
+ return Boolean.TRUE;
+ }
+ }
+
+ permissions = m_permissions.getImplicit(bundle);
+
+ if (check(permissions, permission, bundle))
+ {
+ return Boolean.TRUE;
+ }
+
+ if ((cpai != null) && !file)
+ {
+ return null;
+ }
+ return Boolean.FALSE;
+ }
+
+ private boolean check(PermissionInfo[] permissions, Permission permission,
+ Bundle bundle)
+ {
+ Permissions permissionsObject =
+ m_permissions.getPermissions(permissions);
+
+ return permissionsObject.implies(permission, bundle);
+ }
+
+ public void setDefaultPermissions(PermissionInfo[] permissions)
+ {
+ Object sm = System.getSecurityManager();
+ if (sm != null)
+ {
+ ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+ }
+
+ synchronized (m_cache)
+ {
+ PermissionInfo[] def = null;
+ Map store = null;
+ synchronized (m_store)
+ {
+ def = m_default;
+ store = new HashMap(m_store);
+
+ m_default = (permissions != null) ? notNull(permissions) : null;
+ }
+
+ try
+ {
+ m_cache.write(setDefaults(store, def));
+ }
+ catch (IOException ex)
+ {
+ synchronized (m_store)
+ {
+ m_default = def;
+ }
+
+ ex.printStackTrace();
+ // TODO: log this
+ throw new IllegalStateException(ex.getMessage());
+ }
+ }
+ }
+
+ public void setPermissions(String location, PermissionInfo[] permissions)
+ {
+ Object sm = System.getSecurityManager();
+ if (sm != null)
+ {
+ ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+ }
+
+ synchronized (m_cache)
+ {
+ if (location != null)
+ {
+ Map store = null;
+ Map storeCopy = null;
+ PermissionInfo[] def = null;
+ synchronized (m_store)
+ {
+ storeCopy = new HashMap(m_store);
+ if (permissions != null)
+ {
+ m_store.put(location, notNull(permissions));
+ }
+ else
+ {
+ m_store.remove(location);
+ }
+ store = new HashMap(m_store);
+ }
+ try
+ {
+ m_cache.write(setDefaults(store, def));
+ }
+ catch (IOException ex)
+ {
+ synchronized (m_store)
+ {
+ m_store.clear();
+ m_store.putAll(storeCopy);
+ }
+
+ ex.printStackTrace();
+ // TODO: log this
+ throw new IllegalStateException(ex.getMessage());
+ }
+ }
+ }
+ }
+
+ private Map setDefaults(Map store, PermissionInfo[] def)
+ {
+ if (def != null)
+ {
+ store.put("DEFAULT", def);
+ }
+ else
+ {
+ store.remove("DEFAULT");
+ }
+ return store;
+ }
+
+ private PermissionInfo[] notNull(PermissionInfo[] permissions)
+ {
+ List result = new ArrayList();
+
+ for (int i = 0; i < permissions.length; i++)
+ {
+ if (permissions[i] != null)
+ {
+ result.add(permissions[i]);
+ }
+ }
+ return (PermissionInfo[]) result.toArray(new PermissionInfo[result
+ .size()]);
+ }
+}
Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java Thu Mar 6 16:37:30 2008
@@ -0,0 +1,203 @@
+/*
+ * 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.felix.framework.security.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+import org.apache.felix.framework.util.IteratorToEnumeration;
+import org.apache.felix.moduleloader.IContent;
+
+/**
+ * This class makes a given content available as a inputstream with a jar content.
+ * In other words the stream can be used as input to a JarInputStream.
+ */
+public final class BundleInputStream extends InputStream
+{
+ private final IContent m_root;
+ private final Enumeration m_content;
+ private final OutputStreamBuffer m_outputBuffer = new OutputStreamBuffer();
+
+ private ByteArrayInputStream m_buffer = null;
+ private JarOutputStream m_output = null;
+
+ public BundleInputStream(IContent root) throws IOException
+ {
+ m_root = root;
+
+ List entries = new ArrayList();
+
+ int count = 0;
+ String manifest = null;
+ for (Enumeration e = m_root.getEntries();e.hasMoreElements();)
+ {
+ String entry = (String) e.nextElement();
+ if (entry.equalsIgnoreCase("META-INF/MANIFEST.MF"))
+ {
+ if (manifest == null)
+ {
+ manifest = entry;
+ }
+ }
+ else if (entry.toUpperCase().startsWith("META-INF/"))
+ {
+ entries.add(count++, entry);
+ }
+ else
+ {
+ entries.add(entry);
+ }
+ }
+ if (manifest == null)
+ {
+ manifest = "META-INF/MANIFEST.MF";
+ }
+ m_content = new IteratorToEnumeration(entries.iterator());
+
+ try
+ {
+ m_output = new JarOutputStream(m_outputBuffer);
+ readNext(manifest);
+ m_buffer = new ByteArrayInputStream(
+ m_outputBuffer.m_outBuffer.toByteArray());
+
+ m_outputBuffer.m_outBuffer = null;
+ }
+ catch (IOException ex)
+ {
+ // TODO: figure out what is wrong
+ ex.printStackTrace();
+ throw ex;
+ }
+ }
+
+ public int read() throws IOException
+ {
+ if ((m_output == null) && (m_buffer == null))
+ {
+ return -1;
+ }
+
+ if (m_buffer != null)
+ {
+ int result = m_buffer.read();
+
+ if (result == -1)
+ {
+ m_buffer = null;
+ return read();
+ }
+
+ return result;
+ }
+
+ if (m_content.hasMoreElements())
+ {
+ String current = (String) m_content.nextElement();
+
+ readNext(current);
+
+ if (!m_content.hasMoreElements())
+ {
+ m_output.close();
+ m_output = null;
+ }
+
+ m_buffer = new ByteArrayInputStream(
+ m_outputBuffer.m_outBuffer.toByteArray());
+
+ m_outputBuffer.m_outBuffer = null;
+ }
+
+ return read();
+ }
+
+ private void readNext(String path) throws IOException
+ {
+ m_outputBuffer.m_outBuffer = new ByteArrayOutputStream();
+
+ InputStream in = null;
+ try
+ {
+ in = m_root.getEntryAsStream(path);
+
+ if (in == null)
+ {
+ throw new IOException("Missing entry");
+ }
+
+ JarEntry entry = new JarEntry(path);
+
+ m_output.putNextEntry(entry);
+
+ byte[] buffer = new byte[4 * 1024];
+
+ for (int c = in.read(buffer); c != -1; c = in.read(buffer))
+ {
+ m_output.write(buffer, 0, c);
+ }
+ }
+ finally
+ {
+ if (in != null)
+ {
+ try
+ {
+ in.close();
+ }
+ catch (Exception ex)
+ {
+ // Not much we can do
+ }
+ }
+ }
+
+ m_output.closeEntry();
+
+ m_output.flush();
+ }
+
+ private static final class OutputStreamBuffer extends OutputStream
+ {
+ ByteArrayOutputStream m_outBuffer = null;
+
+ public void write(int b)
+ {
+ m_outBuffer.write(b);
+ }
+
+ public void write(byte[] buffer) throws IOException
+ {
+ m_outBuffer.write(buffer);
+ }
+
+ public void write(byte[] buffer, int offset, int length)
+ {
+ m_outBuffer.write(buffer, offset, length);
+ }
+ }
+}
Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java Thu Mar 6 16:37:30 2008
@@ -0,0 +1,387 @@
+/*
+ * 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.felix.framework.security.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.Map.Entry;
+
+import org.apache.felix.framework.security.verifier.SignerMatcher;
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.framework.Bundle;
+import org.osgi.service.condpermadmin.BundleSignerCondition;
+import org.osgi.service.condpermadmin.Condition;
+import org.osgi.service.condpermadmin.ConditionInfo;
+
+/**
+ * This class caches conditions instances by their infos. Furthermore, it allows
+ * to eval postponed condition permission tuples as per spec (see 9.45).
+ */
+// TODO: maybe use bundle events instead of soft/weak references.
+public final class Conditions
+{
+ private static final ThreadLocal m_conditionStack = new ThreadLocal();
+
+ private final Map m_cache = new WeakHashMap();
+
+ private final Bundle m_bundle;
+ private final String[] m_signers;
+
+ private final ConditionInfo[] m_conditionInfos;
+ private final Condition[] m_conditions;
+ private final SecureAction m_action;
+
+ public Conditions(SecureAction action)
+ {
+ this(null, null, null, action);
+ }
+
+ private Conditions(Bundle bundle, String[] signers,
+ ConditionInfo[] conditions, SecureAction action)
+ {
+ m_bundle = bundle;
+ m_signers = signers;
+ m_conditionInfos = conditions;
+ m_conditions = ((conditions != null) && (bundle != null)) ? new Condition[m_conditionInfos.length] : null;
+ m_action = action;
+ }
+
+ public Conditions getConditions(Bundle bundle, String[] signers,
+ ConditionInfo[] conditions)
+ {
+ Conditions result = null;
+ Map index = null;
+ synchronized (m_cache)
+ {
+ index = (Map) m_cache.get(conditions);
+ if (index == null)
+ {
+ index = new WeakHashMap();
+ m_cache.put(conditions, index);
+ }
+ }
+ synchronized (index)
+ {
+ if (bundle != null)
+ {
+ result = (Conditions) index.get(bundle);
+ }
+ }
+
+ if (result == null)
+ {
+ result = new Conditions(bundle, signers, conditions, m_action);
+ synchronized (index)
+ {
+ index.put(bundle, result);
+ }
+ }
+
+ return result;
+ }
+
+ // See whether the given list is satisfied or not
+ public boolean isSatisfied(List posts)
+ {
+ for (int i = 0; i < m_conditions.length; i++)
+ {
+ if (m_bundle == null)
+ {
+ if (!m_conditionInfos[i].getType().equals(
+ BundleSignerCondition.class.getName()))
+ {
+ return false;
+ }
+ String[] args = m_conditionInfos[i].getArgs();
+
+ boolean match = false;
+ if (args.length == 0)
+ {
+ for (int j = 0; j < m_signers.length; j++)
+ {
+ if (SignerMatcher.match(args[0], m_signers[j]))
+ {
+ match = true;
+ break;
+ }
+ }
+ }
+ if (!match)
+ {
+ return false;
+ }
+ continue;
+ }
+ try
+ {
+ Condition condition = null;
+ boolean add = false;
+ Class clazz = Class.forName(m_conditionInfos[i].getType());
+
+ synchronized (m_conditionInfos)
+ {
+ condition = m_conditions[i];
+
+ }
+
+ if (condition == null)
+ {
+ add = true;
+ condition = createCondition(m_bundle, clazz, m_conditionInfos[i]);
+ }
+
+ if (condition.isPostponed())
+ {
+ posts.add(condition);
+ if (add)
+ {
+ synchronized (m_conditionInfos)
+ {
+ if (m_conditions[i] == null)
+ {
+ m_conditions[i] = condition;
+ }
+ }
+ }
+ }
+ else
+ {
+ Object current = m_conditionStack.get();
+
+ if (current == null)
+ {
+ m_conditionStack.set(clazz);
+ }
+ else
+ {
+ if (current instanceof HashSet)
+ {
+ if (((HashSet) current).contains(clazz))
+ {
+ return false;
+ }
+ ((HashSet) current).add(clazz);
+ }
+ else
+ {
+ if (current == clazz)
+ {
+ return false;
+ }
+ HashSet frame = new HashSet();
+ frame.add(current);
+ frame.add(clazz);
+ m_conditionStack.set(frame);
+ current = frame;
+ }
+ }
+ try
+ {
+ boolean result = condition.isSatisfied();
+
+ if (!condition.isMutable() && ((condition != Condition.TRUE) && (condition != Condition.FALSE)))
+ {
+ synchronized (m_conditionInfos)
+ {
+ m_conditions[i] = result ? Condition.TRUE : Condition.FALSE;
+ }
+ }
+ else
+ {
+ synchronized (m_conditionInfos)
+ {
+ m_conditions[i] = condition;
+ }
+ }
+ if (!result)
+ {
+ return false;
+ }
+ }
+ finally
+ {
+ if (current == null)
+ {
+ m_conditionStack.set(null);
+ }
+ else
+ {
+ ((HashSet) current).remove(clazz);
+ if (((HashSet) current).isEmpty())
+ {
+ m_conditionStack.set(null);
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ // TODO: log this as per spec
+ e.printStackTrace();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean evalRecursive(List entries)
+ {
+ return _evalRecursive(entries, 0, new ArrayList(), new HashMap());
+ }
+
+ private boolean _evalRecursive(List entries, int pos, List acc, Map contexts)
+ {
+ if (pos == entries.size())
+ {
+ // we need to group by type by tuple
+ Map conditions = new HashMap();
+ for (Iterator iter = acc.iterator(); iter.hasNext();)
+ {
+ for (Iterator iter2 = ((List) iter.next()).iterator(); iter2
+ .hasNext();)
+ {
+ Object entry = iter2.next();
+ Set group = (Set) conditions.get(entry.getClass());
+
+ if (group == null)
+ {
+ group = new HashSet();
+ }
+ group.add(entry);
+
+ conditions.put(entry.getClass(), group);
+ }
+ }
+
+ // and then eval per group
+ for (Iterator iter = conditions.entrySet().iterator(); iter.hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+ Class key = (Class) entry.getKey();
+
+ Hashtable context = (Hashtable) contexts.get(key);
+ if (context == null)
+ {
+ context = new Hashtable();
+ contexts.put(key, context);
+ }
+ Set set = (Set) entry.getValue();
+ Condition[] current =
+ (Condition[]) set.toArray(new Condition[set.size()]);
+
+ // We must be catching recursive evaluation as per spec, hence use a thread
+ // local stack to do so
+ Object currentCond = m_conditionStack.get();
+
+ if (currentCond == null)
+ {
+ m_conditionStack.set(key);
+ }
+ else
+ {
+ if (currentCond instanceof HashSet)
+ {
+ if (((HashSet) currentCond).contains(key))
+ {
+ return false;
+ }
+ ((HashSet) currentCond).add(key);
+ }
+ else
+ {
+ if (currentCond == key)
+ {
+ return false;
+ }
+ HashSet frame = new HashSet();
+ frame.add(current);
+ frame.add(key);
+ m_conditionStack.set(frame);
+ currentCond = frame;
+ }
+ }
+ try
+ {
+ if (!current[0].isSatisfied(current, context))
+ {
+ return false;
+ }
+ }
+ finally
+ {
+ if (currentCond == null)
+ {
+ m_conditionStack.set(null);
+ }
+ else
+ {
+ ((HashSet) currentCond).remove(key);
+ if (((HashSet) currentCond).isEmpty())
+ {
+ m_conditionStack.set(null);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ List entry = (List) entries.get(pos);
+
+ for (int i = 0; i < entry.size(); i++)
+ {
+ acc.add(entry.get(i));
+
+ if (_evalRecursive(entries, pos + 1, acc, contexts))
+ {
+ return true;
+ }
+
+ acc.remove(acc.size() - 1);
+ }
+
+ return false;
+ }
+
+ private Condition createCondition(final Bundle bundle, final Class clazz,
+ final ConditionInfo info) throws Exception
+ {
+ try
+ {
+ return (Condition) m_action.getMethod(clazz, "getCondition",
+ new Class[] { Bundle.class, ConditionInfo.class }).invoke(null,
+ new Object[] { bundle, info });
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ return (Condition) m_action.getConstructor(clazz,
+ new Class[] { Bundle.class, ConditionInfo.class }).newInstance(
+ new Object[] { bundle, info });
+ }
+ }
+}