FortiAuth-Import.psm1

Write-Verbose 'Importing from [C:\projects\fortiauth-import\FortiAuth-Import\private]'
# .\FortiAuth-Import\private\bypassCertificatePolicy.ps1
function GetTrustAllCertsPolicy ()
{
    # Trust all certs as we don't use an internal CA
    add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) {
        return true;
    }
}
"@


    return $(New-Object TrustAllCertsPolicy)
}

function SetCertificatePolicy ($Func)
{
    [System.Net.ServicePointManager]::CertificatePolicy = $Func
}

function GetCertificatePolicy ()
{
    return [System.Net.ServicePointManager]::CertificatePolicy
}
# .\FortiAuth-Import\private\callAPI.ps1
function Invoke-FortiAuthRestMethod
{
    #[CmdletBinding()]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [string]
        $Resource,
        [ValidateSet("Get", "Post", "Delete", "Patch")]
        [string]
        $Method,
        [hashtable]
        $Options,
        [hashtable]
        $Body
    )
    if ($null -ne $Script:FortiAuth)
    {
        # Check if we even have a ticket
        Write-Error "Please connect using Connect-FortiAuth."
        return $false
    }
    # Code from other project that might be useful at a later date.
    # It's used to get a new session ticket.
    # if ((Get-Date).Ticks -le $Script:FortiAuth.Expire)
    # {
    # Connect-PveServer -Server $Script:FortiAuth.Server
    # }

    # Bypass ssl checking or servers without a public cert or internal CA cert
    if ($Script:FortiAuth.BypassSSLCheck)
    {
        $CertificatePolicy = GetCertificatePolicy
        SetCertificatePolicy -Func (GetTrustAllCertsPolicy)
    }

    # Setup Headers and cookie for splatting
    switch ($Method)
    {
        Get { $splat = PrepareGetRequest }
        Post { $splat = PreparePostRequest($Body) }
        Delete { $splat = PrepareGetRequest }
        Patch { $splat = PreparePatchRequest($Body)}
        Default { $splat = PrepareGetRequest }
    }

    $Query = "?"
    If ($Options)
    {
        $Options.keys | ForEach-Object {
            $Query = $Query + "$_=$($Options[$_])&"
        }
        $Query = $Query.TrimEnd("&")
    }
    else
    {
        $Query = ""
    }

    $Uri = "https://$($Script:FortiAuth.Server)/api/v1$($Resource)"

    $response = Invoke-RestMethod -Uri "$($Uri)$($Query)" -Credential $(Import-Clixml -Path $Script:FortiAuth.StoredCredential) @splat -ErrorVariable $e
    if ($e)
    {
        return $false
    }


    if ($Script:FortiAuth.BypassSSLCheck)
    {
        # restore original cert policy
        SetCertificatePolicy -Func $CertificatePolicy
    }

    return $response.data
}

function PreparePatchRequest ($Body)
{
    $request = New-Object -TypeName PSCustomObject -Property @{
        Method      = "Patch"
        Headers     = @{"Accept" = "application/json"}
        Body        = $Body
        ContentType = "application/json"
    }
    return $request
}
function PreparePostRequest($Body)
{
    $request = New-Object -TypeName PSCustomObject -Property @{
        Method      = "Post"
        Headers     = @{"Accept" = "application/json"}
        Body        = $Body
        ContentType = "application/json"
    }
    return $request
}

function PrepareGetRequest()
{
    $request = @{
        Method      = "Get"
        Headers     = @{"Accept" = "application/json"}
        ContentType = "application/json"
    }
    return $request
}

function PrepareDeleteRequest()
{
    # $cookie = New-Object System.Net.Cookie -Property @{
    # Name = "AuthCookie"
    # Path = "/"
    # Domain = $Script:FortiAuth.Server
    # Value = $Script:FortiAuth.Ticket
    # }
    # $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
    # $session.cookies.add($cookie)
    $request = New-Object -TypeName PSCustomObject -Property @{
        Method      = "Delete"
        #Headers = @{CSRFPreventionToken = $Script:FortiAuth.CSRFPreventionToken}
        Headers     = @{"Accept" = "application/json"}
        #WebSession = $session
        ContentType = "application/json"
    }
    return $request
}
Write-Verbose 'Importing from [C:\projects\fortiauth-import\FortiAuth-Import\public]'
# .\FortiAuth-Import\public\Connect.ps1

<#
.SYNOPSIS
Create a new session to connect with a Fortinet Authenticator appliance

.DESCRIPTION
Long description

.EXAMPLE
Connect-FortiAuth -Server 10.0.0.10 -UserName "root" -APIKey "asdfghjklqwertyuio"

.EXAMPLE
To bypass an invalid certificate where you aren't using an internal CA
Connect-FortiAuth -Server 10.0.0.10 -UserName "root" -APIKey "asdfghjklqwertyuio" -BypassSSLCheck

.EXAMPLE
Connect-FortiAuth -Server 10.0.0.10 -Crediential (Get-Crediential)

.NOTES
General notes
#>

function Connect-FortiAuth
{
    [CmdletBindings()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [ValidateScript( { (Test-Connection -ComputerName $_ -Count 1 -ErrorAction Ignore) } )]
        $Server,
        [Parameter(Mandatory = $false)]
        [Alias("User", "Account")]
        [string]
        $UserName,
        [Parameter(Mandatory = $false)]
        [Alias("Key", "Password")]
        [string]
        $APIKey,
        [switch]
        $BypassSSLCheck,
        [pscredential]
        $Crediential,
        # Stored Credential Path
        [string]
        $Path = "$($MyInvocation.MyCommand.Path)/Credential.xml"
    )

    begin
    {
    }

    process
    {
        $Script:FortiAuth.Server = $Server
        $Script:FortiAuth.StoredCredential = "$($MyInvocation.MyCommand.Path)/Credential.xml"
        $Script:FortiAuth.BypassSSLCheck = $BypassSSLCheck

        if ($Crediential)
        {
            # Are we getting credentials?
            $mycreds = $Crediential
            Export-Clixml -Path $Script:FortiAuth.StoredCredential -InputObject $mycreds
        }
        elseif ($Username -and $APIKey)
        {
            # Are we makeing our credentials from raw username and apikey?
            $secpasswd = ConvertTo-SecureString $APIKey -AsPlainText -Force
            $mycreds = New-Object System.Management.Automation.PSCredential ($Username, $secpasswd)
            Export-Clixml -Path $Script:FortiAuth.StoredCredential -InputObject $mycreds
        }
        else
        {
            # Are we using the stored credentails?
            if ($PSScriptRoot -and $(Test-Path -Path $Script:FortiAuth.StoredCredential))
            {
                # Found stored credentials so nothing to do!
                #$mycreds = Import-Clixml -Path $Script:FortiAuth.StoredCredential
            }
            else
            {
                # No credetials nor username/apikey specified so prompt for them
                $mycreds = Get-Credential -Message "Username and API Key as the Password"
                Export-Clixml -Path $Script:FortiAuth.StoredCredential -InputObject $mycreds
            }

        }

        # Do a test connection to see if everything works or not.
        $response = Invoke-FortiAuthRestMethod -Resource "/" -Method Get
        if ($response)
        {
            # All is good.
        }
    }

    end
    {
    }
}

Export-ModuleMember -Cmdlet Connect-FortiAuth
# .\FortiAuth-Import\public\Tokens.ps1
Function Get-Token
{
    Param(
        [string]
        $Token
    )
    $response = Invoke-FortiAuthRestMethod -Resource "fortitokens/" -Method Get -Options @{ limit = 1 }
    if ($response.meta.total_count -gt 10)
    {
        return Invoke-FortiAuthRestMethod -Resource "fortitokens/" -Method Get -Options @{ limit = $($response.meta.total_count) }
    }
    else
    {
        $response = Invoke-FortiAuthRestMethod -Resource "fortitokens/" -Method Get
        $data = $returnedData.objects
        if ($returnedData.meta)
        {
            do
            {
                $returnedData = Invoke-FortiAuthRestMethod -Resource "fortitokens/$($returnedData.meta.next)" -Method Get
                $data = $data + $returnedData.objects
            }while ($returnedData.meta.next)
        }
        return $data
    }
}

Export-ModuleMember -Cmdlet Get-Token
# .\FortiAuth-Import\public\UserGroups.ps1
Function Get-UserGroup
{
    Param(
        [ValidateNotNullOrEmpty()]
        $Name,
        [ValidateNotNullOrEmpty()]
        $Id
    )
    if ($Id)
    {
        $returnedData = Invoke-FortiAuthRestMethod -Resource "usergroups/$($Id)/" -Method Get
        $data = $returnedData.objects
        return $data
    }
    elseif ($Name)
    {
        $returnedData = Invoke-FortiAuthRestMethod -Resource "usergroups/" -Method Get
        $data = $returnedData.objects
        if ($returnedData.meta)
        {
            do
            {
                $returnedData = Invoke-FortiAuthRestMethod -Resource "usergroups/$($returnedData.meta.next)" -Method Get
                $data = $data + $returnedData.objects
            }while ($returnedData.meta.next)

        }
        $data | ForEach-Object {
            [PSCustomObject]@{
                Name = $_.Name
                Id   = $_.idtype
            }
        }
        return $data
    }
    else
    {
        Write-Log -Message "Group $Name$Id not found" -EventID 100 -Level Error -Method $Script:FortiAuth.LogMethod
        return $false
    }

}

Function Add-UserToGroup
{
    Param(
        [ValidateNotNullOrEmpty()]
        $GroupID,
        [ValidateNotNullOrEmpty()]
        $UserID
    )
    $UserList = (Get-UserGroup -Id $GroupID).Id

    # Create data object that will be output, and new user(s) to current list
    $Data = @{users = $UserList}
    $Data.users = $Data.users + $UserID
    $Data.users = $Data.users | ForEach-Object {
        "/api/v1/localusers/$($_)/"
    }

    $returnedData = Invoke-FortiAuthRestMethod -Resource "usergroups/$($GroupID)/" -Method Patch -Body $Data
    return $returnedData
}

Export-ModuleMember -Cmdlet Add-UserToGroup, Get-UserGroup
# .\FortiAuth-Import\public\Users.ps1
Function Remove-TokenFromUser
{
    [CmdletBindings()]
    Param(
        [ValidateNotNullOrEmpty()]
        $ID
    )
    Set-User -ID $ID -TokenAuth $false -TokenSerial "" -TokenType ""
}

Function Get-User
{
    Param(
    )
    $returnedData = Invoke-FortiAuthRestMethod -Resource "localusers/" -Method Get

    $data = $returnedData.objects
    if ($returnedData.meta)
    {
        do
        {
            $returnedData = Invoke-FortiAuthRestMethod -Resource "localusers/$($returnedData.meta.next)" -Method Get
            $data = $data + $returnedData.objects
        }while ($returnedData.meta.next)

    }
    return $data
}

<#
.SYNOPSIS
Changes User information

.DESCRIPTION
Changes User Information, such as Token, Password, Last Name, Email, etc.

.PARAMETER ID
ID of user in the server's database, can be found with Get-User.

.PARAMETER Create
If set then a new user will be created.

.PARAMETER TokenAuth
If true, then the user will be using a token.

.PARAMETER TokenSerial
The serial number of the token.

.PARAMETER TokenType
The type of token that will be used, accepts ftk,�ftm,�email,�or sms.

.PARAMETER Passcode
The passcode or password for user authentication.

.PARAMETER Expire
The number of days till the account expires, used with Passcode/Password. Note: the api requires ISO-8601 formatted user expiration time in UTC, but this is taken care of.

.PARAMETER MobileNumber
Mobile number for sms resets. Must follow international number format: +[country_code]-[number]

.PARAMETER Email
Email of the user.

.PARAMETER Active
If set then the user account is active.

.PARAMETER FirstName
First Name of user.

.PARAMETER LastName
Last Name of user.

.PARAMETER Address
Address of user.

.PARAMETER City
City of user.

.PARAMETER State
State of user.

.PARAMETER Country
Country of user. Note: Must be a country code from ISO-3166 list, but this is in the Parrameter Set.

.PARAMETER Custom1
Custom1 of user.

.PARAMETER Custom2
Custom2 of user.

.PARAMETER Custom3
Custom3 of user.

.EXAMPLE
Set-User -ID '1' -TokenAuth -TokenSerial "abcdef123456789" -TokenType ftk -Active

.NOTES
General notes
#>

Function Set-User
{
    #[CmdletBindings()]
    Param(
        [ValidateNotNullOrEmpty()]
        $ID,
        [switch]
        $Create,
        [Parameter(
            Mandatory = $True,
            ParameterSetName = "Token"
        )]
        [switch]
        $TokenAuth,
        [Parameter(
            Mandatory = $False,
            ParameterSetName = "Token"
        )]
        $TokenSerial,
        [Parameter(
            Mandatory = $True,
            ParameterSetName = "Token"
        )]
        [Parameter(
            Mandatory = $False,
            ParameterSetName = "SMS"
        )]
        [Parameter(
            Mandatory = $False,
            ParameterSetName = "Email"
        )]
        [ValidateSetAttribute("ftk", "ftm", "email", "sms")]
        $TokenType,
        [Parameter(
            Mandatory = $True,
            ParameterSetName = "Passcode"
        )]
        [Alias("Password")]
        $Passcode,
        [Parameter(
            Mandatory = $False,
            ParameterSetName = "Passcode"
        )]
        [int]
        $Expire = 60,
        [Parameter(
            Mandatory = $True,
            ParameterSetName = "SMS"
        )]
        [ValidateLength(4, 25)]
        [ValidatePatternAttribute("\+\d{1,6}\-\d{4,20}")]
        [string]
        $MobileNumber,
        [Parameter(
            Mandatory = $True,
            ParameterSetName = "Email"
        )]
        [string]
        $Email,
        [switch]
        $Active,
        [ValidateLength(0, 30)]
        [string]
        $FirstName,
        [ValidateLength(0, 30)]
        [string]
        $LastName,
        [ValidateLength(0, 80)]
        [string]
        $Address,
        [ValidateLength(0, 40)]
        [string]
        $City,
        [ValidateLength(0, 40)]
        [string]
        $State,
        [ValidateSet(
            'AX', 'AF', 'AL', 'DZ', 'AS', 'AD', 'AO', 'AI', 'AQ', 'AG', 'AR', 'AM', 'AW', 'AU', 'AT', 'AZ', 'BS', 'BH', 'BD', 'BB', 'BY', 'BE', 'BZ', 'BJ', 'BM', 'BT', 'BO',
            'BA', 'BW', 'BV', 'BR', 'IO', 'BN', 'BG', 'BF', 'BI', 'KH', 'CM', 'CA', 'CV', 'KY', 'CF', 'TD', 'CL', 'CN', 'CX', 'CC', 'CO', 'KM', 'CD', 'CG', 'CK', 'CR', 'CI',
            'HR', 'CU', 'CY', 'CZ', 'DK', 'DJ', 'DM', 'DO', 'EC', 'EG', 'SV', 'GQ', 'ER', 'EE', 'ET', 'FK', 'FO', 'FJ', 'FI', 'FR', 'GF', 'PF', 'TF', 'GA', 'GM', 'GE', 'DE',
            'GH', 'GI', 'GR', 'GL', 'GD', 'GP', 'GU', 'GT', 'GN', 'GW', 'GY', 'HT', 'HM', 'HN', 'HK', 'HU', 'IS', 'IN', 'ID', 'IR', 'IQ', 'IE', 'IL', 'IT', 'JM', 'JP', 'JO',
            'KZ', 'KE', 'KI', 'KP', 'KR', 'KW', 'KG', 'LA', 'LV', 'LB', 'LS', 'LR', 'LY', 'LI', 'LT', 'LU', 'MO', 'MK', 'MG', 'MW', 'MY', 'MV', 'ML', 'MT', 'MH', 'MQ', 'MR',
            'MU', 'YT', 'MX', 'FM', 'MD', 'MC', 'MN', 'MS', 'MA', 'MZ', 'MM', 'NA', 'NR', 'NP', 'NL', 'AN', 'NC', 'NZ', 'NI', 'NE', 'NG', 'NU', 'NF', 'MP', 'NO', 'OM', 'PK',
            'PW', 'PS', 'PA', 'PG', 'PY', 'PE', 'PH', 'PN', 'PL', 'PT', 'PR', 'QA', 'RE', 'RO', 'RU', 'RW', 'SH', 'KN', 'LC', 'PM', 'VC', 'WS', 'SM', 'ST', 'SA', 'SN', 'CS',
            'SC', 'SL', 'SG', 'SK', 'SI', 'SB', 'SO', 'ZA', 'GS', 'ES', 'LK', 'SD', 'SR', 'SJ', 'SZ', 'SE', 'CH', 'SY', 'TW', 'TJ', 'TZ', 'TH', 'TL', 'TG', 'TK', 'TO', 'TT',
            'TN', 'TR', 'TM', 'TC', 'TV', 'UG', 'UA', 'AE', 'GB', 'US', 'UM', 'UY', 'UZ', 'VU', 'VA', 'VE', 'VN', 'VG', 'VI', 'WF', 'EH', 'YE', 'ZM', 'ZW')]
        [string]
        $Country,
        [ValidateLength(0, 255)]
        [string]
        $Custom1,
        [ValidateLength(0, 255)]
        [string]
        $Custom2,
        [ValidateLength(0, 255)]
        [string]
        $Custom3
    )
    $Body = @{
        active = $Active
    }
    if ($TokenAuth)
    {
        if ($TokenType -like "ftk" -or $TokenType -like "ftm")
        {
            $Body.token_serial = $TokenSerial
            $Body.token_auth = $TokenAuth
            $Body.ftk_only = $True
        }
        elseif ($TokenType -like "sms")
        {
            $Body.ftk_only = $False
            $Body.token_auth = $False
            $Body.mobile_number = $MobileNumber
        }
        elseif ($TokenType -like "email")
        {
            $Body.ftk_only = $False
            $Body.token_auth = $False
            $Body.email = $Email
        }
        $Body.token_type = $TokenType
    }
    if ($Passcode)
    {
        $Body.password = $Passcode
        $Body.expires_at = (Get-Date).AddDays($Expire).ToUniversalTime() | Get-Date -format s
    }
    if ($FirstName) {$Body.first_name = $FirstName}
    if ($LastName) {$Body.last_name = $LastName}
    if ($Address) {$Body.address = $Address}
    if ($City) {$Body.city = $City}
    if ($State) {$Body.state = $State}
    if ($Country) {$Body.country = $Country}
    if ($Custom1) {$Body.custom1 = $Custom1}
    if ($Custom2) {$Body.custom2 = $Custom2}
    if ($Custom3) {$Body.custom3 = $Custom3}

    if ($Create)
    {
        $UserList = Get-User
        $UserList | ForEach-Object {
            if ($TokenSerial -like $_.token_serial)
            {
                # Remove/Unassign token
                Remove-TokenFromUser -ID $_.id
                # There shouldn't ever be one token assigned to more than one user
                break
            }
        }
        $UserList | ForEach-Object {
            if ($UserName -match $_.username)
            {
                # Remove-User -ID $_.id -Server $Server -Resource $Resource -Credentials $Credentials
                return @{UserFound = $true}
            }
        }
        Invoke-FortiAuthRestMethod -Resource "localusers/$($ID)/" -Method Post -Body $Body
    }
    else
    {
        Invoke-FortiAuthRestMethod -Resource "localusers/$($ID)/" -Method Patch -Body $Body
    }


}

Export-ModuleMember -Cmdlet Remove-TokenFromUser, Get-User, Set-User
Write-Verbose 'Importing from [C:\projects\fortiauth-import\FortiAuth-Import\classes]'