/*
* #%L
* BroadleafCommerce Open Admin Platform
* %%
* Copyright (C) 2009 - 2014 Broadleaf Commerce
* %%
* 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%
*/
package org.broadleafcommerce.openadmin.server.security.service;
import org.broadleafcommerce.openadmin.dto.Entity;
import org.broadleafcommerce.openadmin.dto.PersistencePackage;
import org.broadleafcommerce.openadmin.server.security.domain.AdminUser;
import org.broadleafcommerce.openadmin.server.service.persistence.module.criteria.CriteriaTranslatorEventHandler;
import org.broadleafcommerce.openadmin.server.service.persistence.module.criteria.CriteriaTranslatorImpl;
import org.broadleafcommerce.openadmin.server.service.persistence.module.criteria.FilterMapping;
import org.broadleafcommerce.openadmin.server.service.persistence.validation.GlobalValidationResult;
import org.broadleafcommerce.openadmin.server.service.persistence.validation.PropertyValidator;
import org.broadleafcommerce.openadmin.web.form.entity.DefaultEntityFormActions;
import org.broadleafcommerce.openadmin.web.form.entity.EntityForm;
import org.broadleafcommerce.openadmin.web.service.FormBuilderServiceImpl;
import java.io.Serializable;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
/**
* <p>
* A component that can apply row-level security to the admin
*
* <p>
* Implementations of this class should extend from the {@link AbstractRowLevelSecurityProvider}
*
* @author Phillip Verheyden (phillipuniverse)
* @see {@link AbstractRowLevelSecurityProvider}
* @see {@link RowLevelSecurityService}
*/
public interface RowLevelSecurityProvider {
/**
* <p>
* Used to further restrict a result set in the admin for a particular admin user. This can be done by adding additional
* {@link Predicate}s to the given list of <b>restrictions</b>. You can also attach additional sorting from the given
* list of <b>sorts</b>.
*
* <p>
* You should not attach any of these {@link Predicate}s to the given <b>criteria</b>, you should instead modify the
* given lists. These lists will be automatically attached to the <b>criteria</b> after execution.
*
* <p>
* Existing {@link Predicate}s and sorts will already be added into the given <b>restrictions</b> and <b>sorts</b> lists.
*
* <p>
* If you are filtering on a property that exists only in a subclass of an OOB framework entity (like if you extended
* ProductImpl and you need to add a restriction targeting MyProductImpl) then you need to override
* {@link #getFetchRestrictionRoot(AdminUser, Class, List)} to return the class that you are adding criteria to.
* Otherwise, the <b>entityRoot</b> will be wrong and if you try to get a {@link Path} from it then you will run into
* {@link IllegalArgumentException}s.
*
* <p>
* This method is executed <i>prior</i> to any {@link CriteriaTranslatorEventHandler}.
*
* @param currentUser the currently logged in {@link AdminUser}
* @param ceilingEntity the entity currently being queried from
* @param restrictions the restrictions that will be applied to the <b>criteria</b> but have not been yet. Additional
* {@link Predicate}s to further filter the query should be added to this list
* @param sorts the sorts that will be applied to the <b>criteria</b>. Additional sorts should be added to this list
* @param entityRoot the JPA root for <b>ceilingEntity</b>
* @param criteria the criteria that will be executed. No {@link Predicate}s or {@link Order}s have been applied
* to this criteria, and nor should they be. All modifications should instead be to the given <b>restrictions</b> and/or
* <b>sorts</b>
* @param criteriaBuilder used to create additional {@link Predicate}s or {@link Order}s to add to <b>restrictions</b>
* and/or <b>sorts</b>
* @see {@link #getFetchRestrictionRoot(Class, List)}
* @see {@link CriteriaTranslatorImpl#addRestrictions}
*/
public void addFetchRestrictions(AdminUser currentUser, String ceilingEntity, List<Predicate> restrictions, List<Order> sorts,
Root entityRoot,
CriteriaQuery criteria,
CriteriaBuilder criteriaBuilder);
/**
* <p>
* Contributes to {@link Root} determination for {@link #addFetchRestrictions(AdminUser, String, List, List, Root, CriteriaQuery, CriteriaBuilder)}.
* Normally, the query {@link Root} is determined in the admin via the given <b>filterMappings</b>. Since row security deals with
* a {@link CriteriaBuilder} directly, if you want to be able to target subclasses then a new {@link Root} must be
* established for that specific subclass.
*
* <p>
* Note that depending on how you have your filters in the admin frontend (the list grids) set up, you might have to take
* into account the given <b>filterMappings</b>. The admin will not be able to find a correct root if there is an active
* filter set on a sibling class that you are attempting to also add more criteria to. For instance, if a class hierarchy
* exists for A -> B and also A -> C, if there is an active {@link FilterMapping} for a property from B and you attempt
* to add a fetch restriction on a property from C that will not work.
*
* <p>
* It is acceptable to return null from this method if {@link #addFetchRestrictions(AdminUser, String, List, List, Root, CriteriaQuery, CriteriaBuilder)}
* does not rely on any properties from a child class.
*
* @param ceilingEntity the entity being queried for
* @param filterMappings the existing filters passed from the admin frontend
*
* @return the root class that is going to be used for {@link #addFetchRestrictions(AdminUser, String, List, List, Root, CriteriaQuery, CriteriaBuilder)}
* or <b>null</b> if no specific root needs to be used
* @see {@link CriteriaTranslatorImpl#determineRoot}
* @see {@link FilterMapping#getInheritedFromClass()}
*/
public Class<Serializable> getFetchRestrictionRoot(AdminUser currentUser, Class<Serializable> ceilingEntity, List<FilterMapping> filterMappings);
/**
* <p>
* Hook to determine if the given <b>entity</b> can be updated or not. This is used to drive the form displayed in the
* admin frontend to remove modifier actions and set the entire {@link EntityForm} as readonly.
*
* <p>
* If the entity cannot be updated, then by default it can also not be removed. You can change this by explicitly
* overriding {@link #canRemove(AdminUser, Entity)}
*
* @param currentUser the currently logged in {@link AdminUser}
* @param entity the {@link Entity} DTO that is attempting to be updated
* @return <b>true</b> if the given <b>entity</b> can be updated, <b>false</b> otherwise
* @see {@link FormBuilderServiceImpl#setReadOnlyState}
*/
public boolean canUpdate(AdminUser currentUser, Entity entity);
/**
* <p>
* Hook to determine if the given <b>entity</b> can be deleted by a user. This is used to drive the {@link DefaultEntityFormActions#DELETE}
* button from appearing on the admin frontend.
*
* <p>
* You might consider tying the remove to {@link #canUpdate(AdminUser, Entity)} and explicitly invoking that action yourself.
*
* @param currentUser the currently logged in {@link AdminUser}
* @param entity
* @return <b>true</b> if the given <b>entity</b> can be deleted, <b>false</b> otherwise
* @see {@link FormBuilderServiceImpl#addDeleteActionIfAllowed}
*/
public boolean canRemove(AdminUser currentUser, Entity entity);
/**
* <p>
* Validates whether a user has permissions to actually perform the update. The result of this method is a
* validation result that indicates if something in the entire entity is in error. The message key from the resulting
* {@link GlobalValidationResult} will be automatically added to the given <b>entity</b> {@link Entity#getGlobalValidationErrors()}.
*
* <p>
* If you would like to add individual property errors, you can do that with the given <b>entity</b> by using
* {@link Entity#addValidationError(String, String)}. Even if you attach errors to specific properties you should still
* return an appropriate {@link GlobalValidationResult}. In that case however, it might be more suitable to use a
* {@link PropertyValidator} instead.
*
* <p>
* For convenience, this is usually a simple invocation to {@link #canUpdate(Entity)}. However, it might be that you want
* to allow the user to see certain update fields but not allow the user to save certain fields for update.
*
* @param currentUser the currently logged in {@link AdminUser}
* @param entity the DTO representation that is attempting to be deleted. Comes from {@link PersistencePackage#getEntity()}
* @param persistencePackage the full persiste
* @return a {@link GlobalValidationResult} with {@link GlobalValidationResult#isValid()} set to denote if the given
* <b>entity</b> failed row-level security validation or not.
*/
public GlobalValidationResult validateUpdateRequest(AdminUser currentUser, Entity entity, PersistencePackage persistencePackage);
/**
* <p>
* Validates whether a user has permissions to actually perform the record deletion. The result of this method is a
* validation result that indicates if something in the entire entity is in error. The message key from the resulting
* {@link GlobalValidationResult} will be automatically added to the given <b>entity</b> {@link Entity#getGlobalValidationErrors()}.
*
* <p>
* If you would like to add individual property errors, you can do that with the given <b>entity</b> by using
* {@link Entity#addValidationError(String, String)}. Even if you attach errors to specific properties you should still
* return an appropriate {@link GlobalValidationResult}. In that case however, it might be more suitable to use a
* {@link PropertyValidator} instead.
*
* <p>
* This is usually a simple invocation to {@link #canDelete(Entity)}.
*
* @param currentUser the currently logged in {@link AdminUser}
* @param entity the DTO representation that is attempting to be deleted. Comes from {@link PersistencePackage#getEntity()}
* @param persistencePackage the full request sent from the frontend through the admin pipeline
* @return a {@link GlobalValidationResult} with {@link GlobalValidationResult#isValid()} set to denote if the given
* <b>entity</b> failed row-level security validation or not.
*/
public GlobalValidationResult validateRemoveRequest(AdminUser currentUser, Entity entity, PersistencePackage persistencePackage);
}