/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.sa.api; import static com.emc.sa.api.mapper.CatalogCategoryMapper.createNewCatalogCategory; import static com.emc.sa.api.mapper.CatalogCategoryMapper.map; import static com.emc.sa.api.mapper.CatalogCategoryMapper.updateCatalogCategoryObject; import static com.emc.storageos.db.client.URIUtil.uri; import static com.emc.storageos.api.mapper.DbObjectMapper.toNamedRelatedResource; import java.io.IOException; import java.net.URI; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import com.emc.sa.api.utils.CatalogConfigUtils; import com.emc.storageos.db.client.model.StringSetMap; import com.emc.storageos.model.auth.ACLEntry; import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.emc.sa.api.mapper.CatalogCategoryFilter; import com.emc.sa.api.mapper.CatalogCategoryMapper; import com.emc.sa.api.mapper.CatalogServiceMapper; import com.emc.sa.api.utils.CatalogACLInputFilter; import com.emc.sa.catalog.CatalogCategoryManager; import com.emc.sa.catalog.CatalogServiceManager; import com.emc.sa.descriptor.ServiceDescriptors; import com.emc.storageos.db.client.model.uimodels.CatalogCategory; import com.emc.storageos.db.client.model.uimodels.CatalogService; import com.emc.sa.model.util.SortedIndexUtils; import com.emc.storageos.api.service.impl.resource.ArgValidator; import com.emc.storageos.api.service.impl.response.BulkList; import com.emc.storageos.db.client.model.NamedURI; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.db.exceptions.DatabaseException; import com.emc.storageos.model.BulkIdParam; import com.emc.storageos.model.NamedRelatedResourceRep; import com.emc.storageos.model.ResourceTypeEnum; import com.emc.storageos.model.auth.ACLAssignmentChanges; import com.emc.storageos.model.auth.ACLAssignments; import com.emc.storageos.security.authentication.StorageOSUser; import com.emc.storageos.security.authorization.ACL; import com.emc.storageos.security.authorization.CheckPermission; import com.emc.storageos.security.authorization.DefaultPermissions; import com.emc.storageos.security.authorization.Role; import com.emc.storageos.services.OperationTypeEnum; import com.emc.storageos.volumecontroller.impl.monitoring.RecordableEventManager; import com.emc.vipr.client.catalog.impl.SearchConstants; import com.emc.vipr.model.catalog.CatalogCategoryBulkRep; import com.emc.vipr.model.catalog.CatalogCategoryCommonParam; import com.emc.vipr.model.catalog.CatalogCategoryCreateParam; import com.emc.vipr.model.catalog.CatalogCategoryList; import com.emc.vipr.model.catalog.CatalogCategoryRestRep; import com.emc.vipr.model.catalog.CatalogCategoryUpdateParam; import com.emc.vipr.model.catalog.CatalogServiceList; import com.emc.vipr.model.catalog.CatalogUpgrade; @DefaultPermissions( readRoles = { Role.TENANT_ADMIN, Role.SYSTEM_MONITOR, Role.SYSTEM_ADMIN }, writeRoles = { Role.TENANT_ADMIN }, readAcls = { ACL.ANY }) @Path("/catalog/categories") public class CatalogCategoryService extends CatalogTaggedResourceService { private static final Logger log = LoggerFactory.getLogger(CatalogCategoryService.class); private static final String EVENT_SERVICE_TYPE = "catalog-category"; @Autowired private CatalogCategoryManager catalogCategoryManager; @Autowired private CatalogServiceManager catalogServiceManager; @Autowired private ServiceDescriptors serviceDescriptors; @Autowired private RecordableEventManager eventManager; private CatalogConfigUtils catalogConfigUtils; public void setCatalogConfigUtils(CatalogConfigUtils catalogConfigUtils) { this.catalogConfigUtils = catalogConfigUtils; } @Override public String getServiceType() { return EVENT_SERVICE_TYPE; } @Override protected CatalogCategory queryResource(URI id) { return getCatalogCategoryById(id, false); } private CatalogCategory getCatalogCategoryById(URI id, boolean checkInactive) { CatalogCategory catalogCategory = catalogCategoryManager.getCatalogCategoryById(id); ArgValidator.checkEntity(catalogCategory, id, isIdEmbeddedInURL(id), checkInactive); return catalogCategory; } @Override protected URI getTenantOwner(URI id) { CatalogCategory catalogCategory = queryResource(id); return uri(catalogCategory.getTenant()); } @Override protected ResourceTypeEnum getResourceType() { return ResourceTypeEnum.CATALOG_CATEGORY; } /** * Get the root catalog category for a tenant * * @prereq none * @brief Get Root Catalog Category * @return Root Catalog Category */ @GET @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("") public CatalogCategoryRestRep getRootCatalogCategory(@DefaultValue("") @QueryParam(SearchConstants.TENANT_ID_PARAM) String tenantId) { StorageOSUser user = getUserFromContext(); if (StringUtils.isBlank(tenantId)) { tenantId = user.getTenantId(); } verifyAuthorizedInTenantOrg(uri(tenantId), user); CatalogCategory catalogCategory = catalogCategoryManager.getOrCreateRootCategory(uri(tenantId)); return map(catalogCategory); } /** * List data for the specified categories. * * @param param POST data containing the id list. * @prereq none * @brief List data of specified clusters * @return list of representations. * * @throws DatabaseException When an error occurs querying the database. */ @POST @Path("/bulk") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public CatalogCategoryBulkRep getBulkResources(BulkIdParam param) { return (CatalogCategoryBulkRep) super.getBulkResources(param); } @SuppressWarnings("unchecked") @Override public Class<CatalogCategory> getResourceClass() { return CatalogCategory.class; } @Override public CatalogCategoryBulkRep queryBulkResourceReps(List<URI> ids) { Iterator<CatalogCategory> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); List<CatalogCategoryRestRep> bulkList = BulkList.wrapping(_dbIterator, CatalogCategoryMapper.getInstance()); List<CatalogCategoryRestRep> catalogCategoryRestReps = SortedIndexUtils.createSortedList(bulkList.iterator()); return new CatalogCategoryBulkRep(catalogCategoryRestReps); } @Override public CatalogCategoryBulkRep queryFilteredBulkResourceReps(List<URI> ids) { Iterator<CatalogCategory> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); BulkList.ResourceFilter filter = new CatalogCategoryFilter(getUserFromContext(), _permissionsHelper); List<CatalogCategoryRestRep> bulkList = BulkList.wrapping(_dbIterator, CatalogCategoryMapper.getInstance(), filter); List<CatalogCategoryRestRep> catalogCategoryRestReps = SortedIndexUtils.createSortedList(bulkList.iterator()); return new CatalogCategoryBulkRep(catalogCategoryRestReps); } /** * Get the root catalog category for a tenant * * @prereq none * @brief Get Root Catalog Category * @return Root Catalog Category */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN }) @Path("/reset") public Response resetRootCatalogCategory(@DefaultValue("") @QueryParam(SearchConstants.TENANT_ID_PARAM) String tenantId) { StorageOSUser user = getUserFromContext(); if (StringUtils.isBlank(tenantId)) { tenantId = user.getTenantId(); } verifyAuthorizedInTenantOrg(uri(tenantId), user); try { catalogCategoryManager.restoreDefaultCatalog(uri(tenantId)); } catch (IOException e) { log.error("Failed to reset catalog", e); return Response.serverError().build(); } return Response.ok().build(); } /** * Check to see if an update to the service catalog is available * * @prereq none * @brief Check for Service Catalog Update * @return True if Update Available */ @GET @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN }) @Path("/upgrade") public CatalogUpgrade upgradeAvailable(@DefaultValue("") @QueryParam(SearchConstants.TENANT_ID_PARAM) String tenantId) { StorageOSUser user = getUserFromContext(); if (StringUtils.isBlank(tenantId)) { tenantId = user.getTenantId(); } verifyAuthorizedInTenantOrg(uri(tenantId), user); CatalogUpgrade catalogUpgrade = new CatalogUpgrade(); catalogUpgrade.setUpgradeAvailable(catalogCategoryManager.isCatalogUpdateAvailable(uri(tenantId))); return catalogUpgrade; } /** * Update the service catalog to the latest version * * @prereq none * @brief Service Catalog Update * @return none */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN }) @Path("/upgrade") public Response upgradeCatalog(@DefaultValue("") @QueryParam(SearchConstants.TENANT_ID_PARAM) String tenantId) { StorageOSUser user = getUserFromContext(); if (StringUtils.isBlank(tenantId)) { tenantId = user.getTenantId(); } verifyAuthorizedInTenantOrg(uri(tenantId), user); try { catalogCategoryManager.upgradeCatalog(uri(tenantId)); } catch (IOException e) { log.error("Failed to upgrade catalog", e); return Response.serverError().build(); } return Response.ok().build(); } /** * Creates a new catalog category in the supplied parent catalog category * * @param createParam * the parameter to create a new catalog category * @prereq none * @brief Create Catalog Category * @return none */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN }) @Path("") public CatalogCategoryRestRep createCatalogCategory(CatalogCategoryCreateParam createParam) { StorageOSUser user = getUserFromContext(); String tenantId = createParam.getTenantId(); if (StringUtils.isBlank(tenantId)) { tenantId = user.getTenantId(); } verifyAuthorizedInTenantOrg(uri(tenantId), user); CatalogCategory parentCatalogCategory = queryResource(createParam.getCatalogCategoryId()); validateCatalogCategoryParam(createParam.getCatalogCategoryId(), createParam, null); CatalogCategory catalogCategory = createNewCatalogCategory(parentCatalogCategory, createParam); catalogCategoryManager.createCatalogCategory(catalogCategory); auditOpSuccess(OperationTypeEnum.CREATE_CATALOG_CATEGORY, catalogCategory.auditParameters()); return map(catalogCategory); } /** * Get info for catalog category * * @param id the URN of a Catalog Category * @prereq none * @brief Show catalog category * @return Catalog Category details */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}") public CatalogCategoryRestRep getCatalogCategory(@PathParam("id") URI id) { CatalogCategory catalogCategory = queryResource(id); return map(catalogCategory); } @PUT @Path("/{id}/move/up") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN }) public Response moveUpCatalogCategory(@PathParam("id") URI id) { catalogCategoryManager.moveUpCatalogCategory(id); return Response.ok().build(); } @PUT @Path("/{id}/move/down") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN }) public Response moveDownCatalogCategory(@PathParam("id") URI id) { catalogCategoryManager.moveDownCatalogCategory(id); return Response.ok().build(); } @GET @Path("/{id}/acl") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN, Role.TENANT_ADMIN }, acls = { ACL.OWN }) public ACLAssignments getRoleAssignments(@PathParam("id") URI id) { return getRoleAssignmentsResponse(id); } @PUT @Path("/{id}/acl") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN, Role.TENANT_ADMIN }, acls = { ACL.OWN }, blockProxies = true) public ACLAssignments updateRoleAssignments(@PathParam("id") URI id, ACLAssignmentChanges changes) { CatalogCategory catalogCategory = catalogCategoryManager.getCatalogCategoryById(id); URI tenantId = uri(catalogCategory.getTenant()); _permissionsHelper.updateACLs(catalogCategory, changes, new CatalogACLInputFilter(tenantId)); catalogCategoryManager.updateCatalogCategory(catalogCategory); ; auditOpSuccess(OperationTypeEnum.MODIFY_CATALOG_CATEGORY_ACL, catalogCategory.getId() .toString(), catalogCategory.getLabel(), changes); catalogConfigUtils.notifyCatalogAclChange(); return getRoleAssignmentsResponse(id); } private ACLAssignments getRoleAssignmentsResponse(URI id) { CatalogCategory catalogCategory = catalogCategoryManager.getCatalogCategoryById(id); ACLAssignments response = new ACLAssignments(); response.setAssignments(_permissionsHelper.convertToACLEntries(catalogCategory.getAcls())); return response; } /** * Gets the list of sub categories within the category * * @param id the URN of a Catalog Category * @brief List Catalog Categories * @return a list of sub categories that belong to the category * @throws DatabaseException when a DB error occurs */ @GET @Path("/{id}/categories") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public CatalogCategoryList getCatalogCategories(@PathParam("id") URI id) throws DatabaseException { CatalogCategory parentCatalogCategory = queryResource(id); // check the user permissions verifyAuthorizedInTenantOrg(uri(parentCatalogCategory.getTenant()), getUserFromContext()); List<CatalogCategory> subCatalogCategories = catalogCategoryManager.getSubCategories(id); subCatalogCategories = filterCategoriesByACLs(subCatalogCategories); CatalogCategoryList subCatalogCategoryList = new CatalogCategoryList(); for (CatalogCategory subCatalogCategory : subCatalogCategories) { NamedRelatedResourceRep subCatalogCategoryRestRep = toNamedRelatedResource(ResourceTypeEnum.CATALOG_CATEGORY, subCatalogCategory.getId(), subCatalogCategory.getLabel()); subCatalogCategoryList.getCatalogCategories().add(subCatalogCategoryRestRep); } return subCatalogCategoryList; } /** * Update info for catalog category * * @param catalogCategoryUpdate Catalog Category update parameters * @param id the URN of a Catalog Category * @prereq none * @brief Update Catalog Category * @return No data returned in response body */ @PUT @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}") @CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN }) public CatalogCategoryRestRep updateCatalogCategory(@PathParam("id") URI id, CatalogCategoryUpdateParam catalogCategoryUpdate) { CatalogCategory catalogCategory = getCatalogCategoryById(id, true); StorageOSUser user = getUserFromContext(); verifyAuthorizedInTenantOrg(uri(catalogCategory.getTenant()), user); URI parentCategoryId = getTargetParentCategoryId(catalogCategory, catalogCategoryUpdate); validateCatalogCategoryParam(parentCategoryId, catalogCategoryUpdate, catalogCategory); CatalogCategory parentCatalogCategory = parentCategoryId != null ? queryResource(parentCategoryId) : null; updateCatalogCategoryObject(parentCatalogCategory, catalogCategory, catalogCategoryUpdate); catalogCategoryManager.updateCatalogCategory(catalogCategory); auditOpSuccess(OperationTypeEnum.UPDATE_CATALOG_CATEGORY, catalogCategory.auditParameters()); catalogCategory = catalogCategoryManager.getCatalogCategoryById(catalogCategory.getId()); return map(catalogCategory); } /** * Gets the target parent category ID for a category update. * * @param category the category to update. * @param update the update parameters. * @return the target parent ID. */ private URI getTargetParentCategoryId(CatalogCategory category, CatalogCategoryUpdateParam update) { if (update.getCatalogCategoryId() != null) { return NullColumnValueGetter.normalize(update.getCatalogCategoryId()); } if (CatalogCategory.isRoot(category)) { return null; } NamedURI parentId = NullColumnValueGetter.normalize(category.getCatalogCategoryId()); return parentId != null ? parentId.getURI() : null; } /** * Deactivates the catalog category * * @param id the URN of a catalog category to be deactivated * @brief Deactivate Catalog Category * @return OK if deactivation completed successfully * @throws DatabaseException when a DB error occurs */ @POST @Path("/{id}/deactivate") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN }) public Response deactivateCatalogCategory(@PathParam("id") URI id) throws DatabaseException { CatalogCategory catalogCategory = queryResource(id); ArgValidator.checkEntity(catalogCategory, id, true); catalogCategoryManager.deleteCatalogCategory(catalogCategory); auditOpSuccess(OperationTypeEnum.DELETE_CATALOG_CATEGORY, catalogCategory.auditParameters()); return Response.ok().build(); } /** * Gets the list of catalog services * * @param catalogCategoryId the URN of a catalog category * @brief List Catalog Services * @return a list of catalog services * @throws DatabaseException when a DB error occurs */ @GET @Path("/{id}/services") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public CatalogServiceList getCatalogServices(@PathParam("id") String catalogCategoryId) throws DatabaseException { List<CatalogService> catalogServices = catalogServiceManager.getCatalogServices(uri(catalogCategoryId)); catalogServices = filterServicesByACLs(catalogServices); return CatalogServiceMapper.toCatalogServiceList(catalogServices); } private void validateCatalogCategoryParam(URI parentCatalogCategoryId, CatalogCategoryCommonParam input, CatalogCategory existingCatalogCategory) { if (nameChanged(input, existingCatalogCategory)) { checkForDuplicateName(input.getName(), CatalogCategory.class, parentCatalogCategoryId, CatalogCategory.CATALOG_CATEGORY_ID, _dbClient); } } private boolean nameChanged(CatalogCategoryCommonParam catalogCategoryCommonParam, CatalogCategory existingCatalogCategory) { return existingCatalogCategory == null || (StringUtils.isNotBlank(catalogCategoryCommonParam.getName()) && !StringUtils.equals( catalogCategoryCommonParam.getName(), existingCatalogCategory.getLabel())); } /** * filter out the services which user don't have access to * * @param services * @return */ private List<CatalogService> filterServicesByACLs(List<CatalogService> services) { List<CatalogService> filteredCatalogServices = Lists.newArrayList(); StorageOSUser storageOSUser = getUserFromContext(); String username = storageOSUser.getName(); if (isAdministrator(storageOSUser)) { log.debug(username + " has SystemAdmin or TenantAdmin Role, can view all categories."); filteredCatalogServices.addAll(services); return filteredCatalogServices; } for (CatalogService service : services) { if (hasAccess(storageOSUser, service)) { filteredCatalogServices.add(service); } } return filteredCatalogServices; } /** * check if user has access to the service * * @param storageOSUser * @param service * @return */ private boolean hasAccess(StorageOSUser storageOSUser, CatalogService service) { log.debug("check if " + storageOSUser.getName() + " has access for " + service.getTitle()); return hasAccess(storageOSUser, service.getAcls()); } /** * check if user has access to the category * * @param storageOSUser * @param category * @return */ private boolean hasAccess(StorageOSUser storageOSUser, CatalogCategory category) { log.debug("check if " + storageOSUser.getName() + " has access for " + category.getTitle()); return hasAccess(storageOSUser, category.getAcls()); } /** * check if specified acls permission user to access * * @param storageOSUser * @param acls * @return */ private boolean hasAccess(StorageOSUser storageOSUser, StringSetMap acls) { // no acl set if (acls == null || acls.isEmpty()) { log.debug("acls is empty, pass"); return true; } // acl is not empty, check if the user is allowed. List<ACLEntry> aclEntries = _permissionsHelper.convertToACLEntries(acls); String username = storageOSUser.getName(); for (ACLEntry entry : aclEntries) { if (entry.getSubjectId() != null && entry.getSubjectId().equalsIgnoreCase(username)) { log.debug("has acls contain subjectId for current user: " + username); return true; } else if (entry.getGroup() != null) { for (String group : storageOSUser.getGroups()) { if (group.equalsIgnoreCase(entry.getGroup())) { log.debug("has acls contain group for current user: " + entry.getGroup()); return true; } } } else { continue; } } // acl is set, but user log.debug("has acls, but current user is not in them: " + username); return false; } /** * filter out the categories which user don't have access to * * @param categories * @return */ private List<CatalogCategory> filterCategoriesByACLs(List<CatalogCategory> categories) { List<CatalogCategory> filteredCatalogCategories = Lists.newArrayList(); StorageOSUser storageOSUser = getUserFromContext(); String username = storageOSUser.getName(); if (isAdministrator(storageOSUser)) { log.debug(username + " has SystemAdmin or TenantAdmin Role, can view all categories."); filteredCatalogCategories.addAll(categories); return filteredCatalogCategories; } for (CatalogCategory category : categories) { if (hasAccess(storageOSUser, category)) { filteredCatalogCategories.add(category); } } return filteredCatalogCategories; } /** * check if user has System admin or TenantAdmin role * * @param storageOSUser * @return */ private boolean isAdministrator(StorageOSUser storageOSUser) { for (String role : storageOSUser.getRoles()) { if (role.equalsIgnoreCase(Role.SYSTEM_ADMIN.toString())) { return true; } } Set<String> tenantRoles = _permissionsHelper.getTenantRolesForUser(storageOSUser, URI.create(storageOSUser.getTenantId()), false); for (String role : tenantRoles) { if (role.equalsIgnoreCase(Role.TENANT_ADMIN.toString())) { return true; } } return false; } }