diff --git a/docker/api/docker.conf b/docker/api/docker.conf index c36851288..9a7f42e84 100644 --- a/docker/api/docker.conf +++ b/docker/api/docker.conf @@ -188,6 +188,9 @@ vinyldns { "fd69:27cc:fe91:0:0:0:ffff:0" ] } + + # types of unowned records that users can access in shared zones + shared-approved-types = ["A", "AAAA", "CNAME", "PTR"] } akka { diff --git a/modules/api/functional_test/live_tests/recordsets/create_recordset_test.py b/modules/api/functional_test/live_tests/recordsets/create_recordset_test.py index 5b2ebeb5c..4ae189196 100644 --- a/modules/api/functional_test/live_tests/recordsets/create_recordset_test.py +++ b/modules/api/functional_test/live_tests/recordsets/create_recordset_test.py @@ -1874,28 +1874,44 @@ def test_create_in_shared_zone_without_owner_group_id_succeeds(shared_zone_test_ delete_result = dummy_client.delete_recordset(create_rs['zoneId'], create_rs['id'], status=202) shared_client.wait_until_recordset_change_status(delete_result, 'Complete') -def test_create_in_shared_zone_by_unassociated_user_succeeds(shared_zone_test_context): +def test_create_in_shared_zone_by_unassociated_user_succeeds_if_record_type_is_approved(shared_zone_test_context): """ - Test that creating a record in a shared zone by an unassociated user succeeds + Test that creating a record in a shared zone by a user with no write permissions succeeds if the record type is approved """ - dummy_client = shared_zone_test_context.dummy_vinyldns_client - shared_client = shared_zone_test_context.shared_zone_vinyldns_client + client = shared_zone_test_context.dummy_vinyldns_client zone = shared_zone_test_context.shared_zone group = shared_zone_test_context.dummy_group + + record_json = get_recordset_json(zone, 'test_shared_approved_record_type', 'A', [{'address': '1.1.1.1'}]) + record_json['ownerGroupId'] = group['id'] + create_rs = None - record_json = get_recordset_json(zone, 'test_shared_bad_user', 'A', [{'address': '1.1.1.1'}], ownergroup_id=group['id']) - try: - create_response = dummy_client.create_recordset(record_json, status=202) - create_rs = shared_client.wait_until_recordset_change_status(create_response, 'Complete')['recordSet'] + create_response = client.create_recordset(record_json, status=202) + create_rs = client.wait_until_recordset_change_status(create_response, 'Complete')['recordSet'] assert_that(create_rs['ownerGroupId'], is_(group['id'])) finally: if create_rs: - delete_result = dummy_client.delete_recordset(create_rs['zoneId'], create_rs['id'], status=202) - shared_client.wait_until_recordset_change_status(delete_result, 'Complete') + delete_result = client.delete_recordset(zone['id'], create_rs['id'], status=202) + client.wait_until_recordset_change_status(delete_result, 'Complete') + + +def test_create_in_shared_zone_by_unassociated_user_fails_if_record_type_is_not_approved(shared_zone_test_context): + """ + Test that creating a record in a shared zone by a user with no write permissions fails if the record type is not approved + """ + + client = shared_zone_test_context.dummy_vinyldns_client + zone = shared_zone_test_context.shared_zone + group = shared_zone_test_context.dummy_group + + record_json = get_recordset_json(zone, 'test_shared_not_approved_record_type', 'MX', [{'preference': 3, 'exchange': 'mx'}]) + record_json['ownerGroupId'] = group['id'] + error = client.create_recordset(record_json, status=403) + assert_that(error, is_('User dummy does not have access to create test-shared-not-approved-record-type.shared.')) def test_create_with_not_found_owner_group_fails(shared_zone_test_context): """ diff --git a/modules/api/functional_test/live_tests/recordsets/delete_recordset_test.py b/modules/api/functional_test/live_tests/recordsets/delete_recordset_test.py index 3ec6b5708..153ebb0be 100644 --- a/modules/api/functional_test/live_tests/recordsets/delete_recordset_test.py +++ b/modules/api/functional_test/live_tests/recordsets/delete_recordset_test.py @@ -662,53 +662,6 @@ def test_no_delete_access_non_test_zone(shared_zone_test_context): client.delete_recordset(zone_id, record_delete['id'], status=403) -def test_delete_for_user_not_in_record_owner_group_in_shared_zone_fails(shared_zone_test_context): - """ - Test that a user cannot delete a record in a shared zone if not part of record owner group - """ - - dummy_client = shared_zone_test_context.dummy_vinyldns_client - shared_client = shared_zone_test_context.shared_zone_vinyldns_client - shared_zone = shared_zone_test_context.shared_zone - result_rs = None - - record_json = get_recordset_json(shared_zone, 'test_shared_del_nonog', 'A', [{'address': '1.1.1.1'}], ownergroup_id = shared_zone_test_context.shared_record_group['id']) - - try: - create_rs = shared_client.create_recordset(record_json, status=202) - result_rs = shared_client.wait_until_recordset_change_status(create_rs, 'Complete')['recordSet'] - - error = dummy_client.delete_recordset(shared_zone['id'], result_rs['id'], status=403) - assert_that(error, is_('User dummy does not have access to delete test-shared-del-nonog.shared.')) - - finally: - if result_rs: - delete_rs = shared_client.delete_recordset(result_rs['zoneId'], result_rs['id'], status=202) - shared_client.wait_until_recordset_change_status(delete_rs, 'Complete') - -def test_delete_for_user_in_record_owner_group_in_non_shared_zone_fails(shared_zone_test_context): - """ - Test that a user in record owner group cannot delete a record in a non-shared zone - """ - ok_client = shared_zone_test_context.ok_vinyldns_client - shared_client = shared_zone_test_context.shared_zone_vinyldns_client - ok_zone = shared_zone_test_context.ok_zone - result_rs = None - - record_json = get_recordset_json(ok_zone, 'test_non_shared_del_og', 'A', [{'address': '1.1.1.1'}], ownergroup_id = shared_zone_test_context.shared_record_group['id']) - - try: - create_rs = ok_client.create_recordset(record_json, status=202) - result_rs = ok_client.wait_until_recordset_change_status(create_rs, 'Complete')['recordSet'] - - error = shared_client.delete_recordset(ok_zone['id'], result_rs['id'], status=403) - assert_that(error, is_('User sharedZoneUser does not have access to delete test-non-shared-del-og.ok.')) - - finally: - if result_rs: - delete_rs = ok_client.delete_recordset(result_rs['zoneId'], result_rs['id'], status=202) - ok_client.wait_until_recordset_change_status(delete_rs, 'Complete') - def test_delete_for_user_in_record_owner_group_in_shared_zone_succeeds(shared_zone_test_context): """ Test that a user in record owner group can delete a record in a shared zone @@ -740,3 +693,90 @@ def test_delete_for_zone_admin_in_shared_zone_succeeds(shared_zone_test_context) delete_rs = shared_client.delete_recordset(result_rs['zoneId'], result_rs['id'], status=202) shared_client.wait_until_recordset_change_status(delete_rs, 'Complete') + +def test_delete_for_unowned_record_with_approved_record_type_in_shared_zone_succeeds(shared_zone_test_context): + """ + Test that a user not associated with a unowned record can delete it in a shared zone + """ + shared_client = shared_zone_test_context.shared_zone_vinyldns_client + shared_zone = shared_zone_test_context.shared_zone + ok_client = shared_zone_test_context.ok_vinyldns_client + + record_json = get_recordset_json(shared_zone, 'test_shared_approved_record_type', 'A', [{'address': '1.1.1.1'}]) + + create_rs = shared_client.create_recordset(record_json, status=202) + result_rs = shared_client.wait_until_recordset_change_status(create_rs, 'Complete')['recordSet'] + + delete_rs = ok_client.delete_recordset(result_rs['zoneId'], result_rs['id'], status=202) + ok_client.wait_until_recordset_change_status(delete_rs, 'Complete') + +def test_delete_for_user_not_in_record_owner_group_in_shared_zone_fails(shared_zone_test_context): + """ + Test that a user cannot delete a record in a shared zone if not part of record owner group + """ + + dummy_client = shared_zone_test_context.dummy_vinyldns_client + shared_client = shared_zone_test_context.shared_zone_vinyldns_client + shared_zone = shared_zone_test_context.shared_zone + result_rs = None + + record_json = get_recordset_json(shared_zone, 'test_shared_del_nonog', 'A', [{'address': '1.1.1.1'}], ownergroup_id = shared_zone_test_context.shared_record_group['id']) + + try: + create_rs = shared_client.create_recordset(record_json, status=202) + result_rs = shared_client.wait_until_recordset_change_status(create_rs, 'Complete')['recordSet'] + + error = dummy_client.delete_recordset(shared_zone['id'], result_rs['id'], status=403) + assert_that(error, is_('User dummy does not have access to delete test-shared-del-nonog.shared.')) + + finally: + if result_rs: + delete_rs = shared_client.delete_recordset(result_rs['zoneId'], result_rs['id'], status=202) + shared_client.wait_until_recordset_change_status(delete_rs, 'Complete') + +def test_delete_for_user_not_in_unowned_record_in_shared_zone_fails_if_record_type_is_not_approved(shared_zone_test_context): + """ + Test that a user cannot delete a record in a shared zone if the record is unowned and the record type is not approved + """ + + dummy_client = shared_zone_test_context.dummy_vinyldns_client + shared_client = shared_zone_test_context.shared_zone_vinyldns_client + shared_zone = shared_zone_test_context.shared_zone + result_rs = None + + record_json = get_recordset_json(shared_zone, 'test_shared_del_not_approved_record_type', 'MX', [{'preference': 3, 'exchange': 'mx'}]) + + try: + create_rs = shared_client.create_recordset(record_json, status=202) + result_rs = shared_client.wait_until_recordset_change_status(create_rs, 'Complete')['recordSet'] + + error = dummy_client.delete_recordset(shared_zone['id'], result_rs['id'], status=403) + assert_that(error, is_('User dummy does not have access to delete test-shared-del-not-approved-record-type.shared.')) + + finally: + if result_rs: + delete_rs = shared_client.delete_recordset(result_rs['zoneId'], result_rs['id'], status=202) + shared_client.wait_until_recordset_change_status(delete_rs, 'Complete') + +def test_delete_for_user_in_record_owner_group_in_non_shared_zone_fails(shared_zone_test_context): + """ + Test that a user in record owner group cannot delete a record in a non-shared zone + """ + ok_client = shared_zone_test_context.ok_vinyldns_client + shared_client = shared_zone_test_context.shared_zone_vinyldns_client + ok_zone = shared_zone_test_context.ok_zone + result_rs = None + + record_json = get_recordset_json(ok_zone, 'test_non_shared_del_og', 'A', [{'address': '1.1.1.1'}], ownergroup_id = shared_zone_test_context.shared_record_group['id']) + + try: + create_rs = ok_client.create_recordset(record_json, status=202) + result_rs = ok_client.wait_until_recordset_change_status(create_rs, 'Complete')['recordSet'] + + error = shared_client.delete_recordset(ok_zone['id'], result_rs['id'], status=403) + assert_that(error, is_('User sharedZoneUser does not have access to delete test-non-shared-del-og.ok.')) + + finally: + if result_rs: + delete_rs = ok_client.delete_recordset(result_rs['zoneId'], result_rs['id'], status=202) + ok_client.wait_until_recordset_change_status(delete_rs, 'Complete') diff --git a/modules/api/functional_test/live_tests/recordsets/get_recordset_test.py b/modules/api/functional_test/live_tests/recordsets/get_recordset_test.py index a6ea7ec54..5ca82043c 100644 --- a/modules/api/functional_test/live_tests/recordsets/get_recordset_test.py +++ b/modules/api/functional_test/live_tests/recordsets/get_recordset_test.py @@ -158,22 +158,47 @@ def test_get_recordset_from_shared_zone(shared_zone_test_context): delete_result = client.delete_recordset(retrieved_rs['zoneId'], retrieved_rs['id'], status=202) client.wait_until_recordset_change_status(delete_result, 'Complete') -def test_get_unowned_recordset_from_shared_zone(shared_zone_test_context): +def test_get_unowned_recordset_from_shared_zone_succeeds_if_record_type_approved(shared_zone_test_context): """ - Test getting an unowned recordset with no admin rights succeeds + Test getting an unowned recordset with no admin rights succeeds if the record type is approved + """ + client = shared_zone_test_context.shared_zone_vinyldns_client + ok_client = shared_zone_test_context.ok_vinyldns_client + result_rs = None + try: + new_rs = get_recordset_json(shared_zone_test_context.shared_zone, + "test_get_unowned_recordset_approved_type", "A", [{"address": "1.2.3.4"}]) + + result = client.create_recordset(new_rs, status=202) + result_rs = client.wait_until_recordset_change_status(result, 'Complete')['recordSet'] + + # Get the recordset we just made and verify + retrieved = ok_client.get_recordset(result_rs['zoneId'], result_rs['id'], status=200) + retrieved_rs = retrieved['recordSet'] + verify_recordset(retrieved_rs, new_rs) + + finally: + if result_rs: + delete_result = ok_client.delete_recordset(result_rs['zoneId'], result_rs['id'], status=202) + ok_client.wait_until_recordset_change_status(delete_result, 'Complete') + +def test_get_unowned_recordset_from_shared_zone_fails_if_record_type_not_approved(shared_zone_test_context): + """ + Test getting an unowned recordset with no admin rights fails if the record type is not approved """ client = shared_zone_test_context.shared_zone_vinyldns_client result_rs = None try: new_rs = get_recordset_json(shared_zone_test_context.shared_zone, - "test_get_unowned_recordset", "TXT", [{'text':'should-not-work'}]) + "test_get_unowned_recordset", "MX", [{'preference': 3, 'exchange': 'mx'}]) result = client.create_recordset(new_rs, status=202) result_rs = client.wait_until_recordset_change_status(result, 'Complete')['recordSet'] # Get the recordset we just made and verify ok_client = shared_zone_test_context.ok_vinyldns_client - ok_client.get_recordset(result_rs['zoneId'], result_rs['id'], status=200) + error = ok_client.get_recordset(result_rs['zoneId'], result_rs['id'], status=403) + assert_that(error, is_("User ok does not have access to view test-get-unowned-recordset.shared.")) finally: if result_rs: diff --git a/modules/api/functional_test/live_tests/recordsets/update_recordset_test.py b/modules/api/functional_test/live_tests/recordsets/update_recordset_test.py index e05b2cab3..cd88558e0 100644 --- a/modules/api/functional_test/live_tests/recordsets/update_recordset_test.py +++ b/modules/api/functional_test/live_tests/recordsets/update_recordset_test.py @@ -2032,11 +2032,11 @@ def test_update_owner_group_from_user_in_record_owner_group_for_shared_zone_pass ok_client = shared_zone_test_context.ok_vinyldns_client shared_record_group = shared_zone_test_context.shared_record_group shared_client = shared_zone_test_context.shared_zone_vinyldns_client - zone = shared_zone_test_context.shared_zone + shared_zone = shared_zone_test_context.shared_zone update_rs = None try: - record_json = get_recordset_json(zone, 'test_shared_success', 'A', [{'address': '1.1.1.1'}]) + record_json = get_recordset_json(shared_zone, 'test_shared_success', 'A', [{'address': '1.1.1.1'}]) record_json['ownerGroupId'] = shared_record_group['id'] create_response = shared_client.create_recordset(record_json, status=202) update = shared_client.wait_until_recordset_change_status(create_response, 'Complete')['recordSet'] @@ -2051,7 +2051,7 @@ def test_update_owner_group_from_user_in_record_owner_group_for_shared_zone_pass finally: if update_rs: - delete_result = shared_client.delete_recordset(zone['id'], update_rs['id'], status=202) + delete_result = shared_client.delete_recordset(shared_zone['id'], update_rs['id'], status=202) shared_client.wait_until_recordset_change_status(delete_result, 'Complete') @@ -2084,10 +2084,35 @@ def test_update_owner_group_from_admin_in_shared_zone_passes(shared_zone_test_co delete_result = shared_client.delete_recordset(zone['id'], update_rs['id'], status=202) shared_client.wait_until_recordset_change_status(delete_result, 'Complete') - -def test_update_from_unassociated_user_in_shared_zone_succeeds(shared_zone_test_context): +def test_update_from_unassociated_user_in_shared_zone_passes_when_record_type_is_approved(shared_zone_test_context): """ - Test that an unassociated user updating record without existing owner group ID in shared zone succeeds + Test that updating with a user that does not have write access succeeds in a shared zone if the record type is approved + """ + + ok_client = shared_zone_test_context.ok_vinyldns_client + shared_client = shared_zone_test_context.shared_zone_vinyldns_client + zone = shared_zone_test_context.shared_zone + update_rs = None + + try: + record_json = get_recordset_json(zone, 'test_shared_approved_record_type', 'A', [{'address': '1.1.1.1'}]) + create_response = shared_client.create_recordset(record_json, status=202) + create_rs = shared_client.wait_until_recordset_change_status(create_response, 'Complete')['recordSet'] + assert_that(create_rs, is_not(has_key('ownerGroupId'))) + + update = create_rs + update['ttl'] = update['ttl'] + 100 + update_response = ok_client.update_recordset(update, status=202) + update_rs = shared_client.wait_until_recordset_change_status(update_response, 'Complete')['recordSet'] + + finally: + if update_rs: + delete_result = shared_client.delete_recordset(zone['id'], update_rs['id'], status=202) + shared_client.wait_until_recordset_change_status(delete_result, 'Complete') + +def test_update_from_unassociated_user_in_shared_zone_fails(shared_zone_test_context): + """ + Test that updating with a user that does not have write access fails in a shared zone """ ok_client = shared_zone_test_context.ok_vinyldns_client @@ -2096,16 +2121,15 @@ def test_update_from_unassociated_user_in_shared_zone_succeeds(shared_zone_test_ create_rs = None try: - record_json = get_recordset_json(zone, 'test_shared_success', 'A', [{'address': '1.1.1.1'}]) + record_json = get_recordset_json(zone, 'test_shared_unapproved_record_type', 'MX', [{'preference': 3, 'exchange': 'mx'}]) create_response = shared_client.create_recordset(record_json, status=202) create_rs = shared_client.wait_until_recordset_change_status(create_response, 'Complete')['recordSet'] assert_that(create_rs, is_not(has_key('ownerGroupId'))) update = create_rs update['ttl'] = update['ttl'] + 100 - update_response = ok_client.update_recordset(update, status=202) - update_rs = shared_client.wait_until_recordset_change_status(update_response, 'Complete') - assert_that(update_rs, is_not(has_key('ownerGroupId'))) + error = ok_client.update_recordset(update, status=403) + assert_that(error, is_('User ok does not have access to update test-shared-unapproved-record-type.shared.')) finally: if create_rs: @@ -2127,7 +2151,7 @@ def test_update_from_acl_for_shared_zone_passes(shared_zone_test_context): try: add_shared_zone_acl_rules(shared_zone_test_context, [acl_rule]) - record_json = get_recordset_json(zone, 'test_shared_success', 'A', [{'address': '1.1.1.1'}]) + record_json = get_recordset_json(zone, 'test_shared_acl', 'A', [{'address': '1.1.1.1'}]) create_response = shared_client.create_recordset(record_json, status=202) update = shared_client.wait_until_recordset_change_status(create_response, 'Complete')['recordSet'] assert_that(update, is_not(has_key('ownerGroupId'))) diff --git a/modules/api/src/it/resources/application.conf b/modules/api/src/it/resources/application.conf index 935ac6cd1..c7132b079 100644 --- a/modules/api/src/it/resources/application.conf +++ b/modules/api/src/it/resources/application.conf @@ -53,6 +53,8 @@ vinyldns { ] } + # types of unowned records that users can access in shared zones + shared-approved-types = ["A", "AAAA", "CNAME", "PTR"] dynamodb.repositories { record-set { diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 9a26a8879..ff6e1e850 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -116,4 +116,7 @@ vinyldns { "fd69:27cc:fe91:0:0:0:ffff:0" ] } + + # types of unowned records that users can access in shared zones + shared-approved-types = ["A", "AAAA", "CNAME", "PTR"] } diff --git a/modules/api/src/main/scala/vinyldns/api/VinylDNSConfig.scala b/modules/api/src/main/scala/vinyldns/api/VinylDNSConfig.scala index ab8d4abe6..92a424abb 100644 --- a/modules/api/src/main/scala/vinyldns/api/VinylDNSConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/VinylDNSConfig.scala @@ -23,6 +23,9 @@ import com.typesafe.config.{Config, ConfigFactory} import pureconfig.module.catseffect.loadConfigF import vinyldns.api.crypto.Crypto import com.comcast.ip4s._ +import net.ceedubs.ficus.Ficus._ +import net.ceedubs.ficus.readers.EnumerationReader._ +import vinyldns.core.domain.record.RecordType import scala.collection.JavaConverters._ import scala.util.matching.Regex @@ -63,6 +66,9 @@ object VinylDNSConfig { lazy val highValueIpList: List[IpAddress] = getOptionalStringList("high-value-domains.ip-list").flatMap(ip => IpAddress(ip)) + lazy val sharedApprovedTypes: Set[RecordType.Value] = + vinyldnsConfig.as[Option[Set[RecordType.Value]]]("shared-approved-types").getOrElse(Set()) + lazy val defaultZoneConnection: ZoneConnection = { val connectionConfig = VinylDNSConfig.vinyldnsConfig.getConfig("defaultZoneConnection") val name = connectionConfig.getString("name") diff --git a/modules/api/src/main/scala/vinyldns/api/domain/AccessValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/AccessValidations.scala index 826d5663a..b75de0753 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/AccessValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/AccessValidations.scala @@ -17,6 +17,7 @@ package vinyldns.api.domain import vinyldns.api.Interfaces.ensuring +import vinyldns.api.VinylDNSConfig import vinyldns.api.domain.zone._ import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.record.RecordType @@ -184,11 +185,18 @@ object AccessValidations extends AccessValidationAlgebra { case testUser if testUser.isTestUser && !zone.isTest => AccessLevel.NoAccess case admin if admin.canEditAll || admin.isGroupMember(zone.adminGroupId) => AccessLevel.Delete - case recordOwner if zone.shared && recordOwnerGroupId.forall(recordOwner.isGroupMember) => + case recordOwner if zone.shared && sharedRecordAccess(recordOwner, recordType, recordOwnerGroupId) => AccessLevel.Delete case supportUser if supportUser.canReadAll => val aclAccess = getAccessFromAcl(auth, recordName, recordType, zone) if (aclAccess == AccessLevel.NoAccess) AccessLevel.Read else aclAccess case _ => getAccessFromAcl(auth, recordName, recordType, zone) } + + def sharedRecordAccess( + auth: AuthPrincipal, + recordType: RecordType, + recordOwnerGroupId: Option[String]): Boolean = + recordOwnerGroupId.exists(auth.isGroupMember) || + (recordOwnerGroupId.isEmpty && VinylDNSConfig.sharedApprovedTypes.contains(recordType)) } diff --git a/modules/api/src/test/resources/application.conf b/modules/api/src/test/resources/application.conf index 154033d70..9f0605430 100644 --- a/modules/api/src/test/resources/application.conf +++ b/modules/api/src/test/resources/application.conf @@ -37,6 +37,9 @@ vinyldns { ] } + # types of unowned records that users can access in shared zones + shared-approved-types = ["A", "AAAA", "CNAME", "PTR"] + # used for testing only string-list-test = ["test"] diff --git a/modules/api/src/test/scala/vinyldns/api/domain/AccessValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/AccessValidationsSpec.scala index f238f31e7..15cc39943 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/AccessValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/AccessValidationsSpec.scala @@ -490,18 +490,28 @@ class AccessValidationsSpec result shouldBe AccessLevel.Delete } - "return AccessLevel.Delete if the record is unowned and the zone is shared" in { - val recordOwnerAuth = AuthPrincipal(dummyUser.copy(isSupport = true), Seq()) + "return AccessLevel.Delete if the zone is shared and the record is unowned and an approved record type" in { val result = accessValidationTest.getAccessLevel( - recordOwnerAuth, + okAuth, sharedZoneRecordNoOwnerGroup.name, - sharedZoneRecordNoOwnerGroup.typ, + RecordType.AAAA, sharedZone, - sharedZoneRecordNoOwnerGroup.ownerGroupId) + None) result shouldBe AccessLevel.Delete } + "return AccessLevel.NoAccess if the zone is shared and the record is unowned but not an approved record type" in { + val result = + accessValidationTest.getAccessLevel( + okAuth, + sharedZoneRecordNotApprovedRecordType.name, + RecordType.MX, + sharedZone, + None) + result shouldBe AccessLevel.NoAccess + } + "return the result of getAccessLevel if the user is a record owner but zone is not shared" in { val result = accessValidationTest.getAccessLevel( diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 9b93c86aa..8fdaf8e1a 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -654,17 +654,32 @@ class RecordSetServiceSpec result shouldBe a[NotAuthorizedError] } - "fail when the account is not authorized for the record" in { - doReturn(IO.pure(Some(sharedZoneRecordNotFoundOwnerGroup))) + "return the unowned record in a shared zone when the record has an approved record type" in { + doReturn(IO.pure(Some(sharedZoneRecordNoOwnerGroup))) .when(mockRecordRepo) .getRecordSet(sharedZone.id, sharedZoneRecordNotFoundOwnerGroup.id) doReturn(IO.pure(None)).when(mockGroupRepo).getGroup(any[String]) + val expectedRecordSetInfo = RecordSetInfo(sharedZoneRecordNoOwnerGroup, None) + + val result: RecordSetInfo = + rightResultOf( + underTest.getRecordSet(sharedZoneRecordNoOwnerGroup.id, sharedZone.id, sharedAuth).value) + result shouldBe expectedRecordSetInfo + } + + "fail when the unowned record in a shared zone is not an approved record type and user is unassociated with it" in { + doReturn(IO.pure(Some(sharedZoneRecordNotApprovedRecordType))) + .when(mockRecordRepo) + .getRecordSet(sharedZone.id, sharedZoneRecordNotApprovedRecordType.id) + + doReturn(IO.pure(None)).when(mockGroupRepo).getGroup(any[String]) + val result = leftResultOf( underTest - .getRecordSet(sharedZoneRecordNotFoundOwnerGroup.id, sharedZone.id, okAuth) + .getRecordSet(sharedZoneRecordNotApprovedRecordType.id, sharedZone.id, okAuth) .value) result shouldBe a[NotAuthorizedError] } diff --git a/modules/core/src/test/scala/vinyldns/core/TestRecordSetData.scala b/modules/core/src/test/scala/vinyldns/core/TestRecordSetData.scala index be8a1f9e2..03e7a09af 100644 --- a/modules/core/src/test/scala/vinyldns/core/TestRecordSetData.scala +++ b/modules/core/src/test/scala/vinyldns/core/TestRecordSetData.scala @@ -183,6 +183,17 @@ object TestRecordSetData { val sharedZoneRecordNotFoundOwnerGroup: RecordSet = sharedZoneRecord.copy(name = "records", ownerGroupId = Some("not-in-backend")) + val sharedZoneRecordNotApprovedRecordType: RecordSet = + RecordSet( + sharedZone.id, + "mxsharedrecord", + RecordType.MX, + 200, + RecordSetStatus.Pending, + DateTime.now, + None, + List(MXData(3, "mx"))) + /* RECORDSET CHANGES */ def makeTestAddChange( diff --git a/modules/portal/.gitignore b/modules/portal/.gitignore index b2709c0be..872a17b7a 100644 --- a/modules/portal/.gitignore +++ b/modules/portal/.gitignore @@ -6,6 +6,7 @@ bower_components/ node_modules/ public/javascripts/ public/stylesheets/ +public/test_frameworks/ public/custom/views.vinyl.js package-lock.json release.version diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 07b38fc9e..73242ee4e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -48,7 +48,8 @@ object Dependencies { "org.typelevel" %% "cats-effect" % catsEffectV, "com.47deg" %% "github4s" % "0.18.6", "com.comcast" %% "ip4s-core" % ip4sV, - "com.comcast" %% "ip4s-cats" % ip4sV + "com.comcast" %% "ip4s-cats" % ip4sV, + "com.iheart" %% "ficus" % "1.4.3" ) lazy val coreDependencies = Seq(