# 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);
    }