001/* 002 * (C) Copyright 2020 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Florent Guillaume 018 */ 019package org.nuxeo.ecm.core.model; 020 021import java.util.HashMap; 022import java.util.ListIterator; 023import java.util.Map; 024import java.util.Optional; 025 026import org.apache.logging.log4j.LogManager; 027import org.apache.logging.log4j.Logger; 028import org.nuxeo.ecm.core.api.security.ACE; 029import org.nuxeo.ecm.core.api.security.ACL; 030import org.nuxeo.ecm.core.api.security.ACP; 031import org.nuxeo.ecm.core.api.security.Access; 032import org.nuxeo.ecm.core.api.security.SecurityConstants; 033import org.nuxeo.ecm.core.api.security.impl.ACLImpl; 034import org.nuxeo.ecm.core.api.security.impl.ACPImpl; 035import org.nuxeo.ecm.core.blob.DocumentBlobManager; 036import org.nuxeo.ecm.core.query.QueryFilter; 037import org.nuxeo.runtime.api.Framework; 038import org.nuxeo.runtime.services.config.ConfigurationService; 039 040/** 041 * Common code for VCS and DBS {@link Session} implementations. 042 * 043 * @since 11.3 044 */ 045public abstract class BaseSession implements Session<QueryFilter> { 046 047 private static final Logger log = LogManager.getLogger(BaseSession.class); 048 049 /** 050 * Configuration property controlling whether ACLs on versions are disabled. 051 * 052 * @since 11.3 053 */ 054 public static final String VERSION_ACL_DISABLED_PROP = "org.nuxeo.version.acl.disabled"; 055 056 /** 057 * Configuration property controlling whether ReadVersion permission is disabled. 058 * 059 * @since 11.3 060 */ 061 public static final String READ_VERSION_PERM_DISABLED_PROP = "org.nuxeo.version.readversion.disabled"; 062 063 /** INTERNAL. How we deal with ACLs on versions. */ 064 public enum VersionAclMode { 065 /** Version ACL enabled. */ 066 ENABLED, 067 /** Version ACL disabled. */ 068 DISABLED, 069 /** Version ACL disabled for direct access but enabled for queries. */ 070 LEGACY; 071 072 public static VersionAclMode getConfiguration() { 073 if (!Framework.isInitialized()) { 074 // unit tests 075 return ENABLED; 076 } 077 ConfigurationService configurationService = Framework.getService(ConfigurationService.class); 078 String val = configurationService.getString(VERSION_ACL_DISABLED_PROP).orElse("false"); 079 switch (val) { 080 case "false": 081 return ENABLED; 082 case "true": 083 return DISABLED; 084 case "legacy": 085 return LEGACY; 086 default: 087 log.error("Invalid value for configuration property {}: '{}'", VERSION_ACL_DISABLED_PROP, val); 088 return ENABLED; 089 } 090 } 091 } 092 093 public static boolean isReadVersionPermissionDisabled() { 094 if (!Framework.isInitialized()) { 095 // unit tests 096 return false; 097 } 098 ConfigurationService configurationService = Framework.getService(ConfigurationService.class); 099 return configurationService.isBooleanTrue(READ_VERSION_PERM_DISABLED_PROP); 100 } 101 102 protected final Repository repository; 103 104 protected final VersionAclMode versionAclMode; 105 106 protected final boolean disableReadVersionPermission; 107 108 protected BaseSession(Repository repository) { 109 this.repository = repository; 110 versionAclMode = VersionAclMode.getConfiguration(); 111 disableReadVersionPermission = isReadVersionPermissionDisabled(); 112 } 113 114 protected DocumentBlobManager getDocumentBlobManager() { 115 return Framework.getService(DocumentBlobManager.class); 116 } 117 118 protected void notifyAfterCopy(Document doc) { 119 getDocumentBlobManager().notifyAfterCopy(doc); 120 } 121 122 /* 123 * ----- Common ACP code ----- 124 */ 125 126 protected void checkNegativeAcl(ACP acp) { 127 if (acp == null || isNegativeAclAllowed()) { 128 return; 129 } 130 for (ACL acl : acp.getACLs()) { 131 if (acl.getName().equals(ACL.INHERITED_ACL)) { 132 continue; 133 } 134 for (ACE ace : acl.getACEs()) { 135 if (ace.isGranted()) { 136 continue; 137 } 138 String permission = ace.getPermission(); 139 if (permission.equals(SecurityConstants.EVERYTHING) 140 && ace.getUsername().equals(SecurityConstants.EVERYONE)) { 141 continue; 142 } 143 // allow Write, as we're sure it doesn't include Read/Browse 144 if (permission.equals(SecurityConstants.WRITE)) { 145 continue; 146 } 147 throw new IllegalArgumentException("Negative ACL not allowed: " + ace); 148 } 149 } 150 } 151 152 /** 153 * Gets the ACP for the document (without any inheritance). 154 * 155 * @param doc the document 156 * @return the ACP 157 */ 158 public abstract ACP getACP(Document doc); 159 160 protected ACP getACP(Document doc, boolean replaceReadVersionPermission) { 161 ACP acp = getACP(doc); 162 if (acp != null && replaceReadVersionPermission) { 163 // if ReadVersion is present in an ACE, turn it into a Read 164 acp.replacePermission(SecurityConstants.READ_VERSION, SecurityConstants.READ); 165 } 166 return acp; 167 } 168 169 @Override 170 public ACP getMergedACP(Document doc) { 171 boolean replaceReadVersionPermission = false; 172 if (doc.isVersion()) { 173 replaceReadVersionPermission = !disableReadVersionPermission; 174 if (versionAclMode != VersionAclMode.ENABLED) { 175 doc = doc.getSourceDocument(); 176 if (doc == null) { 177 // version with no live doc 178 return null; 179 } 180 } 181 } 182 ACP acp = getACP(doc, replaceReadVersionPermission); 183 ACP mergedAcp = acp; 184 ACL inherited = new ACLImpl(ACL.INHERITED_ACL, true); // collected inherited ACEs 185 for (;;) { 186 if (acp != null && acp.getAccess(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING) == Access.DENY) { 187 // blocking, no need to continue 188 break; 189 } 190 if (doc.isVersion()) { 191 replaceReadVersionPermission = !disableReadVersionPermission; 192 doc = doc.getSourceDocument(); 193 } else { 194 doc = doc.getParent(); 195 } 196 if (doc == null) { 197 // can't go up 198 break; 199 } 200 // collect inherited ACEs for this level 201 acp = getACP(doc, replaceReadVersionPermission); 202 if (acp != null) { 203 inherited.addAll(acp.getMergedACLs(ACL.INHERITED_ACL)); 204 } 205 } 206 if (!inherited.isEmpty()) { 207 if (mergedAcp == null) { 208 mergedAcp = new ACPImpl(); 209 } 210 mergedAcp.addACL(inherited); 211 } 212 return mergedAcp; 213 } 214 215 /** 216 * Returns the merge of two ACPs. 217 */ 218 protected ACP updateACP(ACP curAcp, ACP addAcp) { 219 if (curAcp == null) { 220 return addAcp; 221 } 222 ACP newAcp = curAcp.clone(); // clone as we may modify ACLs and ACPs 223 Map<String, ACL> acls = new HashMap<>(); 224 for (ACL acl : newAcp.getACLs()) { 225 String name = acl.getName(); 226 if (ACL.INHERITED_ACL.equals(name)) { 227 throw new IllegalStateException(curAcp.toString()); 228 } 229 acls.put(name, acl); 230 } 231 for (ACL acl : addAcp.getACLs()) { 232 String name = acl.getName(); 233 if (ACL.INHERITED_ACL.equals(name)) { 234 continue; 235 } 236 ACL curAcl = acls.get(name); 237 if (curAcl != null) { 238 // TODO avoid duplicates 239 curAcl.addAll(acl); 240 } else { 241 newAcp.addACL(acl); 242 } 243 } 244 return newAcp; 245 } 246 247}