Logonme.ps1
enum DataType { Tenants OidcAuthorizations OidcTokens Saml2Tokens AuditEvents Users Links Devices Claims Roles Applications HotpTokens TotpTokens Fido2Tokens Passwords Templates Sessions } enum ConfigType { CertificatesSigning CertificatesEncryption CertificatesConnection CertificateStoreCurrentUser CertificateStoreLocalMachine CertificateStoreFile CertificateStoreSql CredsDirectoryService CredsOtp CredsPassword CredsSharedSecret CredsFido2 DirectoryAd DirectoryAdam DirectorySql DirectoryPortWise DirectoryNull BankIdV5 Sts StsClaimsMapIngress StsClaimsMapEgress StsRelyingParty Oidc OidcClient OidcClientSettings OidcResource OidcScope Saml2 Saml2IdentityProvider Saml2ServiceProvider AccountSelfService AuthenticationPolicy AuthenticationPolicyPath AuthenticationUserService AuthenticationProvisioningService AuthenticationEmailChallenge FormsMethodBankID FormsMethodCapcha FormsMethodEmailChallenge FormsMethodFido2 FormsMethodFido2Enrollment FormsMethodImpersonation FormsMethodOtpEnrollment FormsMethodPassword FormsMethodPasswordReset FormsMethodUserRegistration FormsMethodSaml2 FormsMethodSmsOtp FormsMethodSplash EmailSmtp EmailSendGrid SmsTelia SmsMobilstart SmsEmail SmsTurnpike UserVerificationBankID UserSyncLogonme4 UserSyncNull } class ApiEndpointConfigs { [Hashtable] $dataMap_ = @{ [DataType]::Tenants = '/data/tenants' [DataType]::OidcAuthorizations = '/data/oidc_authorizations' [DataType]::OidcTokens = '/data/oidc_tokens' [DataType]::Saml2Tokens = '/data/saml2_tokens' [DataType]::AuditEvents = '/data/audit_events' [DataType]::Users = '/data/users' [DataType]::Devices = '/data/user_devices' [DataType]::Claims = '/data/user_claims' [DataType]::Roles = '/data/user_roles' [DataType]::Applications = '/data/user_applications' [DataType]::Links = '/data/user_links' [DataType]::HotpTokens = '/data/creds_tokens_hotp' [DataType]::TotpTokens = '/data/creds_tokens_totp' [DataType]::Fido2Tokens = '/data/creds_tokens_fido2' [DataType]::Passwords = '/data/creds_password' [DataType]::Templates = '/data/templates' [DataType]::Sessions = '/data/sessions' } [Hashtable] $configMap_ = @{ [ConfigType]::CertificatesSigning = '/config/certificates_signing' [ConfigType]::CertificatesEncryption = '/config/certificates_encryption' [ConfigType]::CertificatesConnection = '/config/certificates_connection' [ConfigType]::CertificateStoreCurrentUser = '/config/certificates_store_current_user' [ConfigType]::CertificateStoreLocalMachine = '/config/certificates_store_local_machine' [ConfigType]::CertificateStoreFile = '/config/certificates_store_file' [ConfigType]::CertificateStoreSql = '/config/certificates_store_sql' [ConfigType]::CredsDirectoryService = '/config/credstore_directory' [ConfigType]::CredsOtp = '/config/credstore_otp' [ConfigType]::CredsPassword = '/config/credstore_password' [ConfigType]::CredsSharedSecret = '/config/credstore_shared_secret' [ConfigType]::CredsFido2 = '/config/credstore_fido2' [ConfigType]::DirectoryAd = '/config/directory_service_ad' [ConfigType]::DirectoryAdam = '/config/directory_service_adam' [ConfigType]::DirectoryNull = '/config/directory_service_null' [ConfigType]::DirectoryPortWise = '/config/directory_service_portwise' [ConfigType]::DirectorySql = '/config/directory_service_sql' [ConfigType]::BankIdV5 = '/config/eid_client_bankidv5' [ConfigType]::Sts = '/config/sts' [ConfigType]::StsClaimsMapIngress = '/config/sts_claimsmap_ingress' [ConfigType]::StsClaimsMapEgress = '/config/sts_claimsmap_egress' [ConfigType]::StsRelyingParty = '/config/sts_relying_party' [Configtype]::Oidc = '/config/oidc' [ConfigType]::OidcClient = '/config/oidc_client' [ConfigType]::OidcClientSettings = '/config/oidc_client_settings' [ConfigType]::OidcResource = '/config/oidc_resource' [ConfigType]::OidcScope = '/config/oidc_scope' [ConfigType]::Saml2 = '/config/saml2' [ConfigType]::Saml2ServiceProvider = '/config/saml2_service_provider' [ConfigType]::Saml2IdentityProvider = '/config/saml2_identity_provider' [ConfigType]::AccountSelfService = '/config/provisioning_account_self_service' [ConfigType]::AuthenticationPolicy = '/config/authentication_policy' [ConfigType]::AuthenticationPolicyPath = '/config/authentication_policy_path' [ConfigType]::AuthenticationUserService = '/config/authentication_user_service' [ConfigType]::AuthenticationProvisioningService = '/config/authentication_provisioning_service' [ConfigType]::AuthenticationEmailChallenge = '/config/authentication_email_challenge' [ConfigType]::FormsMethodBankID = '/config/forms_bankid' [ConfigType]::FormsMethodCapcha = '/config/forms_capcha' [ConfigType]::FormsMethodEmailChallenge = '/config/forms_email_challenge' [ConfigType]::FormsMethodFido2 = '/config/forms_fido2' [ConfigType]::FormsMethodFido2Enrollment = '/config/forms_fido2_enrollment' [ConfigType]::FormsMethodImpersonation = '/config/forms_impersonation' [ConfigType]::FormsMethodOtpEnrollment = '/config/forms_otp_enrollment' [ConfigType]::FormsMethodPassword = '/config/forms_password' [ConfigType]::FormsMethodPasswordReset = '/config/forms_password_reset' [ConfigType]::FormsMethodUserRegistration = '/config/forms_user_registration' [ConfigType]::FormsMethodSaml2 = '/config/forms_saml2' [ConfigType]::FormsMethodSmsOtp = '/config/forms_smsotp' [ConfigType]::FormsMethodSplash = '/config/forms_splash' [ConfigType]::EmailSmtp = '/config/notifier_mail_smtp' [ConfigType]::EmailSendGrid = '/config/notifier_mail_sendgrid' [ConfigType]::SmsTelia = '/config/notifier_sms_telia' [ConfigType]::SmsMobilstart = '/config/notifier_sms_mobilstart' [ConfigType]::SmsEmail = '/config/notifier_sms_email' [ConfigType]::SmsTurnpike = '/config/notifier_sms_turnpike' [ConfigType]::UserVerificationBankID = '/config/user_verification_bankid' [ConfigType]::UserSyncLogonme4 = '/config/usersync_logonme4' [ConfigType]::UserSyncNull = '/config/usersync_null' } [string] GetDataOdataPath([DataType] $dataType) { return $this.dataMap_[$dataType] } [string] GetConfigOdataPath([ConfigType] $configType) { return $this.configMap_[$configType] } } class ApiCredentials { [string] $AccessToken [string] $TokenType [int] $ExpiresIn; ApiCredentials([string]$accessToken, [string]$tokenType, [int]$expiresIn) { $this.AccessToken = $accessToken; $this.TokenType = $tokenType; $this.ExpiresIn = $expiresIn; } } class ApiConfig { [string] $Environment [string] $LoginServiceUrl [string] $ApiBaseUrl [string] $ApiTenantId [string] $ApiClientId [string] $ApiClientSecret [string] $ApiTimeout [string] $ApiSetupUsername [string] $ApiSetupPassword [string] $ServerRoot ApiConfig([string]$filePath, [string]$environment) { $ini = $this.GetIniContent($filePath) $this.Environment = $environment $this.LoginServiceUrl = $ini[$environment]["LoginServiceUrl"] $this.ApiBaseUrl = $ini[$environment]["ApiBaseUrl"] $this.ApiTenantId = $ini[$environment]["ApiTenantId"] $this.ApiClientId = $ini[$environment]["ApiClientId"] $this.ApiClientSecret = $ini[$environment]["ApiClientSecret"] $this.ApiTimeout = $ini[$environment]["ApiTimeout"] $this.ApiSetupUsername = $ini[$environment]["ApiSetupUsername"] $this.ApiSetupPassword = $ini[$environment]["ApiSetupPassword"] $this.ServerRoot = $ini[$environment]["ServerRoot"] } [HashTable] GetIniContent($filePath) { $ini = @{} $section = "" $CommentCount = 0 switch -regex -file $filePath { "^\[(.+)\]" { # Section $section = $matches[1] $ini[$section] = @{} $CommentCount = 0 } "^(;.*)$" { # Comment $value = $matches[1] $CommentCount = $CommentCount + 1 $name = "Comment" + $CommentCount $ini[$section][$name] = $value } "(.+?)\s*=(.*)" { # Key $name, $value = $matches[1..2] $ini[$section][$name] = $value } } return $ini } Dump() { Write-Host -Separator "-" Write-Host "Environment =" $this.Environment Write-Host "LoginServiceUrl =" $this.LoginServiceUrl Write-Host "ApiBaseUrl =" $this.ApiBaseUrl Write-Host "ApiTenantId =" $this.ApiTenantId Write-Host "ApiClientId =" $this.ApiClientId Write-Host "ApiClientSecret =" $this.ApiClientSecret Write-Host "ApiTimeout =" $this.ApiTimeout Write-Host "ApiSetupUsername =" $this.ApiSetupUsername Write-Host "ApiSetupPassword =" $this.ApiSetupPassword Write-Host -Separator "-" } DumpEnvironmentVariables() { Write-Host -Separator "-" Write-Host " LogonmeModuleFile = '$env:LogonmeModuleFile'" Write-Host " LogonmeVersion = '$env:LogonmeVersion'" Write-Host " LogonmeSharedVolumeDirectory = '$env:LogonmeSharedVolumeDirectory'" Write-Host " LogonmeTenant = '$env:LogonmeTenant'" Write-Host -Separator "-" } } if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) { $certCallback = @" using System; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; public class ServerCertificateValidationCallback { public static void Ignore() { if(ServicePointManager.ServerCertificateValidationCallback ==null) { ServicePointManager.ServerCertificateValidationCallback += delegate ( Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors ) { return true; }; } } } "@ Add-Type $certCallback } [ServerCertificateValidationCallback]::Ignore() [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; class ApiClient { [ApiConfig] $Config [ApiEndpointConfigs] $Endpoints [ApiCredentials] $Credentials ApiClient([ApiConfig] $config) { $this.Config = $config; $this.Endpoints = [ApiEndpointConfigs]::new(); $this.Credentials = $this.AquireAccessToken(); } [object] GetByFilter([string] $odataPath, [string] $odataFilter) { $uri = ($this.Config.ApiBaseUrl + $odataPath + '/?$filter=' + $odataFilter) $accessToken = $this.Credentials.AccessToken $headers = @{Authorization = "Bearer $accessToken" } $result = Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Get -Headers $headers | ForEach-Object { $_ } if ($result -isnot [array]) { $result = @( $result ) } $result | ForEach-Object { $refUri = ($this.Config.ApiBaseUrl + $odataPath + '/' + $_.id) $_ | Add-Member -MemberType NoteProperty -Name "URL" -Value $refUri } return $result } [object] GetWithBasicAuth([string] $path) { $uri = ($this.Config.ApiBaseUrl + $path) $accessToken = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}@{1}:{2}" -f $this.Config.ApiSetupUsername, $this.Config.ApiTenantId, $this.Config.ApiSetupPassword))) $headers = @{Authorization = "Basic $accessToken" } return Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Get -Headers $headers -ContentType "application/json" } [object] Get([string] $odataPath) { return $this.Get($odataPath, $null) } [object] Get([string] $odataPath, [object] $id) { $uri = ($this.Config.ApiBaseUrl + $odataPath + '/' + $id) $accessToken = $this.Credentials.AccessToken $headers = @{Authorization = "Bearer $accessToken" } $result = Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Get -Headers $headers -ContentType "application/json" if ($result -isnot [array]) { $result | Add-Member -MemberType NoteProperty -Name "URL" -Value $uri $result = @( $result ) return $result; } $result | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Name "URL" -Value ($uri + $_.id) } return $result } [object] Post([string] $odataPath, [object] $objData) { $uri = ($this.Config.ApiBaseUrl + $odataPath) $accessToken = $this.Credentials.AccessToken $headers = @{Authorization = "Bearer $accessToken" } $obj = $objData.PsObject.Copy() $obj.PSObject.Properties.Remove("URL") $json = $objData | ConvertTo-Json return Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Post -Body $json -Headers $headers -ContentType "application/json" } [object] Upload([string] $odataPath, [object] $id, [string] $uploadFile) { $uri = ($this.Config.ApiBaseUrl + $odataPath + '/upload/' + $id) $accessToken = $this.Credentials.AccessToken $headers = @{Authorization = "Bearer $accessToken" } $fileContents = Get-Item $uploadFile return Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Post -ContentType "multipart/form-data" -Headers $headers -Form @{ file = $fileContents } } [object] Put([object] $objData) { $uri = $objData.URL $accessToken = $this.Credentials.AccessToken $headers = @{Authorization = "Bearer $accessToken" } $obj = $objData.PsObject.Copy() $obj.PSObject.Properties.Remove("URL") $json = $obj | ConvertTo-Json return Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Put -Body $json -Headers $headers -ContentType "application/json" } [string] Delete([object] $objData) { $uri = $objData.URL $accessToken = $this.Credentials.AccessToken $headers = @{Authorization = "Bearer $accessToken" } return Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Delete -Headers $headers -ContentType "application/json" } [ApiCredentials] AquireAccessToken() { $Uri = $this.Config.LoginServiceUrl + "/ls/" + $this.Config.ApiTenantId + "/connect/token" try { $Body = @{ grant_type = "client_credentials" client_id = $this.Config.ApiClientId client_secret = $this.Config.ApiClientSecret scope = 'management' redirect_uri = 'https://localhost/' } $res = Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Post -Body $body -ContentType "application/x-www-form-urlencoded" return [ApiCredentials]::new($res.access_token, $res.token_type, $res.expires_in) } catch { $exception = $Error[0] Write-Warning "Failed to aquire access token from '$Uri', exception is '$exception'"; return $null } } } function Get-LogonmeApiClient { param( [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("env")] [string] $ConfigEnvironment = 'DEFAULT' ) $configFile = '.\Logonme.ini' $configTemplateFile = (Join-Path -Path $PSScriptRoot -ChildPath 'LogonmeTemplate.ini') if (-not (Test-Path -Path $configFile)) { Write-Warning "Logon.me API Client Configuration file '$configFile' does not exists, copying default template file '$configTemplateFile' to current directory" Copy-Item $configTemplateFile $configFile } $config = [ApiConfig]::new($configFile, $ConfigEnvironment) Write-Host "Imported configuration" -ForegroundColor green $config.Dump() switch ($config.ServerRoot) { '.' { $dataDirectory = (Get-Location).Path } Default { $dataDirectory = $config.ServerRoot } } $env:LogonmeTenant = ($config.ApiTenantId) $env:LogonmeSharedVolumeDirectory = ($dataDirectory) Write-Host "Exported environment variables" -ForegroundColor green $config.DumpEnvironmentVariables() return [ApiClient]::new($config) } function Get-LogonmeDockerComposeFile { $dockerComposeTemplateFile = (Join-Path -Path $PSScriptRoot -ChildPath 'docker-compose-template.yml') Get-Content -Path $dockerComposeTemplateFile } function Get-EnumValues { Param([string]$enum) $enumValues = @{} [enum]::getvalues([type]$enum) | ForEach-Object { $enumValues.add($_, $_.value__) } return $enumValues } function Get-LogonmeConfig { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("type")] [ConfigType]$ConfigType , [Parameter( Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true) ] [Alias("filter")] [String]$OdataFilter ) $odataPath = $ApiClient.Endpoints.GetConfigOdataPath($ConfigType) if ($OdataFilter) { $ApiClient.GetByFilter($odataPath, $odataFilter) } else { $ApiClient.Get($odataPath) } } function Add-LogonmeConfigFromFileData { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("type")] [ConfigType]$ConfigType , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [string]$Id , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [string]$UploadFile ) $odataPath = $ApiClient.Endpoints.GetConfigOdataPath($ConfigType) $ApiClient.Upload($odataPath, $id, $uploadFile) } function Add-LogonmeConfig { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("type")] [ConfigType]$ConfigType , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [string]$Id ) $odataPath = $ApiClient.Endpoints.GetConfigOdataPath($ConfigType) $obj = @{ id = $id } $temp = $ApiClient.Post($odataPath, $obj) $ApiClient.Get($odataPath, $id) } function Add-LogonmeData { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("type")] [DataType]$DataType , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [object]$obj ) $odataPath = $ApiClient.Endpoints.GetDataOdataPath($DataType) return $ApiClient.Post($odataPath, $obj) } function Get-LogonmeData { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("type")] [DataType]$DataType , [Parameter( Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true) ] [Alias("filter")] [String]$OdataFilter ) $odataPath = $ApiClient.Endpoints.GetDataOdataPath($DataType) if ($OdataFilter) { $ApiClient.GetByFilter($odataPath, $odataFilter) } else { $ApiClient.Get($odataPath) } } function Remove-LogonmeObject { param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("obj")] [object]$Object ) $ApiClient.Delete($Object) } function Set-LogonmeObject { param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("obj")] [object]$Object ) $ApiClient.Put($Object) } Set-Alias -Name Set-LogonmeConfig -Value Set-LogonmeObject Set-Alias -Name Set-LogonmeData -Value Set-LogonmeObject Set-Alias -Name Remove-LogonmeConfig -Value Remove-LogonmeObject Set-Alias -Name Remove-LogonmeData -Value Remove-LogonmeObject enum SetupOperation { SeedConfiguration SeedData VerifyConfiguration VerifyLocalization DumpLocalization ClearConfigurationCache ClearSessionCache Drop } function Invoke-LogonmeSetup { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [SetupOperation]$Operation , [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("env")] [string] $Environment = "develop" ) switch ( $Operation ) { SeedConfiguration { $ApiClient.GetWithBasicAuth("/setup/seed_configuration" + '?environment=' + $Environment) } SeedData { $ApiClient.GetWithBasicAuth("/setup/seed_data" + '?environment=' + $Environment) } VerifyConfiguration { $ApiClient.GetWithBasicAuth("/setup/verify_configuration") } VerifyLocalization { $uri = ($ApiClient.Config.LoginServiceUrl + '/ls/' + $ApiClient.Config.ApiTenantId + '/localization/verify') Invoke-RestMethod -SkipCertificateCheck -Method Get -Uri $uri } DumpLocalization { $uri = ($ApiClient.Config.LoginServiceUrl + '/ls/' + $ApiClient.Config.ApiTenantId + '/localization/i18n') Invoke-RestMethod -SkipCertificateCheck -Method Get -Uri $uri } ClearConfigurationCache { $uri = ($ApiClient.Config.LoginServiceUrl + '/ls/' + $ApiClient.Config.ApiTenantId + '/config/clear') $res = Invoke-RestMethod -SkipCertificateCheck -Method Get -Uri $uri Write-Host $res } ClearSessionCache { Get-LogonmeData -ApiClient $ApiClient -DataType Sessions | ForEach-Object { Logonme5-DeleteData -client $ApiClient -Object $_ } } Drop { $ApiClient.GetWithBasicAuth("/setup/drop") } } } function Get-LogonmeVersion { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient ) $managementServiceVersion = $ApiClient.GetWithBasicAuth("/about/version") $uri = ($ApiClient.Config.LoginServiceUrl + '/ls/' + $ApiClient.Config.ApiTenantId + '/about/version') $loginServiceVersion = Invoke-RestMethod -SkipCertificateCheck -Method Get -Uri $uri Write-Host "Management-Service: $managementServiceVersion" Write-Host "Login-Service: $loginServiceVersion" } function MakeARestCall { param( [string] $Uri , [string] $AccessToken , [string] $Username , [string] $LogFile ) try { $Headers = @{Authorization = "Bearer $AccessToken" } return Invoke-RestMethod -SkipCertificateCheck -Uri $Uri -Method Get -Headers $Headers -ContentType "application/json" -Verbose } catch { Write-Warning "$Username FAILED" Add-Content $LogFile $Username } } function Sync-LogonmeUsers { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("if")] [string]$ImportFile , [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("uid")] [string]$Username ) if (-not ($ImportFile -or $Username)) { Write-Host "parameter ``ImportFile`` or ``Username`` must be specified" return } $baseUrl = $ApiClient.Config.ApiBaseUrl $accessToken = $ApiClient.Credentials.AccessToken $funcDef = $function:MakeARestCall.ToString() $logFile = "sync-user-errors.txt" if (Test-Path $logFile -PathType leaf) { Clear-Content $logFile } if ($ImportFile) { $users = Get-Content $importFile } if ($Username) { $users = @( $Username ) } $users | ForEach-Object -Parallel { $function:MakeARestCall = $using:funcDef $uri = $using:baseUrl + '/user_provisioning/sync-user?loginName=' + $_ MakeARestCall -Uri $uri -AccessToken $using:accessToken -Username $_ -LogFile $using:logFile } -ThrottleLimit 10 } enum AdfsConfigOperation { Install Uninstall } function Get-LogonmeAdfsConfig { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [AdfsConfigOperation]$Operation ) switch ( $Operation ) { Install { $ApiClient.Get('/config/adfs/install') } Uninstall { $ApiClient.Get('/config/adfs/uninstall') } } } function Find-LogonmeUsers { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("l")] [string]$Login ) $escLogin = [uri]::EscapeDataString($Login) $result = New-Object System.Collections.ArrayList $u = Get-LogonmeData -ApiClient $ApiClient -DataType Users -OdataFilter "username eq '$escLogin'" if ($null -ne $u) { $result.Add($u) | Out-Null } $users = Get-LogonmeData -ApiClient $ApiClient -DataType Users -OdataFilter "emailAddress eq '$escLogin'" if ($null -ne $users -and $users.count -gt 0) { $result += $users } $users = Get-LogonmeData -ApiClient $ApiClient -DataType Users -OdataFilter "mobilePhone eq '$escLogin'" if ($null -ne $users -and $users.count -gt 0) { $result += $users } return $result } function Get-LogonmeUserData { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("l")] [string]$Login , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [DataType]$DataType ) $users = Find-LogonmeUsers -ApiClient $ApiClient -Login $Login if ($null -ne $users) { if ($users.count -gt 1) { Write-Error "User search retured more than multiple users, use 'username' resolve this" return } $u = $users[0] $uid = $u.id return Get-LogonmeData -ApiClient $ApiClient -DataType $DataType -OdataFilter "userId eq '$uid'" } } function Add-LogonmeUserRole { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("l")] [string]$Login , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [string]$Name , [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [string]$Issuer , [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [string]$ClaimType ) if (-not $Issuer) { $Issuer = "Logonme5" # default Issuer } if (-not $ClaimType) { $ClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" # default claim type } $users = Find-LogonmeUsers -ApiClient $ApiClient -Login $login if ($null -ne $users) { if ($users.count -gt 1) { Write-Error "User search retured more than multiple users, use 'username' resolve this" return } $u = $users[0] $obj = @{ id = "rid-" + [guid]::NewGuid().ToString("n") userId = $u.id issuer = $Issuer name = $Name claimType = $ClaimType } $temp = Add-LogonmeData -ApiClient $ApiClient -DataType Roles -obj $obj if ($temp) { $uid = $obj.userId $rid = $obj.id Write-Host "Successfully created role '$Name' ($rid) for user '$uid'" return Get-LogonmeData -ApiClient $ApiClient -DataType Roles -OdataFilter "id eq '$rid'" } Write-Error "Failed to create role '$Name' for user '$u'" } else { Write-Error "User not found" } } function Export-LogonmeData { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient ) Get-LogonmeData -ApiClient $ApiClient -DataType Tenants Get-LogonmeData -ApiClient $ApiClient -DataType OidcAuthorizations Get-LogonmeData -ApiClient $ApiClient -DataType OidcTokens Get-LogonmeData -ApiClient $ApiClient -DataType Saml2Tokens Get-LogonmeData -ApiClient $ApiClient -DataType AuditEvents Get-LogonmeData -ApiClient $ApiClient -DataType Users Get-LogonmeData -ApiClient $ApiClient -DataType Devices Get-LogonmeData -ApiClient $ApiClient -DataType Claims Get-LogonmeData -ApiClient $ApiClient -DataType Roles Get-LogonmeData -ApiClient $ApiClient -DataType Applications Get-LogonmeData -ApiClient $ApiClient -DataType Links Get-LogonmeData -ApiClient $ApiClient -DataType HotpTokens Get-LogonmeData -ApiClient $ApiClient -DataType TotpTokens Get-LogonmeData -ApiClient $ApiClient -DataType Fido2Tokens Get-LogonmeData -ApiClient $ApiClient -DataType Passwords Get-LogonmeData -ApiClient $ApiClient -DataType Sessions } function Export-LogonmeConfigs { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("client")] [ApiClient] $ApiClient ) Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificatesSigning Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificatesEncryption Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificatesConnection Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificateStoreCurrentUser Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificateStoreLocalMachine Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificateStoreFile Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificateStoreSql Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CredsDirectoryService Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CredsOtp Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CredsPassword Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CredsSharedSecret Get-LogonmeConfig -ApiClient $ApiClient -ConfigType DirectoryAd Get-LogonmeConfig -ApiClient $ApiClient -ConfigType DirectoryAdam Get-LogonmeConfig -ApiClient $ApiClient -ConfigType DirectorySql Get-LogonmeConfig -ApiClient $ApiClient -ConfigType DirectoryPortWise Get-LogonmeConfig -ApiClient $ApiClient -ConfigType BankIdV5 Get-LogonmeConfig -ApiClient $ApiClient -ConfigType Sts Get-LogonmeConfig -ApiClient $ApiClient -ConfigType StsClaimsMapIngress Get-LogonmeConfig -ApiClient $ApiClient -ConfigType StsClaimsMapEgress Get-LogonmeConfig -ApiClient $ApiClient -ConfigType StsRelyingParty Get-LogonmeConfig -ApiClient $ApiClient -ConfigType Oidc Get-LogonmeConfig -ApiClient $ApiClient -ConfigType OidcClient Get-LogonmeConfig -ApiClient $ApiClient -ConfigType OidcClientSettings Get-LogonmeConfig -ApiClient $ApiClient -ConfigType OidcResource Get-LogonmeConfig -ApiClient $ApiClient -ConfigType OidcScope Get-LogonmeConfig -ApiClient $ApiClient -ConfigType Saml2 Get-LogonmeConfig -ApiClient $ApiClient -ConfigType Saml2ServiceProvider Get-LogonmeConfig -ApiClient $ApiClient -ConfigType Saml2IdentityProvider Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AccountSelfService Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AuthenticationPolicy Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AuthenticationPolicyPath Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AuthenticationUserService Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AuthenticationProvisioningService Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AuthenticationEmailChallenge Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodBankID Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodCapcha Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodEmailChallenge Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodFido2 Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodFido2Enrollment Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodImpersonation Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodOtpEnrollment Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodPassword Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodPasswordReset Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodUserRegistration Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodSmsOtp Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodSplash Get-LogonmeConfig -ApiClient $ApiClient -ConfigType EmailSmtp Get-LogonmeConfig -ApiClient $ApiClient -ConfigType EmailSendGrid Get-LogonmeConfig -ApiClient $ApiClient -ConfigType SmsTelia Get-LogonmeConfig -ApiClient $ApiClient -ConfigType SmsMobilstart Get-LogonmeConfig -ApiClient $ApiClient -ConfigType SmsEmail Get-LogonmeConfig -ApiClient $ApiClient -ConfigType SmsTurnpike Get-LogonmeConfig -ApiClient $ApiClient -ConfigType UserVerificationBankID Get-LogonmeConfig -ApiClient $ApiClient -ConfigType UserSyncLogonme4 Get-LogonmeConfig -ApiClient $ApiClient -ConfigType UserSyncNull } function New-LogonmeIisWebSite { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("dir")] [string] $deploymentDirectory , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("svc")] [string] $svcName , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("site")] [string] $siteName , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("port")] [int] $svcPort , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("cert")] [string] $svcServerCertThumbprint ) $physicalPath = Join-Path -Path $deploymentDirectory -ChildPath "Services/$svcName/Bin" if (-not (Test-Path $physicalPath)) { Write-Error "IIS physical path for service '$svcName' is set to directory '$deplophysicalPathymentDirectory' but this directory does not exists." return $false } New-Item -Path $physicalPath -Name $siteName -ItemType "directory" -Force | Out-Null New-WebAppPool -Name $siteName | Out-Null New-WebSite -Name $siteName -Port $svcPort -Ssl -SslFlags 0 -PhysicalPath $physicalPath -ApplicationPool $siteName | Out-Null $site = Get-ChildItem -Path "IIS:\Sites" | Where-Object {( $_.Name -eq $siteName )} $binding = $site.Bindings.Collection | Where-Object {( $_.protocol -eq 'https')} $binding.AddSslCertificate($svcServerCertThumbprint, "my") Set-ItemProperty -Path "IIS:\AppPools\$siteName" -Name managedRuntimeVersion -Value "" return $true } function Remove-LogonmeIisWebSite { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("site")] [string] $siteName ) Remove-WebAppPool -Name $siteName Remove-WebSite -Name $siteName } function Set-LogonmeDeploymentDirectoryPermissions { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("dir")] [string] $deploymentDirectory , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("svc")] [string] $svcName ) $svcPool = "IIS AppPool\$svcName" $aclPath = $deploymentDirectory $acl = Get-ACL -Path $aclPath $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($svcPool, "Read", "ContainerInherit, ObjectInherit", "None", "Allow") $acl.SetAccessRule($accessRule) Write-Host "Service $svcName setting default inherited 'Read' permissions on '$aclPath' and subfolders" $acl | Set-Acl -Path $aclPath $aclPath = Join-Path -Path $deploymentDirectory -ChildPath "ServerRoot\Db" $acl = Get-ACL -Path $aclPath $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($svcPool, "Modify", "None", "None", "Allow") $acl.SetAccessRule($accessRule) Write-Host "Service $svcName setting 'Modify' permissions on '$aclPath'" $acl | Set-Acl -Path $aclPath $aclPath = Join-Path -Path $deploymentDirectory -ChildPath "ServerRoot\Logs" $acl = Get-ACL -Path $aclPath $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($svcPool, "Modify", "None", "None", "Allow") $acl.SetAccessRule($accessRule) Write-Host "Service $svcName setting 'Modify' permissions on '$aclPath'" $acl | Set-Acl -Path $aclPath $aclPath = Join-Path -Path $deploymentDirectory -ChildPath "ServerRoot\Keys" $acl = Get-ACL -Path $aclPath $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($svcPool, "Modify", "None", "None", "Allow") $acl.SetAccessRule($accessRule) Write-Host "Service $svcName setting 'Modify' permissions on '$aclPath'" $acl | Set-Acl -Path $aclPath } enum IisSetupOperation { Install Uninstall } function Invoke-LogonmeIisSetup { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [IisSetupOperation]$Operation , [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("dir")] [string] $deploymentDirectory , [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("cert")] [X509Certificate] $serverCertificate ) Import-Module WebAdministration if (-not (Test-Path $deploymentDirectory)) { Write-Error "Deployment directory '$deploymentDirectory' does not exists, please unpack release tar, e.g. 'tar xvf Logonme.x.y.x.tar' file to create this directory." return $false } Write-Host "Using deployment directory '$deploymentDirectory'." switch ( $Operation ) { Install { if (-not $serverCertificate) { Write-Error "Logon.me server certificate '$serverCertificate' not found." return $false } $thumb = $serverCertificate.GetCertHashString() Write-Host "Using server certificate with thumbprint '$thumb'" New-LogonmeIisWebSite -deploymentDirectory $deploymentDirectory -svcName "LoginService" -siteName "login-service" -svcPort 443 -svcServerCertThumbprint $thumb New-LogonmeIisWebSite -deploymentDirectory $deploymentDirectory -svcName "ManagementService" -siteName "management-service" -svcPort 5006 -svcServerCertThumbprint $thumb Set-LogonmeDeploymentDirectoryPermissions -deploymentDirectory $deploymentDirectory -svcName "LoginService" Set-LogonmeDeploymentDirectoryPermissions -deploymentDirectory $deploymentDirectory -svcName "ManagementService" } Uninstall { Remove-LogonmeIisWebSite "login-service" Remove-LogonmeIisWebSite "management-service" } } } function Get-FirstDockerImageFromFile { param ( [Parameter( Mandatory = $true) ] [string]$DockerComposeFile ) $text = Get-Content -Path $DockerComposeFile $regex = "(.*?):[\s]{0,2}([^']{0,1}[\%]?[\w\d]+[\%]?[\/,\-\.\(\)\%\\h\p{L}]+)" $res = [regex]::Matches($text, $regex); $firstImage = $res[1].Groups[2].Value return $firstImage.Trim() } function Build-LogonmeDockerImageFromServiceDirectory { param ( [Parameter( Mandatory = $true) ] [string]$ServiceDirectory , [Parameter()] [Switch] $Publish ) Push-Location (Get-Location) Set-Location $ServiceDirectory $DockerComposeFile = 'docker-compose.yml' $DockerImage = Get-FirstDockerImageFromFile -DockerComposeFile $DockerComposeFile $ServiceName = Split-Path -Path $ServiceDirectory -Leaf $ServiceExecutable = "Bin\$ServiceName.dll" if (-not (Test-Path $ServiceExecutable)) { Write-Error "Service executable '$ServiceExecutable' does not exists" return $false } $gitVersion = (Get-Item $ServiceExecutable).VersionInfo.FileVersion Write-Host "Retrieved file version '$gitVersion' for '$ServiceExecutable'" -ForegroundColor Cyan Write-Host "Creating docker image content for project service '$DockerImage' in '$ServiceDirectory'" -ForegroundColor Cyan Remove-Item -Force 'logonme.tar' -ErrorAction Ignore | Out-Null ($foo = tar cvf logonme.tar Bin) 2> $null Write-Host "Composing docker image for service '$DockerImage'" docker-compose -f $DockerComposeFile build Write-Host "Setting docker image tags to: $gitVersion" docker tag $DockerImage ${DockerImage}:$gitVersion $success = $false if ($Publish) { Write-Host "Publishing docker image to registry" docker push ${DockerImage}:$gitVersion if ($LASTEXITCODE -ne 0) { $success = $false } } Pop-Location return $success } function Build-LogonmeDockerImages { param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true) ] [Alias("dir")] [string] $deploymentDirectory ) $services = Get-ChildItem (Join-Path $deploymentDirectory -ChildPath "Services") -Directory foreach ($serviceDirectory in $services) { Write-Host "Building docker image in service directory '$serviceDirectory'" $success = Build-LogonmeDockerImageFromServiceDirectory -ServiceDirectory $serviceDirectory if (!$success) { Write-Error "Failed to build/publish docker image for servcice directory '$serviceDirectory'" return } } Write-Host "Successfully built docker images" -ForegroundColor Green } |