Java tutorial
/******************************************************************************* * Copyright 2015, The IKANOW Open Source Project. * * 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. *******************************************************************************/ package com.ikanow.aleph2.security.service; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import scala.Tuple2; import com.fasterxml.jackson.databind.JsonNode; import com.ikanow.aleph2.data_model.interfaces.shared_services.IBasicSearchService; import com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService; import com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService; import com.ikanow.aleph2.data_model.interfaces.shared_services.ISecurityService; import com.ikanow.aleph2.data_model.interfaces.shared_services.IServiceContext; import com.ikanow.aleph2.data_model.objects.shared.AuthorizationBean; import com.ikanow.aleph2.data_model.objects.shared.ProjectBean; import com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent; import com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent; import com.ikanow.aleph2.data_model.utils.FutureUtils; import com.ikanow.aleph2.data_model.utils.FutureUtils.ManagementFuture; public class SecuredCrudManagementDbService<T> implements IManagementCrudService<T> { private static final Logger logger = LogManager.getLogger(SecuredCrudManagementDbService.class); final IManagementCrudService<T> _delegate; protected AuthorizationBean authBean = null; protected IServiceContext serviceContext = null; protected ISecurityService securityService; protected PermissionExtractor permissionExtractor = new PermissionExtractor(); // default permission extractor; //BiConsumer<? super Optional<T>, ? super Throwable> action = new protected BiConsumer<? super Optional<T>, ? super Throwable> readCheckOne = (o, t) -> { logger.debug("readCheckOne:" + o + ",t=" + t); //System.out.println(t); if (o.isPresent()) { checkReadPermissions(o.get(), true); } }; Function<? super com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService.Cursor<T>, ? extends com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService.Cursor<T>> convertCursor = ( c) -> { return new SecuredCursor(c); }; protected String principalName; public PermissionExtractor getPermissionExtractor() { return permissionExtractor; } public void setPermissionExtractor(PermissionExtractor permissionExtractor) { this.permissionExtractor = permissionExtractor; } public SecuredCrudManagementDbService(IServiceContext serviceContext, IManagementCrudService<T> delegate, AuthorizationBean authBean) { _delegate = delegate; this.serviceContext = serviceContext; this.authBean = authBean; this.securityService = serviceContext.getSecurityService(); this.principalName = getPrincipal(authBean); } protected String getPrincipal(AuthorizationBean authBean2) { return authBean2.getPrincipalName(); } /** * @param authorization_fieldname * @param client_auth * @param project_auth * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getFilteredRepo(java.lang.String, * java.util.Optional, java.util.Optional) */ public IManagementCrudService<T> getFilteredRepo(String authorization_fieldname, Optional<AuthorizationBean> client_auth, Optional<ProjectBean> project_auth) { return _delegate.getFilteredRepo(authorization_fieldname, client_auth, project_auth).readOnlyVersion(); } /** * @param new_object * @param replace_if_present * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#storeObject(java.lang.Object, * boolean) */ public ManagementFuture<Supplier<Object>> storeObject(T new_object, boolean replace_if_present) { checkWritePermissions(new_object); return _delegate.storeObject(new_object, replace_if_present); } /** * @param new_object * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#storeObject(java.lang.Object) */ public ManagementFuture<Supplier<Object>> storeObject(T new_object) { checkWritePermissions(new_object); return _delegate.storeObject(new_object); } /** * @param new_objects * @param continue_on_error * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#storeObjects(java.util.List, * boolean) */ public ManagementFuture<Tuple2<Supplier<List<Object>>, Supplier<Long>>> storeObjects(List<T> new_objects, boolean continue_on_error) { checkWritePermissions(new_objects); return _delegate.storeObjects(new_objects, continue_on_error); } /** * @param new_objects * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#storeObjects(java.util.List) */ public ManagementFuture<Tuple2<Supplier<List<Object>>, Supplier<Long>>> storeObjects(List<T> new_objects) { checkWritePermissions(new_objects); return _delegate.storeObjects(new_objects); } /** * @param ordered_field_list * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#optimizeQuery(java.util.List) */ public ManagementFuture<Boolean> optimizeQuery(List<String> ordered_field_list) { return _delegate.optimizeQuery(ordered_field_list); } /** * @param unique_spec * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent) */ public ManagementFuture<Optional<T>> getObjectBySpec(QueryComponent<T> unique_spec) { ManagementFuture<Optional<T>> mf = _delegate.getObjectBySpec(unique_spec); return FutureUtils.createManagementFuture(mf.whenComplete(readCheckOne)); } /** * @param ordered_field_list * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService#deregisterOptimizedQuery(java.util.List) */ public boolean deregisterOptimizedQuery(List<String> ordered_field_list) { return _delegate.deregisterOptimizedQuery(ordered_field_list); } /** * @param unique_spec * @param field_list * @param include * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent, * java.util.List, boolean) */ public ManagementFuture<Optional<T>> getObjectBySpec(QueryComponent<T> unique_spec, List<String> field_list, boolean include) { return _delegate.getObjectBySpec(unique_spec, field_list, include); } /** * @param id * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectById(java.lang.Object) */ public ManagementFuture<Optional<T>> getObjectById(Object id) { return _delegate.getObjectById(id); } /** * @param id * @param field_list * @param include * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectById(java.lang.Object, * java.util.List, boolean) */ public ManagementFuture<Optional<T>> getObjectById(Object id, List<String> field_list, boolean include) { return _delegate.getObjectById(id, field_list, include); } /** * @param spec * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectsBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent) */ public ManagementFuture<com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService.Cursor<T>> getObjectsBySpec( QueryComponent<T> spec) { ManagementFuture<com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService.Cursor<T>> mf = _delegate .getObjectsBySpec(spec); return FutureUtils.createManagementFuture(mf.thenApply(convertCursor)); } /** * @param spec * @param field_list * @param include * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectsBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent, * java.util.List, boolean) */ public ManagementFuture<com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService.Cursor<T>> getObjectsBySpec( QueryComponent<T> spec, List<String> field_list, boolean include) { ManagementFuture<com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService.Cursor<T>> mf = _delegate .getObjectsBySpec(spec, field_list, include); return FutureUtils.createManagementFuture(mf.thenApply(convertCursor)); } /** * @param spec * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#countObjectsBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent) */ public ManagementFuture<Long> countObjectsBySpec(QueryComponent<T> spec) { return _delegate.countObjectsBySpec(spec); } /** * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#countObjects() */ public ManagementFuture<Long> countObjects() { return _delegate.countObjects(); } /** * @param id * @param update * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#updateObjectById(java.lang.Object, * com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent) */ public ManagementFuture<Boolean> updateObjectById(Object id, UpdateComponent<T> update) { return _delegate.updateObjectById(id, update); } /** * @param unique_spec * @param upsert * @param update * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#updateObjectBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent, * java.util.Optional, * com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent) */ public ManagementFuture<Boolean> updateObjectBySpec(QueryComponent<T> unique_spec, Optional<Boolean> upsert, UpdateComponent<T> update) { return _delegate.updateObjectBySpec(unique_spec, upsert, update); } /** * @param spec * @param upsert * @param update * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#updateObjectsBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent, * java.util.Optional, * com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent) */ public ManagementFuture<Long> updateObjectsBySpec(QueryComponent<T> spec, Optional<Boolean> upsert, UpdateComponent<T> update) { return _delegate.updateObjectsBySpec(spec, upsert, update); } /** * @param unique_spec * @param upsert * @param update * @param before_updated * @param field_list * @param include * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#updateAndReturnObjectBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent, * java.util.Optional, * com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent, * java.util.Optional, java.util.List, boolean) */ public ManagementFuture<Optional<T>> updateAndReturnObjectBySpec(QueryComponent<T> unique_spec, Optional<Boolean> upsert, UpdateComponent<T> update, Optional<Boolean> before_updated, List<String> field_list, boolean include) { return _delegate.updateAndReturnObjectBySpec(unique_spec, upsert, update, before_updated, field_list, include); } /** * @param id * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#deleteObjectById(java.lang.Object) */ public ManagementFuture<Boolean> deleteObjectById(Object id) { return _delegate.deleteObjectById(id); } /** * @param unique_spec * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#deleteObjectBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent) */ public ManagementFuture<Boolean> deleteObjectBySpec(QueryComponent<T> unique_spec) { return _delegate.deleteObjectBySpec(unique_spec); } /** * @param spec * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#deleteObjectsBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent) */ public ManagementFuture<Long> deleteObjectsBySpec(QueryComponent<T> spec) { return _delegate.deleteObjectsBySpec(spec); } /** * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#deleteDatastore() */ public ManagementFuture<Boolean> deleteDatastore() { checkDeletePermission(); return _delegate.deleteDatastore(); } /** * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getRawService() */ public IManagementCrudService<JsonNode> getRawService() { return _delegate.getRawService(); } /** * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService#getSearchService() */ public Optional<IBasicSearchService<T>> getSearchService() { return _delegate.getSearchService(); } /** * @param driver_class * @param driver_options * @return * @see com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService#getUnderlyingPlatformDriver(java.lang.Class, * java.util.Optional) */ public <X> Optional<X> getUnderlyingPlatformDriver(Class<X> driver_class, Optional<String> driver_options) { return _delegate.getUnderlyingPlatformDriver(driver_class, driver_options); } @Override public Optional<ICrudService<T>> getCrudService() { return Optional.of(this); } protected void checkWritePermissions(T new_object) { //boolean permitted = securityService.hasRole(principalName,ROLE_ADMIN); boolean permitted = securityService.isUserPermitted(principalName, new_object, Optional.of(ISecurityService.ACTION_WRITE)); //still not permitted, we are out of luck if (!permitted) { //String msg = "principalName "+principalName+" has no write permissions ("+permissions+")for "+new_object.getClass(); String msg = "Subject " + principalName + " has no write permissions for " + new_object.getClass(); logger.error(msg); throw new SecurityException(msg); } } protected void checkDeletePermission() { boolean permitted = securityService.hasUserRole(principalName, ISecurityService.ROLE_ADMIN); if (!permitted) { String msg = "Subject " + principalName + " has no write permissions for deletions"; logger.error(msg); throw new SecurityException(msg); } } protected void checkWritePermissions(List<T> new_objects) { for (T t : new_objects) { checkWritePermissions(t); } } /** * Read permissions are the default permissions. * @param new_object */ protected boolean checkReadPermissions(Object new_object, boolean throwOrReturn) { List<String> permissions = permissionExtractor.extractPermissionIdentifiers(new_object, Optional.of(ISecurityService.ACTION_READ)); boolean permitted = false; if (permissions != null && permissions.size() > 0) { for (String permission : permissions) { permitted = securityService.isUserPermitted(principalName, permission); if (permitted) { break; } } if (!permitted && throwOrReturn) { String msg = "Subject '" + principalName + "' has no read permissions (" + permissions + ")for " + new_object.getClass(); logger.error(msg); throw new SecurityException(msg); } } return permitted; } /** * Read permissions are the default permissions. * @param new_object */ protected boolean checkPermissions(Object new_object, Optional<String> oAction, boolean throwOrReturn) { List<String> permissions = permissionExtractor.extractPermissionIdentifiers(new_object, oAction); boolean permitted = false; if (permissions != null && permissions.size() > 0) { for (String permission : permissions) { permitted = securityService.isUserPermitted(principalName, permission); if (permitted) { break; } } if (!permitted && throwOrReturn) { String msg = "Subject '" + principalName + "' has no permissions (" + permissions + ")for " + oAction + " on " + new_object.getClass(); logger.error(msg); throw new SecurityException(msg); } } return permitted; } /** * This class is wrapping the crud cursor and additionally performs security checks. * @author jfreydank * */ protected class SecuredCursor extends com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService.Cursor<T> { private com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService.Cursor<T> delegate; public SecuredCursor( com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService.Cursor<T> delegate) { this.delegate = delegate; } @Override public Iterator<T> iterator() { return new SecureIterator(delegate.iterator()); } @Override public void close() throws Exception { delegate.close(); } @Override public long count() { // TODO return delegate.count(); } protected class SecureIterator implements Iterator<T> { private Iterator<T> itDelegate; private T nextValid = null; public SecureIterator(Iterator<T> itDelegate) { this.itDelegate = itDelegate; } @Override public boolean hasNext() { while (itDelegate.hasNext()) { T nextCandidate = itDelegate.next(); if (checkReadPermissions(nextCandidate, false)) { // found next one with nextValid = nextCandidate; break; } } // while return nextValid != null; } @Override public T next() { T n = nextValid; // reset nextValid, cannot be retrieved twice nextValid = null; return n; } } } // cursor }