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