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..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;
@@ -70,6 +71,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;
@@ -145,6 +147,39 @@ public boolean isAccessAllowed(AtlasAdminAccessRequest request) {
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 + ")");
+ }
+
+ 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());
+ 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(AtlasEntityAccessRequest request) {
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..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
@@ -79,12 +79,14 @@ 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 ADMIN_USERNAME_DEFAULT = "admin";
public static final String TAGSYNC_USERNAME_DEFAULT = "rangertagsync";
public static final String ENTITY_TYPE_USER_PROFILE = "__AtlasUserProfile";
@@ -265,6 +267,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 +390,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..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;
@@ -45,6 +46,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 +476,51 @@ public void testAudits() {
.isTrue();
}
+ @Test
+ public void testPostNotification() {
+ AtlasNotificationRequest request = new AtlasNotificationRequest(AtlasPrivilege.POST_NOTIFICATION, TOPIC_ATLAS_HOOK);
+
+ 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() {
+ AtlasNotificationRequest request = new AtlasNotificationRequest(AtlasPrivilege.POST_NOTIFICATION, 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() {
+ AtlasNotificationRequest request = new AtlasNotificationRequest(AtlasPrivilege.POST_NOTIFICATION, 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();
+ }
+
@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/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")) {
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 8ecc59f4aa4..3fa29fa2ce6 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
@@ -2029,4 +2029,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 ddca10bb5d5..315c13a3b95 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
@@ -2253,5 +2253,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 654d19aa732..f59166bbe7b 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
@@ -2186,6 +2186,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 678fe343b91..91b5ccd4f06 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
@@ -2421,6 +2421,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 68c864e74e1..66b12452735 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
@@ -4626,5 +4626,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));
+ }
+}