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