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}