blob: 801182ed421f026cd51f6892cce5cf5df7831412 [file] [log] [blame]
/*
* 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.File;
import java.io.FilePermission;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.PropertyPermission;
import org.apache.felix.framework.util.SecureAction;
import org.osgi.framework.AdminPermission;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.CapabilityPermission;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.PackagePermission;
import org.osgi.framework.ServicePermission;
import org.osgi.framework.ServiceReference;
import org.osgi.service.packageadmin.ExportedPackage;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.permissionadmin.PermissionInfo;
/**
* A permission cache that uses permisssion infos as keys. Permission are
* created from the parent classloader or any exported package.
*/
// TODO: maybe use bundle events instead of soft/weak references
public final class Permissions
{
private static final ClassLoader m_classLoader = Permissions.class
.getClassLoader();
private static final Map m_permissionCache = new HashMap();
private static final Map m_permissions = new HashMap();
private static final ReferenceQueue m_permissionsQueue = new ReferenceQueue();
private static final ThreadLocal m_stack = new ThreadLocal();
private final Map m_cache;
private final ReferenceQueue m_queue;
private final BundleContext m_context;
private final PermissionInfo[] m_permissionInfos;
private final boolean m_allPermission;
private final SecureAction m_action;
public static final AllPermission ALL_PERMISSION = new AllPermission();
private static final PermissionInfo[] IMPLICIT = new PermissionInfo[] { new PermissionInfo(
FilePermission.class.getName(), "-", "read,write,delete") };
Permissions(PermissionInfo[] permissionInfos, BundleContext context,
SecureAction action)
{
m_context = context;
m_permissionInfos = permissionInfos;
m_cache = new HashMap();
m_queue = new ReferenceQueue();
m_action = action;
for (int i = 0; i < m_permissionInfos.length; i++)
{
if (m_permissionInfos[i].getType().equals(
AllPermission.class.getName()))
{
m_allPermission = true;
return;
}
}
m_allPermission = false;
}
public Permissions(BundleContext context, SecureAction action)
{
m_context = context;
m_permissionInfos = null;
m_cache = null;
m_queue = null;
m_allPermission = true;
m_action = action;
}
public PermissionInfo[] getImplicit(Bundle bundle)
{
return new PermissionInfo[] {
IMPLICIT[0],
new PermissionInfo(PropertyPermission.class.getName(), "org.osgi.framework.*", "read"),
new PermissionInfo(
AdminPermission.class.getName(),
"(id=" + bundle.getBundleId() + ")",
AdminPermission.CLASS + "," + AdminPermission.METADATA + "," + AdminPermission.RESOURCE + "," + AdminPermission.CONTEXT),
new PermissionInfo(CapabilityPermission.class.getName(), "(|(capability.namespace=osgi.ee)(capability.namespace=osgi.native))", CapabilityPermission.REQUIRE),
new PermissionInfo(PackagePermission.class.getName(),"(package.name=java.*)",PackagePermission.IMPORT),
new PermissionInfo(ServicePermission.class.getName(),"org.osgi.service.condition.Condition", ServicePermission.GET)
};
}
public Permissions getPermissions(PermissionInfo[] permissionInfos)
{
cleanUp(m_permissionsQueue, m_permissions);
Permissions result = null;
synchronized (m_permissions)
{
result = (Permissions) m_permissions.get(new Entry(permissionInfos));
}
if (result == null)
{
//permissionInfos may not be referenced by the new Permissions, as
//otherwise the reference in m_permissions prevents the key from
//being garbage collectable.
PermissionInfo[] permissionInfosClone = new PermissionInfo[permissionInfos.length];
System.arraycopy(permissionInfos, 0, permissionInfosClone, 0, permissionInfos.length);
result = new Permissions(permissionInfosClone, m_context, m_action);
synchronized (m_permissions)
{
m_permissions.put(
new Entry(permissionInfos, m_permissionsQueue), result);
}
}
return result;
}
private static final class Entry extends WeakReference
{
private final int m_hashCode;
Entry(Object entry, ReferenceQueue queue)
{
super(entry, queue);
m_hashCode = entry.hashCode();
}
Entry(Object entry)
{
super(entry);
m_hashCode = entry.hashCode();
}
public Object get()
{
return super.get();
}
public int hashCode()
{
return m_hashCode;
}
public boolean equals(Object o)
{
if (o == null)
{
return false;
}
if (o == this)
{
return true;
}
Object entry = super.get();
if (o instanceof Entry)
{
Object otherEntry = ((Entry) o).get();
if (entry == null)
{
return otherEntry == null;
}
if (otherEntry == null)
{
return false;
}
if (!entry.getClass().equals(otherEntry.getClass()))
{
return false;
}
if (entry instanceof Object[])
{
return Arrays.equals((Object[])entry, (Object[])otherEntry);
}
return entry.equals(((Entry) o).get());
}
else
{
return false;
}
}
}
private static final class DefaultPermissionCollection extends
PermissionCollection
{
private final Map m_perms = new HashMap();
public void add(Permission perm)
{
synchronized (m_perms)
{
m_perms.put(perm, perm);
}
}
public Enumeration elements()
{
throw new IllegalStateException("Not implemented");
}
public boolean implies(Permission perm)
{
Map perms = null;
synchronized (m_perms)
{
perms = m_perms;
}
Permission permission = (Permission) perms.get(perm);
if ((permission != null) && permission.implies(perm))
{
return true;
}
for (Iterator iter = perms.values().iterator(); iter.hasNext();)
{
Permission current = (Permission) iter.next();
if ((current != null) && (current != permission)
&& current.implies(perm))
{
return true;
}
}
return false;
}
}
private void cleanUp(ReferenceQueue queue, Map cache)
{
for (Entry entry = (Entry) queue.poll(); entry != null; entry = (Entry) queue
.poll())
{
synchronized (cache)
{
cache.remove(entry);
}
}
}
/**
* @param target
* the permission to be implied
* @param bundle
* if not null then allow implicit permissions like file access
* to local data area
* @return true if the permission is implied by this permissions object.
*/
public boolean implies(Permission target, final Bundle bundle)
{
if (m_allPermission)
{
return true;
}
Class targetClass = target.getClass();
cleanUp(m_queue, m_cache);
if ((bundle != null) && targetClass == FilePermission.class)
{
for (int i = 0; i < m_permissionInfos.length; i++)
{
if (m_permissionInfos[i].getType().equals(
FilePermission.class.getName()))
{
String postfix = "";
String name = m_permissionInfos[i].getName();
if (!"<<ALL FILES>>".equals(name))
{
if (name.endsWith("*") || name.endsWith("-"))
{
postfix = name.substring(name.length() - 1);
name = name.substring(0, name.length() - 1);
}
if (!(new File(name)).isAbsolute())
{
BundleContext context = (BundleContext) AccessController
.doPrivileged(new PrivilegedAction()
{
public Object run()
{
return bundle.getBundleContext();
}
});
if (context == null)
{
break;
}
name = m_action.getAbsolutePath(new File(context
.getDataFile(""), name));
}
if (postfix.length() > 0)
{
if ((name.length() > 0) && !name.endsWith("/"))
{
name += "/" + postfix;
}
else
{
name += postfix;
}
}
}
Permission source = createPermission(new PermissionInfo(
FilePermission.class.getName(), name,
m_permissionInfos[i].getActions()), targetClass);
if (source.implies(target))
{
return true;
}
}
}
return false;
}
Object current = m_stack.get();
if (current == null)
{
m_stack.set(targetClass);
}
else
{
if (current instanceof HashSet)
{
if (((HashSet) current).contains(targetClass))
{
return false;
}
((HashSet) current).add(targetClass);
}
else
{
if (current == targetClass)
{
return false;
}
HashSet frame = new HashSet();
frame.add(current);
frame.add(targetClass);
m_stack.set(frame);
current = frame;
}
}
try
{
SoftReference collectionEntry = null;
PermissionCollection collection = null;
synchronized (m_cache)
{
collectionEntry = (SoftReference) m_cache.get(targetClass);
}
if (collectionEntry != null)
{
collection = (PermissionCollection) collectionEntry.get();
}
if (collection == null)
{
collection = target.newPermissionCollection();
if (collection == null)
{
collection = new DefaultPermissionCollection();
}
for (int i = 0; i < m_permissionInfos.length; i++)
{
PermissionInfo permissionInfo = m_permissionInfos[i];
String infoType = permissionInfo.getType();
String permissionType = targetClass.getName();
if (infoType.equals(permissionType))
{
Permission permission = createPermission(
permissionInfo, targetClass);
if (permission != null)
{
collection.add(permission);
}
}
}
synchronized (m_cache)
{
m_cache.put(new Entry(target.getClass(), m_queue),
new SoftReference(collection));
}
}
return collection.implies(target);
}
finally
{
if (current == null)
{
m_stack.set(null);
}
else
{
((HashSet) current).remove(targetClass);
if (((HashSet) current).isEmpty())
{
m_stack.set(null);
}
}
}
}
private Permission addToCache(String encoded, Permission permission)
{
if (permission == null)
{
return null;
}
synchronized (m_permissionCache)
{
Map inner = null;
SoftReference ref = (SoftReference) m_permissionCache.get(encoded);
if (ref != null)
{
inner = (Map) ref.get();
}
if (inner == null)
{
inner = new HashMap();
m_permissionCache.put(encoded,
new SoftReference(inner));
}
inner.put(new Entry(permission.getClass()), new Entry(permission));
}
return permission;
}
private Permission getFromCache(String encoded, Class target)
{
synchronized (m_permissionCache)
{
SoftReference ref = (SoftReference) m_permissionCache.get(encoded);
if (ref != null)
{
Map inner = (Map) ref.get();
if (inner != null)
{
Entry entry = (Entry) inner.get(target);
if (entry != null)
{
Permission result = (Permission) entry.get();
if (result != null)
{
return result;
}
inner.remove(entry);
}
if (inner.isEmpty())
{
m_permissionCache.remove(encoded);
}
}
else
{
m_permissionCache.remove(encoded);
}
}
}
return null;
}
private Permission createPermission(final PermissionInfo permissionInfo,
final Class target)
{
return (Permission) AccessController
.doPrivileged(new PrivilegedAction()
{
public Object run()
{
Permission cached = getFromCache(permissionInfo
.getEncoded(), target);
if (cached != null)
{
return cached;
}
try
{
if (m_classLoader.loadClass(target.getName()) == target)
{
return addToCache(permissionInfo.getEncoded(),
createPermission(permissionInfo.getName(),
permissionInfo.getActions(), target));
}
}
catch (ClassNotFoundException e1)
{
}
ServiceReference[] refs = null;
try
{
refs = m_context.getServiceReferences(
PackageAdmin.class.getName(), null);
}
catch (InvalidSyntaxException e)
{
}
if (refs != null)
{
for (int i = 0; i < refs.length; i++)
{
PackageAdmin admin = (PackageAdmin) m_context
.getService(refs[i]);
if (admin != null)
{
Permission result = null;
Bundle bundle = admin.getBundle(target);
if (bundle != null)
{
ExportedPackage[] exports = admin
.getExportedPackages(bundle);
if (exports != null)
{
String name = target.getName();
name = name.substring(0, name
.lastIndexOf('.'));
for (int j = 0; j < exports.length; j++)
{
if (exports[j].getName().equals(
name))
{
result = createPermission(
permissionInfo.getName(),
permissionInfo.getActions(),
target);
break;
}
}
}
}
m_context.ungetService(refs[i]);
return addToCache(permissionInfo.getEncoded(),
result);
}
}
}
return null;
}
});
}
private Permission createPermission(String name, String action, Class target)
{
// System.out.println("\n\n|" + name + "|\n--\n|" + action + "|\n--\n" +
// target + "\n\n");
try
{
return (Permission) m_action.getConstructor(target,
new Class[] { String.class, String.class }).newInstance(
new Object[] { name, action });
}
catch (Exception ex)
{
// TODO: log this or something
}
return null;
}
}