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