blob: 277724f2358d3d7c813ef3efe9c5c1cf24d16f91 [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.user;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.spi.commit.DefaultValidator;
import org.apache.jackrabbit.oak.spi.commit.Validator;
import org.apache.jackrabbit.oak.spi.commit.VisibleValidator;
import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType;
import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil;
import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
import org.apache.jackrabbit.util.Text;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Validator that enforces user management specific constraints. Please note that
* is this validator is making implementation specific assumptions; if the
* user management implementation is replace it is most probably necessary to
* provide a custom validator as well.
*/
class UserValidator extends DefaultValidator implements UserConstants {
private final Tree parentBefore;
private final Tree parentAfter;
private final UserValidatorProvider provider;
private final AuthorizableType authorizableType;
UserValidator(@Nullable Tree parentBefore, @Nullable Tree parentAfter, @NotNull UserValidatorProvider provider) {
this.parentBefore = parentBefore;
this.parentAfter = parentAfter;
this.provider = provider;
authorizableType = (parentAfter == null) ? null : UserUtil.getType(parentAfter);
}
//----------------------------------------------------------< Validator >---
@Override
public void propertyAdded(PropertyState after) throws CommitFailedException {
if (authorizableType == null) {
return;
}
String name = after.getName();
if (REP_DISABLED.equals(name) && isAdminUser(parentAfter)) {
String msg = "Admin user cannot be disabled.";
throw constraintViolation(20, msg);
}
if (JcrConstants.JCR_UUID.equals(name) && !isValidUUID(parentAfter, after.getValue(Type.STRING))) {
String msg = "Invalid jcr:uuid for authorizable " + parentAfter.getName();
throw constraintViolation(21, msg);
}
}
@Override
public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException {
if (authorizableType == null) {
return;
}
String name = before.getName();
if (REP_PRINCIPAL_NAME.equals(name) || REP_AUTHORIZABLE_ID.equals(name)) {
String msg = "Authorizable property " + name + " may not be altered after user/group creation.";
throw constraintViolation(22, msg);
} else if (JcrConstants.JCR_UUID.equals(name)) {
checkNotNull(parentAfter);
if (!isValidUUID(parentAfter, after.getValue(Type.STRING))) {
String msg = "Invalid jcr:uuid for authorizable " + parentAfter.getName();
throw constraintViolation(23, msg);
}
} else if (JcrConstants.JCR_PRIMARYTYPE.equals(name)) {
// if primary type changed to authorizable type -> need to perform
// validation as if a new authorizable node had been added
validateAuthorizable(parentAfter, UserUtil.getType(after.getValue(Type.STRING)));
}
boolean isUser = authorizableType == AuthorizableType.USER;
if (isUser && REP_PASSWORD.equals(name) && PasswordUtil.isPlainTextPassword(after.getValue(Type.STRING))) {
String msg = "Password may not be plain text.";
throw constraintViolation(24, msg);
}
}
@Override
public void propertyDeleted(PropertyState before) throws CommitFailedException {
if (authorizableType == null) {
return;
}
String name = before.getName();
if (REP_PASSWORD.equals(name) || REP_PRINCIPAL_NAME.equals(name) || REP_AUTHORIZABLE_ID.equals(name)) {
String msg = "Authorizable property " + name + " may not be removed.";
throw constraintViolation(25, msg);
}
}
@Override
public Validator childNodeAdded(String name, NodeState after) throws CommitFailedException {
Tree tree = checkNotNull(parentAfter.getChild(name));
validateAuthorizable(tree, UserUtil.getType(tree));
return newValidator(null, tree, provider);
}
@Override
public Validator childNodeChanged(String name, NodeState before, NodeState after) {
return newValidator(parentBefore.getChild(name),
parentAfter.getChild(name), provider);
}
@Override
public Validator childNodeDeleted(String name, NodeState before) throws CommitFailedException {
Tree tree = parentBefore.getChild(name);
AuthorizableType type = UserUtil.getType(tree);
if (type == AuthorizableType.USER || type == AuthorizableType.GROUP) {
if (isAdminUser(tree)) {
String msg = "The admin user cannot be removed.";
throw constraintViolation(27, msg);
}
return null;
} else {
return newValidator(tree, null, provider);
}
}
//------------------------------------------------------------< private >---
private static Validator newValidator(@Nullable Tree parentBefore,
@Nullable Tree parentAfter,
@NotNull UserValidatorProvider provider) {
return new VisibleValidator(
new UserValidator(parentBefore, parentAfter, provider),
true,
true);
}
private boolean isAdminUser(@NotNull Tree userTree) {
if (userTree.exists() && isUser(userTree)) {
String id = UserUtil.getAuthorizableId(userTree);
return UserUtil.getAdminId(provider.getConfig()).equals(id);
} else {
return false;
}
}
private void validateAuthorizable(@NotNull Tree tree, @Nullable AuthorizableType type) throws CommitFailedException {
boolean isSystemUser = (type == AuthorizableType.USER) && UserUtil.isSystemUser(tree);
String authRoot = UserUtil.getAuthorizableRootPath(provider.getConfig(), type);
if (isSystemUser) {
String sysRelPath = provider.getConfig().getConfigValue(PARAM_SYSTEM_RELATIVE_PATH, DEFAULT_SYSTEM_RELATIVE_PATH);
authRoot = authRoot + '/' + sysRelPath;
}
if (authRoot != null) {
assertHierarchy(tree, authRoot);
// assert rep:principalName is present (that should actually by covered
// by node type validator)
if (TreeUtil.getString(tree, REP_PRINCIPAL_NAME) == null) {
throw constraintViolation(26, "Mandatory property rep:principalName missing.");
}
if (isSystemUser) {
if (TreeUtil.getString(tree, REP_PASSWORD) != null) {
throw constraintViolation(32, "Attempt to set password with system user.");
}
if (tree.hasChild(REP_PWD)) {
throw constraintViolation(33, "Attempt to add rep:pwd node to a system user.");
}
}
}
}
private boolean isValidUUID(@NotNull Tree parent, @NotNull String uuid) {
String id = UserUtil.getAuthorizableId(parent);
return id != null && uuid.equals(provider.getMembershipProvider().getContentID(id));
}
private static boolean isUser(@NotNull Tree tree) {
return NT_REP_USER.equals(TreeUtil.getPrimaryTypeName(tree));
}
/**
* Make sure user and group nodes are located underneath the configured path
* and that path consists of rep:authorizableFolder nodes.
*
* @param tree The tree representing a user or group.
* @param pathConstraint The path constraint.
* @throws CommitFailedException If the hierarchy isn't valid.
*/
private static void assertHierarchy(@NotNull Tree tree, @NotNull String pathConstraint) throws CommitFailedException {
if (!Text.isDescendant(pathConstraint, tree.getPath())) {
String msg = "Attempt to create user/group outside of configured scope " + pathConstraint;
throw constraintViolation(28, msg);
}
if (!tree.isRoot()) {
Tree parent = tree.getParent();
while (parent.exists() && !parent.isRoot()) {
if (!NT_REP_AUTHORIZABLE_FOLDER.equals(TreeUtil.getPrimaryTypeName(parent))) {
String msg = "Cannot create user/group: Intermediate folders must be of type rep:AuthorizableFolder.";
throw constraintViolation(29, msg);
}
parent = parent.getParent();
}
}
}
private static CommitFailedException constraintViolation(int code, @NotNull String message) {
return new CommitFailedException(CommitFailedException.CONSTRAINT, code, message);
}
}