001/*
002 * (C) Copyright 2006-2012 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 *     Thomas Roger <troger@nuxeo.com>
016 */
017
018package org.nuxeo.ecm.multi.tenant;
019
020import static org.nuxeo.ecm.multi.tenant.Constants.TENANT_ADMINISTRATORS_GROUP_SUFFIX;
021import static org.nuxeo.ecm.multi.tenant.Constants.TENANT_CONFIG_FACET;
022import static org.nuxeo.ecm.multi.tenant.Constants.TENANT_GROUP_PREFIX;
023import static org.nuxeo.ecm.multi.tenant.Constants.TENANT_MEMBERS_GROUP_SUFFIX;
024
025import java.security.Principal;
026import java.util.ArrayList;
027import java.util.List;
028import java.util.concurrent.TimeUnit;
029
030import org.nuxeo.ecm.core.api.CoreSession;
031import org.nuxeo.ecm.core.api.DocumentModel;
032import org.nuxeo.ecm.core.api.SystemPrincipal;
033import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
034import org.nuxeo.ecm.core.api.local.ClientLoginModule;
035import org.nuxeo.ecm.platform.usermanager.UserManager;
036import org.nuxeo.runtime.api.Framework;
037
038import com.google.common.cache.Cache;
039import com.google.common.cache.CacheBuilder;
040
041/**
042 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a>
043 * @since 5.6
044 */
045public class MultiTenantHelper {
046
047    protected static final Integer CACHE_CONCURRENCY_LEVEL = 10;
048
049    protected static final Integer CACHE_MAXIMUM_SIZE = 1000;
050
051    protected static final Integer CACHE_TIMEOUT = 10;
052
053    protected static final String NO_TENANT = "NO_TENANT";
054
055    protected final static Cache<String, String> pathCache = CacheBuilder.newBuilder().concurrencyLevel(
056            CACHE_CONCURRENCY_LEVEL).maximumSize(CACHE_MAXIMUM_SIZE).expireAfterWrite(CACHE_TIMEOUT, TimeUnit.MINUTES).build();
057
058    protected final static Cache<String, String> tenantBinding = CacheBuilder.newBuilder().concurrencyLevel(
059            CACHE_CONCURRENCY_LEVEL).maximumSize(CACHE_MAXIMUM_SIZE).expireAfterWrite(CACHE_TIMEOUT, TimeUnit.MINUTES).build();
060
061    private MultiTenantHelper() {
062        // helper class
063    }
064
065    public static String computeTenantAdministratorsGroup(String tenantId) {
066        return TENANT_GROUP_PREFIX + tenantId + TENANT_ADMINISTRATORS_GROUP_SUFFIX;
067    }
068
069    public static String computeTenantMembersGroup(String tenantId) {
070        return TENANT_GROUP_PREFIX + tenantId + TENANT_MEMBERS_GROUP_SUFFIX;
071    }
072
073    /**
074     * Returns the current tenantId for the given {@code principal}, or from the principal stored in the login stack.
075     * <p>
076     * The {@code principal} is used if it is a {@link SystemPrincipal}, then the tenantId is retrieved from the
077     * Principal matching the {@link SystemPrincipal#getOriginatingUser()}.
078     */
079    public static String getCurrentTenantId(Principal principal) {
080        if (principal instanceof SystemPrincipal) {
081            String originatingUser = ((SystemPrincipal) principal).getOriginatingUser();
082            if (originatingUser != null) {
083                return getTenantId(originatingUser);
084            } else {
085                return null;
086            }
087
088        } else {
089            return ClientLoginModule.getCurrentPrincipal().getTenantId();
090        }
091    }
092
093    /**
094     * Returns the tenantId for the given {@code username} if any, {@code null} otherwise.
095     */
096    public static String getTenantId(String username) {
097        UserManager userManager = Framework.getLocalService(UserManager.class);
098        String tenantId = null;
099        DocumentModel userModel = userManager.getUserModel(username);
100        if (userModel != null) {
101            tenantId = (String) userModel.getPropertyValue("user:tenantId");
102        }
103        return tenantId;
104    }
105
106    /**
107     * Returns the path of the tenant document matching the {@code tenantId}, or {@code null} if there is no document
108     * matching.
109     */
110    public static String getTenantDocumentPath(CoreSession session, final String tenantId) {
111        final List<String> paths = new ArrayList<String>();
112        String path = pathCache.getIfPresent(tenantId);
113        if (path == null) {
114            new UnrestrictedSessionRunner(session) {
115                @Override
116                public void run() {
117                    String query = String.format("SELECT * FROM Document WHERE tenantconfig:tenantId = '%s'", tenantId);
118                    List<DocumentModel> docs = session.query(query);
119                    if (!docs.isEmpty()) {
120                        paths.add(docs.get(0).getPathAsString());
121                    }
122                }
123            }.runUnrestricted();
124            path = paths.isEmpty() ? null : paths.get(0);
125            if (path != null) {
126                pathCache.put(tenantId, path);
127            }
128        }
129        return path;
130    }
131
132    /**
133     * Return the Tenant containing the provided DocumentModel if any
134     * 
135     * @param doc
136     * @return DocumentModel corresponding to the Tenant container, null otherwise
137     */
138    public static String getOwningTenantId(final DocumentModel doc) {
139        String tenantId = tenantBinding.getIfPresent(doc.getId());
140        if (NO_TENANT.equals(tenantId)) {
141            return null;
142        }
143
144        if (tenantId == null) {
145            TenantIdFinder finder = new TenantIdFinder(doc);
146            finder.runUnrestricted();
147            tenantId = finder.getTenantId();
148
149            if (tenantId == null) {
150                tenantBinding.put(doc.getId(), NO_TENANT);
151            } else {
152                tenantBinding.put(doc.getId(), tenantId);
153            }
154        }
155        return tenantId;
156    }
157
158    protected static class TenantIdFinder extends UnrestrictedSessionRunner {
159
160        protected String tenantId;
161
162        protected final DocumentModel target;
163
164        protected TenantIdFinder(DocumentModel target) {
165            super(target.getCoreSession());
166            this.target = target;
167        }
168
169        @Override
170        public void run() {
171            List<DocumentModel> parents = session.getParentDocuments(target.getRef());
172            for (int i = parents.size() - 1; i >= 0; i--) {
173                DocumentModel parent = parents.get(i);
174                if (parent.hasFacet(TENANT_CONFIG_FACET)) {
175                    tenantId = (String) parent.getPropertyValue(Constants.TENANT_ID_PROPERTY);
176                    return;
177                }
178            }
179        }
180
181        public String getTenantId() {
182            return tenantId;
183        }
184
185    }
186}