GutoVeronezi commented on a change in pull request #5103: URL: https://github.com/apache/cloudstack/pull/5103#discussion_r675643119
########## File path: api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/UpdateAnnotationVisibilityCmd.java ########## @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 org.apache.cloudstack.api.command.admin.annotation; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.AnnotationResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = UpdateAnnotationVisibilityCmd.APINAME, description = "update an annotation visibility.", + responseObject = AnnotationResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.16", authorized = {RoleType.Admin}) +public class UpdateAnnotationVisibilityCmd extends BaseCmd { + + public static final String APINAME = "updateAnnotationVisibility"; + + @Parameter(name = ApiConstants.ID, type = CommandType.STRING, required = true, + description = "the id of the annotation") + private String uuid; + + @Parameter(name = ApiConstants.ADMINS_ONLY, type = CommandType.BOOLEAN, required = true, + description = "the annotation is visible for admins only") + private Boolean adminsOnly; + + public String getUuid() { + return uuid; + } + + public Boolean getAdminsOnly() { + return adminsOnly; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, + ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + AnnotationResponse annotationResponse = annotationService.updateAnnotationVisibility(this); + annotationResponse.setResponseName(getCommandName()); + this.setResponseObject(annotationResponse); + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; Review comment: We can use String.format to concatenate these strings. ```suggestion return String.format("%s%s", APINAME.toLowerCase(), BaseCmd.RESPONSE_SUFFIX); ``` ########## File path: engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41510to41600.java ########## @@ -64,6 +64,32 @@ public boolean supportsRollingUpgrade() { @Override public void performDataMigration(Connection conn) { + generateUuidForExistingSshKeyPairs(conn); + } + + private void generateUuidForExistingSshKeyPairs(Connection conn) { + LOG.debug("Generating uuid for existing ssh key-pairs"); + try { + PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`ssh_keypairs` WHERE uuid is null"); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + long sshKeyId = rs.getLong(1); + pstmt = conn.prepareStatement("UPDATE `cloud`.`ssh_keypairs` SET `uuid` = UUID() WHERE id = ?"); + pstmt.setLong(1, sshKeyId); + pstmt.executeUpdate(); + } + if (!rs.isClosed()) { + rs.close(); + } + if (!pstmt.isClosed()) { + pstmt.close(); + } + LOG.debug("Successfully generated uuid for existing ssh key-pairs"); + } catch (SQLException e) { + String errMsg = "Exception while generating uuid for existing ssh key-pairs: " + e.getMessage(); + LOG.error(errMsg); Review comment: We could pass the exception as parameter here. ########## File path: api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java ########## @@ -124,7 +124,7 @@ public void execute() { try { result = _resourceService.updateHost(this); if(getAnnotation() != null) { - annotationService.addAnnotation(getAnnotation(), AnnotationService.EntityType.HOST, result.getUuid()); + annotationService.addAnnotation(getAnnotation(), AnnotationService.EntityType.HOST, result.getUuid(),true); Review comment: ```suggestion annotationService.addAnnotation(getAnnotation(), AnnotationService.EntityType.HOST, result.getUuid(), true); ``` ########## File path: server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java ########## @@ -53,64 +113,198 @@ @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity") public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) { - return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), addAnnotationCmd.getEntityUuid()); + return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), + addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly()); } - public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) { - CallContext ctx = CallContext.current(); - String userUuid = ctx.getCallingUserUuid(); + public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) { + UserVO userVO = getCallingUserFromContext(); + String userUuid = userVO.getUuid(); + checkAnnotationPermissions(userUuid, type, userVO); - AnnotationVO annotation = new AnnotationVO(text, type, uuid); + AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly); annotation.setUserUuid(userUuid); annotation = annotationDao.persist(annotation); return createAnnotationResponse(annotation); } + private void checkAnnotationPermissions(String entityUuid, EntityType type, UserVO user) { + if (!isCallingUserAdmin()) { Review comment: We could invert this first if and add a return (and maybe a log), to reduce code indentation. ########## File path: server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java ########## @@ -53,64 +113,198 @@ @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity") public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) { - return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), addAnnotationCmd.getEntityUuid()); + return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), + addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly()); } - public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) { - CallContext ctx = CallContext.current(); - String userUuid = ctx.getCallingUserUuid(); + public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) { + UserVO userVO = getCallingUserFromContext(); + String userUuid = userVO.getUuid(); + checkAnnotationPermissions(userUuid, type, userVO); - AnnotationVO annotation = new AnnotationVO(text, type, uuid); + AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly); annotation.setUserUuid(userUuid); annotation = annotationDao.persist(annotation); return createAnnotationResponse(annotation); } + private void checkAnnotationPermissions(String entityUuid, EntityType type, UserVO user) { + if (!isCallingUserAdmin()) { + if (!type.isUserAllowed()) { + throw new CloudRuntimeException("User " + user.getUsername() + " is not allowed to add annotations on type " + type.name()); Review comment: We could use `String.format` here. ########## File path: server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java ########## @@ -53,64 +113,198 @@ @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity") public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) { - return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), addAnnotationCmd.getEntityUuid()); + return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), + addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly()); } - public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) { - CallContext ctx = CallContext.current(); - String userUuid = ctx.getCallingUserUuid(); + public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) { + UserVO userVO = getCallingUserFromContext(); + String userUuid = userVO.getUuid(); + checkAnnotationPermissions(userUuid, type, userVO); - AnnotationVO annotation = new AnnotationVO(text, type, uuid); + AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly); annotation.setUserUuid(userUuid); annotation = annotationDao.persist(annotation); return createAnnotationResponse(annotation); } + private void checkAnnotationPermissions(String entityUuid, EntityType type, UserVO user) { + if (!isCallingUserAdmin()) { + if (!type.isUserAllowed()) { + throw new CloudRuntimeException("User " + user.getUsername() + " is not allowed to add annotations on type " + type.name()); + } + ensureEntityIsOwnedByTheUser(type.name(), entityUuid, user); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_REMOVE, eventDescription = "removing an annotation on an entity") public AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd) { String uuid = removeAnnotationCmd.getUuid(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("marking annotation removed: " + uuid); + AnnotationVO annotation = annotationDao.findByUuid(uuid); + if (isCallingUserAllowedToRemoveAnnotation(annotation)) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("marking annotation removed: " + uuid); + } + annotationDao.remove(annotation.getId()); + } else { + throw new CloudRuntimeException("Only administrators or entity owner users can delete annotations, cannot remove annotation: " + uuid); } + + return createAnnotationResponse(annotation); + } + + @Override + public AnnotationResponse updateAnnotationVisibility(UpdateAnnotationVisibilityCmd cmd) { + String uuid = cmd.getUuid(); + Boolean adminsOnly = cmd.getAdminsOnly(); AnnotationVO annotation = annotationDao.findByUuid(uuid); - annotationDao.remove(annotation.getId()); + if (annotation != null && isCallingUserAdmin()) { Review comment: We could invert this if to reduce code indentation. ########## File path: server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java ########## @@ -53,64 +113,198 @@ @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity") public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) { - return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), addAnnotationCmd.getEntityUuid()); + return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), + addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly()); } - public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) { - CallContext ctx = CallContext.current(); - String userUuid = ctx.getCallingUserUuid(); + public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) { + UserVO userVO = getCallingUserFromContext(); + String userUuid = userVO.getUuid(); + checkAnnotationPermissions(userUuid, type, userVO); - AnnotationVO annotation = new AnnotationVO(text, type, uuid); + AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly); annotation.setUserUuid(userUuid); annotation = annotationDao.persist(annotation); return createAnnotationResponse(annotation); } + private void checkAnnotationPermissions(String entityUuid, EntityType type, UserVO user) { + if (!isCallingUserAdmin()) { + if (!type.isUserAllowed()) { + throw new CloudRuntimeException("User " + user.getUsername() + " is not allowed to add annotations on type " + type.name()); + } + ensureEntityIsOwnedByTheUser(type.name(), entityUuid, user); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_REMOVE, eventDescription = "removing an annotation on an entity") public AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd) { String uuid = removeAnnotationCmd.getUuid(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("marking annotation removed: " + uuid); + AnnotationVO annotation = annotationDao.findByUuid(uuid); + if (isCallingUserAllowedToRemoveAnnotation(annotation)) { Review comment: We could invert this if to reduce code indentation. ########## File path: server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java ########## @@ -53,64 +113,198 @@ @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity") public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) { - return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), addAnnotationCmd.getEntityUuid()); + return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), + addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly()); } - public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) { - CallContext ctx = CallContext.current(); - String userUuid = ctx.getCallingUserUuid(); + public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) { + UserVO userVO = getCallingUserFromContext(); + String userUuid = userVO.getUuid(); + checkAnnotationPermissions(userUuid, type, userVO); - AnnotationVO annotation = new AnnotationVO(text, type, uuid); + AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly); annotation.setUserUuid(userUuid); annotation = annotationDao.persist(annotation); return createAnnotationResponse(annotation); } + private void checkAnnotationPermissions(String entityUuid, EntityType type, UserVO user) { + if (!isCallingUserAdmin()) { + if (!type.isUserAllowed()) { + throw new CloudRuntimeException("User " + user.getUsername() + " is not allowed to add annotations on type " + type.name()); + } + ensureEntityIsOwnedByTheUser(type.name(), entityUuid, user); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_REMOVE, eventDescription = "removing an annotation on an entity") public AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd) { String uuid = removeAnnotationCmd.getUuid(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("marking annotation removed: " + uuid); + AnnotationVO annotation = annotationDao.findByUuid(uuid); + if (isCallingUserAllowedToRemoveAnnotation(annotation)) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("marking annotation removed: " + uuid); + } + annotationDao.remove(annotation.getId()); + } else { + throw new CloudRuntimeException("Only administrators or entity owner users can delete annotations, cannot remove annotation: " + uuid); } + + return createAnnotationResponse(annotation); + } + + @Override + public AnnotationResponse updateAnnotationVisibility(UpdateAnnotationVisibilityCmd cmd) { + String uuid = cmd.getUuid(); + Boolean adminsOnly = cmd.getAdminsOnly(); AnnotationVO annotation = annotationDao.findByUuid(uuid); - annotationDao.remove(annotation.getId()); + if (annotation != null && isCallingUserAdmin()) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("updating annotation visibility: " + uuid); + } + annotation.setAdminsOnly(adminsOnly); + annotationDao.update(annotation.getId(), annotation); + } else { + throw new CloudRuntimeException("Cannot update visibility for annotation: " + uuid); Review comment: We could improve this exception by adding more context info. ########## File path: server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java ########## @@ -53,64 +113,198 @@ @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity") public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) { - return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), addAnnotationCmd.getEntityUuid()); + return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), + addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly()); } - public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) { - CallContext ctx = CallContext.current(); - String userUuid = ctx.getCallingUserUuid(); + public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) { + UserVO userVO = getCallingUserFromContext(); + String userUuid = userVO.getUuid(); + checkAnnotationPermissions(userUuid, type, userVO); - AnnotationVO annotation = new AnnotationVO(text, type, uuid); + AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly); annotation.setUserUuid(userUuid); annotation = annotationDao.persist(annotation); return createAnnotationResponse(annotation); } + private void checkAnnotationPermissions(String entityUuid, EntityType type, UserVO user) { + if (!isCallingUserAdmin()) { + if (!type.isUserAllowed()) { + throw new CloudRuntimeException("User " + user.getUsername() + " is not allowed to add annotations on type " + type.name()); + } + ensureEntityIsOwnedByTheUser(type.name(), entityUuid, user); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_REMOVE, eventDescription = "removing an annotation on an entity") public AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd) { String uuid = removeAnnotationCmd.getUuid(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("marking annotation removed: " + uuid); + AnnotationVO annotation = annotationDao.findByUuid(uuid); + if (isCallingUserAllowedToRemoveAnnotation(annotation)) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("marking annotation removed: " + uuid); + } + annotationDao.remove(annotation.getId()); + } else { + throw new CloudRuntimeException("Only administrators or entity owner users can delete annotations, cannot remove annotation: " + uuid); } + + return createAnnotationResponse(annotation); + } + + @Override + public AnnotationResponse updateAnnotationVisibility(UpdateAnnotationVisibilityCmd cmd) { + String uuid = cmd.getUuid(); + Boolean adminsOnly = cmd.getAdminsOnly(); AnnotationVO annotation = annotationDao.findByUuid(uuid); - annotationDao.remove(annotation.getId()); + if (annotation != null && isCallingUserAdmin()) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("updating annotation visibility: " + uuid); Review comment: We could improve this log by adding more context info. ########## File path: server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java ########## @@ -53,64 +113,198 @@ @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity") public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) { - return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), addAnnotationCmd.getEntityUuid()); + return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), + addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly()); } - public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) { - CallContext ctx = CallContext.current(); - String userUuid = ctx.getCallingUserUuid(); + public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) { + UserVO userVO = getCallingUserFromContext(); + String userUuid = userVO.getUuid(); + checkAnnotationPermissions(userUuid, type, userVO); - AnnotationVO annotation = new AnnotationVO(text, type, uuid); + AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly); annotation.setUserUuid(userUuid); annotation = annotationDao.persist(annotation); return createAnnotationResponse(annotation); } + private void checkAnnotationPermissions(String entityUuid, EntityType type, UserVO user) { + if (!isCallingUserAdmin()) { + if (!type.isUserAllowed()) { + throw new CloudRuntimeException("User " + user.getUsername() + " is not allowed to add annotations on type " + type.name()); + } + ensureEntityIsOwnedByTheUser(type.name(), entityUuid, user); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_REMOVE, eventDescription = "removing an annotation on an entity") public AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd) { String uuid = removeAnnotationCmd.getUuid(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("marking annotation removed: " + uuid); + AnnotationVO annotation = annotationDao.findByUuid(uuid); + if (isCallingUserAllowedToRemoveAnnotation(annotation)) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("marking annotation removed: " + uuid); + } + annotationDao.remove(annotation.getId()); + } else { + throw new CloudRuntimeException("Only administrators or entity owner users can delete annotations, cannot remove annotation: " + uuid); Review comment: We could improve this exception by adding more context info. ########## File path: server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java ########## @@ -53,64 +113,198 @@ @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity") public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) { - return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), addAnnotationCmd.getEntityUuid()); + return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), + addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly()); } - public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) { - CallContext ctx = CallContext.current(); - String userUuid = ctx.getCallingUserUuid(); + public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) { + UserVO userVO = getCallingUserFromContext(); + String userUuid = userVO.getUuid(); + checkAnnotationPermissions(userUuid, type, userVO); - AnnotationVO annotation = new AnnotationVO(text, type, uuid); + AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly); annotation.setUserUuid(userUuid); annotation = annotationDao.persist(annotation); return createAnnotationResponse(annotation); } + private void checkAnnotationPermissions(String entityUuid, EntityType type, UserVO user) { + if (!isCallingUserAdmin()) { + if (!type.isUserAllowed()) { + throw new CloudRuntimeException("User " + user.getUsername() + " is not allowed to add annotations on type " + type.name()); + } + ensureEntityIsOwnedByTheUser(type.name(), entityUuid, user); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_REMOVE, eventDescription = "removing an annotation on an entity") public AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd) { String uuid = removeAnnotationCmd.getUuid(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("marking annotation removed: " + uuid); + AnnotationVO annotation = annotationDao.findByUuid(uuid); + if (isCallingUserAllowedToRemoveAnnotation(annotation)) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("marking annotation removed: " + uuid); Review comment: We could improve this log by adding more context info. ########## File path: server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java ########## @@ -53,64 +113,198 @@ @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity") public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) { - return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), addAnnotationCmd.getEntityUuid()); + return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), + addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly()); } - public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) { - CallContext ctx = CallContext.current(); - String userUuid = ctx.getCallingUserUuid(); + public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) { + UserVO userVO = getCallingUserFromContext(); + String userUuid = userVO.getUuid(); + checkAnnotationPermissions(userUuid, type, userVO); - AnnotationVO annotation = new AnnotationVO(text, type, uuid); + AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly); annotation.setUserUuid(userUuid); annotation = annotationDao.persist(annotation); return createAnnotationResponse(annotation); } + private void checkAnnotationPermissions(String entityUuid, EntityType type, UserVO user) { + if (!isCallingUserAdmin()) { + if (!type.isUserAllowed()) { + throw new CloudRuntimeException("User " + user.getUsername() + " is not allowed to add annotations on type " + type.name()); + } + ensureEntityIsOwnedByTheUser(type.name(), entityUuid, user); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_REMOVE, eventDescription = "removing an annotation on an entity") public AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd) { String uuid = removeAnnotationCmd.getUuid(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("marking annotation removed: " + uuid); + AnnotationVO annotation = annotationDao.findByUuid(uuid); + if (isCallingUserAllowedToRemoveAnnotation(annotation)) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("marking annotation removed: " + uuid); + } + annotationDao.remove(annotation.getId()); + } else { + throw new CloudRuntimeException("Only administrators or entity owner users can delete annotations, cannot remove annotation: " + uuid); } + + return createAnnotationResponse(annotation); + } + + @Override + public AnnotationResponse updateAnnotationVisibility(UpdateAnnotationVisibilityCmd cmd) { + String uuid = cmd.getUuid(); + Boolean adminsOnly = cmd.getAdminsOnly(); AnnotationVO annotation = annotationDao.findByUuid(uuid); - annotationDao.remove(annotation.getId()); + if (annotation != null && isCallingUserAdmin()) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("updating annotation visibility: " + uuid); + } + annotation.setAdminsOnly(adminsOnly); + annotationDao.update(annotation.getId(), annotation); + } else { + throw new CloudRuntimeException("Cannot update visibility for annotation: " + uuid); + } return createAnnotationResponse(annotation); } + private boolean isCallingUserAllowedToRemoveAnnotation(AnnotationVO annotation) { + if (annotation == null) { + return false; + } + if (isCallingUserAdmin()) { + return true; + } + UserVO callingUser = getCallingUserFromContext(); + String annotationOwnerUuid = annotation.getUserUuid(); + return annotationOwnerUuid != null && annotationOwnerUuid.equals(callingUser.getUuid()); + } + + private UserVO getCallingUserFromContext() { + CallContext ctx = CallContext.current(); + long userId = ctx.getCallingUserId(); + UserVO userVO = userDao.findById(userId); + if (userVO == null) { + throw new CloudRuntimeException("Cannot find a user with ID " + userId); + } + return userVO; + } + + private boolean isCallingUserAdmin() { + UserVO userVO = getCallingUserFromContext(); + long accountId = userVO.getAccountId(); + AccountVO accountVO = accountDao.findById(accountId); + if (accountVO == null) { + throw new CloudRuntimeException("Cannot find account with ID + " + accountId); + } + Long roleId = accountVO.getRoleId(); + Role role = roleService.findRole(roleId); + if (role == null) { + throw new CloudRuntimeException("Cannot find role with ID " + roleId); + } + return adminRoles.contains(role.getRoleType()); + } + private List<AnnotationVO> getAnnotationsForApiCmd(ListAnnotationsCmd cmd) { List<AnnotationVO> annotations; - if(cmd.getUuid() != null) { + String userUuid = cmd.getUserUuid(); + String entityUuid = cmd.getEntityUuid(); + String entityType = cmd.getEntityType(); + String annotationFilter = isNotBlank(cmd.getAnnotationFilter()) ? cmd.getAnnotationFilter() : "all"; + boolean isCallerAdmin = isCallingUserAdmin(); + if ((isBlank(entityUuid) || isBlank(entityType)) && !isCallerAdmin && annotationFilter.equalsIgnoreCase("all")) { Review comment: `StringUtils` has the method `isAnyEmpty`, where we can validate several strings at the same time. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
