001/*
002 * (C) Copyright 2011 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 *     bjalon
018 */
019package org.nuxeo.ecm.platform.usermanager;
020
021import static org.nuxeo.ecm.directory.localconfiguration.DirectoryConfigurationConstants.DIRECTORY_CONFIGURATION_FACET;
022
023import java.io.Serializable;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029import org.nuxeo.ecm.core.api.DocumentModel;
030import org.nuxeo.ecm.core.api.NuxeoException;
031import org.nuxeo.ecm.core.api.localconfiguration.LocalConfigurationService;
032import org.nuxeo.ecm.core.query.QueryParseException;
033import org.nuxeo.ecm.core.query.sql.model.Expression;
034import org.nuxeo.ecm.core.query.sql.model.IdentityQueryTransformer;
035import org.nuxeo.ecm.core.query.sql.model.Literal;
036import org.nuxeo.ecm.core.query.sql.model.MultiExpression;
037import org.nuxeo.ecm.core.query.sql.model.Operator;
038import org.nuxeo.ecm.core.query.sql.model.Predicate;
039import org.nuxeo.ecm.core.query.sql.model.Predicates;
040import org.nuxeo.ecm.core.query.sql.model.QueryBuilder;
041import org.nuxeo.ecm.core.query.sql.model.Reference;
042import org.nuxeo.ecm.core.query.sql.model.StringLiteral;
043import org.nuxeo.ecm.directory.BaseSession.FieldDetector;
044import org.nuxeo.ecm.directory.localconfiguration.DirectoryConfiguration;
045import org.nuxeo.runtime.api.Framework;
046
047/**
048 * @author bjalon
049 */
050public class DefaultUserMultiTenantManagement implements UserMultiTenantManagement {
051
052    protected static final Log log = LogFactory.getLog(DefaultUserMultiTenantManagement.class);
053
054    protected static final String SUFFIX_SEPARATOR = "-";
055
056    protected String getDirectorySuffix(DocumentModel documentContext) {
057        LocalConfigurationService localConfigurationService = Framework.getService(LocalConfigurationService.class);
058        DirectoryConfiguration configuration = localConfigurationService.getConfiguration(DirectoryConfiguration.class,
059                DIRECTORY_CONFIGURATION_FACET, documentContext);
060        if (configuration != null && configuration.getDirectorySuffix() != null) {
061            return SUFFIX_SEPARATOR + configuration.getDirectorySuffix();
062        }
063        return null;
064    }
065
066    @Override
067    public void queryTransformer(UserManager um, Map<String, Serializable> filter, Set<String> fulltext,
068            DocumentModel context) {
069        String groupId = um.getGroupIdField();
070        if (filter == null || fulltext == null) {
071            throw new NuxeoException("Filter and Fulltext must be not null");
072        }
073
074        if (getDirectorySuffix(context) == null) {
075            log.debug("Directory Local Configuration is null, don't need to filter");
076            return;
077        }
078
079        String groupIdSuffix = getDirectorySuffix(context);
080
081        if (!filter.containsKey(groupId)) {
082            log.debug("no filter on group id, need to filter with the directory local " + "configuration suffix : "
083                    + groupId + " = %" + groupIdSuffix);
084            filter.put(groupId, "%" + groupIdSuffix);
085            fulltext.add(groupId);
086            return;
087        }
088
089        if (!(filter.get(groupId) instanceof String)) {
090            throw new UnsupportedOperationException("Filter value on " + "group id is not a string : "
091                    + filter.get(groupId));
092        }
093
094        String filterIdValue = (String) filter.get(um.getGroupIdField());
095        filter.put(groupId, filterIdValue + groupIdSuffix);
096    }
097
098    @Override
099    public QueryBuilder groupQueryTransformer(UserManager um, QueryBuilder queryBuilder, DocumentModel context) {
100        String suffix = getDirectorySuffix(context);
101        if (suffix == null) {
102            log.debug("No tenant configuration");
103            return queryBuilder;
104        }
105        queryBuilder = new QueryBuilder(queryBuilder); // copy
106        MultiExpression multiExpr = queryBuilder.predicate();
107        String groupIdField = um.getGroupIdField();
108        if (FieldDetector.hasField(multiExpr, groupIdField)) {
109            QueryTenantAdder qta = new QueryTenantAdder(groupIdField, suffix);
110            multiExpr = qta.transform(multiExpr);
111        }
112        Predicate predicate = Predicates.like(groupIdField, "%" + suffix); // filter for this tenant
113        queryBuilder.predicate(predicate).and(multiExpr);
114        return queryBuilder;
115    }
116
117    /**
118     * Changes group equality or difference matches to take into account a suffix.
119     * <p>
120     * Throws for any more complex query on groups.
121     *
122     * @since 10.3
123     */
124    public static class QueryTenantAdder extends IdentityQueryTransformer {
125
126        protected final String groupIdField;
127
128        protected final String suffix;
129
130        protected boolean isGroupPredicate;
131
132        public QueryTenantAdder(String groupIdField, String suffix) {
133            this.groupIdField = groupIdField;
134            this.suffix = suffix;
135        }
136
137        @Override
138        public Expression transform(Expression node) {
139            if (node.lvalue instanceof Reference && ((Reference) node.lvalue).name.equals(groupIdField)) {
140                if (node.operator == Operator.EQ || node.operator == Operator.NOTEQ || node.operator == Operator.IN
141                        || node.operator == Operator.NOTIN) {
142                    isGroupPredicate = true;
143                    node = super.transform(node);
144                    isGroupPredicate = false;
145                    return node;
146                }
147            }
148            return super.transform(node);
149        }
150
151        @Override
152        public Predicate transform(Predicate node) {
153            if (node.lvalue instanceof Reference && ((Reference) node.lvalue).name.equals(groupIdField)) {
154                if (node.operator == Operator.EQ || node.operator == Operator.NOTEQ || node.operator == Operator.IN
155                        || node.operator == Operator.NOTIN) {
156                    isGroupPredicate = true;
157                    node = super.transform(node);
158                    isGroupPredicate = false;
159                    return node;
160                }
161            }
162            return super.transform(node);
163        }
164
165        @Override
166        public Literal transform(StringLiteral node) {
167            if (!isGroupPredicate) {
168                return node;
169            }
170            return new StringLiteral(node.value + suffix);
171        }
172
173        @Override
174        public Reference transform(Reference node) {
175            if (isGroupPredicate) {
176                return node;
177            }
178            if (node.name.equals(groupIdField)) {
179                throw new QueryParseException("Cannot evaluate expression in multi-tenant mode");
180            }
181            return node;
182        }
183    }
184
185    @Override
186    public DocumentModel groupTransformer(UserManager um, DocumentModel group, DocumentModel context)
187            {
188        if (context == null) {
189            return group;
190        }
191        String groupIdValue = group.getPropertyValue(um.getGroupIdField()) + getDirectorySuffix(context);
192        group.setPropertyValue(um.getGroupIdField(), groupIdValue);
193        return group;
194    }
195
196    @Override
197    public String groupnameTranformer(UserManager um, String groupname, DocumentModel context) {
198        String suffix = getDirectorySuffix(context);
199        if (suffix != null) {
200            groupname += suffix;
201        }
202        return groupname;
203    }
204}