blob: cb880c16b778542e0831c42ae3f535d687d02a28 [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.jackrabbit.oak.security.authorization.composite;
import java.util.function.Supplier;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.plugins.tree.TreeProvider;
import org.apache.jackrabbit.oak.plugins.tree.TreeType;
import org.apache.jackrabbit.oak.plugins.tree.TreeTypeProvider;
import org.apache.jackrabbit.oak.security.authorization.composite.CompositeAuthorizationConfiguration.CompositionType;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.AggregatedPermissionProvider;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.TreePermission;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static org.apache.jackrabbit.oak.security.authorization.composite.CompositeAuthorizationConfiguration.CompositionType.AND;
import static org.apache.jackrabbit.oak.security.authorization.composite.CompositeAuthorizationConfiguration.CompositionType.OR;
/**
* {@code TreePermission} implementation that combines multiple {@code TreePermission}
* implementations.
*/
final class CompositeTreePermission implements TreePermission {
private final Tree tree;
private final TreeType type;
private final CompositionType compositionType;
private final TreeProvider treeProvider;
private final TreeTypeProvider typeProvider;
private final AggregatedPermissionProvider[] providers;
private final TreePermission[] treePermissions;
private final int childSize;
private Boolean canRead;
private Boolean canReadProperties;
private CompositeTreePermission(@NotNull Tree tree, @NotNull TreeType type,
@NotNull TreeProvider treeProvider,
@NotNull TreeTypeProvider typeProvider, @NotNull AggregatedPermissionProvider[] providers,
@NotNull TreePermission[] treePermissions, int cnt, @NotNull CompositionType compositionType) {
this.tree = tree;
this.type = type;
this.treeProvider = treeProvider;
this.typeProvider = typeProvider;
this.providers = providers;
this.treePermissions = treePermissions;
this.childSize = providers.length - cnt;
this.compositionType = compositionType;
}
static TreePermission create(@NotNull Tree rootTree,
@NotNull TreeProvider treeProvider,
@NotNull TreeTypeProvider typeProvider,
@NotNull AggregatedPermissionProvider[] providers,
@NotNull CompositionType compositionType) {
switch (providers.length) {
case 0 : return TreePermission.EMPTY;
case 1 : return providers[0].getTreePermission(rootTree, TreeType.DEFAULT, TreePermission.EMPTY);
default :
int cnt = 0;
TreePermission[] treePermissions = new TreePermission[providers.length];
for (int i = 0; i < providers.length; i++) {
TreePermission tp = providers[i].getTreePermission(rootTree, TreeType.DEFAULT, TreePermission.EMPTY);
if (!isValid(tp)) {
cnt++;
}
treePermissions[i] = tp;
}
return new CompositeTreePermission(rootTree, TreeType.DEFAULT, treeProvider, typeProvider, providers, treePermissions,
cnt, compositionType);
}
}
static TreePermission create(@NotNull final Tree tree, @NotNull TreeProvider treeProvider, @NotNull CompositeTreePermission parentPermission) {
return create(() -> tree, tree.getName(), treeProvider.asNodeState(tree), parentPermission, null);
}
static TreePermission create(@NotNull final Tree tree, @NotNull TreeProvider treeProvider, @NotNull CompositeTreePermission parentPermission,
@Nullable TreeType treeType) {
return create(() -> tree, tree.getName(), treeProvider.asNodeState(tree), parentPermission, treeType);
}
private static TreePermission create(@NotNull Supplier<Tree> lazyTree,
@NotNull String childName, @NotNull NodeState childState,
@NotNull CompositeTreePermission parentPermission,
@Nullable TreeType treeType) {
switch (parentPermission.childSize) {
case 0: return TreePermission.EMPTY;
case 1:
TreePermission parent = null;
for (TreePermission tp : parentPermission.treePermissions) {
if (isValid(tp)) {
parent = tp;
break;
}
}
return (parent == null) ? TreePermission.EMPTY : parent.getChildPermission(childName, childState);
default:
Tree tree = lazyTree.get();
TreeType type;
if (treeType != null) {
type = treeType;
} else {
type = getType(tree, parentPermission);
}
AggregatedPermissionProvider[] pvds = new AggregatedPermissionProvider[parentPermission.childSize];
TreePermission[] tps = new TreePermission[parentPermission.childSize];
int cnt = 0;
for (int i = 0, j = 0; i < parentPermission.providers.length; i++) {
parent = parentPermission.treePermissions[i];
if (isValid(parent)) {
AggregatedPermissionProvider provider = parentPermission.providers[i];
TreePermission tp = provider.getTreePermission(tree, type, parent);
if (!isValid(tp)) {
cnt++;
}
tps[j] = tp;
pvds[j] = provider;
j++;
}
}
return new CompositeTreePermission(tree, type, parentPermission.treeProvider, parentPermission.typeProvider, pvds, tps, cnt,
parentPermission.compositionType);
}
}
//-----------------------------------------------------< TreePermission >---
@NotNull
@Override
public TreePermission getChildPermission(@NotNull final String childName, @NotNull final NodeState childState) {
return create(() -> treeProvider.createReadOnlyTree(tree, childName, childState), childName, childState, this, null);
}
@Override
public boolean canRead() {
if (canRead == null) {
canRead = grantsRead(null);
}
return canRead;
}
@Override
public boolean canRead(@NotNull PropertyState property) {
return grantsRead(property);
}
@Override
public boolean canReadAll() {
return false;
}
@Override
public boolean canReadProperties() {
if (canReadProperties == null) {
boolean readable = false;
for (int i = 0; i < providers.length; i++) {
TreePermission tp = treePermissions[i];
long supported = providers[i].supportedPermissions(tp, null, Permissions.READ_PROPERTY);
if (doEvaluate(supported)) {
readable = tp.canReadProperties();
if (!readable && compositionType == AND) {
break;
}
if (readable && compositionType == OR) {
break;
}
}
}
canReadProperties = readable;
}
return canReadProperties;
}
@Override
public boolean isGranted(long permissions) {
return grantsPermission(permissions, null);
}
@Override
public boolean isGranted(long permissions, @NotNull PropertyState property) {
return grantsPermission(permissions, property);
}
//------------------------------------------------------------< private >---
private boolean grantsPermission(long permissions, @Nullable PropertyState property) {
boolean isGranted = false;
long coveredPermissions = Permissions.NO_PERMISSION;
for (int i = 0; i < providers.length; i++) {
TreePermission tp = treePermissions[i];
long supported = providers[i].supportedPermissions(tp, property, permissions);
if (doEvaluate(supported)) {
if (compositionType == AND) {
isGranted = (property == null) ? tp.isGranted(supported) : tp.isGranted(supported, property);
if (!isGranted) {
return false;
}
coveredPermissions |= supported;
} else {
for (long p : Permissions.aggregates(permissions)) {
boolean aGrant = (property == null) ? tp.isGranted(p) : tp.isGranted(p, property);
if (aGrant) {
coveredPermissions |= p;
isGranted = true;
}
}
}
}
}
return isGranted && coveredPermissions == permissions;
}
private boolean grantsRead(@Nullable PropertyState property) {
if (property != null && canReadProperties()) {
return true;
}
boolean readable = false;
for (int i = 0; i < providers.length; i++) {
TreePermission tp = treePermissions[i];
long supported = providers[i].supportedPermissions(tp, property, (property == null) ? Permissions.READ_NODE : Permissions.READ_PROPERTY);
if (doEvaluate(supported)) {
readable = (property == null) ? tp.canRead() : tp.canRead(property);
if (!readable && compositionType == AND) {
return false;
}
if (readable && compositionType == OR) {
return true;
}
}
}
return readable;
}
private static boolean doEvaluate(long supportedPermissions) {
return supportedPermissions != Permissions.NO_PERMISSION;
}
private static boolean isValid(@NotNull TreePermission tp) {
return NO_RECOURSE != tp;
}
private static TreeType getType(@NotNull Tree tree, @NotNull CompositeTreePermission parent) {
return parent.typeProvider.getType(tree, parent.type);
}
}