RiverMeadow.Development.Login/RiverMeadow.Development.Login.psm1

using module '../Common/Result'
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath Common | Join-Path -ChildPath Error | Join-Path -ChildPath Error)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath Common |Join-Path -ChildPath Wrappers | Join-Path -ChildPath Wrappers)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath Util |Join-Path -ChildPath Util)
function Invoke-RMLogin {
    param (
        [Alias("ea")]
        [string] $EmailAddress,
        [Alias("pw")]
        [string] $Password,
        [Alias("mfavc")]
        [string] $MFAVerificationCode
    )
    try {
        [RMReturn] $RMReturn = [RMReturn]::new()
        if (0 -eq $PSBoundParameters.Count) {
            $EmailAddress = Read-RMString -UserMessage "Enter email address" -IsRequired $true -ParameterName "Email address"
            $Password = Read-RMSecureString -UserMessage "Enter password" -IsRequired $true -ParameterName "Password"
        } elseif ([string]::IsNullOrEmpty($EmailAddress) -or [string]::IsNullOrEmpty($Password)) {
            $RMReturn.SetReturnCode([RMReturn]::ERROR)
            if ([string]::IsNullOrEmpty($EmailAddress)) {
                $UserMessage = "EmailAddress is required"
                Write-RMError -Message $UserMessage
                $RMReturn.AddRMError([RMError]::new($UserMessage))
            }
            if ([string]::IsNullOrEmpty($Password)) {
                $UserMessage = "Password is required"
                Write-RMError -Message $UserMessage
                $RMReturn.AddRMError([RMError]::new($UserMessage))
            }
            return $RMReturn
        }

        $Uri = Get-RMUri
        $Body = @{
            email = $EmailAddress
            password = $Password
        }

        if ($MFAVerificationCode) {
            $Body['mfa_token'] = $MFAVerificationCode
        }

        $JsonBody = $Body | ConvertTo-Json
        $Headers = @{
            Accept = "application/rm+json"
        }

        $Params = @{
            Method = "Post"
            Uri = $Uri + "/login"
            Body = $JsonBody
            ContentType = "application/json"
            Headers = $Headers
        }

        if (Get-Variable -Name "RMContext-UserLogin" -ErrorAction SilentlyContinue) {
            # Delete the previous Login result before attempting a new one
            Remove-RMVariable
        }

        $UserLogin = Invoke-RestMethod @Params
    
        Set-Variable -Name "RMContext-UserLogin" -Value $UserLogin -Scope Global
        Set-Variable -Name "RMContext-ReactorURI" -Value $Uri -Scope Global
        Get-RMUser -UserId $UserLogin.user_id -Uri $Uri
    
        $CurrentOrg = (Get-Variable -Name "RMContext-CurrentOrganizationName").Value
        $CurrentProjectName = (Get-Variable -Name "RMContext-CurrentProjectName").Value
    
        Write-Output "Current Organization Name: $CurrentOrg" | Out-Host
        Write-Output "Current Project Name: $CurrentProjectName" | Out-Host
        $RMReturn.SetReturnCode([RMReturn]::SUCCESS)
        $RMReturn.SetOutputData(@{"Current_Organization_Name" = $CurrentOrg; `
            "Current_Project_Name" = $CurrentProjectName;})
        return $RMReturn

    } catch {
        $ErrorString = $PSItem.ToString()
        if ($ErrorString.Contains("error_code")) {
            $ErrorString = $ErrorString | ConvertFrom-Json
            if ($ErrorString.error_code -eq "mfa_token_required") {
                return Get-RMMFACode -InputParameter $PSBoundParameters -ErrorCode $ErrorString.error_code
            } else {
                Show-RMError -ErrorObj $PSItem
                return Build-RMError -ErrorObj $PSItem
            }
        } elseif ($PSVersionTable.PSVersion.Major -eq 5 -and $ErrorString.Contains("403")) {
            return Get-RMMFACode -InputParameter $PSBoundParameters
        } else {
            Show-RMError -ErrorObj $PSItem
            return Build-RMError -ErrorObj $PSItem
        }
    }
}

function Invoke-RMLogout {
    param ()
    try {
        [RMReturn] $RMReturn = [RMReturn]::new()        
        if (-not (Get-Variable -Name "RMContext-UserLogin" -ErrorAction SilentlyContinue)) {
            $UserMessage = "No user is currently logged in."
            Write-RMError -Message $UserMessage
            $RMReturn.SetReturnCode([RMReturn]::ERROR)
            $RMReturn.AddRMError([RMError]::new($UserMessage))
            return $RMReturn
        }

        Remove-RMVariable
        Write-Output "Logout was successful." | Out-Host
        $RMReturn.SetReturnCode([RMReturn]::SUCCESS)
        return $RMReturn
    } catch {
        Show-RMError -ErrorObj $PSItem
        return Build-RMError -ErrorObj $PSItem
    }
}

function Get-RMUri {
    param()
    $Uri = "https://migrate.rivermeadow.com/api/v3"
    $RMHost = Get-RMHost
    if ([string]::IsNullOrEmpty($RMHost)) {
        # Env. variable RMHost is not set, use production URI
        return $Uri
    }

    if ($RMHost.Contains("http://") -or $RMHost.Contains("https://")) {
        $Uri = $RMHost.TrimEnd("/") + "/api/v3"
    } else {
        $Uri = "http://" + $RMHost.TrimEnd("/") + "/api/v3"
    }
    return $Uri
}

# Currently Pester doesn't support mocking .Net methods, so in order
# to able to mock the method "GetEnvironmentVariable", we are putting it
# in the wrapper method below.
function Get-RMHost {
    param()
    return [Environment]::GetEnvironmentVariable('RMHost')
}

function Get-RMUser {
    param(
        [Parameter(Mandatory)]
        [string]$UserId,
        [Parameter(Mandatory)]
        [string]$Uri
    )

    $LoginResult = Get-Variable -Name "RMContext-UserLogin"

    $Headers = @{
        "Accept" = "application/rm+json"
        "X-Auth-Token" = $LoginResult.Value.token
    }

    $Params = @{
        Method = "Get"
        Uri = $Uri + "/users/" + $UserId
        Headers = $Headers
    }

    $UserInfo = Invoke-RestMethod @Params

    Set-Variable -Name "RMContext-UserOrganizations" -Value $UserInfo.organizations -Scope Global
    $CurrentOrgName  = Get-RMParentOrganizationNameById -OrganizationId $UserInfo.organization
    $CurrentProjectName = Get-RMOrganizationNameById -OrganizationId $UserInfo.organization
    Set-Variable -Name "RMContext-CurrentOrganizationName" -Value $CurrentOrgName -Scope Global
    Set-Variable -Name "RMContext-CurrentProjectName" -Value $CurrentProjectName -Scope Global
    Set-Variable -Name "RMContext-CurrentProjectId" -Value $UserInfo.organization -Scope Global
}

function Get-RMOrganizationNameById {
    param(
        [string] $OrganizationId
    )

    $UserOrgs = Get-Variable -Name "RMContext-UserOrganizations"

    foreach($Org in $UserOrgs.Value) {
        if ($Org.id -eq $OrganizationId) {
            return $Org.name
        }
    }

    throw "No organization or project exists by the given $OrganizationId"
}

function Get-RMParentOrganizationIdById {
    param(
        [string] $OrganizationId
    )

    $UserOrgs = Get-Variable -Name "RMContext-UserOrganizations"

    foreach($Org in $UserOrgs.Value) {
        if ($Org.id -eq $OrganizationId) {
            return $Org.parent_organization_id
        }
    }

    throw "No organization or project exists by the given $OrganizationId"

}

function Get-RMParentOrganizationNameById {
    param(
        [string] $OrganizationId
    )

    $ParentOrgId = Get-RMParentOrganizationIdById -OrganizationId $OrganizationId

    $UserOrgs = Get-Variable -Name "RMContext-UserOrganizations"

    foreach($Org in $UserOrgs.Value) {
        if ($Org.id -eq $ParentOrgId) {
            return $Org.name
        }
    }

    throw "No organization or project exists by the given $OrganizationId"

}

function Remove-RMVariable {
    param()
    Remove-Variable -Name "RMContext-UserLogin" -Scope Global -ErrorAction SilentlyContinue
    Remove-Variable -Name "RMContext-UserOrganizations" -Scope Global -ErrorAction SilentlyContinue
    Remove-Variable -Name "RMContext-CurrentOrganizationName" -Scope Global -ErrorAction SilentlyContinue
    Remove-Variable -Name "RMContext-CurrentProjectName" -Scope Global -ErrorAction SilentlyContinue
    Remove-Variable -Name "RMContext-CurrentProjectId" -Scope Global -ErrorAction SilentlyContinue
}

function Get-RMMFACode {
    param(
        [hashtable] $InputParameter,
        [string] $ErrorCode
    )
    if (0 -eq $InputParameter.Count) {
        $VerificationCode = Read-Host "We sent you the Verification Code. Please enter it here"
        Invoke-RMLogin -EmailAddress $EmailAddress -Password $Password -MFAVerificationCode $VerificationCode
        # This is an internal recursive call, no need to return an object of RMReturn from here.
        return
    }
    $UserMessage = "We sent you the Verification Code, please re-run this cmdlet as 'Invoke-RMLogin -EmailAddress <email address> -Password <password> -MFAVerificationCode <received verification code>'"
    Write-Output $UserMessage |Out-Host
    $RMReturn.SetReturnCode([RMReturn]::ERROR)
    $RMReturn.AddRMError([RMError]::new($ErrorCode, $UserMessage))
    return $RMReturn
}
Export-ModuleMember -Function Invoke-RMLogin, Invoke-RMLogout