Skip to content

Instantly share code, notes, and snippets.

@nickmalcolm
Created August 10, 2016 01:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nickmalcolm/0d3479c52ea77893c3f325368268b0a3 to your computer and use it in GitHub Desktop.
Save nickmalcolm/0d3479c52ea77893c3f325368268b0a3 to your computer and use it in GitHub Desktop.
Simple ruby script to pull events from Okta's API, and push them to ThisData
require 'this_data'
require 'httparty'
# A simple proof of concept which will pull a page of events from Okta, and push
# them to ThisData. This enables ThisData to detect behavioural anomalies, and
# keep a third-party access log.
#
# Requires the ThisData and HTTParty ruby gems.
#
# Usage:
# OktaSync.new(
# okta_org: "yourorg",
# okta_token: "abc",
# thisdata_api_key: "def"
# ).sync
#
# Improvements: add pagination, better event transformation when user is not
# in the users field
class OktaSync
def initialize(okta_org: nil, okta_token: nil, thisdata_api_key: nil)
@okta_org = okta_org
@okta_token = okta_token
# Set up ThisData
ThisData.setup do |config|
config.api_key = thisdata_api_key
config.logger = Logger.new($stdout)
config.async = false
end
end
def sync
okta_events = fetch_events_from_okta
okta_events.each do |okta_event|
if event = transform_okta_to_thisdata(okta_event)
ThisData.track(event)
end
end
end
# Fetch a single page of events from Okta
def fetch_events_from_okta
url = "https://#{@okta_org}.oktapreview.com/api/v1/events"
response = HTTParty.get(url, headers: {"Authorization" => "SSWS #{@okta_token}"})
JSON.parse(response.body)
end
# Some Okta events have equivalents in ThisData, which we should use.
def event_verb_from_okta_object_type(object_type)
case object_type
when "core.user_auth.login_success"
ThisData::Verbs::LOG_IN
when "core.user_auth.logout_success"
ThisData::Verbs::LOG_OUT
when "core.user_auth.login_failed"
ThisData::Verbs::LOG_IN_DENIED
when "core.user.sms.message_sent.factor"
ThisData::Verbs::LOG_IN_CHALLENGE
when "core.user.email.message_sent.self_service.password_reset"
ThisData::Verbs::PASSWORD_RESET
when "core.user.config.password_update.success"
ThisData::Verbs::PASSWORD_UPDATE
else
# No-op
object_type
end
end
# Takes an event from Okta, and turns it into an event ThisData can understand
def transform_okta_to_thisdata(event)
# Where is Okta keeping the user who performed this event?
if ["core.user.email.message_sent.self_service.password_reset", "core.user_auth.login_failed"].include?(event["action"]["objectType"])
actor = event["targets"].find {|a| a["objectType"] == "User" }
else
actor = event["actors"].find {|a| a["objectType"] == "User" }
end
client = event["actors"].find {|a| a["objectType"] == "Client" }
# Skip system events
return nil unless !actor.nil? && !client.nil?
ip = client["ipAddress"]
user_agent = client["id"]
{
verb: event_verb_from_okta_object_type(event["action"]["objectType"]),
ip: ip,
user_agent: user_agent,
user: {
id: actor["id"],
email: actor["login"],
name: actor["displayName"]
},
original: event
}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment