<#
    File Name :  Moodle-AzureAD-Script.ps1
    
    Copyright (c) Microsoft Corporation. All rights reserved.
    Licensed under the MIT License.
#>

# Allow for the script to be run
Write-Host "Deployment started..." -ForegroundColor Green
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser

# Install necessary modules
Write-Host "Installing required packages..." -ForegroundColor Cyan
Install-Module AzureAD -AllowClobber -Scope CurrentUser -Force
Install-Module Az -AllowClobber -Scope CurrentUser -Force
if ((Get-Module -ListAvailable -Name "Az.*") -and (Get-Module -ListAvailable -Name "AzureAD")) {
    Write-Host "Installation completed" -ForegroundColor Cyan
}

# Check if Azure CLI is installed.
Write-Host "Checking if Azure CLI is installed." -ForegroundColor Green
$localPath = [Environment]::GetEnvironmentVariable("ProgramFiles(x86)")
if ($localPath -eq $null) {
    $localPath = "C:\Program Files (x86)"
}

$localPath = $localPath + "\Microsoft SDKs\Azure\CLI2"
if (-not(Test-Path -Path $localPath)) {
    Write-Host "Azure CLI is not installed!" -ForegroundColor Green
    $confirmationtitle      = "Please select YES to install Azure CLI."
    $confirmationquestion   = "Do you want to proceed?"

    # Set up popup options
    $YesNoButtons =  4
    $QuestionIcon = 32
    $confirmationChoices = $YesNoButtons + $QuestionIcon

    $consentErrorMessage = "Azure CLI installation Failed"

    # Zero means = no timeout
    $Timeout = 0

    # Creat WSH Shell object and launch popup
    $WshShell = New-Object -ComObject WScript.Shell
    $updateDecision = $WshShell.Popup($confirmationQuestion, $Timeout, $confirmationTitle, $confirmationChoices)

    # Cleanup
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WshShell) |Out-Null
       
    if ($updateDecision -eq 6) {
        Write-Host "Selected Yes.Installing Azure CLI ..." -ForegroundColor Cyan
        Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; rm .\AzureCLI.msi
               
        if ($LASTEXITCODE -ne 0) {
            Write-Host $consentErrorMessage -ForegroundColor Red
        } else {
            Write-Host "Azure CLI is installed! Please close this PowerShell window and re-run this script in a new PowerShell session." -ForegroundColor Green
        }
	    EXIT
    } elseif ($updateDecision -eq 7) { 
        Write-Host "Selected No.Azure CLI is not installed.`nPlease install the CLI from https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest and re-run this script in a new PowerShell session" -ForegroundColor DarkRed
        EXIT
    }
} else {
    Write-Host "Azure CLI is installed." -ForegroundColor Green
}

#Overarching requirement - log into Azure first!
Write-Host "You would get an pop up to login to azure" -ForegroundColor Yellow
Connect-AzureAD

Write-Host "Please continue the login in the web browser that will open" -ForegroundColor Yellow
az login --allow-no-subscriptions

<#
.DESCRIPTION 
This function will be able to create an array of type RequiredResourceAccess which will be then passed to the New-AzureADApplication cmdlet
#>
function Get-Resources {
    [Microsoft.Open.AzureAD.Model.RequiredResourceAccess[]] $outputArray = @();
    
    $localPath = Get-Location
    $jsonPath = -Join($localPath,'\Json\permissions.json');
    $jsonObj = (New-Object System.Net.WebClient).DownloadString($jsonPath) | ConvertFrom-Json;
    # Output the number of objects to push into the array outputArray
    Write-Host 'From the json path:'$jsonPath', we can find' $jsonObj.requiredResourceAccess.length'attributes to populate' -ForegroundColor Green;
    for ($i = 0; $i -lt $jsonObj.requiredResourceAccess.length; $i++) {
        # Step A - Create a new object fo the type RequiredResourceAccess
        $reqResourceAccess = New-Object -TypeName Microsoft.Open.AzureAD.Model.RequiredResourceAccess; 
        # Step B - Straightforward setting the ResourceAppId accordingly
        $reqResourceAccess.ResourceAppId = $jsonObj.requiredResourceAccess[$i].resourceAppId;
        # Step C - Having to set the ResourceAccess carefully
        if ($jsonObj.requiredResourceAccess[$i].resourceAccess.length -gt 1) {
            $reqResourceAccess.ResourceAccess = $jsonObj.requiredResourceAccess[$i].resourceAccess;
        } else {
            $reqResourceAccess.ResourceAccess = $jsonObj.requiredResourceAccess[$i].resourceAccess[0];
        }
        # Step D - Add the element to the array
        $outputArray += $reqResourceAccess;
    }
    $outputArray;
}

<#
.DESCRIPTION 
This function will allow to create and add Microsoft Graph scope 
#>
function Create-Scope([string] $value, [string] $userConsentDisplayName, [string] $userConsentDescription,
    [string] $adminConsentDisplayName, [string] $adminConsentDescription) {
    $scope = New-Object Microsoft.Open.MsGraph.Model.PermissionScope
    $scope.Id = New-Guid
    $scope.Value = $value
    $scope.UserConsentDisplayName = $userConsentDisplayName
    $scope.UserConsentDescription = $userConsentDescription
    $scope.AdminConsentDisplayName = $adminConsentDisplayName
    $scope.AdminConsentDescription = $adminConsentDescription
    $scope.IsEnabled = $true
    $scope.Type = "User"
    return $scope
}

<#
.DESCRIPTION 
This function will allow to add preauthroized application for Azure AD application 
#>
function Create-PreAuthorizedApplication([string] $applicationIdToPreAuthorize, [string] $scopeId) {
    $preAuthorizedApplication = New-Object 'Microsoft.Open.MSGraph.Model.PreAuthorizedApplication'
    $preAuthorizedApplication.AppId = $applicationIdToPreAuthorize
    $preAuthorizedApplication.DelegatedPermissionIds = @($scopeId)
    return $preAuthorizedApplication
}

function IsValidateSecureUrl {
    param(
        [Parameter(Mandatory = $true)] [string] $url
    )
    # Url with https prefix REGEX matching
    return ($url -match "https:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)")
}

# Grant Admin consent
function GrantAdminConsent {
    Param(
        [Parameter(Mandatory = $true)] $appId
    )

    $confirmationTitle = "Admin consent permissions is required for app registration using CLI"
    $confirmationQuestion = "Do you want to grant admin consent?"
   
    # Set up popup options
    $YesNoButtons =  4
    $QuestionIcon = 32
    $confirmationChoices = $YesNoButtons + $QuestionIcon

    $consentErrorMessage = "Current user does not have the privilege to consent the permissions on this app."

    # Zero means = no timeout
    $Timeout = 0

    # Creat WSH Shell object and launch popup
    $WshShell = New-Object -ComObject WScript.Shell
    $updateDecision = $WshShell.Popup($confirmationQuestion, $Timeout, $confirmationTitle, $confirmationChoices)

    # Cleanup
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WshShell) |Out-Null
       
    if ($updateDecision -eq 6) {
        # Grant admin consent for app registration required permissions using CLI
        Write-Host "Selected Yes. Waiting for admin consent to finish..." -ForegroundColor Green
        # A delay is added here to allow the appId being recognised after creation. 
        Start-Sleep -s 20
        $retry = 0
        do {
            az ad app permission admin-consent --id $appId

            if ($LASTEXITCODE -ne 0) {
                $retry++
                Write-Host "Retry " $retry
                Start-Sleep 5
            } else {
                Write-Host "Admin consent has been granted." -ForegroundColor Green
                $retry = 10
            }
        } while ($retry -lt 5)
        
        if ($retry -ne 10) {
           Write-Host $consentErrorMessage -ForegroundColor Red
        }
    } elseif ($updateDecision -eq 7) { 
        Write-Host "Selected No. Skipping admin consent..." -ForegroundColor Green
    }
}

function InputMoodleUrl {
    $url = Read-Host -Prompt "Enter the URL of your Moodle server (ex: https://www.moodleserver.com)"

    if (-not(IsValidateSecureUrl($url))) {
        Write-Host "The URL is incorrect and should start with HTTPS" -ForegroundColor Red
        InputMoodleUrl
    } else {
        return $url
    }
}

# Step 1 - Getting the necessary information
$displayName = Read-Host -Prompt "Enter the AAD app name (ex: Moodle plugin)"
$moodleUrl = InputMoodleUrl

if ($moodleUrl -notmatch '.+?\/$') {
    $moodleUrl += '/'
}

# Step 2 - Construct the reply URLs
$botFrameworkUrl = 'https://token.botframework.com/.auth/web/redirect'
$authUrl = $moodleUrl + 'auth/oidc/'
$ssoEndUrl = $moodleUrl + 'local/o365/sso_end.php'
$replyUrls = ($moodleUrl, $botFrameworkUrl, $authUrl, $ssoEndUrl)

# Step 3 - Compile the Required Resource Access object
[Microsoft.Open.AzureAD.Model.RequiredResourceAccess[]] $requiredResourceAccess = Get-Resources

# Step 4 - Making sure to officially register the application
$app = New-AzureADApplication -DisplayName $displayName 
if ($LASTEXITCODE -eq 0){
    Write-Host "Successfully registered app with Appid:" $app.AppId -ForegroundColor Green
} else {
    Write-Host "App registration failed" -ForegroundColor Red
    EXIT
}

$appId = $app.AppId
$appObjectId = $app.ObjectId

# Removing default scope user_impersonation, access and optional claims
$retries = 0
while ($retries -lt 3) {
	try {
        Write-Host "Removing default scope user_impersonation" -ForegroundColor Green
		$retries++
	    $localPath = Get-Location
        $resetOptionalClaimPath = -Join($localPath,'\Json\AadOptionalClaims_Reset.json');
        $DEFAULT_SCOPE=$(az ad app show --id $appId | .\jq '.oauth2Permissions[0].isEnabled = false' | .\jq -r '.oauth2Permissions')
        $DEFAULT_SCOPE>>scope.json
        az ad app update --id $appId --set oauth2Permissions=@scope.json
        if ($LASTEXITCODE -eq 0) {
            Remove-Item .\scope.json
            az ad app update --id $appId --remove oauth2Permissions
            if ($LASTEXITCODE -eq 0) {
                Write-Host "Removed default scope user_impersonation" -ForegroundColor Green
                break
            } else {
                Write-Host "Failed to remove user_impersonation scope" -ForegroundColor Red
            }
        } else {
            Write-Host "Failed to remove user_impersonation scope" -ForegroundColor Red
        }
    } catch {
        $errorMessage = $_.Exception.Message
	    Write-Error "Failed to remove user_impersonation scope. Error message: $errorMessage"
    }
}

# Removing old Reply Urls if any
az ad app update --id $appId --remove replyUrls
if ($LASTEXITCODE -eq 0) {
    Write-Host "Removed old Reply Urls if any" -ForegroundColor Green
} else {
    Write-Host "Failed to remove old Reply Urls" -ForegroundColor Red
}

# Removing old IdentifierUris if any
az ad app update --id $appId --remove IdentifierUris
if ($LASTEXITCODE -eq 0) {
    Write-Host "Removed old IdentifierUris if any" -ForegroundColor Green
} else {
    Write-Host "Failed to remove old IdentifierUris" -ForegroundColor Red
}

# Resetting default optional claims if any
az ad app update --id $appId --optional-claims $resetOptionalClaimPath
if ($LASTEXITCODE -eq 0) {
    Write-Host "Successfully reset the default optional claims" -ForegroundColor Green
} else {
    Write-Host "Failed to reset the default optional claims" -ForegroundColor Red
}

# Removing default api permissions
az ad app update --id $appId --remove requiredResourceAccess
if ($LASTEXITCODE -eq 0) {
    Write-Host "Removed default api permissions" -ForegroundColor Green
} else {
    Write-Host "Failed to remove default api permissions" -ForegroundColor Red
}

# Update reply URLs and API permissions
Set-AzureADApplication -ObjectId $appObjectId -ReplyUrls $replyUrls -RequiredResourceAccess $requiredResourceAccess
if ($LASTEXITCODE -eq 0) {
    Write-Host "Reply urls and api permissions added" -ForegroundColor Green
} else {
    Write-Host "Failed to add required reply urls and api permissions" -ForegroundColor Red
}

# Grant Admin consent 
GrantAdminConsent $appId

# Add user who created the app as the owner of the app 
$userUPN = az account show --query user.name -o json
$userDetails = Get-AzureADUser -Filter "userPrincipalName eq '$($userUPN.Trim('"'))'"
Add-AzureADApplicationOwner -ObjectId $appObjectId -RefObjectId $userDetails.ObjectId
if ($LASTEXITCODE -eq 0) {
    Write-Host "Added $userUPN as owner of the app" -ForegroundColor Green
} else {
    Write-Host "Failed to add $userUPN as owner of the app" -ForegroundColor Red
}

# Set AAD optional claims:
$localPath = Get-Location
$setOptionalClaimPath = -Join($localPath,'\Json\AadOptionalClaims.json');
az ad app update --id $appId --optional-claims $setOptionalClaimPath
if ($LASTEXITCODE -eq 0) {
    az ad app update --id $appId --set oauth2AllowImplicitFlow=true
    if ($LASTEXITCODE -eq 0) {
        Write-Host "AAD optional claims updated" -ForegroundColor Green
    } else {
        Write-Host "Failed to add AAD optional claims" -ForegroundColor Red
    }
} else {
    Write-Host "Failed to add AAD optional claims" -ForegroundColor Red
}

# Step 5 - Take the object id generated in Step 2, create a new Password
$pwdVars = New-AzureADApplicationPasswordCredential -ObjectId $app.ObjectId
if ($LASTEXITCODE -eq 0) {
    Write-Host "Client secret generated" -ForegroundColor Green
} else {
    Write-Host "Failed to generate client secret" -ForegroundColor Red
}

# Step 6 - Update the logo for the Azure AD app
$location = Get-Location
$imgLocation = -Join($location, '\Assets\moodle-logo.jpg')
Set-AzureADApplicationLogo -ObjectId $app.ObjectId -FilePath $imgLocation

# Step 7 - Add expose an API 
# Reference https://docs.microsoft.com/en-us/answers/questions/29893/azure-ad-teams-dev-how-to-automate-the-app-registr.html
# Expose an API
$msApplication = Get-AzureADMSApplication -ObjectId $appObjectId

# Set identifier URL
if ($moodleUrl -imatch 'https://') {
    $identifierUris = 'api://' + $moodleUrl.Remove(0,8) + $appId
}
Set-AzureADMSApplication -ObjectId $msApplication.Id -IdentifierUris $identifierUris
if ($LASTEXITCODE -eq 0) {
    Write-Host "AppId url added" -ForegroundColor Green
} else {
    Write-Host "Adding appId url failed" -ForegroundColor Red
}
                          
# Create access_as_user scope
$scopes = New-Object System.Collections.Generic.List[Microsoft.Open.MsGraph.Model.PermissionScope]
$msApplication.Api.Oauth2PermissionScopes | foreach-object { $scopes.Add($_) }
$scope = Create-Scope -value "access_as_user"  `
    -userConsentDisplayName "Teams can access the user profile and make requests on the user's behalf"  `
    -userConsentDescription "Enable Teams to call this app's APIs with the same rights as the user"  `
    -adminConsentDisplayName "Teams can access the user's profile"  `
    -adminConsentDescription "Allows Teams to call the app's web APIs as the current user"
$scopes.Add($scope)
$msApplication.Api.Oauth2PermissionScopes = $scopes
Set-AzureADMSApplication -ObjectId $msApplication.Id -Api $msApplication.Api
if ($LASTEXITCODE -eq 0) {
    Write-Host "Scope access_as_user added." -ForegroundColor Green
} else {
    Write-Host "Adding scope access_as_user failed" -ForegroundColor Red
}
             
# Authorize Teams mobile/desktop client and Teams web client to access API
$preAuthorizedApplications = New-Object 'System.Collections.Generic.List[Microsoft.Open.MSGraph.Model.PreAuthorizedApplication]'
$teamsRichClientPreauthorization = Create-PreAuthorizedApplication `
    -applicationIdToPreAuthorize '1fec8e78-bce4-4aaf-ab1b-5451cc387264' `
    -scopeId $scope.Id
$teamsWebClientPreauthorization = Create-PreAuthorizedApplication `
    -applicationIdToPreAuthorize '5e3ce6c0-2b1f-4285-8d4b-75ee78787346' `
    -scopeId $scope.Id
$preAuthorizedApplications.Add($teamsRichClientPreauthorization)
$preAuthorizedApplications.Add($teamsWebClientPreauthorization)
$msApplication.Api.PreAuthorizedApplications = $preAuthorizedApplications
Set-AzureADMSApplication -ObjectId $msApplication.Id -Api $msApplication.Api
if ($LASTEXITCODE -eq 0) {
    Write-Host "Teams mobile/desktop and web clients applications pre-authorized." -ForegroundColor Green
} else {
    Write-Host "Failed to pre-authorize teams mobile/desktop and web clients applications" -ForegroundColor Red
}

# Step 8 - Write out the newly generated app Id and azure app password
Write-Host 'Your AD Application ID: '$appId
Write-Host 'Your AD Application Secret: '$pwdVars.Value
Write-Host 'Deployment ended' -ForegroundColor Green