From af878d3f8b123e64c6cb512dd66ca15e5a7df2d5 Mon Sep 17 00:00:00 2001 From: eoinmcdonnell113 Date: Tue, 8 Sep 2026 09:54:35 -0400 Subject: [PATCH 1/2] RANGER-5759: Add Atlas notification-topic and post-notification privilege --- .../service-defs/ranger-servicedef-atlas.json | 21 +- dev-support/checkstyle-suppressions.xml | 1 + .../authorizer/RangerAtlasAuthorizer.java | 52 +++- .../services/atlas/RangerServiceAtlas.java | 10 + .../authorizer/TestRangerAtlasAuthorizer.java | 62 ++++ .../src/test/resources/atlas-policies.json | 32 ++- .../current/ranger_core_db_mysql.sql | 1 + .../current/ranger_core_db_oracle.sql | 1 + .../current/ranger_core_db_postgres.sql | 1 + .../current/ranger_core_db_sqlanywhere.sql | 2 + .../current/ranger_core_db_sqlserver.sql | 1 + ...PatchForAtlasNotificationTopic_J10067.java | 167 +++++++++++ ...PatchForAtlasNotificationTopic_J10067.java | 266 ++++++++++++++++++ 13 files changed, 611 insertions(+), 6 deletions(-) create mode 100644 security-admin/src/main/java/org/apache/ranger/patch/PatchForAtlasNotificationTopic_J10067.java create mode 100644 security-admin/src/test/java/org/apache/ranger/patch/TestPatchForAtlasNotificationTopic_J10067.java diff --git a/agents-common/src/main/resources/service-defs/ranger-servicedef-atlas.json b/agents-common/src/main/resources/service-defs/ranger-servicedef-atlas.json index 5c52487dfc9..4f622c6e8aa 100644 --- a/agents-common/src/main/resources/service-defs/ranger-servicedef-atlas.json +++ b/agents-common/src/main/resources/service-defs/ranger-servicedef-atlas.json @@ -258,6 +258,24 @@ "accessTypeRestrictions": [ "entity-add-classification", "entity-update-classification", "entity-remove-classification" ] + }, + { + "itemId": 17, + "name": "notification-topic", + "parent": "", + "level": 10, + "type": "string", + "mandatory": true, + "isValidLeaf": true, + "lookupSupported": true, + "excludesSupported": true, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { "wildCard": "true", "ignoreCase": "false" }, + "label": "Notification Topic", + "description": "Notification Topic", + "accessTypeRestrictions": [ + "post-notification" + ] } ], "accessTypes": [ @@ -281,7 +299,8 @@ { "itemId": 18, "name": "entity-remove-label", "label": "Remove Label", "category": "UPDATE" }, { "itemId": 19, "name": "entity-update-business-metadata", "label": "Update Business Metadata", "category": "UPDATE" }, { "itemId": 20, "name": "type-read", "label": "Read Type", "category": "READ" }, - { "itemId": 21, "name": "admin-audits", "label": "Admin Audits", "category": "MANAGE" } + { "itemId": 21, "name": "admin-audits", "label": "Admin Audits", "category": "MANAGE" }, + { "itemId": 22, "name": "post-notification", "label": "Post Notification", "category": "UPDATE" } ], "configs": [ { "itemId": 1, "name": "username", "type": "string", "mandatory": true, "label": "Username" }, diff --git a/dev-support/checkstyle-suppressions.xml b/dev-support/checkstyle-suppressions.xml index f20f609e94a..5788457b2c8 100644 --- a/dev-support/checkstyle-suppressions.xml +++ b/dev-support/checkstyle-suppressions.xml @@ -91,6 +91,7 @@ + diff --git a/plugin-atlas/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java b/plugin-atlas/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java index 839d2a8018f..7532b01bc65 100644 --- a/plugin-atlas/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java +++ b/plugin-atlas/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java @@ -46,6 +46,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -55,6 +56,8 @@ import java.util.Map; import java.util.Set; +import static org.apache.ranger.services.atlas.RangerServiceAtlas.ACCESS_TYPE_POST_NOTIFICATION; +import static org.apache.ranger.services.atlas.RangerServiceAtlas.ACCESS_TYPE_SERVICE_NOTIFICATION_POST; import static org.apache.ranger.services.atlas.RangerServiceAtlas.ACCESS_TYPE_TYPE_READ; import static org.apache.ranger.services.atlas.RangerServiceAtlas.ENTITY_NOT_CLASSIFIED; import static org.apache.ranger.services.atlas.RangerServiceAtlas.RESOURCE_CLASSIFICATION; @@ -70,6 +73,7 @@ import static org.apache.ranger.services.atlas.RangerServiceAtlas.RESOURCE_ENTITY_LABEL; import static org.apache.ranger.services.atlas.RangerServiceAtlas.RESOURCE_ENTITY_OWNER; import static org.apache.ranger.services.atlas.RangerServiceAtlas.RESOURCE_ENTITY_TYPE; +import static org.apache.ranger.services.atlas.RangerServiceAtlas.RESOURCE_NOTIFICATION_TOPIC; import static org.apache.ranger.services.atlas.RangerServiceAtlas.RESOURCE_RELATIONSHIP_TYPE; import static org.apache.ranger.services.atlas.RangerServiceAtlas.RESOURCE_SERVICE; import static org.apache.ranger.services.atlas.RangerServiceAtlas.RESOURCE_TYPE_CATEGORY; @@ -126,8 +130,16 @@ public boolean isAccessAllowed(AtlasAdminAccessRequest request) { } String action = request.getAction() != null ? request.getAction().getType() : null; - RangerAccessResourceImpl rangerResource = new RangerAccessResourceImpl(Collections.singletonMap(RESOURCE_SERVICE, "*")); - RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl(rangerResource, action, request.getUser(), request.getUserGroups(), null); + RangerAccessResourceImpl rangerResource; + + if (isPostNotificationAction(action)) { + action = ACCESS_TYPE_POST_NOTIFICATION; + rangerResource = new RangerAccessResourceImpl(Collections.singletonMap(RESOURCE_NOTIFICATION_TOPIC, getNotificationTopic(request))); + } else { + rangerResource = new RangerAccessResourceImpl(Collections.singletonMap(RESOURCE_SERVICE, "*")); + } + + RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl(rangerResource, action, request.getUser(), request.getUserGroups(), null); rangerRequest.setClientIPAddress(request.getClientIPAddress()); rangerRequest.setAccessTime(request.getAccessTime()); @@ -353,6 +365,42 @@ private RangerServiceDef getServiceDef() { return plugin != null ? plugin.getServiceDef() : null; } + private boolean isPostNotificationAction(String action) { + return ACCESS_TYPE_POST_NOTIFICATION.equals(action) || ACCESS_TYPE_SERVICE_NOTIFICATION_POST.equals(action); + } + + private String getNotificationTopic(AtlasAccessRequest request) { + String topic = invokeStringGetter(request, "getTopicName"); + + if (StringUtils.isBlank(topic)) { + topic = invokeStringGetter(request, "getTopic"); + } + + return StringUtils.isNotBlank(topic) ? topic : "*"; + } + + private String invokeStringGetter(Object target, String methodName) { + if (target == null) { + return null; + } + + try { + Method method = target.getClass().getMethod(methodName); + + if (method.getReturnType() == String.class) { + Object value = method.invoke(target); + + return value != null ? value.toString() : null; + } + } catch (NoSuchMethodException ignored) { + // Atlas 2.4 AtlasAdminAccessRequest has no topic accessor + } catch (Exception e) { + LOG.debug("Failed to invoke {} on {}", methodName, target.getClass().getName(), e); + } + + return null; + } + private boolean isAccessAllowed(AtlasEntityAccessRequest request, RangerAtlasAuditHandler auditHandler) { LOG.debug("==> isAccessAllowed({})", request); diff --git a/plugin-atlas/src/main/java/org/apache/ranger/services/atlas/RangerServiceAtlas.java b/plugin-atlas/src/main/java/org/apache/ranger/services/atlas/RangerServiceAtlas.java index 6c9e92bb100..69daddf63a9 100644 --- a/plugin-atlas/src/main/java/org/apache/ranger/services/atlas/RangerServiceAtlas.java +++ b/plugin-atlas/src/main/java/org/apache/ranger/services/atlas/RangerServiceAtlas.java @@ -79,12 +79,15 @@ public class RangerServiceAtlas extends RangerBaseService { public static final String RESOURCE_END_TWO_ENTITY_TYPE = "end-two-entity-type"; public static final String RESOURCE_END_TWO_ENTITY_CLASSIFICATION = "end-two-entity-classification"; public static final String RESOURCE_END_TWO_ENTITY_ID = "end-two-entity"; + public static final String RESOURCE_NOTIFICATION_TOPIC = "notification-topic"; public static final String SEARCH_FEATURE_POLICY_NAME = "Allow users to manage favorite searches"; public static final String ACCESS_TYPE_ENTITY_READ = "entity-read"; public static final String ACCESS_TYPE_TYPE_READ = "type-read"; public static final String ACCESS_TYPE_ENTITY_CREATE = "entity-create"; public static final String ACCESS_TYPE_ENTITY_UPDATE = "entity-update"; public static final String ACCESS_TYPE_ENTITY_DELETE = "entity-delete"; + public static final String ACCESS_TYPE_POST_NOTIFICATION = "post-notification"; + public static final String ACCESS_TYPE_SERVICE_NOTIFICATION_POST = "service-notification-post"; public static final String ADMIN_USERNAME_DEFAULT = "admin"; public static final String TAGSYNC_USERNAME_DEFAULT = "rangertagsync"; public static final String ENTITY_TYPE_USER_PROFILE = "__AtlasUserProfile"; @@ -265,6 +268,7 @@ private Map getSearchFeaturePolicyResource() { private static class AtlasServiceClient extends BaseClient { private static final String[] TYPE_CATEGORIES = new String[] {"classification", "enum", "entity", "relationship", "struct", "business_metadata"}; + private static final String[] NOTIFICATION_TOPICS = new String[] {"ATLAS_HOOK", "ATLAS_ENTITIES"}; private Map> typesDef = new HashMap<>(); @@ -387,6 +391,12 @@ public List lookupResource(ResourceLookupContext lookupContext) { } break; + case RESOURCE_NOTIFICATION_TOPIC: + for (String topic : NOTIFICATION_TOPICS) { + addIfStartsWithAndNotExcluded(ret, topic, userInput, currentValues); + } + break; + default: { ret.add(lookupContext.getResourceName()); } diff --git a/plugin-atlas/src/test/java/org/apache/ranger/authorization/atlas/authorizer/TestRangerAtlasAuthorizer.java b/plugin-atlas/src/test/java/org/apache/ranger/authorization/atlas/authorizer/TestRangerAtlasAuthorizer.java index 502dd93aba8..347bb66224e 100644 --- a/plugin-atlas/src/test/java/org/apache/ranger/authorization/atlas/authorizer/TestRangerAtlasAuthorizer.java +++ b/plugin-atlas/src/test/java/org/apache/ranger/authorization/atlas/authorizer/TestRangerAtlasAuthorizer.java @@ -45,6 +45,10 @@ public class TestRangerAtlasAuthorizer { private static final Set USER_STEWARD1_GROUPS = Collections.singleton("stewards"); private static final String USER_FINANCE_STEWARD1 = "finance-data-steward1"; private static final Set USER_FINANCE_STEWARD1_GROUPS = new HashSet<>(Arrays.asList("finance-stewards", "stewards")); + private static final String USER_HOOK1 = "hook1"; + private static final Set USER_HOOK1_GROUPS = Collections.singleton("hook-users"); + private static final String TOPIC_ATLAS_HOOK = "ATLAS_HOOK"; + private static final String TOPIC_OTHER = "OTHER_TOPIC"; private static final AtlasEntityDef ENTITY_DEF_HIVE_TABLE = new AtlasEntityDef("hive_table"); private static final AtlasClassificationDef CLASSIFICATION_DEF_FINANCE = new AtlasClassificationDef("FINANCE"); @@ -471,6 +475,64 @@ public void testAudits() { .isTrue(); } + @Test + public void testPostNotification() { + AtlasAdminAccessRequest request = new AtlasAdminAccessRequest(AtlasPrivilege.SERVICE_NOTIFICATION_POST); + + request.setUser(USER_USER1, USER_USER1_GROUPS); + + assertThat(authorizer.isAccessAllowed(request)) + .as("%s should be denied to post notification", request.getUser()) + .isFalse(); + + request.setUser(USER_STEWARD1, USER_STEWARD1_GROUPS); + + assertThat(authorizer.isAccessAllowed(request)) + .as("%s should be denied to post notification", request.getUser()) + .isFalse(); + + request.setUser(USER_ADMIN1, USER_ADMIN1_GROUPS); + + assertThat(authorizer.isAccessAllowed(request)) + .as("%s should be allowed to post notification", request.getUser()) + .isTrue(); + } + + @Test + public void testPostNotificationOnAllowedTopic() { + AtlasAdminAccessRequest request = new TopicAccessRequest(AtlasPrivilege.SERVICE_NOTIFICATION_POST, TOPIC_ATLAS_HOOK); + + request.setUser(USER_HOOK1, USER_HOOK1_GROUPS); + + assertThat(authorizer.isAccessAllowed(request)) + .as("%s should be allowed to post notification to %s", request.getUser(), TOPIC_ATLAS_HOOK) + .isTrue(); + } + + @Test + public void testPostNotificationOnDeniedTopic() { + AtlasAdminAccessRequest request = new TopicAccessRequest(AtlasPrivilege.SERVICE_NOTIFICATION_POST, TOPIC_OTHER); + + request.setUser(USER_HOOK1, USER_HOOK1_GROUPS); + + assertThat(authorizer.isAccessAllowed(request)) + .as("%s should be denied to post notification to %s", request.getUser(), TOPIC_OTHER) + .isFalse(); + } + + private static class TopicAccessRequest extends AtlasAdminAccessRequest { + private final String topicName; + + TopicAccessRequest(AtlasPrivilege action, String topicName) { + super(action); + this.topicName = topicName; + } + + public String getTopicName() { + return topicName; + } + } + @Test public void testAddRelationship() { AtlasRelationshipAccessRequest request = new AtlasRelationshipAccessRequest(typeRegistry, AtlasPrivilege.RELATIONSHIP_ADD, RELATIONSHIP_TYPE_CLONE_OF, ENTITY_HIVE_TABLE_DB1_TBL1, ENTITY_HIVE_TABLE_DB1_TBL2); diff --git a/plugin-atlas/src/test/resources/atlas-policies.json b/plugin-atlas/src/test/resources/atlas-policies.json index 1020dd8f697..70eab66d5f3 100644 --- a/plugin-atlas/src/test/resources/atlas-policies.json +++ b/plugin-atlas/src/test/resources/atlas-policies.json @@ -1,5 +1,5 @@ { - "serviceName": "dev_atlas", "serviceId": 8, "policyVersion": 19, + "serviceName": "dev_atlas", "serviceId": 8, "policyVersion": 21, "policies": [ { "id": 1, "name": "Default: all type-category, type", @@ -111,6 +111,30 @@ } ] }, + { + "id": 9, "name": "Default: all notification-topic", + "resources": { + "notification-topic": { "values": [ "*" ] } + }, + "policyItems": [ + { + "accesses": [ { "type": "post-notification" } ], + "groups": [ "admins" ] + } + ] + }, + { + "id": 10, "name": "Allow hook users to post to ATLAS_HOOK", + "resources": { + "notification-topic": { "values": [ "ATLAS_HOOK" ] } + }, + "policyItems": [ + { + "accesses": [ { "type": "post-notification" } ], + "groups": [ "hook-users" ] + } + ] + }, { "id": 8, "name": "Entities having FINANCE classification", "isDenyAllElse": true, "resources": { @@ -145,7 +169,8 @@ { "itemId": 13, "name": "end-one-entity", "level": 40, "parent": "end-one-entity-classification", "accessTypeRestrictions": [ "add-relationship", "update-relationship", "remove-relationship" ] }, { "itemId": 14, "name": "end-two-entity-type", "level": 50, "parent": "end-one-entity" }, { "itemId": 15, "name": "end-two-entity-classification", "level": 60, "parent": "end-two-entity-type" }, - { "itemId": 16, "name": "end-two-entity", "level": 70, "parent": "end-two-entity-classification", "accessTypeRestrictions": [ "add-relationship", "update-relationship", "remove-relationship" ] } + { "itemId": 16, "name": "end-two-entity", "level": 70, "parent": "end-two-entity-classification", "accessTypeRestrictions": [ "add-relationship", "update-relationship", "remove-relationship" ] }, + { "itemId": 17, "name": "notification-topic", "level": 10, "accessTypeRestrictions": [ "post-notification" ] } ], "accessTypes": [ { "itemId": 1, "name": "type-read", "category": "READ", "label": "Read Type" }, @@ -168,7 +193,8 @@ { "itemId": 18, "name": "admin-export", "category": "MANAGE", "label": "Export" }, { "itemId": 19, "name": "admin-import", "category": "MANAGE", "label": "Import" }, { "itemId": 20, "name": "admin-purge", "category": "MANAGE", "label": "Purge" }, - { "itemId": 21, "name": "admin-audits", "category": "MANAGE", "label": "Admin Audits" } + { "itemId": 21, "name": "admin-audits", "category": "MANAGE", "label": "Admin Audits" }, + { "itemId": 22, "name": "post-notification", "category": "UPDATE", "label": "Post Notification" } ], "configs": [ { "itemId": 1, "name": "username", "type": "string", "mandatory": true }, diff --git a/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql b/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql index cfc8fd645c6..078835193f2 100644 --- a/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql +++ b/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql @@ -2007,4 +2007,5 @@ INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10064',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y'); INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10065',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y'); INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10066',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y'); +INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10067',UTC_TIMESTAMP(),'Ranger 3.0.0',UTC_TIMESTAMP(),'localhost','Y'); INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('JAVA_PATCHES',UTC_TIMESTAMP(),'Ranger 1.0.0',UTC_TIMESTAMP(),'localhost','Y'); diff --git a/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql b/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql index 24f12a461ee..430f97a1df1 100644 --- a/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql +++ b/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql @@ -2230,5 +2230,6 @@ INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,act INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES (X_DB_VERSION_H_SEQ.nextval,'J10064',sys_extract_utc(systimestamp),'Ranger 3.0.0',sys_extract_utc(systimestamp),'localhost','Y'); INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES (X_DB_VERSION_H_SEQ.nextval,'J10065',sys_extract_utc(systimestamp),'Ranger 3.0.0',sys_extract_utc(systimestamp),'localhost','Y'); INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES (X_DB_VERSION_H_SEQ.nextval,'J10066',sys_extract_utc(systimestamp),'Ranger 3.0.0',sys_extract_utc(systimestamp),'localhost','Y'); +INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES (X_DB_VERSION_H_SEQ.nextval,'J10067',sys_extract_utc(systimestamp),'Ranger 3.0.0',sys_extract_utc(systimestamp),'localhost','Y'); INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES (X_DB_VERSION_H_SEQ.nextval,'JAVA_PATCHES',sys_extract_utc(systimestamp),'Ranger 1.0.0',sys_extract_utc(systimestamp),'localhost','Y'); commit; diff --git a/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql b/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql index 8ef5bbca490..ba067c5bd50 100644 --- a/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql +++ b/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql @@ -2163,6 +2163,7 @@ INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10064',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y'); INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10065',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y'); INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10066',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y'); +INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10067',current_timestamp,'Ranger 3.0.0',current_timestamp,'localhost','Y'); INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('JAVA_PATCHES',current_timestamp,'Ranger 1.0.0',current_timestamp,'localhost','Y'); DROP VIEW IF EXISTS vx_principal; diff --git a/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql b/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql index e558454ffcd..c15431ae9d2 100644 --- a/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql +++ b/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql @@ -2394,6 +2394,8 @@ INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active GO INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10066',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y'); GO +INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10067',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y'); +GO INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('JAVA_PATCHES',CURRENT_TIMESTAMP,'Ranger 1.0.0',CURRENT_TIMESTAMP,'localhost','Y'); GO exit diff --git a/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql b/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql index 4b83351513a..600efeb2365 100644 --- a/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql +++ b/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql @@ -4596,5 +4596,6 @@ INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10064',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y'); INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10065',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y'); INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10066',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y'); +INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10067',CURRENT_TIMESTAMP,'Ranger 3.0.0',CURRENT_TIMESTAMP,'localhost','Y'); INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('JAVA_PATCHES',CURRENT_TIMESTAMP,'Ranger 1.0.0',CURRENT_TIMESTAMP,'localhost','Y'); GO diff --git a/security-admin/src/main/java/org/apache/ranger/patch/PatchForAtlasNotificationTopic_J10067.java b/security-admin/src/main/java/org/apache/ranger/patch/PatchForAtlasNotificationTopic_J10067.java new file mode 100644 index 00000000000..85d69315039 --- /dev/null +++ b/security-admin/src/main/java/org/apache/ranger/patch/PatchForAtlasNotificationTopic_J10067.java @@ -0,0 +1,167 @@ +/* + * 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.ranger.patch; + +import org.apache.ranger.biz.ServiceDBStore; +import org.apache.ranger.common.RangerValidatorFactory; +import org.apache.ranger.db.RangerDaoManager; +import org.apache.ranger.entity.XXServiceDef; +import org.apache.ranger.plugin.model.RangerServiceDef; +import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef; +import org.apache.ranger.plugin.model.validation.RangerServiceDefValidator; +import org.apache.ranger.plugin.model.validation.RangerValidator.Action; +import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil; +import org.apache.ranger.util.CLIUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Component +public class PatchForAtlasNotificationTopic_J10067 extends BaseLoader { + private static final Logger logger = LoggerFactory.getLogger(PatchForAtlasNotificationTopic_J10067.class); + + private static final List ATLAS_RESOURCES = new ArrayList<>(Collections.singletonList("notification-topic")); + private static final List ATLAS_ACCESS_TYPES = new ArrayList<>(Collections.singletonList("post-notification")); + + @Autowired + RangerDaoManager daoMgr; + + @Autowired + ServiceDBStore svcDBStore; + + @Autowired + RangerValidatorFactory validatorFactory; + + @Autowired + ServiceDBStore svcStore; + + public static void main(String[] args) { + logger.info("main()"); + + try { + PatchForAtlasNotificationTopic_J10067 loader = (PatchForAtlasNotificationTopic_J10067) CLIUtil.getBean(PatchForAtlasNotificationTopic_J10067.class); + + loader.init(); + + while (loader.isMoreToProcess()) { + loader.load(); + } + + logger.info("Load complete. Exiting!!!"); + + System.exit(0); + } catch (Exception e) { + logger.error("Error loading", e); + + System.exit(1); + } + } + + @Override + public void init() throws Exception { + // Do Nothing + } + + @Override + public void printStats() { + logger.info("PatchForAtlasNotificationTopic_J10067 Logs"); + } + + @Override + public void execLoad() { + logger.info("==> PatchForAtlasNotificationTopic_J10067.execLoad()"); + + try { + addNotificationTopicPermissionInServiceDef(); + } catch (Exception e) { + throw new RuntimeException("Error while updating " + EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME + " service-def"); + } + + logger.info("<== PatchForAtlasNotificationTopic_J10067.execLoad()"); + } + + private void addNotificationTopicPermissionInServiceDef() throws Exception { + RangerServiceDef embeddedAtlasServiceDef = EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME); + + if (embeddedAtlasServiceDef != null) { + XXServiceDef xXServiceDefObj = daoMgr.getXXServiceDef().findByName(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME); + + if (xXServiceDefObj == null) { + logger.info("{}: service-def not found. No patching is needed", EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME); + + return; + } + + RangerServiceDef dbAtlasServiceDef = svcDBStore.getServiceDefByName(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME); + + List embeddedAtlasResourceDefs = embeddedAtlasServiceDef.getResources(); + List embeddedAtlasAccessTypes = embeddedAtlasServiceDef.getAccessTypes(); + + if (checkResourcePresent(embeddedAtlasResourceDefs)) { + dbAtlasServiceDef.setResources(embeddedAtlasResourceDefs); + + if (checkAccessPresent(embeddedAtlasAccessTypes)) { + dbAtlasServiceDef.setAccessTypes(embeddedAtlasAccessTypes); + } + } + + RangerServiceDefValidator validator = validatorFactory.getServiceDefValidator(svcStore); + + validator.validate(dbAtlasServiceDef, Action.UPDATE); + + RangerServiceDef ret = svcStore.updateServiceDef(dbAtlasServiceDef); + + if (ret == null) { + logger.error("Error while updating {} service-def", EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME); + + throw new RuntimeException("Error while updating " + EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME + " service-def"); + } + } + } + + private boolean checkResourcePresent(List resourceDefs) { + boolean ret = false; + + for (RangerServiceDef.RangerResourceDef resourceDef : resourceDefs) { + if (ATLAS_RESOURCES.contains(resourceDef.getName())) { + ret = true; + break; + } + } + + return ret; + } + + private boolean checkAccessPresent(List embeddedAtlasAccessTypes) { + boolean ret = false; + + for (RangerServiceDef.RangerAccessTypeDef accessDef : embeddedAtlasAccessTypes) { + if (ATLAS_ACCESS_TYPES.contains(accessDef.getName())) { + ret = true; + break; + } + } + + return ret; + } +} diff --git a/security-admin/src/test/java/org/apache/ranger/patch/TestPatchForAtlasNotificationTopic_J10067.java b/security-admin/src/test/java/org/apache/ranger/patch/TestPatchForAtlasNotificationTopic_J10067.java new file mode 100644 index 00000000000..85849d6eec1 --- /dev/null +++ b/security-admin/src/test/java/org/apache/ranger/patch/TestPatchForAtlasNotificationTopic_J10067.java @@ -0,0 +1,266 @@ +/* + * 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.ranger.patch; + +import org.apache.ranger.biz.ServiceDBStore; +import org.apache.ranger.common.RangerValidatorFactory; +import org.apache.ranger.db.RangerDaoManager; +import org.apache.ranger.db.XXServiceDefDao; +import org.apache.ranger.entity.XXServiceDef; +import org.apache.ranger.plugin.model.RangerServiceDef; +import org.apache.ranger.plugin.model.validation.RangerServiceDefValidator; +import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil; +import org.apache.ranger.util.CLIUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.Permission; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TestPatchForAtlasNotificationTopic_J10067 { + private static void setIfPresent(Object target, String fieldName, Object value) { + try { + Field f = target.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + f.set(target, value); + } catch (NoSuchFieldException ignored) { + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void testExecLoadAndPrintStats() throws Exception { + try (MockedStatic utilMock = Mockito.mockStatic(EmbeddedServiceDefsUtil.class)) { + EmbeddedServiceDefsUtil util = Mockito.mock(EmbeddedServiceDefsUtil.class); + utilMock.when(EmbeddedServiceDefsUtil::instance).thenReturn(util); + + RangerServiceDef embeddedAtlas = new RangerServiceDef(); + embeddedAtlas.setResources(Collections.emptyList()); + embeddedAtlas.setAccessTypes(Collections.emptyList()); + utilMock.when(() -> EmbeddedServiceDefsUtil.instance() + .getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME)) + .thenReturn(embeddedAtlas); + + PatchForAtlasNotificationTopic_J10067 patch = new PatchForAtlasNotificationTopic_J10067(); + RangerDaoManager daoMgr = Mockito.mock(RangerDaoManager.class); + ServiceDBStore svcDBStore = Mockito.mock(ServiceDBStore.class); + RangerValidatorFactory validatorFactory = Mockito.mock(RangerValidatorFactory.class); + RangerServiceDefValidator validator = Mockito.mock(RangerServiceDefValidator.class); + + XXServiceDefDao xxServiceDefDao = Mockito.mock(XXServiceDefDao.class); + Mockito.when(daoMgr.getXXServiceDef()).thenReturn(xxServiceDefDao); + Mockito.when(xxServiceDefDao.findByName(Mockito.anyString())).thenReturn(new XXServiceDef()); + + RangerServiceDef dbAtlas = new RangerServiceDef(); + Mockito.when( + svcDBStore.getServiceDefByName(Mockito.eq(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME))) + .thenReturn(dbAtlas); + Mockito.when(validatorFactory.getServiceDefValidator(Mockito.any(ServiceDBStore.class))) + .thenReturn(validator); + Mockito.when(svcDBStore.updateServiceDef(Mockito.any(RangerServiceDef.class))) + .thenAnswer(inv -> inv.getArgument(0)); + + setIfPresent(patch, "daoMgr", daoMgr); + setIfPresent(patch, "svcDBStore", svcDBStore); + setIfPresent(patch, "validatorFactory", validatorFactory); + setIfPresent(patch, "svcStore", svcDBStore); + + patch.execLoad(); + patch.printStats(); + } + } + + @Test + public void testInit_DoesNothing() throws Exception { + PatchForAtlasNotificationTopic_J10067 patch = new PatchForAtlasNotificationTopic_J10067(); + Assertions.assertDoesNotThrow(() -> patch.init()); + } + + @Test + public void testMain_Success_ExitZero() { + SecurityManager originalSm = System.getSecurityManager(); + try (MockedStatic cliMock = Mockito.mockStatic(CLIUtil.class)) { + PatchForAtlasNotificationTopic_J10067 patch = new PatchForAtlasNotificationTopic_J10067(); + patch.setMoreToProcess(false); + cliMock.when(() -> CLIUtil.getBean(PatchForAtlasNotificationTopic_J10067.class)).thenReturn(patch); + CLIUtil cli = Mockito.mock(CLIUtil.class); + cliMock.when(() -> CLIUtil.getBean(CLIUtil.class)).thenReturn(cli); + + System.setSecurityManager(new SecurityManager() { + @Override + public void checkExit(int status) { + throw new SecurityException(String.valueOf(status)); + } + + @Override + public void checkPermission(Permission perm) { + } + }); + + try { + PatchForAtlasNotificationTopic_J10067.main(new String[] {}); + } catch (SecurityException se) { + Assertions.assertNotNull(se.getMessage()); + } + } finally { + System.setSecurityManager(originalSm); + } + } + + @Test + public void testMain_Failure_ExitOne() { + SecurityManager originalSm = System.getSecurityManager(); + try (MockedStatic cliMock = Mockito.mockStatic(CLIUtil.class)) { + cliMock.when(() -> CLIUtil.getBean(PatchForAtlasNotificationTopic_J10067.class)).thenThrow(new RuntimeException("boom")); + + System.setSecurityManager(new SecurityManager() { + @Override + public void checkExit(int status) { + throw new SecurityException(String.valueOf(status)); + } + + @Override + public void checkPermission(Permission perm) { + } + }); + + try { + PatchForAtlasNotificationTopic_J10067.main(new String[] {}); + } catch (SecurityException se) { + Assertions.assertEquals("1", se.getMessage()); + } + } finally { + System.setSecurityManager(originalSm); + } + } + + @Test + public void testAddNotificationTopicPermissionInServiceDef_ServiceDefNotFound_ReturnsEarly() throws Exception { + try (MockedStatic utilMock = Mockito.mockStatic(EmbeddedServiceDefsUtil.class)) { + EmbeddedServiceDefsUtil util = Mockito.mock(EmbeddedServiceDefsUtil.class); + utilMock.when(EmbeddedServiceDefsUtil::instance).thenReturn(util); + + RangerServiceDef embeddedAtlas = new RangerServiceDef(); + embeddedAtlas.setResources(new ArrayList<>()); + embeddedAtlas.setAccessTypes(new ArrayList<>()); + Mockito.when(util.getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME)).thenReturn(embeddedAtlas); + + RangerDaoManager daoMgr = Mockito.mock(RangerDaoManager.class); + XXServiceDefDao xxServiceDefDao = Mockito.mock(XXServiceDefDao.class); + Mockito.when(daoMgr.getXXServiceDef()).thenReturn(xxServiceDefDao); + Mockito.when(xxServiceDefDao.findByName(Mockito.anyString())).thenReturn(null); + + PatchForAtlasNotificationTopic_J10067 patch = new PatchForAtlasNotificationTopic_J10067(); + setIfPresent(patch, "daoMgr", daoMgr); + setIfPresent(patch, "svcDBStore", Mockito.mock(ServiceDBStore.class)); + setIfPresent(patch, "validatorFactory", Mockito.mock(RangerValidatorFactory.class)); + setIfPresent(patch, "svcStore", Mockito.mock(ServiceDBStore.class)); + + Assertions.assertDoesNotThrow(() -> patch.execLoad()); + } + } + + @Test + public void testExecLoad_UpdateReturnsNull_ThrowsRuntime() throws Exception { + try (MockedStatic utilMock = Mockito.mockStatic(EmbeddedServiceDefsUtil.class)) { + EmbeddedServiceDefsUtil util = Mockito.mock(EmbeddedServiceDefsUtil.class); + utilMock.when(EmbeddedServiceDefsUtil::instance).thenReturn(util); + + RangerServiceDef embeddedAtlas = new RangerServiceDef(); + RangerServiceDef.RangerResourceDef res = new RangerServiceDef.RangerResourceDef(); + res.setName("notification-topic"); + embeddedAtlas.setResources(new ArrayList<>(Collections.singletonList(res))); + embeddedAtlas.setAccessTypes(new ArrayList<>(Collections.singletonList(new RangerServiceDef.RangerAccessTypeDef("post-notification")))); + utilMock.when(() -> EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_ATLAS_NAME)).thenReturn(embeddedAtlas); + + PatchForAtlasNotificationTopic_J10067 patch = new PatchForAtlasNotificationTopic_J10067(); + RangerDaoManager daoMgr = Mockito.mock(RangerDaoManager.class); + XXServiceDefDao xxServiceDefDao = Mockito.mock(XXServiceDefDao.class); + Mockito.when(daoMgr.getXXServiceDef()).thenReturn(xxServiceDefDao); + Mockito.when(xxServiceDefDao.findByName(Mockito.anyString())).thenReturn(new XXServiceDef()); + setIfPresent(patch, "daoMgr", daoMgr); + + ServiceDBStore svcDBStore = Mockito.mock(ServiceDBStore.class); + RangerServiceDef dbAtlas = new RangerServiceDef(); + Mockito.when(svcDBStore.getServiceDefByName(Mockito.anyString())).thenReturn(dbAtlas); + Mockito.when(svcDBStore.updateServiceDef(Mockito.any())).thenReturn(null); + setIfPresent(patch, "svcDBStore", svcDBStore); + setIfPresent(patch, "svcStore", svcDBStore); + + RangerValidatorFactory validatorFactory = Mockito.mock(RangerValidatorFactory.class); + RangerServiceDefValidator validator = Mockito.mock(RangerServiceDefValidator.class); + Mockito.when(validatorFactory.getServiceDefValidator(Mockito.any(ServiceDBStore.class))).thenReturn(validator); + setIfPresent(patch, "validatorFactory", validatorFactory); + + Assertions.assertThrows(RuntimeException.class, patch::execLoad); + } + } + + @Test + public void testPrivate_Checkers() throws Exception { + PatchForAtlasNotificationTopic_J10067 patch = new PatchForAtlasNotificationTopic_J10067(); + Method cr = PatchForAtlasNotificationTopic_J10067.class.getDeclaredMethod("checkResourcePresent", List.class); + Method ca = PatchForAtlasNotificationTopic_J10067.class.getDeclaredMethod("checkAccessPresent", List.class); + cr.setAccessible(true); + ca.setAccessible(true); + + RangerServiceDef.RangerResourceDef r = new RangerServiceDef.RangerResourceDef(); + r.setName("notification-topic"); + List rList = new ArrayList<>(); + rList.add(r); + Assertions.assertTrue((Boolean) cr.invoke(patch, rList)); + + RangerServiceDef.RangerAccessTypeDef a = new RangerServiceDef.RangerAccessTypeDef("post-notification"); + List aList = new ArrayList<>(); + aList.add(a); + Assertions.assertTrue((Boolean) ca.invoke(patch, aList)); + } + + @Test + public void testPrivate_Checkers_FalseBranches() throws Exception { + PatchForAtlasNotificationTopic_J10067 patch = new PatchForAtlasNotificationTopic_J10067(); + Method cr = PatchForAtlasNotificationTopic_J10067.class.getDeclaredMethod("checkResourcePresent", List.class); + Method ca = PatchForAtlasNotificationTopic_J10067.class.getDeclaredMethod("checkAccessPresent", List.class); + cr.setAccessible(true); + ca.setAccessible(true); + + RangerServiceDef.RangerResourceDef r = new RangerServiceDef.RangerResourceDef(); + r.setName("not-notification-topic"); + List rList = new ArrayList<>(); + rList.add(r); + Assertions.assertFalse((Boolean) cr.invoke(patch, rList)); + + RangerServiceDef.RangerAccessTypeDef a = new RangerServiceDef.RangerAccessTypeDef("not-post-notification"); + List aList = new ArrayList<>(); + aList.add(a); + Assertions.assertFalse((Boolean) ca.invoke(patch, aList)); + } +} From e1d5ec85c19b4002f41b75ee0000708666c99bd8 Mon Sep 17 00:00:00 2001 From: eoinmcdonnell113 Date: Mon, 14 Sep 2026 14:10:14 -0400 Subject: [PATCH 2/2] RANGER-5759: Authorize Atlas notifications via AtlasNotificationRequest Align with ATLAS-5388 by implementing isAccessAllowed(AtlasNotificationRequest) and removing the reflection-based admin-request path. --- .../authorizer/RangerAtlasAuthorizer.java | 79 ++++++++----------- .../services/atlas/RangerServiceAtlas.java | 1 - .../authorizer/TestRangerAtlasAuthorizer.java | 20 +---- .../authorizer/RangerAtlasAuthorizer.java | 8 ++ 4 files changed, 45 insertions(+), 63 deletions(-) diff --git a/plugin-atlas/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java b/plugin-atlas/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java index 7532b01bc65..0defeba3ecc 100644 --- a/plugin-atlas/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java +++ b/plugin-atlas/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java @@ -23,6 +23,7 @@ import org.apache.atlas.authorize.AtlasAdminAccessRequest; import org.apache.atlas.authorize.AtlasAuthorizer; import org.apache.atlas.authorize.AtlasEntityAccessRequest; +import org.apache.atlas.authorize.AtlasNotificationRequest; import org.apache.atlas.authorize.AtlasPrivilege; import org.apache.atlas.authorize.AtlasRelationshipAccessRequest; import org.apache.atlas.authorize.AtlasSearchResultScrubRequest; @@ -46,7 +47,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -56,8 +56,6 @@ import java.util.Map; import java.util.Set; -import static org.apache.ranger.services.atlas.RangerServiceAtlas.ACCESS_TYPE_POST_NOTIFICATION; -import static org.apache.ranger.services.atlas.RangerServiceAtlas.ACCESS_TYPE_SERVICE_NOTIFICATION_POST; import static org.apache.ranger.services.atlas.RangerServiceAtlas.ACCESS_TYPE_TYPE_READ; import static org.apache.ranger.services.atlas.RangerServiceAtlas.ENTITY_NOT_CLASSIFIED; import static org.apache.ranger.services.atlas.RangerServiceAtlas.RESOURCE_CLASSIFICATION; @@ -130,16 +128,41 @@ public boolean isAccessAllowed(AtlasAdminAccessRequest request) { } String action = request.getAction() != null ? request.getAction().getType() : null; - RangerAccessResourceImpl rangerResource; + RangerAccessResourceImpl rangerResource = new RangerAccessResourceImpl(Collections.singletonMap(RESOURCE_SERVICE, "*")); + RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl(rangerResource, action, request.getUser(), request.getUserGroups(), null); - if (isPostNotificationAction(action)) { - action = ACCESS_TYPE_POST_NOTIFICATION; - rangerResource = new RangerAccessResourceImpl(Collections.singletonMap(RESOURCE_NOTIFICATION_TOPIC, getNotificationTopic(request))); - } else { - rangerResource = new RangerAccessResourceImpl(Collections.singletonMap(RESOURCE_SERVICE, "*")); + rangerRequest.setClientIPAddress(request.getClientIPAddress()); + rangerRequest.setAccessTime(request.getAccessTime()); + rangerRequest.setAction(action); + rangerRequest.setForwardedAddresses(request.getForwardedAddresses()); + rangerRequest.setRemoteIPAddress(request.getRemoteIPAddress()); + + ret = checkAccess(rangerRequest); + } finally { + RangerPerfTracer.log(perf); + } + + LOG.debug("<== isAccessAllowed({}): {}", request, ret); + + return ret; + } + + @Override + public boolean isAccessAllowed(AtlasNotificationRequest request) { + LOG.debug("==> isAccessAllowed({})", request); + + final boolean ret; + RangerPerfTracer perf = null; + + try { + if (RangerPerfTracer.isPerfTraceEnabled(PERF_LOG)) { + perf = RangerPerfTracer.getPerfTracer(PERF_LOG, "RangerAtlasAuthorizer.isAccessAllowed(" + request + ")"); } - RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl(rangerResource, action, request.getUser(), request.getUserGroups(), null); + String action = request.getAction() != null ? request.getAction().getType() : null; + String topicName = StringUtils.isNotBlank(request.getTopicName()) ? request.getTopicName() : "*"; + RangerAccessResourceImpl rangerResource = new RangerAccessResourceImpl(Collections.singletonMap(RESOURCE_NOTIFICATION_TOPIC, topicName)); + RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl(rangerResource, action, request.getUser(), request.getUserGroups(), null); rangerRequest.setClientIPAddress(request.getClientIPAddress()); rangerRequest.setAccessTime(request.getAccessTime()); @@ -365,42 +388,6 @@ private RangerServiceDef getServiceDef() { return plugin != null ? plugin.getServiceDef() : null; } - private boolean isPostNotificationAction(String action) { - return ACCESS_TYPE_POST_NOTIFICATION.equals(action) || ACCESS_TYPE_SERVICE_NOTIFICATION_POST.equals(action); - } - - private String getNotificationTopic(AtlasAccessRequest request) { - String topic = invokeStringGetter(request, "getTopicName"); - - if (StringUtils.isBlank(topic)) { - topic = invokeStringGetter(request, "getTopic"); - } - - return StringUtils.isNotBlank(topic) ? topic : "*"; - } - - private String invokeStringGetter(Object target, String methodName) { - if (target == null) { - return null; - } - - try { - Method method = target.getClass().getMethod(methodName); - - if (method.getReturnType() == String.class) { - Object value = method.invoke(target); - - return value != null ? value.toString() : null; - } - } catch (NoSuchMethodException ignored) { - // Atlas 2.4 AtlasAdminAccessRequest has no topic accessor - } catch (Exception e) { - LOG.debug("Failed to invoke {} on {}", methodName, target.getClass().getName(), e); - } - - return null; - } - private boolean isAccessAllowed(AtlasEntityAccessRequest request, RangerAtlasAuditHandler auditHandler) { LOG.debug("==> isAccessAllowed({})", request); diff --git a/plugin-atlas/src/main/java/org/apache/ranger/services/atlas/RangerServiceAtlas.java b/plugin-atlas/src/main/java/org/apache/ranger/services/atlas/RangerServiceAtlas.java index 69daddf63a9..64e34e2a304 100644 --- a/plugin-atlas/src/main/java/org/apache/ranger/services/atlas/RangerServiceAtlas.java +++ b/plugin-atlas/src/main/java/org/apache/ranger/services/atlas/RangerServiceAtlas.java @@ -87,7 +87,6 @@ public class RangerServiceAtlas extends RangerBaseService { public static final String ACCESS_TYPE_ENTITY_UPDATE = "entity-update"; public static final String ACCESS_TYPE_ENTITY_DELETE = "entity-delete"; public static final String ACCESS_TYPE_POST_NOTIFICATION = "post-notification"; - public static final String ACCESS_TYPE_SERVICE_NOTIFICATION_POST = "service-notification-post"; public static final String ADMIN_USERNAME_DEFAULT = "admin"; public static final String TAGSYNC_USERNAME_DEFAULT = "rangertagsync"; public static final String ENTITY_TYPE_USER_PROFILE = "__AtlasUserProfile"; diff --git a/plugin-atlas/src/test/java/org/apache/ranger/authorization/atlas/authorizer/TestRangerAtlasAuthorizer.java b/plugin-atlas/src/test/java/org/apache/ranger/authorization/atlas/authorizer/TestRangerAtlasAuthorizer.java index 347bb66224e..5c6f0fbb9e2 100644 --- a/plugin-atlas/src/test/java/org/apache/ranger/authorization/atlas/authorizer/TestRangerAtlasAuthorizer.java +++ b/plugin-atlas/src/test/java/org/apache/ranger/authorization/atlas/authorizer/TestRangerAtlasAuthorizer.java @@ -19,6 +19,7 @@ import org.apache.atlas.authorize.AtlasAdminAccessRequest; import org.apache.atlas.authorize.AtlasEntityAccessRequest; +import org.apache.atlas.authorize.AtlasNotificationRequest; import org.apache.atlas.authorize.AtlasPrivilege; import org.apache.atlas.authorize.AtlasRelationshipAccessRequest; import org.apache.atlas.authorize.AtlasTypeAccessRequest; @@ -477,7 +478,7 @@ public void testAudits() { @Test public void testPostNotification() { - AtlasAdminAccessRequest request = new AtlasAdminAccessRequest(AtlasPrivilege.SERVICE_NOTIFICATION_POST); + AtlasNotificationRequest request = new AtlasNotificationRequest(AtlasPrivilege.POST_NOTIFICATION, TOPIC_ATLAS_HOOK); request.setUser(USER_USER1, USER_USER1_GROUPS); @@ -500,7 +501,7 @@ public void testPostNotification() { @Test public void testPostNotificationOnAllowedTopic() { - AtlasAdminAccessRequest request = new TopicAccessRequest(AtlasPrivilege.SERVICE_NOTIFICATION_POST, TOPIC_ATLAS_HOOK); + AtlasNotificationRequest request = new AtlasNotificationRequest(AtlasPrivilege.POST_NOTIFICATION, TOPIC_ATLAS_HOOK); request.setUser(USER_HOOK1, USER_HOOK1_GROUPS); @@ -511,7 +512,7 @@ public void testPostNotificationOnAllowedTopic() { @Test public void testPostNotificationOnDeniedTopic() { - AtlasAdminAccessRequest request = new TopicAccessRequest(AtlasPrivilege.SERVICE_NOTIFICATION_POST, TOPIC_OTHER); + AtlasNotificationRequest request = new AtlasNotificationRequest(AtlasPrivilege.POST_NOTIFICATION, TOPIC_OTHER); request.setUser(USER_HOOK1, USER_HOOK1_GROUPS); @@ -520,19 +521,6 @@ public void testPostNotificationOnDeniedTopic() { .isFalse(); } - private static class TopicAccessRequest extends AtlasAdminAccessRequest { - private final String topicName; - - TopicAccessRequest(AtlasPrivilege action, String topicName) { - super(action); - this.topicName = topicName; - } - - public String getTopicName() { - return topicName; - } - } - @Test public void testAddRelationship() { AtlasRelationshipAccessRequest request = new AtlasRelationshipAccessRequest(typeRegistry, AtlasPrivilege.RELATIONSHIP_ADD, RELATIONSHIP_TYPE_CLONE_OF, ENTITY_HIVE_TABLE_DB1_TBL1, ENTITY_HIVE_TABLE_DB1_TBL2); diff --git a/ranger-atlas-plugin-shim/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java b/ranger-atlas-plugin-shim/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java index 8c308c81eb8..1167b14ffe1 100644 --- a/ranger-atlas-plugin-shim/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java +++ b/ranger-atlas-plugin-shim/src/main/java/org/apache/ranger/authorization/atlas/authorizer/RangerAtlasAuthorizer.java @@ -23,6 +23,7 @@ import org.apache.atlas.authorize.AtlasAuthorizationException; import org.apache.atlas.authorize.AtlasAuthorizer; import org.apache.atlas.authorize.AtlasEntityAccessRequest; +import org.apache.atlas.authorize.AtlasNotificationRequest; import org.apache.atlas.authorize.AtlasRelationshipAccessRequest; import org.apache.atlas.authorize.AtlasSearchResultScrubRequest; import org.apache.atlas.authorize.AtlasTypeAccessRequest; @@ -91,6 +92,13 @@ public boolean isAccessAllowed(AtlasRelationshipAccessRequest request) throws At } } + @Override + public boolean isAccessAllowed(AtlasNotificationRequest request) throws AtlasAuthorizationException { + try (PluginClassLoaderActivator ignored = new PluginClassLoaderActivator(pluginClassLoader, "isAccessAllowed:notificationAccess")) { + return rangerAtlasAuthorizerImpl.isAccessAllowed(request); + } + } + @Override public void scrubSearchResults(AtlasSearchResultScrubRequest request) throws AtlasAuthorizationException { try (PluginClassLoaderActivator ignored = new PluginClassLoaderActivator(pluginClassLoader, "scrubSearchResults")) {