|
| 1 | +package test.integration.auth; |
| 2 | + |
| 3 | +import com.bazaarvoice.emodb.auth.apikey.ApiKey; |
| 4 | +import com.bazaarvoice.emodb.auth.identity.TableAuthIdentityManager; |
| 5 | +import com.bazaarvoice.emodb.sor.api.AuditBuilder; |
| 6 | +import com.bazaarvoice.emodb.sor.api.DataStore; |
| 7 | +import com.bazaarvoice.emodb.sor.api.Intrinsic; |
| 8 | +import com.bazaarvoice.emodb.sor.core.test.InMemoryDataStore; |
| 9 | +import com.bazaarvoice.emodb.sor.delta.Deltas; |
| 10 | +import com.bazaarvoice.emodb.sor.uuid.TimeUUIDs; |
| 11 | +import com.codahale.metrics.MetricRegistry; |
| 12 | +import com.google.common.collect.ImmutableList; |
| 13 | +import com.google.common.collect.ImmutableMap; |
| 14 | +import com.google.common.collect.ImmutableSet; |
| 15 | +import com.google.common.hash.Hashing; |
| 16 | +import org.testng.annotations.Test; |
| 17 | + |
| 18 | +import java.util.Map; |
| 19 | +import java.util.Set; |
| 20 | + |
| 21 | +import static org.testng.Assert.assertEquals; |
| 22 | +import static org.testng.Assert.assertFalse; |
| 23 | +import static org.testng.Assert.assertNotNull; |
| 24 | + |
| 25 | +public class TableAuthIdentityManagerTest { |
| 26 | + |
| 27 | + /** |
| 28 | + * There are two tables which store identities in TableAuthIdentityManager: One table keyed by a hash of the |
| 29 | + * API key, and an index table ID'd by the internal ID which contains the API key hash. This second table is used |
| 30 | + * to look up API keys by internal ID. It should be rare, but it is possible for an API key record to exist |
| 31 | + * without a corresponding internal ID. One possible way for this to happen is grandfathered in API keys |
| 32 | + * created before the introduction of internal IDs. TableAuthIdentityManager should rebuild the index |
| 33 | + * when there is a missing or incorrect index record. This test verifies that works as expected. |
| 34 | + */ |
| 35 | + @Test |
| 36 | + public void testRebuildInternalIdIndex() { |
| 37 | + DataStore dataStore = new InMemoryDataStore(new MetricRegistry()); |
| 38 | + TableAuthIdentityManager<ApiKey> tableAuthIdentityManager = new TableAuthIdentityManager<>( |
| 39 | + ApiKey.class, dataStore, "__auth:keys", "__auth:internal_ids", "app_global:sys", Hashing.sha256()); |
| 40 | + |
| 41 | + ApiKey apiKey = new ApiKey("testkey", "id0", ImmutableSet.of("role1", "role2")); |
| 42 | + apiKey.setOwner("testowner"); |
| 43 | + tableAuthIdentityManager.updateIdentity(apiKey); |
| 44 | + |
| 45 | + // Verify both tables have been written |
| 46 | + |
| 47 | + String keyTableId = Hashing.sha256().hashUnencodedChars("testkey").toString(); |
| 48 | + |
| 49 | + Map<String, Object> keyMap = dataStore.get("__auth:keys", keyTableId); |
| 50 | + assertFalse(Intrinsic.isDeleted(keyMap)); |
| 51 | + assertEquals(keyMap.get("owner"), "testowner"); |
| 52 | + |
| 53 | + Map<String, Object> indexMap = dataStore.get("__auth:internal_ids", "id0"); |
| 54 | + assertFalse(Intrinsic.isDeleted(indexMap)); |
| 55 | + assertEquals(indexMap.get("hashedId"), keyTableId); |
| 56 | + |
| 57 | + // Deliberately delete the index map record |
| 58 | + dataStore.update("__auth:internal_ids", "id0", TimeUUIDs.newUUID(), Deltas.delete(), |
| 59 | + new AuditBuilder().setComment("test delete").build()); |
| 60 | + |
| 61 | + // Verify that a lookup by internal ID works |
| 62 | + Set<String> roles = tableAuthIdentityManager.getRolesByInternalId("id0"); |
| 63 | + assertEquals(roles, ImmutableSet.of("role1", "role2")); |
| 64 | + |
| 65 | + // Verify that the index record is re-created |
| 66 | + indexMap = dataStore.get("__auth:internal_ids", "id0"); |
| 67 | + assertFalse(Intrinsic.isDeleted(indexMap)); |
| 68 | + assertEquals(indexMap.get("hashedId"), keyTableId); |
| 69 | + } |
| 70 | + |
| 71 | + @Test |
| 72 | + public void testGrandfatheredInInternalId() { |
| 73 | + DataStore dataStore = new InMemoryDataStore(new MetricRegistry()); |
| 74 | + TableAuthIdentityManager<ApiKey> tableAuthIdentityManager = new TableAuthIdentityManager<>( |
| 75 | + ApiKey.class, dataStore, "__auth:keys", "__auth:internal_ids", "app_global:sys", Hashing.sha256()); |
| 76 | + |
| 77 | + // Perform an operation on tableAuthIdentityManager to force it to create API key tables; the actual |
| 78 | + // operation doesn't matter. |
| 79 | + tableAuthIdentityManager.getIdentity("ignore"); |
| 80 | + |
| 81 | + String id = "aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllll"; |
| 82 | + String hash = Hashing.sha256().hashUnencodedChars(id).toString(); |
| 83 | + |
| 84 | + // Write out a record which mimics the pre-internal-id format. Notably missing is the "internalId" attribute. |
| 85 | + Map<String, Object> oldIdentityMap = ImmutableMap.<String, Object>builder() |
| 86 | + .put("maskedId", "aaaa****************************************llll") |
| 87 | + .put("owner", "someone") |
| 88 | + .put("description", "something") |
| 89 | + .put("roles", ImmutableList.of("role1", "role2")) |
| 90 | + .build(); |
| 91 | + |
| 92 | + dataStore.update("__auth:keys", hash, TimeUUIDs.newUUID(), Deltas.literal(oldIdentityMap), |
| 93 | + new AuditBuilder().setComment("test grandfathering").build()); |
| 94 | + |
| 95 | + // Verify the record can be read by ID. The key's internal ID will be the hashed ID. |
| 96 | + ApiKey apiKey = tableAuthIdentityManager.getIdentity(id); |
| 97 | + assertNotNull(apiKey); |
| 98 | + assertEquals(apiKey.getId(), id); |
| 99 | + assertEquals(apiKey.getInternalId(), hash); |
| 100 | + assertEquals(apiKey.getOwner(), "someone"); |
| 101 | + assertEquals(apiKey.getDescription(), "something"); |
| 102 | + assertEquals(apiKey.getRoles(), ImmutableList.of("role1", "role2")); |
| 103 | + |
| 104 | + // Verify that a lookup by internal ID works |
| 105 | + Set<String> roles = tableAuthIdentityManager.getRolesByInternalId(hash); |
| 106 | + assertEquals(roles, ImmutableSet.of("role1", "role2")); |
| 107 | + |
| 108 | + // Verify that the index record was created with the hashed ID as the internal ID |
| 109 | + Map<String, Object> indexMap = dataStore.get("__auth:internal_ids", hash); |
| 110 | + assertFalse(Intrinsic.isDeleted(indexMap)); |
| 111 | + assertEquals(indexMap.get("hashedId"), hash); |
| 112 | + |
| 113 | + // Verify lookup by internal ID still works with the index record in place |
| 114 | + roles = tableAuthIdentityManager.getRolesByInternalId(hash); |
| 115 | + assertEquals(roles, ImmutableSet.of("role1", "role2")); |
| 116 | + } |
| 117 | +} |
0 commit comments