001/*
002 * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
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 *     Bogdan Stefanescu
016 *     George Lefter
017 *     Stéfane Fermigier
018 *     Julien Carsique
019 *     Anahide Tchertchian
020 *     Alexandre Russel
021 *     Thierry Delprat
022 *     Stéphane Lacoin
023 *     Sun Seng David Tan
024 *     Thomas Roger
025 *     Thierry Martins
026 *     Benoit Delbosc
027 *     Florent Guillaume
028 */
029package org.nuxeo.ecm.platform.usermanager;
030
031import java.security.Principal;
032import java.util.ArrayList;
033import java.util.HashMap;
034import java.util.HashSet;
035import java.util.LinkedList;
036import java.util.List;
037import java.util.Set;
038
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.nuxeo.ecm.core.api.DataModel;
042import org.nuxeo.ecm.core.api.DocumentModel;
043import org.nuxeo.ecm.core.api.NuxeoGroup;
044import org.nuxeo.ecm.core.api.NuxeoPrincipal;
045import org.nuxeo.ecm.core.api.PropertyException;
046import org.nuxeo.ecm.core.api.impl.DataModelImpl;
047import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
048import org.nuxeo.ecm.core.api.security.SecurityConstants;
049import org.nuxeo.ecm.directory.DirectoryException;
050import org.nuxeo.runtime.api.Framework;
051
052public class NuxeoPrincipalImpl implements NuxeoPrincipal {
053
054    private static final long serialVersionUID = 1L;
055
056    private static final Log log = LogFactory.getLog(NuxeoPrincipalImpl.class);
057
058    protected UserConfig config = UserConfig.DEFAULT;
059
060    public final List<String> roles = new LinkedList<String>();
061
062    // group not stored in the backend and added at login time
063    public List<String> virtualGroups = new LinkedList<String>();
064
065    // transitive closure of the "member of group" relation
066    public List<String> allGroups;
067
068    public final boolean isAnonymous;
069
070    public boolean isAdministrator;
071
072    public String principalId;
073
074    public DocumentModel model;
075
076    public DataModel dataModel;
077
078    public String origUserName;
079
080    /**
081     * Constructor that sets principal to not anonymous, not administrator, and updates all the principal groups.
082     */
083    public NuxeoPrincipalImpl(String name) {
084        this(name, false, false);
085    }
086
087    /**
088     * Constructor that sets principal to not administrator, and updates all the principal groups.
089     */
090    public NuxeoPrincipalImpl(String name, boolean isAnonymous) {
091        this(name, isAnonymous, false);
092    }
093
094    /**
095     * Constructor that updates all the principal groups.
096     */
097    public NuxeoPrincipalImpl(String name, boolean isAnonymous, boolean isAdministrator) {
098        this(name, isAnonymous, isAdministrator, true);
099    }
100
101    public NuxeoPrincipalImpl(String name, boolean isAnonymous, boolean isAdministrator, boolean updateAllGroups)
102            {
103        DocumentModelImpl documentModelImpl = new DocumentModelImpl(config.schemaName);
104        // schema name hardcoded default when setModel is never called
105        // which happens when a principal is created just to encapsulate
106        // a username
107        documentModelImpl.addDataModel(new DataModelImpl(config.schemaName, new HashMap<String, Object>()));
108        setModel(documentModelImpl, updateAllGroups);
109        dataModel.setData(config.nameKey, name);
110        this.isAnonymous = isAnonymous;
111        this.isAdministrator = isAdministrator;
112    }
113
114    public void setConfig(UserConfig config) {
115        this.config = config;
116    }
117
118    public UserConfig getConfig() {
119        return config;
120    }
121
122    @Override
123    public String getCompany() {
124        try {
125            return (String) dataModel.getData(config.companyKey);
126        } catch (PropertyException e) {
127            return null;
128        }
129    }
130
131    @Override
132    public void setCompany(String company) {
133        dataModel.setData(config.companyKey, company);
134    }
135
136    @Override
137    public String getFirstName() {
138        try {
139            return (String) dataModel.getData(config.firstNameKey);
140        } catch (PropertyException e) {
141            return null;
142        }
143    }
144
145    @Override
146    public void setFirstName(String firstName) {
147        dataModel.setData(config.firstNameKey, firstName);
148    }
149
150    @Override
151    public String getLastName() {
152        try {
153            return (String) dataModel.getData(config.lastNameKey);
154        } catch (PropertyException e) {
155            return null;
156        }
157    }
158
159    @Override
160    public void setLastName(String lastName) {
161        dataModel.setData(config.lastNameKey, lastName);
162    }
163
164    // impossible to modify the name - it is PK
165    @Override
166    public void setName(String name) {
167        dataModel.setData(config.nameKey, name);
168    }
169
170    @Override
171    public void setRoles(List<String> roles) {
172        this.roles.clear();
173        this.roles.addAll(roles);
174    }
175
176    @Override
177    public void setGroups(List<String> groups) {
178        if (virtualGroups != null && !virtualGroups.isEmpty()) {
179            List<String> groupsToWrite = new ArrayList<String>();
180            for (String group : groups) {
181                if (!virtualGroups.contains(group)) {
182                    groupsToWrite.add(group);
183                }
184            }
185            dataModel.setData(config.groupsKey, groupsToWrite);
186        } else {
187            dataModel.setData(config.groupsKey, groups);
188        }
189    }
190
191    @Override
192    public String getName() {
193        try {
194            return (String) dataModel.getData(config.nameKey);
195        } catch (PropertyException e) {
196            return null;
197        }
198    }
199
200    @SuppressWarnings("unchecked")
201    @Override
202    public List<String> getGroups() {
203        List<String> groups = new LinkedList<String>();
204        List<String> storedGroups;
205        try {
206            storedGroups = (List<String>) dataModel.getData(config.groupsKey);
207        } catch (PropertyException e) {
208            return null;
209        }
210        if (storedGroups != null) {
211            groups.addAll(storedGroups);
212        }
213        groups.addAll(virtualGroups);
214        return groups;
215    }
216
217    @Deprecated
218    @Override
219    public List<String> getRoles() {
220        return new ArrayList<String>(roles);
221    }
222
223    @Override
224    public void setPassword(String password) {
225        dataModel.setData(config.passwordKey, password);
226    }
227
228    @Override
229    public String getPassword() {
230        // password should never be read at the UI level for safety reasons
231        // + backend directories usually only store hashes that are useless
232        // except to check authentication at the directory level
233        return null;
234    }
235
236    @Override
237    public String toString() {
238        return (String) dataModel.getData(config.nameKey);
239    }
240
241    @Override
242    public String getPrincipalId() {
243        return principalId;
244    }
245
246    @Override
247    public void setPrincipalId(String principalId) {
248        this.principalId = principalId;
249    }
250
251    @Override
252    public String getEmail() {
253        try {
254            return (String) dataModel.getData(config.emailKey);
255        } catch (PropertyException e) {
256            return null;
257        }
258    }
259
260    @Override
261    public void setEmail(String email) {
262        dataModel.setData(config.emailKey, email);
263    }
264
265    @Override
266    public DocumentModel getModel() {
267        return model;
268    }
269
270    /**
271     * Sets model and recomputes all groups.
272     */
273    public void setModel(DocumentModel model, boolean updateAllGroups) {
274        this.model = model;
275        dataModel = model.getDataModels().values().iterator().next();
276        if (updateAllGroups) {
277            updateAllGroups();
278        }
279    }
280
281    @Override
282    public void setModel(DocumentModel model) {
283        setModel(model, true);
284    }
285
286    @Override
287    public boolean isMemberOf(String group) {
288        return allGroups.contains(group);
289    }
290
291    @Override
292    public List<String> getAllGroups() {
293        return new ArrayList<String>(allGroups);
294    }
295
296    public void updateAllGroups() {
297        UserManager userManager = Framework.getService(UserManager.class);
298        Set<String> checkedGroups = new HashSet<String>();
299        List<String> groupsToProcess = new ArrayList<String>();
300        List<String> resultingGroups = new ArrayList<String>();
301        groupsToProcess.addAll(getGroups());
302
303        while (!groupsToProcess.isEmpty()) {
304            String groupName = groupsToProcess.remove(0);
305            if (!checkedGroups.contains(groupName)) {
306                checkedGroups.add(groupName);
307                NuxeoGroup nxGroup = null;
308                if (userManager != null) {
309                    try {
310                        nxGroup = userManager.getGroup(groupName);
311                    } catch (DirectoryException de) {
312                        if (virtualGroups.contains(groupName)) {
313                            // do not fail while retrieving a virtual group
314                            log.warn("Failed to get group '" + groupName + "' due to '" + de.getMessage()
315                                    + "': permission resolution involving groups may not be correct");
316                            nxGroup = null;
317                        } else {
318                            throw de;
319                        }
320                    }
321                }
322                if (nxGroup == null) {
323                    if (virtualGroups.contains(groupName)) {
324                        // just add the virtual group as is
325                        resultingGroups.add(groupName);
326                    } else if (userManager != null) {
327                        // XXX this should only happens in case of
328                        // inconsistency in DB
329                        log.error("User " + getName() + " references the " + groupName + " group that does not exists");
330                    }
331                } else {
332                    groupsToProcess.addAll(nxGroup.getParentGroups());
333                    // fetch the group name from the returned entry in case
334                    // it does not have the same case than the actual entry in
335                    // directory (for case insensitive directories)
336                    resultingGroups.add(nxGroup.getName());
337                    // XXX: maybe remove group from virtual groups if it
338                    // actually exists? otherwise it would be ignored when
339                    // setting groups
340                }
341            }
342        }
343
344        allGroups = new ArrayList<String>(resultingGroups);
345
346        // set isAdministrator boolean according to groups declared on user
347        // manager
348        if (!isAdministrator() && userManager != null) {
349            List<String> adminGroups = userManager.getAdministratorsGroups();
350            for (String adminGroup : adminGroups) {
351                if (allGroups.contains(adminGroup)) {
352                    isAdministrator = true;
353                    break;
354                }
355            }
356        }
357    }
358
359    public List<String> getVirtualGroups() {
360        return new ArrayList<String>(virtualGroups);
361    }
362
363    public void setVirtualGroups(List<String> virtualGroups, boolean updateAllGroups) {
364        this.virtualGroups = new ArrayList<String>(virtualGroups);
365        if (updateAllGroups) {
366            updateAllGroups();
367        }
368    }
369
370    /**
371     * Sets virtual groups and recomputes all groups.
372     */
373    public void setVirtualGroups(List<String> virtualGroups) {
374        setVirtualGroups(virtualGroups, true);
375    }
376
377    @Override
378    public boolean isAdministrator() {
379        return isAdministrator || SecurityConstants.SYSTEM_USERNAME.equals(getName());
380    }
381
382    @Override
383    public String getTenantId() {
384        return null;
385    }
386
387    @Override
388    public boolean isAnonymous() {
389        return isAnonymous;
390    }
391
392    @Override
393    public boolean equals(Object other) {
394        if (other instanceof Principal) {
395            String name = getName();
396            String otherName = ((Principal) other).getName();
397            if (name == null) {
398                return otherName == null;
399            } else {
400                return name.equals(otherName);
401            }
402        } else {
403            return false;
404        }
405    }
406
407    @Override
408    public int hashCode() {
409        String name = getName();
410        return name == null ? 0 : name.hashCode();
411    }
412
413    @Override
414    public String getOriginatingUser() {
415        return origUserName;
416    }
417
418    @Override
419    public void setOriginatingUser(String originatingUser) {
420        origUserName = originatingUser;
421    }
422
423    @Override
424    public String getActingUser() {
425        return getOriginatingUser() == null ? getName() : getOriginatingUser();
426    }
427
428}