001/* 002 * (C) Copyright 2015 Nuxeo SA (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 * Thomas Roger 018 */ 019 020package org.nuxeo.ecm.permissions; 021 022import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON; 023import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE; 024import static org.nuxeo.ecm.permissions.Constants.ACE_INFO_COMMENT; 025import static org.nuxeo.ecm.permissions.Constants.ACE_INFO_DIRECTORY; 026import static org.nuxeo.ecm.permissions.Constants.ACE_INFO_NOTIFY; 027 028import java.io.Closeable; 029import java.io.IOException; 030import java.io.Serializable; 031import java.util.HashMap; 032import java.util.Map; 033 034import org.codehaus.jackson.JsonGenerator; 035import org.joda.time.DateTime; 036import org.joda.time.format.DateTimeFormatter; 037import org.joda.time.format.ISODateTimeFormat; 038import org.nuxeo.ecm.core.api.DocumentModel; 039import org.nuxeo.ecm.core.api.NuxeoPrincipal; 040import org.nuxeo.ecm.core.api.security.ACE; 041import org.nuxeo.ecm.core.api.security.ACL; 042import org.nuxeo.ecm.core.api.security.ACP; 043import org.nuxeo.ecm.core.io.marshallers.json.enrichers.AbstractJsonEnricher; 044import org.nuxeo.ecm.core.io.registry.context.MaxDepthReachedException; 045import org.nuxeo.ecm.core.io.registry.reflect.Setup; 046import org.nuxeo.ecm.directory.Session; 047import org.nuxeo.ecm.directory.api.DirectoryService; 048import org.nuxeo.ecm.platform.usermanager.UserManager; 049import org.nuxeo.runtime.api.Framework; 050 051/** 052 * Enrich {@link DocumentModel} Json. 053 * <p> 054 * Add {@link DocumentModel}'s ACP as json attachment. 055 * </p> 056 * <p> 057 * Enable if parameter enrichers-document=acls is present. 058 * </p> 059 * <p> 060 * Format is: 061 * 062 * <pre> 063 * {@code 064 * { 065 * "entity-type":"document", 066 * ... 067 * "contextParameters": { 068 * "acls": [ 069 * { 070 * "name": "inherited", 071 * "aces" :[ 072 * { 073 * "username": "administrators", 074 * "permission": "Everything", 075 * "granted": true, 076 * "creator": "Administrator", 077 * "begin": "2014-10-19T09:16:30.291Z", 078 * "end": "2016-10-19T09:16:30.291Z" 079 * "notify": true // optional 080 * "comment": "" // optional 081 * }, 082 * ... 083 * ] 084 * }, 085 * ... 086 * ] 087 * } 088 * } 089 * </pre> 090 * 091 * </p> 092 * <p> 093 * {@code username} and {@code creator} property can be fetched with fetch.acls=username or fetch.acls=creator. 094 * </p> 095 * <p> 096 * Additional ACE fields (such as notify and notification comment) can be written by using fetch.acls=extended. 097 * </p> 098 * 099 * @see org.nuxeo.ecm.platform.usermanager.io.NuxeoPrincipalJsonWriter 100 * @see org.nuxeo.ecm.platform.usermanager.io.NuxeoGroupJsonWriter 101 * @since 7.2 102 */ 103@Setup(mode = SINGLETON, priority = REFERENCE) 104public class ACLJsonEnricher extends AbstractJsonEnricher<DocumentModel> { 105 106 public static final String NAME = "acls"; 107 108 public static final String USERNAME_PROPERTY = "username"; 109 110 public static final String CREATOR_PROPERTY = "creator"; 111 112 public static final String EXTENDED_ACLS_PROPERTY = "extended"; 113 114 public ACLJsonEnricher() { 115 super(NAME); 116 } 117 118 @Override 119 public void write(JsonGenerator jg, DocumentModel document) throws IOException { 120 ACP item = document.getACP(); 121 jg.writeArrayFieldStart(NAME); 122 for (ACL acl : item.getACLs()) { 123 jg.writeStartObject(); 124 jg.writeStringField("name", acl.getName()); 125 jg.writeArrayFieldStart("aces"); 126 for (ACE ace : acl.getACEs()) { 127 jg.writeStartObject(); 128 jg.writeStringField("id", ace.getId()); 129 String username = ace.getUsername(); 130 writePrincipalOrGroup(USERNAME_PROPERTY, username, jg); 131 jg.writeBooleanField("externalUser", NuxeoPrincipal.isTransientUsername(username)); 132 jg.writeStringField("permission", ace.getPermission()); 133 jg.writeBooleanField("granted", ace.isGranted()); 134 writePrincipalOrGroup(CREATOR_PROPERTY, ace.getCreator(), jg); 135 DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTime(); 136 jg.writeStringField("begin", 137 ace.getBegin() != null ? dateTimeFormatter.print(new DateTime(ace.getBegin())) : null); 138 jg.writeStringField("end", ace.getEnd() != null ? dateTimeFormatter.print(new DateTime(ace.getEnd())) 139 : null); 140 jg.writeStringField("status", ace.getStatus().toString().toLowerCase()); 141 142 if (ctx.getFetched(NAME).contains(EXTENDED_ACLS_PROPERTY)) { 143 Map<String, Serializable> m = computeAdditionalFields(document, acl.getName(), ace.getId()); 144 for (Map.Entry<String, Serializable> entry : m.entrySet()) { 145 jg.writeObjectField(entry.getKey(), entry.getValue()); 146 } 147 } 148 149 jg.writeEndObject(); 150 } 151 jg.writeEndArray(); 152 jg.writeEndObject(); 153 } 154 jg.writeEndArray(); 155 } 156 157 protected void writePrincipalOrGroup(String propertyName, String value, JsonGenerator jg) throws IOException { 158 if (value != null && ctx.getFetched(NAME).contains(propertyName)) { 159 try (Closeable resource = ctx.wrap().controlDepth().open()) { 160 UserManager userManager = Framework.getService(UserManager.class); 161 Object entity = userManager.getPrincipal(value); 162 if (entity == null) { 163 entity = userManager.getGroup(value); 164 } 165 166 if (entity != null) { 167 writeEntityField(propertyName, entity, jg); 168 return; 169 } 170 } catch (MaxDepthReachedException e) { 171 // do nothing 172 } 173 } 174 jg.writeStringField(propertyName, value); 175 } 176 177 protected Map<String, Serializable> computeAdditionalFields(DocumentModel doc, String aclName, String aceId) { 178 Map<String, Serializable> m = new HashMap<>(); 179 180 DirectoryService directoryService = Framework.getLocalService(DirectoryService.class); 181 Framework.doPrivileged(() -> { 182 try (Session session = directoryService.open(ACE_INFO_DIRECTORY)) { 183 String id = computeDirectoryId(doc, aclName, aceId); 184 DocumentModel entry = session.getEntry(id); 185 if (entry != null) { 186 m.put("notify", entry.getPropertyValue(ACE_INFO_NOTIFY)); 187 m.put("comment", entry.getPropertyValue(ACE_INFO_COMMENT)); 188 } 189 } 190 }); 191 192 return m; 193 } 194 195 protected String computeDirectoryId(DocumentModel doc, String aclName, String aceId) { 196 return String.format("%s:%s:%s:%s", doc.getId(), doc.getRepositoryName(), aclName, aceId); 197 } 198 199}