Framework/Core/SVT/AAD/AAD.Application.ps1

Set-StrictMode -Version Latest 
class Application: SVTBase
{    
    hidden [PSObject] $ResourceObject;

    Application([string] $tenantId, [SVTResource] $svtResource): Base($tenantId,$svtResource) 
    {

        $objId = $svtResource.ResourceId
        $this.ResourceObject = Get-AzureADObjectByObjectId -ObjectIds $objId
    }

    hidden [PSObject] GetResourceObject()
    {
        return $this.ResourceObject;
    }

    hidden [ControlResult] CheckOldTestDemoApps([ControlResult] $controlResult)
    {
        $demoAppNames = $this.ControlSettings.Application.TestDemoPoCNames 
        $demoAppsRegex = [string]::Join('|', $demoAppNames) 

        $app = $this.GetResourceObject()
        $appName = $app.DisplayName

        if ($appName -eq $null -or -not ($appName -imatch $demoAppsRegex))
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                "No demo/test/pilot apps found.");
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Verify,
                                "Found one or more demo/test apps. Review and cleanup.","(TODO) Review apps that are not in use.");
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckReturnURLsAreHTTPS([ControlResult] $controlResult)
    {
        $app = $this.GetResourceObject()
        $ret = $false
        if($app.replyURLs -eq $null -or $app.replyURLs.Count -eq 0)
        {
            $ret = $true
        }
        else
        {
            $nonHttpURLs = @()
            foreach ($url  in $app.replyURLs)
            {
                if ($url.tolower().startswith("http:"))
                {
                    $nonHttpURLs += $url
                }
            }

            if ($nonHttpURLs.Count -eq 0)
            {
                $ret = $true
            }
            else
            {
                $controlResult.AddMessage("Found $($nonHttpURLs.Count) non-HTTPS URLs.");
            }
        }
        
        if ($ret -eq $true)
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        "No non-HTTPS URLs in replyURLs.");
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                        "Found one or more non-HTTPS URLs in replyURLs.","(TODO) Please review and change them to HTTPS.");
        }

        return $controlResult;
    }

    hidden [ControlResult] CheckHomePageIsHTTPS([ControlResult] $controlResult)
    {
        $app = $this.GetResourceObject()

        if ((-not [String]::IsNullOrEmpty($app.HomePage)) -and $app.Homepage.ToLower().startswith('http:'))
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                        "Homepage url [$($app.HomePage)] for app [$($app.DisplayName)] is not HTTPS.");
        }
        <# elseif ([String]::IsNullOrEmpty($app.HomePage)) #TODO: Given API apps/functions etc. should we enforce this?
        {
            $controlResult.AddMessage([VerificationResult]::Verify,
                                    "Homepage url not set for app: [$($app.DisplayName)].");
        } #>

        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        "Homepage url for app [$($app.DisplayName)] is empty/HTTPS: [$($app.HomePage)].");
        }

        return $controlResult;
    }

    hidden [ControlResult] CheckLogoutURLIsHTTPS([ControlResult] $controlResult)
    {
        $app = $this.GetResourceObject()

        if ((-not [String]::IsNullOrEmpty($app.LogoutUrl)) -and $app.LogoutURL.ToLower().startswith('http:'))
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                        "Logout url [$($app.LogoutUrl)] for app [$($app.DisplayName)] is not HTTPS.");
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        "Logout url for app [$($app.DisplayName)] is empty/HTTPS: [$($app.LogoutUrl)].");
        }

        return $controlResult;
    }

    hidden [ControlResult] CheckPrivacyDisclosure([ControlResult] $controlResult)
    {
        $app = $this.GetResourceObject()

        if ([String]::IsNullOrEmpty($app.InformationalUrls.Privacy) -or (-not ($app.InformationalUrls.Privacy -match [Constants]::RegExForValidURL)))
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                    [MessageData]::new("App [$($app.DisplayName)] does not have a privacy disclosure URL set."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("App [$($app.DisplayName)] has a privacy disclosure URL: [$($app.InformationalUrls.Privacy)]."));
        }
        return $controlResult
    }


    hidden [ControlResult] CheckAppIsCurrentTenantOnly([ControlResult] $controlResult)
    {
        $app = $this.GetResourceObject()
        
        #Currently there are 2 places this might be set, AvailableToOtherTenants setting or SignInAudience = "AzureADMultipleOrgs" (latter is new)
        if ( ($app.AvailableToOtherTenants -eq $true) -or
            (-not [String]::IsNullOrEmpty($app.SignInAudience)) -and ($app.SignInAudience -ne "AzureADMyOrg"))
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                    [MessageData]::new("The app [$($app.DisplayName)] is not limited to current enterprise tenant."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("App [$($app.DisplayName)] is limited to current enterprise tenant."));
        }
        return $controlResult
    }

    
    hidden [ControlResult] CheckOrphanedApp([ControlResult] $controlResult)
    {
        $app = $this.GetResourceObject()

        $owners = [array] (Get-AzureADApplicationOwner -ObjectId $app.ObjectId)
        if ($owners -eq $null -or $owners.Count -eq 0)
        {
                $controlResult.AddMessage([VerificationResult]::Failed,
                                        [MessageData]::new("App [$($app.DisplayName)] has no owner configured."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("App [$($app.DisplayName)] has an owner configured."));
        }
        return $controlResult;
    }
    
    hidden [ControlResult] CheckAppFTEOwner([ControlResult] $controlResult)
    {
        $app = $this.GetResourceObject()

        $owners = [array] (Get-AzureADApplicationOwner -ObjectId $app.ObjectId)
        if ($owners -eq $null -or $owners.Count -eq 0)
        {
                $controlResult.AddMessage([VerificationResult]::Failed,
                                        [MessageData]::new("App [$($app.DisplayName)] has no owner configured."));
        }
        elseif ($owners.Count -gt 0)
        {
            $bFTE = $false
            $owners | % { 
                #If one of the users is non-Guest (== 'Member'), we are good.
                if ($_.UserType -ne 'Guest') {$bFTE = $true}
            }
            if ($bFTE)
            {
                $controlResult.AddMessage([VerificationResult]::Passed,
                                    [MessageData]::new("One or more owners of app [$($app.DisplayName)] are FTEs."));
            }
            else {
                $controlResult.AddMessage([VerificationResult]::Failed,
                                    [MessageData]::new("All owners of app: [$($app.DisplayName)] are 'Guest' users. At least one FTE owner should be added."));                
            }
        }
        return $controlResult;
    }

}