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 <[email protected]> 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.SystemPrincipal; 035import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 036import org.nuxeo.ecm.core.api.local.ClientLoginModule; 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:[email protected]">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 ClientLoginModule.getCurrentPrincipal().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<String>(); 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 * @param doc 138 * @return DocumentModel corresponding to the Tenant container, null otherwise 139 */ 140 public static String getOwningTenantId(final DocumentModel doc) { 141 String tenantId = tenantBinding.getIfPresent(doc.getId()); 142 if (NO_TENANT.equals(tenantId)) { 143 return null; 144 } 145 146 if (tenantId == null) { 147 TenantIdFinder finder = new TenantIdFinder(doc); 148 finder.runUnrestricted(); 149 tenantId = finder.getTenantId(); 150 151 if (tenantId == null) { 152 tenantBinding.put(doc.getId(), NO_TENANT); 153 } else { 154 tenantBinding.put(doc.getId(), tenantId); 155 } 156 } 157 return tenantId; 158 } 159 160 protected static class TenantIdFinder extends UnrestrictedSessionRunner { 161 162 protected String tenantId; 163 164 protected final DocumentModel target; 165 166 protected TenantIdFinder(DocumentModel target) { 167 super(target.getCoreSession()); 168 this.target = target; 169 } 170 171 @Override 172 public void run() { 173 List<DocumentModel> parents = session.getParentDocuments(target.getRef()); 174 for (int i = parents.size() - 1; i >= 0; i--) { 175 DocumentModel parent = parents.get(i); 176 if (parent.hasFacet(TENANT_CONFIG_FACET)) { 177 tenantId = (String) parent.getPropertyValue(Constants.TENANT_ID_PROPERTY); 178 return; 179 } 180 } 181 } 182 183 public String getTenantId() { 184 return tenantId; 185 } 186 187 } 188}