diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/ReportDataResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/ReportDataResource.java index a17190c6acd..88a231258b3 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/ReportDataResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/ReportDataResource.java @@ -115,7 +115,7 @@ public class ReportDataResource { public Response addReportData( @Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid ReportData reportData) throws IOException { - authorizer.authorizeAdmin(securityContext, true); + authorizer.authorizeAdmin(securityContext); return dao.addReportData(reportData); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java index 6942c02359d..273ac415f21 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java @@ -389,7 +389,7 @@ public class WebAnalyticEventResource extends EntityResource { @Parameter(description = "Id of the table", schema = @Schema(type = "UUID")) @PathParam("id") UUID id, @Valid SQLQuery sqlQuery) throws IOException { - authorizer.authorizeAdmin(securityContext, true); + OperationContext operationContext = new OperationContext(entityType, MetadataOperation.EDIT_ALL); + authorizer.authorize(securityContext, operationContext, getResourceContextById(id)); Table table = dao.addQuery(id, sqlQuery); return addHref(uriInfo, table); } @@ -784,7 +785,8 @@ public class TableResource extends EntityResource { @Parameter(description = "Id of the table", schema = @Schema(type = "string")) @PathParam("id") UUID id, @Valid DataModel dataModel) throws IOException { - authorizer.authorizeAdmin(securityContext, true); + OperationContext operationContext = new OperationContext(entityType, MetadataOperation.EDIT_ALL); + authorizer.authorize(securityContext, operationContext, getResourceContextById(id)); Table table = dao.addDataModel(id, dataModel); return addHref(uriInfo, table); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java index f74a0642329..baf3801e0ff 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java @@ -454,7 +454,9 @@ public class TestCaseResource extends EntityResource list(@Context UriInfo uriInfo, @Context SecurityContext securityContext) { - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); return settingsRepository.listAllConfigs(); } @@ -162,7 +162,7 @@ public class SettingsResource { content = @Content(mediaType = "application/json", schema = @Schema(implementation = SettingsList.class))) }) public List getBootstrapFilters(@Context UriInfo uriInfo, @Context SecurityContext securityContext) { - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); return bootStrappedFilters; } @@ -180,7 +180,7 @@ public class SettingsResource { content = @Content(mediaType = "application/json", schema = @Schema(implementation = SettingsList.class))) }) public Response resetFilters(@Context UriInfo uriInfo, @Context SecurityContext securityContext) { - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); Settings settings = new Settings().withConfigType(ACTIVITY_FEED_FILTER_SETTING).withConfigValue(bootStrappedFilters); return settingsRepository.createNewSetting(settings); @@ -203,7 +203,7 @@ public class SettingsResource { @Context UriInfo uriInfo, @Context SecurityContext securityContext, @PathParam("settingName") String settingName) { - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); return settingsRepository.getConfigWithKey(settingName); } @@ -221,7 +221,7 @@ public class SettingsResource { }) public Response createOrUpdateSetting( @Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid Settings settingName) { - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); return settingsRepository.createOrUpdate(settingName); } @@ -245,7 +245,6 @@ public class SettingsResource { @PathParam("entityName") String entityName, @Valid List newFilter) { - authorizer.authorizeAdmin(securityContext, false); return settingsRepository.updateEntityFilter(entityName, newFilter); } @@ -272,7 +271,7 @@ public class SettingsResource { @ExampleObject("[" + "{op:remove, path:/a}," + "{op:add, path: /b, value: val}" + "]") })) JsonPatch patch) { - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); return settingsRepository.patchSetting(settingName, patch); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java index e9568c903e4..18236ce59c8 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java @@ -48,6 +48,7 @@ import org.openmetadata.schema.api.tags.CreateTag; import org.openmetadata.schema.api.tags.CreateTagCategory; import org.openmetadata.schema.entity.tags.Tag; import org.openmetadata.schema.type.Include; +import org.openmetadata.schema.type.MetadataOperation; import org.openmetadata.schema.type.TagCategory; import org.openmetadata.service.Entity; import org.openmetadata.service.OpenMetadataApplicationConfig; @@ -56,7 +57,10 @@ import org.openmetadata.service.jdbi3.ListFilter; import org.openmetadata.service.jdbi3.TagCategoryRepository; import org.openmetadata.service.jdbi3.TagRepository; import org.openmetadata.service.resources.Collection; +import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; +import org.openmetadata.service.security.policyevaluator.OperationContext; +import org.openmetadata.service.security.policyevaluator.ResourceContext; import org.openmetadata.service.util.EntityUtil.Fields; import org.openmetadata.service.util.FullyQualifiedName; import org.openmetadata.service.util.RestUtil; @@ -279,7 +283,9 @@ public class TagResource { public Response createCategory( @Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid CreateTagCategory create) throws IOException { - authorizer.authorizeAdmin(securityContext, true); + OperationContext operationContext = new OperationContext(Entity.TAG_CATEGORY, MetadataOperation.CREATE); + ResourceContext resourceContext = EntityResource.getResourceContext(Entity.TAG_CATEGORY, daoCategory).build(); + authorizer.authorize(securityContext, operationContext, resourceContext); TagCategory category = getTagCategory(securityContext, create); category = addHref(uriInfo, daoCategory.create(uriInfo, category)); return Response.created(category.getHref()).entity(category).build(); @@ -306,7 +312,9 @@ public class TagResource { String category, @Valid CreateTag create) throws IOException { - authorizer.authorizeAdmin(securityContext, true); + OperationContext operationContext = new OperationContext(Entity.TAG, MetadataOperation.CREATE); + ResourceContext resourceContext = EntityResource.getResourceContext(Entity.TAG, dao).build(); + authorizer.authorize(securityContext, operationContext, resourceContext); Tag tag = getTag(securityContext, create, FullyQualifiedName.build(category)); URI categoryHref = RestUtil.getHref(uriInfo, TAG_COLLECTION_PATH, category); tag = addHref(categoryHref, dao.create(uriInfo, tag)); @@ -342,7 +350,9 @@ public class TagResource { String primaryTag, @Valid CreateTag create) throws IOException { - authorizer.authorizeAdmin(securityContext, true); + OperationContext operationContext = new OperationContext(Entity.TAG, MetadataOperation.CREATE); + ResourceContext resourceContext = EntityResource.getResourceContext(Entity.TAG, dao).build(); + authorizer.authorize(securityContext, operationContext, resourceContext); Tag tag = getTag(securityContext, create, FullyQualifiedName.build(category, primaryTag)); URI categoryHref = RestUtil.getHref(uriInfo, TAG_COLLECTION_PATH, category); URI parentHRef = RestUtil.getHref(categoryHref, primaryTag); @@ -364,7 +374,10 @@ public class TagResource { String categoryName, @Valid CreateTagCategory create) throws IOException { - authorizer.authorizeAdmin(securityContext, true); + OperationContext operationContext = new OperationContext(Entity.TAG_CATEGORY, MetadataOperation.EDIT_ALL); + ResourceContext resourceContext = + EntityResource.getResourceContext(Entity.TAG_CATEGORY, daoCategory).name(categoryName).build(); + authorizer.authorize(securityContext, operationContext, resourceContext); TagCategory category = getTagCategory(securityContext, create); // TODO clean this up if (categoryName.equals(create.getName())) { // Not changing the name @@ -398,8 +411,12 @@ public class TagResource { String primaryTag, @Valid CreateTag create) throws IOException { - authorizer.authorizeAdmin(securityContext, true); Tag tag = getTag(securityContext, create, FullyQualifiedName.build(categoryName)); + + OperationContext operationContext = new OperationContext(Entity.TAG, MetadataOperation.EDIT_ALL); + ResourceContext resourceContext = EntityResource.getResourceContext(Entity.TAG, dao).name(categoryName).build(); + authorizer.authorize(securityContext, operationContext, resourceContext); + URI categoryHref = RestUtil.getHref(uriInfo, TAG_COLLECTION_PATH, categoryName); RestUtil.PutResponse response; if (primaryTag.equals(create.getName())) { // Not changing the name @@ -442,7 +459,7 @@ public class TagResource { String secondaryTag, @Valid CreateTag create) throws IOException { - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); Tag tag = getTag(securityContext, create, FullyQualifiedName.build(categoryName, primaryTag)); URI categoryHref = RestUtil.getHref(uriInfo, TAG_COLLECTION_PATH, categoryName); URI parentHRef = RestUtil.getHref(categoryHref, primaryTag); @@ -471,7 +488,10 @@ public class TagResource { @Context SecurityContext securityContext, @Parameter(description = "Tag category id", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) throws IOException { - authorizer.authorizeAdmin(securityContext, true); + OperationContext operationContext = new OperationContext(Entity.TAG_CATEGORY, MetadataOperation.EDIT_ALL); + ResourceContext resourceContext = + EntityResource.getResourceContext(Entity.TAG_CATEGORY, daoCategory).id(id).build(); + authorizer.authorize(securityContext, operationContext, resourceContext); TagCategory tagCategory = daoCategory.delete(uriInfo, id); addHref(uriInfo, tagCategory); return new RestUtil.DeleteResponse<>(tagCategory, RestUtil.ENTITY_DELETED).toResponse(); @@ -490,7 +510,10 @@ public class TagResource { @Parameter(description = "Tag id", schema = @Schema(type = "string")) @PathParam("category") String category, @Parameter(description = "Tag id", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) throws IOException { - authorizer.authorizeAdmin(securityContext, true); + OperationContext operationContext = new OperationContext(Entity.TAG, MetadataOperation.EDIT_ALL); + ResourceContext resourceContext = EntityResource.getResourceContext(Entity.TAG, dao).id(id).build(); + authorizer.authorize(securityContext, operationContext, resourceContext); + Tag tag = dao.delete(uriInfo, id); URI categoryHref = RestUtil.getHref(uriInfo, TAG_COLLECTION_PATH, category); addHref(categoryHref, tag); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java index fcfd6f2373f..e30ba01a7d9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java @@ -286,7 +286,7 @@ public class UserResource extends EntityResource { description = "Generate a random pwd", responses = {@ApiResponse(responseCode = "200", description = "Random pwd")}) public Response generateRandomPassword(@Context UriInfo uriInfo, @Context SecurityContext securityContext) { - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); return Response.status(OK).entity(PasswordUtil.generateRandomPassword()).build(); } @@ -485,7 +485,7 @@ public class UserResource extends EntityResource { @Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid CreateUser create) throws IOException { User user = getUser(securityContext, create); if (Boolean.TRUE.equals(create.getIsAdmin())) { - authorizer.authorizeAdmin(securityContext, true); + authorizer.authorizeAdmin(securityContext); } if (Boolean.TRUE.equals(create.getIsBot())) { addAuthMechanismToBot(user, create, uriInfo); @@ -552,8 +552,8 @@ public class UserResource extends EntityResource { MetadataOperation operation = resourceContext.getEntity() == null ? CREATE : EDIT_ALL; dao.prepare(user); - if (Boolean.TRUE.equals(create.getIsAdmin()) || Boolean.TRUE.equals(create.getIsBot())) { - authorizer.authorizeAdmin(securityContext, true); + if (Boolean.TRUE.equals(create.getIsAdmin())) { + authorizer.authorizeAdmin(securityContext); } else if (!securityContext.getUserPrincipal().getName().equals(user.getName())) { // doing authorization check outside of authorizer here. We are checking if the logged-in user same as the user // we are trying to update. One option is to set users.owner as user, however that is not supported for User. @@ -590,17 +590,10 @@ public class UserResource extends EntityResource { @Valid GenerateTokenRequest generateTokenRequest) throws IOException { User user = dao.get(uriInfo, id, Fields.EMPTY_FIELDS); - authorizeGenerateJWT(user); - authorizer.authorizeAdmin(securityContext, false); - JWTAuthMechanism jwtAuthMechanism = - jwtTokenGenerator.generateJWTToken(user, generateTokenRequest.getJWTTokenExpiry()); - AuthenticationMechanism authenticationMechanism = - new AuthenticationMechanism().withConfig(jwtAuthMechanism).withAuthType(AuthenticationMechanism.AuthType.JWT); - user.setAuthenticationMechanism(authenticationMechanism); + authorizer.authorizeAdmin(securityContext); + jwtTokenGenerator.setAuthMechanism(user, generateTokenRequest); User updatedUser = dao.createOrUpdate(uriInfo, user).getEntity(); - jwtAuthMechanism = - JsonUtils.convertValue(updatedUser.getAuthenticationMechanism().getConfig(), JWTAuthMechanism.class); - return Response.status(Response.Status.OK).entity(jwtAuthMechanism).build(); + return Response.status(Response.Status.OK).entity(jwtTokenGenerator.getAuthMechanism(updatedUser)).build(); } @PUT @@ -622,8 +615,7 @@ public class UserResource extends EntityResource { @Context UriInfo uriInfo, @Context SecurityContext securityContext, @PathParam("id") UUID id) throws IOException { User user = dao.get(uriInfo, id, Fields.EMPTY_FIELDS); - authorizeGenerateJWT(user); - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); JWTAuthMechanism jwtAuthMechanism = new JWTAuthMechanism().withJWTToken(StringUtils.EMPTY); AuthenticationMechanism authenticationMechanism = new AuthenticationMechanism().withConfig(jwtAuthMechanism).withAuthType(JWT); @@ -658,7 +650,7 @@ public class UserResource extends EntityResource { throw new IllegalArgumentException("JWT token is only supported for bot users"); } decryptOrNullify(securityContext, user); - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); AuthenticationMechanism authenticationMechanism = user.getAuthenticationMechanism(); if (authenticationMechanism != null && authenticationMechanism.getConfig() != null @@ -693,7 +685,7 @@ public class UserResource extends EntityResource { throw new IllegalArgumentException("JWT token is only supported for bot users"); } decryptOrNullify(securityContext, user); - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); return user.getAuthenticationMechanism(); } @@ -724,8 +716,8 @@ public class UserResource extends EntityResource { JsonObject patchOpObject = patchOp.asJsonObject(); if (patchOpObject.containsKey("path") && patchOpObject.containsKey("value")) { String path = patchOpObject.getString("path"); - if (path.equals("/isAdmin") || path.equals("/isBot")) { - authorizer.authorizeAdmin(securityContext, true); + if (path.equals("/isAdmin")) { + authorizer.authorizeAdmin(securityContext); } // if path contains team, check if team is joinable by any user if (patchOpObject.containsKey("op") @@ -742,7 +734,7 @@ public class UserResource extends EntityResource { dao.validateTeamAddition(id, UUID.fromString(teamId)); if (!dao.isTeamJoinable(teamId)) { // Only admin can join closed teams - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); } } } @@ -1001,7 +993,7 @@ public class UserResource extends EntityResource { return Response.status(403).entity(new ErrorMessage(403, "Old Password is not correct")).build(); } } else { - authorizer.authorizeAdmin(securityContext, false); + authorizer.authorizeAdmin(securityContext); User storedUser = dao.getByName(uriInfo, request.getUsername(), new Fields(fields, String.join(",", fields))); String newHashedPassword = BCrypt.withDefaults().hashToString(12, request.getNewPassword().toCharArray()); // Admin is allowed to set password for User directly @@ -1191,12 +1183,6 @@ public class UserResource extends EntityResource { .withRoles(EntityUtil.toEntityReferences(create.getRoles(), Entity.ROLE)); } - private void authorizeGenerateJWT(User user) { - if (!Boolean.TRUE.equals(user.getIsBot())) { - throw new IllegalArgumentException("Generating JWT token is only supported for bot users"); - } - } - public User registerUser(UriInfo uriInfo, RegistrationRequest newRegistrationRequest) throws IOException { String newRegistrationRequestEmail = newRegistrationRequest.getEmail(); String[] tokens = newRegistrationRequest.getEmail().split("@"); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java index d4320e2cbaa..bbe1a7c5f69 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java @@ -51,6 +51,7 @@ import org.openmetadata.schema.entity.data.Topic; import org.openmetadata.schema.type.ChangeEvent; import org.openmetadata.schema.type.EntityHistory; import org.openmetadata.schema.type.Include; +import org.openmetadata.schema.type.MetadataOperation; import org.openmetadata.schema.type.topic.TopicSampleData; import org.openmetadata.service.Entity; import org.openmetadata.service.jdbi3.CollectionDAO; @@ -59,6 +60,7 @@ import org.openmetadata.service.jdbi3.TopicRepository; import org.openmetadata.service.resources.Collection; import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; +import org.openmetadata.service.security.policyevaluator.OperationContext; import org.openmetadata.service.util.ResultList; @Path("/v1/topics") @@ -344,7 +346,8 @@ public class TopicResource extends EntityResource { @Parameter(description = "Id of the topic", schema = @Schema(type = "string")) @PathParam("id") UUID id, @Valid TopicSampleData sampleData) throws IOException { - authorizer.authorizeAdmin(securityContext, true); + OperationContext operationContext = new OperationContext(entityType, MetadataOperation.EDIT_SAMPLE_DATA); + authorizer.authorize(securityContext, operationContext, getResourceContextById(id)); Topic topic = dao.addSampleData(id, sampleData); return addHref(uriInfo, topic); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java index 81eae210b3f..2d8cdff37ff 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java @@ -56,6 +56,7 @@ import org.openmetadata.schema.entity.type.Category; import org.openmetadata.schema.entity.type.CustomProperty; import org.openmetadata.schema.type.EntityHistory; import org.openmetadata.schema.type.Include; +import org.openmetadata.schema.type.MetadataOperation; import org.openmetadata.service.Entity; import org.openmetadata.service.OpenMetadataApplicationConfig; import org.openmetadata.service.jdbi3.CollectionDAO; @@ -64,6 +65,7 @@ import org.openmetadata.service.jdbi3.TypeRepository; import org.openmetadata.service.resources.Collection; import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; +import org.openmetadata.service.security.policyevaluator.OperationContext; import org.openmetadata.service.util.EntityUtil.Fields; import org.openmetadata.service.util.JsonUtils; import org.openmetadata.service.util.RestUtil.PutResponse; @@ -389,7 +391,9 @@ public class TypeResource extends EntityResource { @Parameter(description = "Type Id", schema = @Schema(type = "string")) @PathParam("id") UUID id, @Valid CustomProperty property) throws IOException { - authorizer.authorizeAdmin(securityContext, false); + // TODO fix this is the typeID correct? Why are we not doing this by name? + OperationContext operationContext = new OperationContext(entityType, MetadataOperation.CREATE); + authorizer.authorize(securityContext, operationContext, getResourceContextById(id)); PutResponse response = dao.addCustomProperty(uriInfo, securityContext.getUserPrincipal().getName(), id, property); addHref(uriInfo, response.getEntity()); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/Authorizer.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/Authorizer.java index 75588fa33de..f0609b0830f 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/Authorizer.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/Authorizer.java @@ -41,5 +41,7 @@ public interface Authorizer { SecurityContext securityContext, OperationContext operationContext, ResourceContextInterface resourceContext) throws IOException; - void authorizeAdmin(SecurityContext securityContext, boolean allowBots); + void authorizeAdmin(SecurityContext securityContext); + + boolean decryptSecret(SecurityContext securityContext); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/DefaultAuthorizer.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/DefaultAuthorizer.java index 8969fa7b15a..11305f7cebc 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/DefaultAuthorizer.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/DefaultAuthorizer.java @@ -229,14 +229,28 @@ public class DefaultAuthorizer implements Authorizer { } @Override - public void authorizeAdmin(SecurityContext securityContext, boolean allowBots) { + public void authorizeAdmin(SecurityContext securityContext) { SubjectContext subjectContext = getSubjectContext(securityContext); - if (subjectContext.isAdmin() || (allowBots && subjectContext.isBot())) { + if (subjectContext.isAdmin()) { return; } throw new AuthorizationException(notAdmin(securityContext.getUserPrincipal().getName())); } + @Override + public boolean decryptSecret(SecurityContext securityContext) { + SubjectContext subjectContext = getSubjectContext(securityContext); + if (subjectContext.isAdmin()) { // Always decrypt secrets for admin + return true; + } + SecretsManager secretsManager = SecretsManagerFactory.getSecretsManager(); + if (subjectContext.isBot() && secretsManager.isLocal()) { + // Local secretsManager true means secrets are not encrypted. So allow decrypted secrets for bots. + return true; + } + return false; + } + private void addUsers(Set users, String domain, Boolean isAdmin) { for (String userName : users) { User user = user(userName, domain, userName).withIsAdmin(isAdmin); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/NoopAuthorizer.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/NoopAuthorizer.java index 68b87044af8..23d495544fe 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/NoopAuthorizer.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/NoopAuthorizer.java @@ -95,7 +95,12 @@ public class NoopAuthorizer implements Authorizer { } @Override - public void authorizeAdmin(SecurityContext securityContext, boolean allowBots) { + public void authorizeAdmin(SecurityContext securityContext) { /* Always authorize */ } + + @Override + public boolean decryptSecret(SecurityContext securityContext) { + return true; // Always decrypt + } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/jwt/JWTTokenGenerator.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/jwt/JWTTokenGenerator.java index d10ed3adb6e..f43ac94bffd 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/jwt/JWTTokenGenerator.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/jwt/JWTTokenGenerator.java @@ -20,10 +20,13 @@ import java.util.List; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.openmetadata.schema.api.security.jwt.JWTTokenConfiguration; +import org.openmetadata.schema.entity.teams.AuthenticationMechanism; import org.openmetadata.schema.entity.teams.User; +import org.openmetadata.schema.teams.authn.GenerateTokenRequest; import org.openmetadata.schema.teams.authn.JWTAuthMechanism; import org.openmetadata.schema.teams.authn.JWTTokenExpiry; import org.openmetadata.service.security.AuthenticationException; +import org.openmetadata.service.util.JsonUtils; @Slf4j public class JWTTokenGenerator { @@ -65,6 +68,17 @@ public class JWTTokenGenerator { } } + public void setAuthMechanism(User user, GenerateTokenRequest generateTokenRequest) { + JWTAuthMechanism jwtAuthMechanism = generateJWTToken(user, generateTokenRequest.getJWTTokenExpiry()); + AuthenticationMechanism authenticationMechanism = + new AuthenticationMechanism().withConfig(jwtAuthMechanism).withAuthType(AuthenticationMechanism.AuthType.JWT); + user.setAuthenticationMechanism(authenticationMechanism); + } + + public JWTAuthMechanism getAuthMechanism(User user) { + return JsonUtils.convertValue(user.getAuthenticationMechanism().getConfig(), JWTAuthMechanism.class); + } + public JWTAuthMechanism generateJWTToken(User user, JWTTokenExpiry expiry) { try { JWTAuthMechanism jwtAuthMechanism = new JWTAuthMechanism().withJWTTokenExpiry(expiry); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/OperationContext.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/OperationContext.java index c9487150e48..fb8108a4bc7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/OperationContext.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/OperationContext.java @@ -39,6 +39,10 @@ public class OperationContext { return operations; } + public boolean isCreateOperation() { + return operations.contains(MetadataOperation.CREATE); + } + public static boolean isEditOperation(MetadataOperation operation) { return operation.value().startsWith("Edit"); } diff --git a/openmetadata-service/src/main/resources/json/data/ResourceDescriptors.json b/openmetadata-service/src/main/resources/json/data/ResourceDescriptors.json index 0325bd98416..035488fb4e1 100644 --- a/openmetadata-service/src/main/resources/json/data/ResourceDescriptors.json +++ b/openmetadata-service/src/main/resources/json/data/ResourceDescriptors.json @@ -452,6 +452,17 @@ "EditDisplayName" ] }, + { + "name" : "type", + "operations" : [ + "Create", + "Delete", + "ViewAll", + "EditAll", + "EditDescription", + "EditDisplayName" + ] + }, { "name" : "type", "operations" : [ diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/services/ServiceResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/services/ServiceResourceTest.java index 1e1f05dc455..6d20d7a3f49 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/services/ServiceResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/services/ServiceResourceTest.java @@ -21,7 +21,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -42,7 +41,6 @@ import org.openmetadata.service.jdbi3.CollectionDAO; import org.openmetadata.service.jdbi3.ServiceEntityRepository; import org.openmetadata.service.secrets.SecretsManager; import org.openmetadata.service.secrets.SecretsManagerFactory; -import org.openmetadata.service.security.AuthorizationException; import org.openmetadata.service.security.Authorizer; @ExtendWith(MockitoExtension.class) @@ -105,21 +103,16 @@ public abstract class ServiceResourceTest< throws IOException { lenient().when(secretsManager.isLocal()).thenReturn(isLocalSecretManager); - if (isLocalSecretManager && !isAdmin && !isBot) { - lenient() - .doThrow(new AuthorizationException("")) - .when(authorizer) - .authorizeAdmin(any(SecurityContext.class), eq(true)); - } else if (!isLocalSecretManager && !isAdmin) { - lenient() - .doThrow(new AuthorizationException("")) - .when(authorizer) - .authorizeAdmin(any(SecurityContext.class), eq(false)); + if (isAdmin) { + lenient().doReturn(true).when(authorizer).decryptSecret(any(SecurityContext.class)); + } else if (isLocalSecretManager && isBot) { + lenient().doReturn(true).when(authorizer).decryptSecret(any(SecurityContext.class)); + } else { + lenient().doReturn(false).when(authorizer).decryptSecret(any(SecurityContext.class)); } R actual = callGetFromResource(serviceResource); - verify(secretsManager, times(1)).isLocal(); verify(secretsManager) .encryptOrDecryptServiceConnectionConfig( notNull(), eq(serviceConnectionType()), any(), eq(serviceType()), eq(false)); diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/teams/UserResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/teams/UserResourceTest.java index 84ef066c7f5..76c3d962e2d 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/teams/UserResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/teams/UserResourceTest.java @@ -82,6 +82,7 @@ import org.openmetadata.schema.auth.LoginRequest; import org.openmetadata.schema.auth.RegistrationRequest; import org.openmetadata.schema.entity.data.Table; import org.openmetadata.schema.entity.teams.AuthenticationMechanism; +import org.openmetadata.schema.entity.teams.AuthenticationMechanism.AuthType; import org.openmetadata.schema.entity.teams.Role; import org.openmetadata.schema.entity.teams.Team; import org.openmetadata.schema.entity.teams.User; @@ -90,6 +91,7 @@ import org.openmetadata.schema.teams.authn.GenerateTokenRequest; import org.openmetadata.schema.teams.authn.JWTAuthMechanism; import org.openmetadata.schema.teams.authn.JWTTokenExpiry; import org.openmetadata.schema.teams.authn.SSOAuthMechanism; +import org.openmetadata.schema.teams.authn.SSOAuthMechanism.SsoServiceType; import org.openmetadata.schema.type.ChangeDescription; import org.openmetadata.schema.type.EntityReference; import org.openmetadata.schema.type.ImageList; @@ -418,12 +420,7 @@ public class UserResourceTest extends EntityResourceTest { } private CreateUser createBotUserRequest(TestInfo test, int index) { - return createRequest(test, index) - .withIsBot(true) - .withAuthenticationMechanism( - new AuthenticationMechanism() - .withAuthType(AuthenticationMechanism.AuthType.JWT) - .withConfig(new JWTAuthMechanism().withJWTTokenExpiry(JWTTokenExpiry.Unlimited))); + return createBotUserRequest(getEntityName(test, index)); } @Test @@ -685,21 +682,18 @@ public class UserResourceTest extends EntityResourceTest { @Test void put_generateToken_bot_user_200_ok(TestInfo test) throws HttpResponseException { - User user = - createEntity( - createRequest(test, 6) - .withName("ingestion-bot-jwt") - .withDisplayName("ingestion-bot-jwt") - .withEmail("ingestion-bot-jwt@email.com") - .withIsBot(true) - .withAuthenticationMechanism( - new AuthenticationMechanism() - .withAuthType(AuthenticationMechanism.AuthType.SSO) - .withConfig( - new SSOAuthMechanism() - .withSsoServiceType(SSOAuthMechanism.SsoServiceType.GOOGLE) - .withAuthConfig(new GoogleSSOClientConfig().withSecretKey("/path/to/secret.json")))), - authHeaders("ingestion-bot-jwt@email.com")); + AuthenticationMechanism authMechanism = + new AuthenticationMechanism() + .withAuthType(AuthType.SSO) + .withConfig( + new SSOAuthMechanism() + .withSsoServiceType(SsoServiceType.GOOGLE) + .withAuthConfig(new GoogleSSOClientConfig().withSecretKey("/path/to/secret.json"))); + CreateUser create = + createBotUserRequest("ingestion-bot-jwt") + .withEmail("ingestion-bot-jwt@email.com") + .withAuthenticationMechanism(authMechanism); + User user = createEntity(create, authHeaders("ingestion-bot-jwt@email.com")); TestUtils.put( getResource(String.format("users/generateToken/%s", user.getId())), new GenerateTokenRequest().withJWTTokenExpiry(JWTTokenExpiry.Seven), @@ -865,14 +859,14 @@ public class UserResourceTest extends EntityResourceTest { BotResourceTest botResourceTest = new BotResourceTest(); String botName = "test-bot-user-fail"; // create bot user - CreateUser createBotUser = creatBotUserRequest("test-bot-user", true).withBotName(botName); + CreateUser createBotUser = createBotUserRequest("test-bot-user").withBotName(botName); User botUser = updateEntity(createBotUser, CREATED, ADMIN_AUTH_HEADERS); EntityReference botUserRef = Objects.requireNonNull(botUser).getEntityReference(); // assign bot user to a bot CreateBot create = botResourceTest.createRequest(test).withBotUser(botUserRef).withName(botName); botResourceTest.createEntity(create, ADMIN_AUTH_HEADERS); // put user with a different bot name - CreateUser createWrongBotUser = creatBotUserRequest("test-bot-user", true).withBotName("test-bot-user-fail-2"); + CreateUser createWrongBotUser = createBotUserRequest("test-bot-user").withBotName("test-bot-user-fail-2"); assertResponse( () -> updateEntity(createWrongBotUser, BAD_REQUEST, ADMIN_AUTH_HEADERS), BAD_REQUEST, @@ -884,14 +878,14 @@ public class UserResourceTest extends EntityResourceTest { BotResourceTest botResourceTest = new BotResourceTest(); String botName = "test-bot-ok"; // create bot user - CreateUser createBotUser = creatBotUserRequest("test-bot-user-ok", true).withBotName(botName); + CreateUser createBotUser = createBotUserRequest("test-bot-user-ok").withBotName(botName); User botUser = updateEntity(createBotUser, CREATED, ADMIN_AUTH_HEADERS); EntityReference botUserRef = Objects.requireNonNull(botUser).getEntityReference(); // assign bot user to a bot CreateBot create = botResourceTest.createRequest(test).withBotUser(botUserRef).withName(botName); botResourceTest.createEntity(create, ADMIN_AUTH_HEADERS); // put again user with same bot name - CreateUser createDifferentBotUser = creatBotUserRequest("test-bot-user-ok", true).withBotName(botName); + CreateUser createDifferentBotUser = createBotUserRequest("test-bot-user-ok").withBotName(botName); updateEntity(createDifferentBotUser, OK, ADMIN_AUTH_HEADERS); assertNotNull(createDifferentBotUser); } @@ -1044,19 +1038,18 @@ public class UserResourceTest extends EntityResourceTest { return String.join(",", allowedFields); } - public User createUser(String botName, boolean isBot) { + public User createUser(String userName, boolean isBot) { try { - CreateUser createUser = creatBotUserRequest(botName, isBot); + CreateUser createUser = createBotUserRequest(userName).withIsBot(isBot); return createEntity(createUser, ADMIN_AUTH_HEADERS); } catch (Exception ignore) { return null; } } - private CreateUser creatBotUserRequest(String botUserName, boolean isBot) { + private CreateUser createBotUserRequest(String botUserName) { return createRequest(botUserName, "", "", null) - .withIsBot(isBot) - .withIsAdmin(false) + .withIsBot(true) .withAuthenticationMechanism( new AuthenticationMechanism() .withAuthType(AuthenticationMechanism.AuthType.JWT)