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    private static final long serialVersionUID = 1L;
072
073    public static final String INPUT_PARAM_FILTER = "type";
074
075    public static final String FILTER_GROUP = "group";
076
077    public static final String FILTER_USER = "user";
078
079    public static final String NAME = "userManagerResolver";
080
081    public static final String PARAM_INCLUDE_USERS = "includeUsers";
082
083    public static final String PARAM_INCLUDE_GROUPS = "includeGroups";
084
085    private Map<String, Serializable> parameters;
086
087    private boolean includingUsers = true;
088
089    private boolean includingGroups = true;
090
091    private transient UserManager userManager;
092
093    public UserManager getUserManager() {
094        if (userManager == null) {
095            userManager = Framework.getService(UserManager.class);
096        }
097        return userManager;
098    }
099
100    private List<Class<?>> managedClasses = null;
101
102    @Override
103    public List<Class<?>> getManagedClasses() {
104        if (managedClasses == null) {
105            managedClasses = new ArrayList<Class<?>>();
106            if (includingUsers) {
107                managedClasses.add(NuxeoPrincipal.class);
108            }
109            if (includingGroups) {
110                managedClasses.add(NuxeoGroup.class);
111            }
112        }
113        return managedClasses;
114    }
115
116    @Override
117    public void configure(Map<String, String> parameters) throws IllegalStateException {
118        if (this.parameters != null) {
119            throw new IllegalStateException("cannot change configuration, may be already in use somewhere");
120        }
121        if (FILTER_USER.equals(parameters.get(INPUT_PARAM_FILTER))) {
122            includingGroups = false;
123        } else if (FILTER_GROUP.equals(parameters.get(INPUT_PARAM_FILTER))) {
124            includingUsers = false;
125        }
126        this.parameters = new HashMap<String, Serializable>();
127        this.parameters.put(PARAM_INCLUDE_GROUPS, includingGroups);
128        this.parameters.put(PARAM_INCLUDE_USERS, includingUsers);
129    }
130
131    @Override
132    public String getName() throws IllegalStateException {
133        checkConfig();
134        return UserManagerResolver.NAME;
135    }
136
137    @Override
138    public Map<String, Serializable> getParameters() throws IllegalStateException {
139        checkConfig();
140        return Collections.unmodifiableMap(parameters);
141    }
142
143    @Override
144    public boolean validate(Object value) throws IllegalStateException {
145        checkConfig();
146        return fetch(value) != null;
147    }
148
149    @Override
150    public Object fetch(Object value) throws IllegalStateException {
151        checkConfig();
152        if (value != null && value instanceof String) {
153            String name = (String) value;
154            boolean userPrefix = name.startsWith(NuxeoPrincipal.PREFIX);
155            boolean groupPrefix = name.startsWith(NuxeoGroup.PREFIX);
156            if (includingUsers && !includingGroups) {
157                if (userPrefix) {
158                    name = name.substring(NuxeoPrincipal.PREFIX.length());
159                }
160                if (LoginComponent.SYSTEM_USERNAME.equals(name)) {
161                    return new SystemPrincipal(name);
162                }
163                return getUserManager().getPrincipal(name);
164            } else if (!includingUsers && includingGroups) {
165                if (groupPrefix) {
166                    name = name.substring(NuxeoGroup.PREFIX.length());
167                }
168                return getUserManager().getGroup(name);
169            } else {
170                if (userPrefix) {
171                    name = name.substring(NuxeoPrincipal.PREFIX.length());
172                    if (LoginComponent.SYSTEM_USERNAME.equals(name)) {
173                        return new SystemPrincipal(name);
174                    }
175                    return getUserManager().getPrincipal(name);
176                } else if (groupPrefix) {
177                    name = name.substring(NuxeoGroup.PREFIX.length());
178                    return getUserManager().getGroup(name);
179                } else {
180                    if (LoginComponent.SYSTEM_USERNAME.equals(name)) {
181                        return new SystemPrincipal(name);
182                    }
183                    NuxeoPrincipal principal = getUserManager().getPrincipal(name);
184                    if (principal != null) {
185                        return principal;
186                    } else {
187                        return getUserManager().getGroup(name);
188                    }
189                }
190            }
191        }
192        return null;
193    }
194
195    @SuppressWarnings("unchecked")
196    @Override
197    public <T> T fetch(Class<T> type, Object value) throws IllegalStateException {
198        checkConfig();
199        Object principal = fetch(value);
200        if (type.isInstance(principal)) {
201            return (T) principal;
202        }
203        return null;
204    }
205
206    @Override
207    public Serializable getReference(Object entity) throws IllegalStateException {
208        checkConfig();
209        if (entity != null) {
210            if (entity instanceof NuxeoPrincipal && includingUsers) {
211                return NuxeoPrincipal.PREFIX + ((NuxeoPrincipal) entity).getName();
212            } else if (entity instanceof NuxeoGroup && includingGroups) {
213                return NuxeoGroup.PREFIX + ((NuxeoGroup) entity).getName();
214            }
215        }
216        return null;
217    }
218
219    @Override
220    public String getConstraintErrorMessage(Object invalidValue, Locale locale) throws IllegalStateException {
221        checkConfig();
222        if (isIncludingUsers() && isIncludingGroups()) {
223            return Helper.getConstraintErrorMessage(this, "any", invalidValue, locale);
224        } else if (!isIncludingUsers() && isIncludingGroups()) {
225            return Helper.getConstraintErrorMessage(this, "group", invalidValue, locale);
226        } else if (isIncludingUsers() && !isIncludingGroups()) {
227            return Helper.getConstraintErrorMessage(this, "user", invalidValue, locale);
228        }
229        return String.format("%s cannot resolve reference %s", getName(), invalidValue);
230    }
231
232    public boolean isIncludingUsers() throws IllegalStateException {
233        checkConfig();
234        return includingUsers;
235    }
236
237    public boolean isIncludingGroups() throws IllegalStateException {
238        checkConfig();
239        return includingGroups;
240    }
241
242    private void checkConfig() throws IllegalStateException {
243        if (parameters == null) {
244            throw new IllegalStateException(
245                    "you should call #configure(Map<String, String>) before. Please get this resolver throught ExternalReferenceService which is in charge of resolver configuration.");
246        }
247    }
248
249}