001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. 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 * Nicolas Chapurlat <nchapurlat@nuxeo.com> 016 */ 017 018package org.nuxeo.ecm.platform.usermanager; 019 020import java.io.Serializable; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Locale; 026import java.util.Map; 027 028import org.nuxeo.ecm.core.api.NuxeoGroup; 029import org.nuxeo.ecm.core.api.NuxeoPrincipal; 030import org.nuxeo.ecm.core.api.SystemPrincipal; 031import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver; 032import org.nuxeo.runtime.api.Framework; 033import org.nuxeo.runtime.api.login.LoginComponent; 034 035/** 036 * This {@link ObjectResolver} allows to manage integrity for fields containing group or user references. 037 * <p> 038 * References should have a prefix. NuxeoPrincipal.PREFIX for users, NuxeoGroup.PREFIX for groups. 039 * </p> 040 * <p> 041 * If only user or group are configured, the prefix is not needed but still supported. If noth user and group are 042 * configured, reference without prefix are resolved as user first. 043 * </p> 044 * <p> 045 * To use it, put the following code in your schema XSD : 046 * </p> 047 * 048 * <pre> 049 * {@code 050 * <!-- user or group resolver --> 051 * <xs:simpleType name="userOrGroupReference"> 052 * <xs:restriction base="xs:string" ref:resolver="userManagerResolver" /> 053 * </xs:simpleType> 054 * 055 * <!-- user resolver --> 056 * <xs:simpleType name="userReference"> 057 * <xs:restriction base="xs:string" ref:resolver="userManagerResolver" ref:type="user" /> 058 * </xs:simpleType> 059 * 060 * <!-- group resolver --> 061 * <xs:simpleType name="groupReference"> 062 * <xs:restriction base="xs:string" ref:resolver="userManagerResolver" ref:type="group" /> 063 * </xs:simpleType> 064 * } 065 * </pre> 066 * 067 * @since 7.1 068 */ 069public class UserManagerResolver implements ObjectResolver { 070 071 public static final String INPUT_PARAM_FILTER = "type"; 072 073 public static final String FILTER_GROUP = "group"; 074 075 public static final String FILTER_USER = "user"; 076 077 public static final String NAME = "userManagerResolver"; 078 079 public static final String PARAM_INCLUDE_USERS = "includeUsers"; 080 081 public static final String PARAM_INCLUDE_GROUPS = "includeGroups"; 082 083 private Map<String, Serializable> parameters; 084 085 private boolean includingUsers = true; 086 087 private boolean includingGroups = true; 088 089 private UserManager userManager; 090 091 public UserManager getUserManager() { 092 if (userManager == null) { 093 userManager = Framework.getService(UserManager.class); 094 } 095 return userManager; 096 } 097 098 private List<Class<?>> managedClasses = null; 099 100 @Override 101 public List<Class<?>> getManagedClasses() { 102 if (managedClasses == null) { 103 managedClasses = new ArrayList<Class<?>>(); 104 if (includingUsers) { 105 managedClasses.add(NuxeoPrincipal.class); 106 } 107 if (includingGroups) { 108 managedClasses.add(NuxeoGroup.class); 109 } 110 } 111 return managedClasses; 112 } 113 114 @Override 115 public void configure(Map<String, String> parameters) throws IllegalStateException { 116 if (this.parameters != null) { 117 throw new IllegalStateException("cannot change configuration, may be already in use somewhere"); 118 } 119 if (FILTER_USER.equals(parameters.get(INPUT_PARAM_FILTER))) { 120 includingGroups = false; 121 } else if (FILTER_GROUP.equals(parameters.get(INPUT_PARAM_FILTER))) { 122 includingUsers = false; 123 } 124 this.parameters = new HashMap<String, Serializable>(); 125 this.parameters.put(PARAM_INCLUDE_GROUPS, includingGroups); 126 this.parameters.put(PARAM_INCLUDE_USERS, includingUsers); 127 } 128 129 @Override 130 public String getName() throws IllegalStateException { 131 checkConfig(); 132 return UserManagerResolver.NAME; 133 } 134 135 @Override 136 public Map<String, Serializable> getParameters() throws IllegalStateException { 137 checkConfig(); 138 return Collections.unmodifiableMap(parameters); 139 } 140 141 @Override 142 public boolean validate(Object value) throws IllegalStateException { 143 checkConfig(); 144 return fetch(value) != null; 145 } 146 147 @Override 148 public Object fetch(Object value) throws IllegalStateException { 149 checkConfig(); 150 if (value != null && value instanceof String) { 151 String name = (String) value; 152 boolean userPrefix = name.startsWith(NuxeoPrincipal.PREFIX); 153 boolean groupPrefix = name.startsWith(NuxeoGroup.PREFIX); 154 if (includingUsers && !includingGroups) { 155 if (userPrefix) { 156 name = name.substring(NuxeoPrincipal.PREFIX.length()); 157 } 158 if (LoginComponent.SYSTEM_USERNAME.equals(name)) { 159 return new SystemPrincipal(name); 160 } 161 return getUserManager().getPrincipal(name); 162 } else if (!includingUsers && includingGroups) { 163 if (groupPrefix) { 164 name = name.substring(NuxeoGroup.PREFIX.length()); 165 } 166 return getUserManager().getGroup(name); 167 } else { 168 if (userPrefix) { 169 name = name.substring(NuxeoPrincipal.PREFIX.length()); 170 if (LoginComponent.SYSTEM_USERNAME.equals(name)) { 171 return new SystemPrincipal(name); 172 } 173 return getUserManager().getPrincipal(name); 174 } else if (groupPrefix) { 175 name = name.substring(NuxeoGroup.PREFIX.length()); 176 return getUserManager().getGroup(name); 177 } else { 178 if (LoginComponent.SYSTEM_USERNAME.equals(name)) { 179 return new SystemPrincipal(name); 180 } 181 NuxeoPrincipal principal = getUserManager().getPrincipal(name); 182 if (principal != null) { 183 return principal; 184 } else { 185 return getUserManager().getGroup(name); 186 } 187 } 188 } 189 } 190 return null; 191 } 192 193 @SuppressWarnings("unchecked") 194 @Override 195 public <T> T fetch(Class<T> type, Object value) throws IllegalStateException { 196 checkConfig(); 197 Object principal = fetch(value); 198 if (type.isInstance(principal)) { 199 return (T) principal; 200 } 201 return null; 202 } 203 204 @Override 205 public Serializable getReference(Object entity) throws IllegalStateException { 206 checkConfig(); 207 if (entity != null) { 208 if (entity instanceof NuxeoPrincipal && includingUsers) { 209 return NuxeoPrincipal.PREFIX + ((NuxeoPrincipal) entity).getName(); 210 } else if (entity instanceof NuxeoGroup && includingGroups) { 211 return NuxeoGroup.PREFIX + ((NuxeoGroup) entity).getName(); 212 } 213 } 214 return null; 215 } 216 217 @Override 218 public String getConstraintErrorMessage(Object invalidValue, Locale locale) throws IllegalStateException { 219 checkConfig(); 220 if (isIncludingUsers() && isIncludingGroups()) { 221 return Helper.getConstraintErrorMessage(this, "any", invalidValue, locale); 222 } else if (!isIncludingUsers() && isIncludingGroups()) { 223 return Helper.getConstraintErrorMessage(this, "group", invalidValue, locale); 224 } else if (isIncludingUsers() && !isIncludingGroups()) { 225 return Helper.getConstraintErrorMessage(this, "user", invalidValue, locale); 226 } 227 return String.format("%s cannot resolve reference %s", getName(), invalidValue); 228 } 229 230 public boolean isIncludingUsers() throws IllegalStateException { 231 checkConfig(); 232 return includingUsers; 233 } 234 235 public boolean isIncludingGroups() throws IllegalStateException { 236 checkConfig(); 237 return includingGroups; 238 } 239 240 private void checkConfig() throws IllegalStateException { 241 if (parameters == null) { 242 throw new IllegalStateException( 243 "you should call #configure(Map<String, String>) before. Please get this resolver throught ExternalReferenceService which is in charge of resolver configuration."); 244 } 245 } 246 247}