package com.thinkbiganalytics.feedmgr.service.security; /*- * #%L * kylo-feed-manager-controller * %% * 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.thinkbiganalytics.feedmgr.security.FeedServicesAccessControl; import com.thinkbiganalytics.metadata.api.MetadataAccess; import com.thinkbiganalytics.metadata.api.category.Category; import com.thinkbiganalytics.metadata.api.category.CategoryProvider; import com.thinkbiganalytics.metadata.api.datasource.DatasourceProvider; import com.thinkbiganalytics.metadata.api.datasource.UserDatasource; import com.thinkbiganalytics.metadata.api.feed.Feed; import com.thinkbiganalytics.metadata.api.feed.FeedProvider; import com.thinkbiganalytics.metadata.api.security.AccessControlled; import com.thinkbiganalytics.metadata.api.template.FeedManagerTemplate; import com.thinkbiganalytics.metadata.api.template.FeedManagerTemplateProvider; import com.thinkbiganalytics.security.AccessController; import com.thinkbiganalytics.security.action.Action; import com.thinkbiganalytics.security.action.AllowedActions; import com.thinkbiganalytics.security.action.AllowedEntityActionsProvider; import com.thinkbiganalytics.security.rest.controller.SecurityModelTransform; import com.thinkbiganalytics.security.rest.model.ActionGroup; import com.thinkbiganalytics.security.rest.model.PermissionsChange; import com.thinkbiganalytics.security.rest.model.PermissionsChange.ChangeType; import com.thinkbiganalytics.security.rest.model.RoleMembership; import com.thinkbiganalytics.security.rest.model.RoleMembershipChange; import java.security.Principal; import java.security.acl.Group; import java.util.Arrays; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.inject.Inject; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response.Status; /** * Default service implementation for changing the permissions and role memberships of * various metadata entities. * */ //===================== // TODO!! Currently all permission and role changing methods are synchronized as a temporary // fix for KYLO-518. The synchronization should be removed when that issue is corrected // in a more appropriate way. //===================== public class DefaultSecurityService implements SecurityService { @Inject CategoryProvider categoryProvider; @Inject FeedManagerTemplateProvider templateProvider; @Inject FeedProvider feedProvider; @Inject private DatasourceProvider datasourceProvider; @Inject private AllowedEntityActionsProvider actionsProvider; @Inject private MetadataAccess metadata; @Inject private SecurityModelTransform securityTransform; @Inject private AccessController accessController; @Override public Optional<ActionGroup> getAvailableFeedActions(String id) { return getAvailableActions(() -> { return accessFeed(id).flatMap(f -> actionsProvider.getAvailableActions(AllowedActions.FEED)); }); } @Override public Optional<ActionGroup> getAllowedFeedActions(String id, Set<Principal> principals) { return getAllowedActions(principals, supplyFeedActions(id)); } @Override public synchronized Optional<ActionGroup> changeFeedPermissions(String id, PermissionsChange changes) { return changePermissions(changes, supplyFeedActions(id)); } @Override public Optional<Map<String, RoleMembership>> getFeedRoleMemberships(String id) { return getRoleMemberships(supplyFeedRoleMemberships(id)); } @Override public synchronized Optional<RoleMembership> changeFeedRoleMemberships(String id, RoleMembershipChange change) { this.accessController.checkPermission(AccessController.SERVICES, FeedServicesAccessControl.ADMIN_FEEDS); return changeRoleMemberships(change, supplyFeedRoleMembership(id, change.getRoleName())); } @Override public Optional<ActionGroup> getAvailableCategoryActions(String id) { return getAvailableActions(() -> { return accessCategory(id).flatMap(c -> actionsProvider.getAvailableActions(AllowedActions.CATEGORY)); }); } @Override public Optional<ActionGroup> getAllowedCategoryActions(String id, Set<Principal> principals) { return getAllowedActions(principals, supplyCategoryActions(id)); } @Override public synchronized Optional<ActionGroup> changeCategoryPermissions(String id, PermissionsChange changes) { return changePermissions(changes, supplyCategoryActions(id)); } @Override public Optional<Map<String, RoleMembership>> getCategoryRoleMemberships(String id) { return getRoleMemberships(supplyCategoryRoleMemberships(id)); } @Override public synchronized Optional<RoleMembership> changeCategoryRoleMemberships(String id, RoleMembershipChange change) { return changeRoleMemberships(change, supplyCategoryRoleMembership(id, change.getRoleName())); } @Override public Optional<ActionGroup> getAvailableTemplateActions(String id) { return getAvailableActions(() -> { return accessTemplate(id).flatMap(t -> actionsProvider.getAvailableActions(AllowedActions.TEMPLATE)); }); } @Override public Optional<ActionGroup> getAllowedTemplateActions(String id, Set<Principal> principals) { return getAllowedActions(principals, supplyTemplateActions(id)); } @Override public synchronized Optional<ActionGroup> changeTemplatePermissions(String id, PermissionsChange changes) { return changePermissions(changes, supplyTemplateActions(id)); } @Override public Optional<Map<String, RoleMembership>> getTemplateRoleMemberships(String id) { return getRoleMemberships(supplyTemplateRoleMemberships(id)); } @Override public synchronized Optional<RoleMembership> changeTemplateRoleMemberships(String id, RoleMembershipChange change) { return changeRoleMemberships(change, supplyTemplateRoleMembership(id, change.getRoleName())); } @Override public Optional<ActionGroup> getAvailableDatasourceActions(String id) { return getAvailableActions(() -> accessDatasource(id).flatMap(c -> actionsProvider.getAvailableActions(AllowedActions.DATASOURCE))); } @Override public Optional<ActionGroup> getAllowedDatasourceActions(String id, Set<Principal> principals) { return getAllowedActions(principals, supplyDatasourceActions(id)); } @Override public synchronized Optional<ActionGroup> changeDatasourcePermissions(String id, PermissionsChange changes) { return changePermissions(changes, supplyDatasourceActions(id)); } @Override public Optional<Map<String, RoleMembership>> getDatasourceRoleMemberships(String id) { return getRoleMemberships(supplyDatasourceRoleMemberships(id)); } @Override public synchronized Optional<RoleMembership> changeDatasourceRoleMemberships(String id, RoleMembershipChange change) { return changeRoleMemberships(change, supplyDatasourceRoleMembership(id, change.getRoleName())); } @Override public Optional<PermissionsChange> createFeedPermissionChange(String id, ChangeType changeType, Set<Principal> members) { return createPermissionChange(id, changeType, members, supplyFeedActions(id)); } @Override public Optional<PermissionsChange> createCategoryPermissionChange(String id, ChangeType changeType, Set<Principal> members) { return createPermissionChange(id, changeType, members, supplyCategoryActions(id)); } @Override public Optional<PermissionsChange> createTemplatePermissionChange(String id, ChangeType changeType, Set<Principal> members) { return createPermissionChange(id, changeType, members, supplyTemplateActions(id)); } @Override public Optional<PermissionsChange> createDatasourcePermissionChange(String id, ChangeType changeType, Set<Principal> members) { return createPermissionChange(id, changeType, members, supplyDatasourceActions(id)); } private Optional<Feed> accessFeed(String id) { this.accessController.checkPermission(AccessController.SERVICES, FeedServicesAccessControl.ACCESS_FEEDS); Feed.ID feedId = feedProvider.resolveFeed(id); Feed feed = feedProvider.getFeed(feedId); return Optional.ofNullable(feed); } private Optional<Category> accessCategory(String id) { this.accessController.checkPermission(AccessController.SERVICES, FeedServicesAccessControl.ACCESS_CATEGORIES); Category.ID catId = categoryProvider.resolveId(id); Category category = categoryProvider.findById(catId); return Optional.ofNullable(category); } private Optional<FeedManagerTemplate> accessTemplate(String id) { this.accessController.checkPermission(AccessController.SERVICES, FeedServicesAccessControl.ACCESS_TEMPLATES); FeedManagerTemplate.ID templateId = templateProvider.resolveId(id); FeedManagerTemplate template = templateProvider.findById(templateId); return Optional.ofNullable(template); } /** * Retrieves the data source with the specified id. * * @param id the data source id * @return the data source, if found */ @Nonnull private Optional<UserDatasource> accessDatasource(@Nonnull final String id) { accessController.checkPermission(AccessController.SERVICES, FeedServicesAccessControl.ACCESS_DATASOURCES); return Optional.of(id) .map(datasourceProvider::resolve) .map(datasourceProvider::getDatasource) .map(datasource -> (datasource instanceof UserDatasource) ? (UserDatasource) datasource : null); } private Supplier<Optional<AllowedActions>> supplyFeedActions(String id) { return () -> { return accessFeed(id).map(Feed::getAllowedActions); }; } private Supplier<Optional<Set<com.thinkbiganalytics.metadata.api.security.RoleMembership>>> supplyFeedRoleMemberships(String id) { return () -> { return accessFeed(id).map(Feed::getRoleMemberships); }; } private Supplier<Optional<com.thinkbiganalytics.metadata.api.security.RoleMembership>> supplyFeedRoleMembership(String id, String roleName) { return () -> { return accessFeed(id).flatMap(t -> t.getRoleMembership(roleName)); }; } private Supplier<Optional<AllowedActions>> supplyCategoryActions(String id) { return () -> { return accessCategory(id).map(Category::getAllowedActions); }; } private Supplier<Optional<Set<com.thinkbiganalytics.metadata.api.security.RoleMembership>>> supplyCategoryRoleMemberships(String id) { return () -> { return accessCategory(id).map(Category::getRoleMemberships); }; } private Supplier<Optional<com.thinkbiganalytics.metadata.api.security.RoleMembership>> supplyCategoryRoleMembership(String id, String roleName) { return () -> { return accessCategory(id).flatMap(t -> t.getRoleMembership(roleName)); }; } private Supplier<Optional<AllowedActions>> supplyTemplateActions(String id) { return () -> { return accessTemplate(id).map(FeedManagerTemplate::getAllowedActions); }; } private Supplier<Optional<Set<com.thinkbiganalytics.metadata.api.security.RoleMembership>>> supplyTemplateRoleMemberships(String id) { return () -> { return accessTemplate(id).map(FeedManagerTemplate::getRoleMemberships); }; } private Supplier<Optional<com.thinkbiganalytics.metadata.api.security.RoleMembership>> supplyTemplateRoleMembership(String id, String roleName) { return () -> { return accessTemplate(id).flatMap(t -> t.getRoleMembership(roleName)); }; } private Supplier<Optional<AllowedActions>> supplyDatasourceActions(String id) { return () -> accessDatasource(id).map(AccessControlled::getAllowedActions); } private Supplier<Optional<Set<com.thinkbiganalytics.metadata.api.security.RoleMembership>>> supplyDatasourceRoleMemberships(String id) { return () -> accessDatasource(id).map(AccessControlled::getRoleMemberships); } private Supplier<Optional<com.thinkbiganalytics.metadata.api.security.RoleMembership>> supplyDatasourceRoleMembership(String id, String roleName) { return () -> accessDatasource(id).flatMap(t -> t.getRoleMembership(roleName)); } private Optional<ActionGroup> changePermissions(PermissionsChange changes, Supplier<Optional<AllowedActions>> allowedSupplier) { Set<Action> actionSet = this.securityTransform.collectActions(changes); Set<Principal> principals = this.securityTransform.collectPrincipals(changes); metadata.commit(() -> { allowedSupplier.get().ifPresent(allowed -> { principals.forEach(principal -> { switch (changes.getChange()) { case ADD: allowed.enable(principal, actionSet); break; case REMOVE: allowed.disable(principal, actionSet); break; default: allowed.enableOnly(principal, actionSet); } }); }); }); // TODO: look into maybe showing the actions even if the principals no longer have access to the entity // For now just returning action as seen by the user changing the permissions. return metadata.read(() -> getAllowedActions(principals, allowedSupplier)); } private Optional<ActionGroup> getAllowedActions(Set<Principal> principals, Supplier<Optional<AllowedActions>> allowedSupplier) { return this.metadata.read(() -> { return allowedSupplier.get().map(allowed -> this.securityTransform.toActionGroup(null).apply(allowed)); }, principals.stream().toArray(Principal[]::new)); } private Optional<ActionGroup> getAvailableActions(Supplier<Optional<AllowedActions>> allowedSupplier) { return this.metadata.read(() -> { return Optional.of(actionsProvider.getAvailableActions(AllowedActions.TEMPLATE) .map(this.securityTransform.toActionGroup(AllowedActions.TEMPLATE)) .orElseThrow(() -> new WebApplicationException("The available actions were not found", Status.NOT_FOUND))); }); } private Optional<Map<String, RoleMembership>> getRoleMemberships(Supplier<Optional<Set<com.thinkbiganalytics.metadata.api.security.RoleMembership>>> membershipSupplier) { return this.metadata.read(() -> { return membershipSupplier.get().map(members -> members.stream() .collect(Collectors.toMap(m -> m.getRole().getSystemName(), securityTransform.toRoleMembership()))); }); } private Optional<RoleMembership> changeRoleMemberships(RoleMembershipChange change, Supplier<Optional<com.thinkbiganalytics.metadata.api.security.RoleMembership>> domainSupplier) { return this.metadata.commit(() -> { return domainSupplier.get().map(domain -> { switch (change.getChange()) { case ADD: Arrays.stream(securityTransform.asUserPrincipals(change.getUsers())).forEach(p -> domain.addMember(p)); Arrays.stream(securityTransform.asGroupPrincipals(change.getGroups())).forEach(p -> domain.addMember(p)); break; case REMOVE: Arrays.stream(securityTransform.asUserPrincipals(change.getUsers())).forEach(p -> domain.removeMember(p)); Arrays.stream(securityTransform.asGroupPrincipals(change.getGroups())).forEach(p -> domain.removeMember(p)); break; case REPLACE: domain.setMemebers(securityTransform.asUserPrincipals(change.getUsers())); domain.setMemebers(securityTransform.asGroupPrincipals(change.getGroups())); break; default: break; } return securityTransform.toRoleMembership().apply(domain); }); }); } private Optional<PermissionsChange> createPermissionChange(String id, ChangeType changeType, Set<Principal> members, Supplier<Optional<AllowedActions>> allowedSupplier) { Set<String> groupNames = extractGroupNames(members); Set<String> userNames = extractUserNames(members); return this.metadata.read(() -> { return supplyTemplateActions(id).get().map(securityTransform.toPermissionsChange(changeType, null, userNames, groupNames)); }); } private Set<String> extractUserNames(Set<Principal> members) { return members.stream() .filter(p -> !(p instanceof Group)) .map(p -> p.getName()) .collect(Collectors.toSet()); } private Set<String> extractGroupNames(Set<Principal> members) { return members.stream() .filter(p -> p instanceof Group) .map(p -> p.getName()) .collect(Collectors.toSet()); } }