---
description: Pipeline for parsing Salesforce Login (Streaming) logs
processors:
- set:
    field: event.ingested
    value: '{{_ingest.timestamp}}'
- remove:
    field:
      - message
    ignore_missing: true

- set:
    field: salesforce.access_mode
    value: stream
    ignore_failure: true

- date:
    field: json.EventDate
    target_field: "@timestamp"
    formats:
    - ISO8601
    ignore_failure: true
- rename:
    field: json.schema
    target_field: salesforce.login.schema
    ignore_missing: true
- rename:
    field: json.ApiType
    target_field: salesforce.login.api_type
    ignore_missing: true
- rename:
    field: json.AuthMethodReference
    target_field: salesforce.login.auth_method_reference
    ignore_missing: true
- rename:
    field: json.LoginType
    target_field: salesforce.login.login_type
    ignore_missing: true
- rename:
    field: json.PolicyOutcome
    target_field: salesforce.login.policy_outcome
    ignore_missing: true
- rename:
    field: json.AuthServiceId
    target_field: salesforce.login.auth_service_id
    ignore_missing: true
- rename:
    field: json.EvaluationTime
    target_field: salesforce.login.evaluation_time
    ignore_missing: true
- rename:
    field: json.ClientVersion
    target_field: salesforce.login.client_version
    ignore_missing: true
- rename:
    field: json.LoginGeoId
    target_field: salesforce.login.login_geo_id
    ignore_missing: true
- rename:
    field: json.LoginHistoryId
    target_field: salesforce.login.login_history_id
    ignore_missing: true
- rename:
    field: json.CreatedById
    target_field: salesforce.login.created_by_id
    ignore_missing: true
- rename:
    field: json.ApiVersion
    target_field: salesforce.login.api_version
    ignore_missing: true
- rename:
    field: json.RelatedEventIdentifier
    target_field: salesforce.login.related_event_identifier
    ignore_missing: true
- rename:
    field: json.LoginKey
    target_field: salesforce.login.login_key
    ignore_missing: true
- rename:
    field: json.Application
    target_field: salesforce.login.application
    ignore_missing: true
- rename:
    field: json.PolicyId
    target_field: salesforce.login.policy_id
    ignore_missing: true
- rename:
    field: json.SessionLevel
    target_field: salesforce.login.session_level
    ignore_missing: true
- rename:
    field: json.replayId
    target_field: salesforce.login.replay_id
    ignore_missing: true

#######################
## ECS Event Mapping ##
#######################

- set:
    field: event.outcome
    value: success
    if: 'ctx?.json?.Status == "Success" && ctx?.json?.Status != null'
    ignore_failure: true
- set:
    field: event.outcome
    value: failure
    if: 'ctx?.json?.Status != "Success" && ctx?.json?.Status != null'
    ignore_failure: true
- date:
    field: json.CreatedDate
    target_field: event.created
    formats: 
    - ISO8601
    ignore_failure: true
- rename:
    field: json.LoginUrl
    target_field: event.url
    ignore_missing: true
- set:
    field: event.type
    value: "info"
- set:
    field: event.kind
    value: "event"
- set:
    field: event.action
    value: "login-attempt"
- set:
    field: event.category
    value: "authentication"
- set:
    field: event.dataset
    value: "salesforce.login"
- set:
    field: event.module
    value: "salesforce"

######################
## ECS User Mapping ##
######################

# As per the following article, the username must be in the format of an email address.
# Reference: https://help.salesforce.com/s/articleView?language=en_US&type=5&id=sf.basics_intro_usernames_passwords.htm
- rename:
    field: json.Username
    target_field: user.email
    ignore_missing: true
- rename:
    field: json.UserId
    target_field: user.id
    ignore_missing: true
- rename:
    field: json.UserType
    target_field: user.roles
    ignore_missing: true

########################
## ECS Source Mapping ##
########################

- rename:
    field: json.SourceIp
    target_field: source.ip
    ignore_missing: true

############################
## ECS Related.ip Mapping ##
############################

- append:
    field: related.ip
    value: "{{{source.ip}}}"
    if: ctx?.source?.ip != null
    allow_duplicates: false
    ignore_failure: true

############################
## ECS Source.Geo Mapping ##
############################

- rename:
    field: json.LoginLatitude
    target_field: source.geo.location.lat
    ignore_missing: true
- rename:
    field: json.LoginLongitude
    target_field: source.geo.location.lon
    ignore_missing: true
- rename:
    field: json.CountryIso
    target_field: source.geo.country_iso_code
    ignore_missing: true
- rename:
    field: json.PostalCode
    target_field: source.geo.postal_code
    ignore_missing: true
- rename:
    field: json.City
    target_field: source.geo.city_name
    ignore_missing: true
- rename:
    field: json.Subdivision
    target_field: source.geo.region_name
    ignore_missing: true
- rename:
    field: json.Country
    target_field: source.geo.country_name
    ignore_missing: true

############################
## ECS User Agent Mapping ##
############################

- rename:
    field: json.Browser
    target_field: user_agent.name
    ignore_missing: true
- rename:
    field: json.Platform
    target_field: user_agent.os.platform
    ignore_missing: true

######################
## ECS HTTP Mapping ##
######################

- rename:
    field: json.HttpMethod
    target_field: http.request.method
    ignore_missing: true
- rename:
    field: json.AdditionalInfo
    target_field: http.request.body.content
    ignore_missing: true

#####################
## ECS TLS Mapping ##
#####################

- rename:
    field: json.CipherSuite
    target_field: tls.cipher
    ignore_missing: true
- dissect:
    pattern: "%{tls.version_protocol} %{tls.version}"
    field: "json.TlsProtocol"
    ignore_failure: true

#############
## Cleanup ##
#############

- script:
    description: Drops null/empty values recursively
    lang: painless
    source: |
        boolean dropEmptyFields(Object object) {
            if (object == null || object == "") {
                return true;
            } else if (object instanceof Map) {
                ((Map) object).values().removeIf(value -> dropEmptyFields(value));
                return (((Map) object).size() == 0);
            } else if (object instanceof List) {
                ((List) object).removeIf(value -> dropEmptyFields(value));
                return (((List) object).length == 0);
            }
            return false;
        }
        dropEmptyFields(ctx);
- remove:
    field:
      - json
    ignore_missing: true
on_failure:
- set:
    field: error.message
    value: '{{_ingest.on_failure_message}}'
