package com.thinkbiganalytics.metadata.jpa.feed;
/*-
* #%L
* kylo-operational-metadata-jpa
* %%
* Copyright (C) 2017 ThinkBig Analytics
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.ComparablePath;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringTemplate;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.JPQLQuery;
import com.thinkbiganalytics.metadata.config.RoleSetExposingSecurityExpressionRoot;
import com.thinkbiganalytics.metadata.jpa.feed.security.JpaFeedOpsAclEntry;
import com.thinkbiganalytics.metadata.jpa.feed.security.JpaFeedOpsAclEntry.PrincipalType;
import com.thinkbiganalytics.metadata.jpa.feed.security.QJpaFeedOpsAclEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
/**
* Secures queries by checking whether access to them is allowed by having matching roles for current
* user principal in FeedAclIndex table
*/
public abstract class FeedAclIndexQueryAugmentor implements QueryAugmentor {
private static final Logger LOG = LoggerFactory.getLogger(FeedAclIndexQueryAugmentor.class);
private static final StringTemplate CONSTANT_ONE = Expressions.stringTemplate("1");
private static final BooleanExpression ONE_EQUALS_ONE = CONSTANT_ONE.eq(CONSTANT_ONE);
protected abstract <S, T, ID extends Serializable> Path<Object> getFeedId(JpaEntityInformation<T, ID> entityInformation, Root<S> root);
protected abstract ComparablePath<UUID> getFeedId();
protected abstract QOpsManagerFeedId getOpsManagerFeedId();
@Override
public <S, T, ID extends Serializable> Specification<S> augment(Specification<S> spec, Class<S> domainClass,
JpaEntityInformation<T, ID> entityInformation) {
LOG.debug("QueryAugmentor.augment");
return (root, query, criteriaBuilder) -> {
Root<JpaFeedOpsAclEntry> fromAcl = query.from(JpaFeedOpsAclEntry.class);
query.distinct(true);
if (query.getSelection() == null) {
query.select((Selection) root);
}
Path<Object> feedId = getFeedId(entityInformation, root);
javax.persistence.criteria.Predicate rootFeedIdEqualToAclFeedId = criteriaBuilder.equal(feedId, fromAcl.get("feedId"));
RoleSetExposingSecurityExpressionRoot userCxt = getUserContext();
javax.persistence.criteria.Predicate aclPrincipalInGroups = fromAcl.get("principalName").in(userCxt.getGroups());
javax.persistence.criteria.Predicate aclPrincipalTypeIsGroup = criteriaBuilder.equal(fromAcl.get("principalType"), PrincipalType.GROUP);
javax.persistence.criteria.Predicate acePrincipalGroupMatch = criteriaBuilder.and(aclPrincipalInGroups, aclPrincipalTypeIsGroup);
javax.persistence.criteria.Predicate aclPrincipalEqUser = criteriaBuilder.equal(fromAcl.get("principalName"), userCxt.getName());
javax.persistence.criteria.Predicate aclPrincipalTypeIsUser = criteriaBuilder.equal(fromAcl.get("principalType"), PrincipalType.USER);
javax.persistence.criteria.Predicate acePrincipalUserMatch = criteriaBuilder.and(aclPrincipalEqUser, aclPrincipalTypeIsUser);
javax.persistence.criteria.Predicate acePrincipalMatch = criteriaBuilder.or(acePrincipalGroupMatch, acePrincipalUserMatch);
javax.persistence.criteria.Predicate feedIdEqualsAndPrincipalMatch = criteriaBuilder.and(rootFeedIdEqualToAclFeedId, acePrincipalMatch);
if (spec != null) {
javax.persistence.criteria.Predicate predicate = spec.toPredicate(root, query, criteriaBuilder);
return criteriaBuilder.and(predicate, feedIdEqualsAndPrincipalMatch);
} else {
return feedIdEqualsAndPrincipalMatch;
}
};
}
@Override
public List<Predicate> augment(Predicate[] predicate) {
LOG.debug("FeedAclIndexQueryAugmentor.augment(Predicate[])");
QOpsManagerFeedId feed = getOpsManagerFeedId();
BooleanExpression exists = generateExistsExpression(feed);
List<Predicate> predicates = new ArrayList<>();
predicates.addAll(Arrays.asList(predicate));
predicates.add(exists);
return predicates;
}
/**
* Generates the Exist expression for the feed to feedacl table
*
* @return exists expression
*/
private static BooleanExpression generateExistsExpression(QOpsManagerFeedId feedId) {
LOG.debug("FeedAclIndexQueryAugmentor.generateExistsExpression(QOpsManagerFeedId)");
RoleSetExposingSecurityExpressionRoot userCxt = getUserContext();
QJpaFeedOpsAclEntry aclEntry = QJpaFeedOpsAclEntry.jpaFeedOpsAclEntry;
JPQLQuery<JpaFeedOpsAclEntry> subquery = JPAExpressions.selectFrom(aclEntry)
.where(aclEntry.feed.id.eq(feedId)
.and(aclEntry.principalName.in(userCxt.getGroups()).and(aclEntry.principalType.eq(PrincipalType.GROUP))
.or(aclEntry.principalName.eq(userCxt.getName()).and(aclEntry.principalType.eq(PrincipalType.USER))))
);
return subquery.exists();
}
public static BooleanExpression generateExistsExpression(QOpsManagerFeedId id, boolean entityAccessControlled) {
if (entityAccessControlled) {
return generateExistsExpression(id);
} else {
return ONE_EQUALS_ONE;
}
}
@Override
public <S, T, ID extends Serializable> CriteriaQuery<Long> getCountQuery(EntityManager entityManager, JpaEntityInformation<T, ID> entityInformation, Specification<S> spec, Class<S> domainClass) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<S> root = query.from(domainClass);
if (query.isDistinct()) {
query.select(builder.countDistinct(root));
} else {
query.select(builder.count(root));
}
Specification<S> secured = this.augment(spec, domainClass, entityInformation);
query.where(secured.toPredicate(root, query, builder));
return query;
}
private static RoleSetExposingSecurityExpressionRoot getUserContext() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new RoleSetExposingSecurityExpressionRoot(authentication);
}
}