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