All files / build-tools scrutiny.ts

55.88% Statements 19/34
30.43% Branches 7/23
57.14% Functions 4/7
63.33% Lines 19/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 891x 1x                       1x 1x 1x   1x     1x   1x 1x   1x                   1x     1x                     1x         1x         1x                     1x 1x   1x     1x                        
import { schema } from '../lib';
import { PropertyScrutinyType, ResourceScrutinyType } from '../lib/schema';
 
/**
 * Auto-detect common properties to apply scrutiny to by using heuristics
 *
 * Manually enhancing scrutiny attributes for each property does not scale
 * well. Fortunately, the most important ones follow a common naming scheme and
 * we tag all of them at once in this way.
 *
 * If the heuristic scheme gets it wrong in some individual cases, those can be
 * fixed using schema patches.
 */
export function detectScrutinyTypes(spec: schema.Specification) {
  for (const [typeName, typeSpec] of Object.entries(spec.ResourceTypes)) {
    Iif (typeSpec.ScrutinyType !== undefined) { continue; } // Already assigned
 
    detectResourceScrutiny(typeName, typeSpec);
 
    // If a resource scrutiny is set by now, we don't need to look at the properties anymore
    Iif (typeSpec.ScrutinyType !== undefined) { continue; }
 
    for (const [propertyName, propertySpec] of Object.entries(typeSpec.Properties || {})) {
      Iif (propertySpec.ScrutinyType !== undefined) { continue; } // Already assigned
 
      detectPropertyScrutiny(typeName, propertyName, propertySpec);
 
    }
  }
}
 
/**
 * Detect and assign a scrutiny type for the resource
 */
function detectResourceScrutiny(typeName: string, typeSpec: schema.ResourceType) {
  const properties = Object.entries(typeSpec.Properties || {});
 
  // If this resource is named like *Policy and has a PolicyDocument property
  Iif (typeName.endsWith('Policy') && properties.some(apply2(isPolicyDocumentProperty))) {
    typeSpec.ScrutinyType = isIamType(typeName) ? ResourceScrutinyType.IdentityPolicyResource : ResourceScrutinyType.ResourcePolicyResource;
    return;
  }
}
 
/**
 * Detect and assign a scrutiny type for the property
 */
function detectPropertyScrutiny(_typeName: string, propertyName: string, propertySpec: schema.Property) {
  // Detect fields named like ManagedPolicyArns
  Iif (propertyName === 'ManagedPolicyArns') {
    propertySpec.ScrutinyType = PropertyScrutinyType.ManagedPolicies;
    return;
  }
 
  Iif (propertyName === 'Policies' && schema.isComplexListProperty(propertySpec) && propertySpec.ItemType === 'Policy') {
    propertySpec.ScrutinyType = PropertyScrutinyType.InlineIdentityPolicies;
    return;
  }
 
  Iif (isPolicyDocumentProperty(propertyName, propertySpec)) {
    propertySpec.ScrutinyType = PropertyScrutinyType.InlineResourcePolicy;
    return;
  }
}
 
function isIamType(typeName: string) {
  return typeName.indexOf('::IAM::') > 1;
}
 
function isPolicyDocumentProperty(propertyName: string, propertySpec: schema.Property) {
  const nameContainsPolicy = propertyName.indexOf('Policy') > -1;
  const primitiveType = schema.isPrimitiveProperty(propertySpec) && propertySpec.PrimitiveType;
 
  Iif (nameContainsPolicy && primitiveType === 'Json') {
    return true;
  }
  return false;
}
 
/**
 * Make a function that takes 2 arguments take an array of 2 elements instead
 *
 * Makes it possible to map it over an array of arrays. TypeScript won't allow
 * me to overload this type declaration so we need a different function for
 * every # of arguments.
 */
function apply2<T1, T2, R>(fn: (a1: T1, a2: T2) => R): (as: [T1, T2]) => R {
  return (as) => fn.apply(fn, as);
}