Framework/Core/SVT/AAD/AAD.Tenant.ps1

Set-StrictMode -Version Latest 
class Tenant: SVTBase
{    
    hidden [PSObject] $AADPermissions;

    hidden [PSObject] $CASettings;
    hidden [PSObject] $AdminMFASettings;
    hidden [PSObject] $B2BSettings;
    hidden [PSObject] $MFASettings;
    hidden [PSObject] $SSPRSettings;
    hidden [PSObject] $EnterpriseAppSettings;
    hidden [PSObject] $MFABypassList;
    static [int] $RecommendedMaxDevicePerUserLimit = 20; #TODO: ControlSettings
    hidden [PSObject] $DeviceSettings;

    Tenant([string] $tenantId, [SVTResource] $svtResource): Base($tenantId, $svtResource) 
    {
        $this.GetAADSettings()
    }

    hidden GetAADSettings()
    {
        if ($this.AADPermissions -eq $null)
        {
            $this.AADPermissions = [WebRequestHelper]::InvokeAADAPI("/api/Permissions")
        }

        if ($this.CASettings -eq $null)
        {
            $this.CASettings = [WebRequestHelper]::InvokeAADAPI("/api/PasswordReset/PasswordResetPolicies")
        }

        if ($this.AdminMFASettings -eq $null)
        {
            $this.AdminMFASettings = [WebRequestHelper]::InvokeAADAPI("/api/BaselinePolicies/RequireMfaForAdmins")
        }

        if ($this.MFASettings -eq $null)
        {
            $this.MFASettings = [WebRequestHelper]::InvokeAADAPI("/api/MultiFactorAuthentication/TenantModel")
        }

        if ($this.B2BSettings -eq $null)
        {
            $this.B2BSettings = [WebRequestHelper]::InvokeAADAPI("/api/Directories/B2BDirectoryProperties")
        }

        if ($this.DeviceSettings -eq $null)
        {
            $this.DeviceSettings = [WebRequestHelper]::InvokeAADAPI("/api/DeviceSetting")
        }

        if ($this.MFABypassList -eq $null)
        {
            $this.MFABypassList = [WebRequestHelper]::InvokeAADAPI("/api/MultifactorAuthentication/BypassedUser")
        }

        if ($this.SSPRSettings -eq $null)
        {
            $this.SSPRSettings = [WebRequestHelper]::InvokeAADAPI("/api/PasswordReset/PasswordResetPolicies")
        }

        if ($this.EnterpriseAppSettings -eq $null)
        {
            $this.EnterpriseAppSettings = [WebRequestHelper]::InvokeAADAPI("/api/EnterpriseApplications/UserSettings")
        }
    }

    hidden [ControlResult] CheckTenantSecurityContactInfoIsSet([ControlResult] $controlResult)
    {
        $td = Get-AzureADTenantDetail

        $result = $false
        $missing = ""
        try {
            #Check that at least 1 email and at least 1 phone number are set.
            $bEmail = ($td.SecurityComplianceNotificationMails.Count -gt 0 -and -not [string]::IsNullOrEmpty($td.SecurityComplianceNotificationMails[0]))
            $bPhone = ($td.SecurityComplianceNotificationPhones.Count -gt 0 -and -not [string]::IsNullOrEmpty($td.SecurityComplianceNotificationPhones[0]))
            if ($bEmail -and $bPhone )
            {
                $result = $true
            }
            else {
                $missing = if (-not $bEmail) {"`n`tSecurityComplianceNotificationMails "} else {""} 
                $missing += if (-not $bPhone) {"`n`tSecurityComplianceNotificationPhone"} else {""}
            }
        }
        catch {
            $controlResult.AddMessage([VerificationResult]::Error, [MessageData]::new("Error reading Security Compliance Notification settings. Perhaps your AAD SKU does not support them."));
        }

        if ($result -eq $false)
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                    [MessageData]::new("Security compliance notification are not correctly set for the tenant."));
            $controlResult.AddMessage("The following are missing: $missing")
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("Security compliance notification phone/email are both set as expected."));
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckGuestsHaveLimitedAccess([ControlResult] $controlResult)
    {
        $b2b = $this.B2BSettings

        if ($b2b -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        elseif($b2b.restrictDirectoryAccess -ne $true) #Guests permissions are limited?
        {
                $controlResult.AddMessage([VerificationResult]::Failed,
                                        [MessageData]::new("Guest account directory permissions are not restricted."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("Guest account permissions are restricted."));
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckGuestsIfCanInvite([ControlResult] $controlResult)
    {
        $b2b = $this.B2BSettings
        if ($b2b -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        elseif($b2b.limitedAccessCanAddExternalUsers -eq $true) #Guests can invite?
        {
                $controlResult.AddMessage([VerificationResult]::Failed,
                                        [MessageData]::new("Guest have privilege to invite other guests."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("Guest do not have the privilege to invite other guests."));
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckBaselineMFAPolicyForAdmins([ControlResult] $controlResult)
    {
        $adminSettings = $this.AdminMFASettings
        if ($adminSettings -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        elseif($adminSettings.enable -eq $false -or $adminSettings.state -eq  0)
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                        [MessageData]::new("MFA is set as 'not required' for admin accounts."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("MFA is set as 'required' for admin accounts."));
        }
        return $controlResult;
    }


    hidden [ControlResult] MFACheckUsersCanNotifyFraud([ControlResult] $controlResult)
    {
        $mfa = $this.MFASettings
        if ($mfa -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        elseif($mfa.enableFraudAlert -eq $true) #Users can notify about fraud
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                [MessageData]::new("Users have the permission to raise fraud alerts."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                               [MessageData]::new("Users do not have the permission to raise fraud alerts."));

        }
        return $controlResult;
    }

    hidden [ControlResult] MFAReviewBypassedUsers([ControlResult] $controlResult)
    {
        $bp = $this.MFABypassList
        if ($bp -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission")); #TODO: Empty BP list case?
        }
        elseif($bp.Count -eq 0) #No users on bypass list
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                [MessageData]::new("No users found on the MFA bypass list."));
        }
        else
        {
            $bpUsers = @()
            $bp | % {$bpUsers += $_.Username}
            $bpUsersList = $bpUsers -join ", "
            $controlResult.AddMessage([VerificationResult]::Verify,
                               [MessageData]::new("Found the following users on MFA bypass list. Please review.`n`t $bpUsersList" ));

        }
        return $controlResult;
    }


    hidden [ControlResult] CheckUserPermissionsToCreateApps([ControlResult] $controlResult)
    {
        $aadPerms = $this.AADPermissions
        if ($aadPerms -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        elseif ($aadPerms.allowedActions.application.Contains('create')) #has to match case
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                [MessageData]::new("Regular users have privilege to create new apps."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                [MessageData]::new("Regular users do not have privilege to create new apps."));
        }
        
        return $controlResult;
    }

    hidden [ControlResult] CheckEnoughGlobalAdmins([ControlResult] $controlResult)
    {

        $ca = Get-AzureAdDirectoryRole -Filter "DisplayName eq 'Company Administrator'"
        $rm = @()

        try 
        {
            $rm = @(Get-AzureADDirectoryRoleMember -ObjectId $ca.ObjectId)
        }
        catch 
        {
            $rm = $null
        }
        
        if ($rm -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        elseif ($rm.Count -le 2) #TODO: ControlSettings!
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                [MessageData]::new("Only [$($rm.Count)] global administrator found."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                [MessageData]::new("Found [$($rm.Count)] global administrators."));
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckNoGuestsInGlobalAdminRole([ControlResult] $controlResult)
    {
        #TODO: Move this to common/.ctor similar to API calls.
        #TODO: Expand this to other privileged roles (Security Admin, etc. - see AccountHelper)
        #TODO: This and other RBAC checks should cover PIM-eligible members.
        $ca = Get-AzureAdDirectoryRole -Filter "DisplayName eq 'Company Administrator'"
        $rm = @()

        try 
        {
            $rm = @(Get-AzureADDirectoryRoleMember -ObjectId $ca.ObjectId)
        }
        catch 
        {
            $rm = $null
        }
        
        if ($rm -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        else
        {
            $foundGuests = $false
            $guests = @()
            
            $rm | % {if ($_.ObjectType -eq 'User' -and $_.UserType -eq 'Guest') {$foundGuests = $true; $guests += "$($_.DisplayName) ($($_.ObjectId))"}}
            $guestList = $guests -join "`n`t"
            if ($foundGuests)
            {
                $controlResult.AddMessage([VerificationResult]::Failed,
                                    [MessageData]::new("Found the following 'Guest' users in Global Admin role: `n`t$guestList."));
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Passed,
                                    [MessageData]::new("Did not find any 'Guest' member in Global Admin role."));
            }
        }
        return $controlResult;
    }

    
    hidden [ControlResult] CheckTenantDataAccessForApps([ControlResult] $controlResult)
    {
        $eas = $this.EnterpriseAppSettings

        if ($eas -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission")); #TODO: Empty BP list case?
        }
        elseif($eas.usersCanAllowAppsToAccessData -eq $true) #Users can approve apps to access tenant data
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                [MessageData]::new("Users are permitted to approve app access to tenant data without admin consent."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                               [MessageData]::new("Users are not permitted to approve app access to tenant data without admin consent." ));

        }
        return $controlResult;
    }


    hidden [ControlResult] CheckUserPermissionToInviteGuests([ControlResult] $controlResult)
    {
        $aadPerms = $this.AADPermissions

        if ($aadPerms -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        elseif($aadPerms.allowedActions.user.Contains('inviteguest')) #has to match case
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                        [MessageData]::new("Regular users have privilege to invite guests."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("Regular users do not have privilege to invite guests."));
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckMinQuestionsForSSPR([ControlResult] $controlResult)
    {
        $sspr = $this.CASettings
        if ($sspr -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        elseif ($sspr.numberOfQuestionsToReset -lt 3)
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                        [MessageData]::new("Found that less than 3 questions are required for password reset."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("Found that 3 or more questions are required for password reset."));
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckUserNotificationUponSSPR([ControlResult] $controlResult)
    {
        $sspr = $this.CASettings
        if ($sspr -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        elseif($sspr.notifyUsersOnPasswordReset -ne $true)
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                        [MessageData]::new("User notification not configured for password resets."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("User notification is configured for password resets."));
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckAdminNotificationUponSSPR([ControlResult] $controlResult)
    {
        $sspr = $this.CASettings
        if ($sspr -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));
        }
        elseif($sspr.notifyOnAdminPasswordReset -ne $true)
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                                        [MessageData]::new("Notification to all admins not configured for admin password resets."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                        [MessageData]::new("Notification to all admins is configured for admin password resets."));
        }
        return $controlResult;
    }

    

    hidden [ControlResult] SSPRMinAuthNMethodsRequired([ControlResult] $controlResult)
    {
        $sspr = $this.SSPRSettings
        if ($sspr -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission")); #TODO: Empty BP list case?
        }
        elseif($sspr.numberOfAuthenticationMethodsRequired -gt 1) #At least 2 methods. TODO: ControlSettings?
        {
            $controlResult.AddMessage([VerificationResult]::Passed,
                                [MessageData]::new("More than one authentication methods are required to reset password."));
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Failed,
                               [MessageData]::new("Ensure that at least two methods are required during a self-service password reset." ));

        }
        return $controlResult;
    }

    hidden [ControlResult] CheckRequireMFAForJoin([ControlResult] $controlResult)
    {
        $ds = $this.DeviceSettings

        if ($ds -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                            [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));           
        }
        else
        {
            if (-not $ds.requireMfaSetting)
            {
                    $controlResult.AddMessage([VerificationResult]::Failed,
                                            [MessageData]::new("Please enable MFA as a requirement for joining devices to the tenant."));
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Passed,
                                            [MessageData]::new("MFA is enabled as a requirement for joining new devices to the tenant."));
            }
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckMaxDeviceLimitSet([ControlResult] $controlResult)
    {
        $ds = $this.DeviceSettings

        if ($ds -eq $null)
        {
            $controlResult.AddMessage([VerificationResult]::Manual,
                                [MessageData]::new("Unable to evaluate control. You may not have sufficient permission"));           
        }
        else
        {
            if ($ds.maxDeviceNumberPerUserSetting -gt [Tenant]::RecommendedMaxDevicePerUserLimit)
            {
                    $controlResult.AddMessage([VerificationResult]::Failed,
                                            [MessageData]::new("Max device per user limit is not set or too high. Recommended: ["+ [Tenant]::RecommendedMaxDevicePerUserLimit + "]."));
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Passed,
                                            [MessageData]::new("Max device per user limit is set at [$($ds.maxDeviceNumberPerUserSetting)]."));
            }
        }
        return $controlResult;
    }
}