class Chef::Knife::DataBagFromOktaGroup

Public Instance Methods

run() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 68
def run
  validate_arguments
  validate_okta_config
  setup

  begin
    Chef::DataBag.validate_name!(@data_bag_name)
  rescue Chef::Exceptions::InvalidDataBagName => e
    ui.fatal(e.message)
    exit(1)
  end

  if same_as_existing_data_bag_item?
    ui.info("Data bag item #{@data_bag_item_name} already exists and contains no changes, skipping upload")
    exit(0)
  end

  create_data_bag_if_missing
  create_data_bag_item_file
  display_data_bag_item_changes
  display_data_bag_item_members
  check_changes_within_range
  upload_data_bag_item
ensure
  FileUtils.remove_entry tmpdir if tmpdir
end

Private Instance Methods

active_group_members(group_id) click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 282
def active_group_members(group_id)
  okta_client.list_group_members(group_id).shift.select { |user| user[:status] == "ACTIVE" }
end
attribute_key_values() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 141
def attribute_key_values
  @attribute_key_values ||= values_for_key(config[:okta_attribute]).sort
end
check_changes_within_range() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 145
def check_changes_within_range
  return if config[:max_change] == 0

  changes = data_bag_item_additions.size + data_bag_item_removals.size
  return if config[:max_change] > changes

  ui.fatal("Data bag item #{@data_bag_item_name} has more changes than --max-change allows (#{config[:max_change]}).")
  exit(1)
end
create_data_bag_if_missing() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 191
def create_data_bag_if_missing
# Verify if the data bag exists
  rest.get("data/#{@data_bag_name}")
  ui.info("Data bag #{@data_bag_name} already exists")
rescue Net::HTTPServerException => e
  raise unless e.to_s =~ /^404/
  # if it doesn't exists, try to create it
  rest.post("data", { "name" => @data_bag_name })
  ui.info("Created data_bag[#{@data_bag_name}]")
end
create_data_bag_item_file() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 202
def create_data_bag_item_file
  hash = { "id" => @data_bag_item_name, config[:okta_attribute] => attribute_key_values }
  File.open(data_bag_item_file, "w") { |f| f.write(JSON.pretty_generate(hash)) }
end
data_bag_item_additions() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 207
def data_bag_item_additions
  return attribute_key_values if data_bag_item_data.nil?
  attribute_key_values - data_bag_item_data[config[:okta_attribute]]
end
data_bag_item_data() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 173
def data_bag_item_data
  return @data_bag_item_data if @data_bag_item_data
  @data_bag_item_data = rest.get("data/#{@data_bag_name}/#{@data_bag_item_name}")
rescue Net::HTTPServerException
  nil
end
data_bag_item_exist?() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 180
def data_bag_item_exist?
  @data_bag_item_exists ||= data_bag_item_data
  @data_bag_item_exists.nil? ? false : true
rescue Net::HTTPServerException
  false
end
data_bag_item_file() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 187
def data_bag_item_file
  @data_bag_item_file ||= "#{tmpdir}/#{@data_bag_item_name}.json"
end
data_bag_item_removals() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 212
def data_bag_item_removals
  return [] if data_bag_item_data.nil?
  data_bag_item_data[config[:okta_attribute]] - attribute_key_values
end
display_data_bag_item_additions() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 217
def display_data_bag_item_additions
  return if data_bag_item_additions.empty?
  ui.info("The following will be added to the data bag item #{@data_bag_item_name}:")
  data_bag_item_additions.each { |v| ui.info("  * #{v}") }
end
display_data_bag_item_changes() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 229
def display_data_bag_item_changes
  return unless config[:show_changes]

  if data_bag_item_data.nil?
    display_data_bag_item_additions
  elsif data_bag_item_data.keys.reject { |e| e == "id" }.shift.to_s == config[:okta_attribute]
    display_data_bag_item_removals
    display_data_bag_item_additions
  else
    ui.info("A new Okta profile attribute has been specified, replacing existing data bag item with the following:")
    attribute_key_values.each { |v| ui.info("  * #{v}") }
  end
end
display_data_bag_item_members() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 243
def display_data_bag_item_members
  return unless config[:show_members]

  ui.info("The data bag item will be uploaded with the following contents:")
  attribute_key_values.each { |v| ui.info("  * #{v}") }
end
display_data_bag_item_removals() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 223
def display_data_bag_item_removals
  return if data_bag_item_removals.empty?
  ui.info("The following will be removed from the data bag item #{@data_bag_item_name}:")
  data_bag_item_removals.each { |v| ui.info("  * #{v}") }
end
group_hash(group_name) click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 269
def group_hash(group_name)
  group_hash = groups.select { |group| group[:type] == "OKTA_GROUP" && group[:profile][:name] =~ /^#{group_name}$/i }.shift
  if group_hash.nil?
    ui.fatal("Cannot find a group with the name \"#{group_name}\" in the specified Okta tenant")
    exit(1)
  end
  group_hash
end
group_id(group_name) click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 278
def group_id(group_name)
  group_hash(group_name)[:id]
end
groups() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 265
def groups
  @groups ||= okta_client.list_groups.shift
end
okta_client() click to toggle source

Okta

# File lib/chef/knife/data_bag_from_okta_group.rb, line 261
def okta_client
  @okta_client ||= Oktakit.new(token: config[:okta_token], api_endpoint: config[:okta_endpoint])
end
okta_user_active?(user_name) click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 298
def okta_user_active?(user_name)
  user_hash(user_name).status == "ACTIVE"
rescue NoMethodError
  false
end
same_as_existing_data_bag_item?() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 168
def same_as_existing_data_bag_item?
  return false if data_bag_item_data.nil?
  data_bag_item_data[config[:okta_attribute]] == attribute_key_values
end
setup() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 131
def setup
  @data_bag_name = @name_args.shift
  @data_bag_item_name = @name_args.shift
  @okta_groups = @name_args.shift.split(',')
end
tmpdir() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 137
def tmpdir
  @tmpdir ||= Dir.mktmpdir
end
upload_data_bag_item() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 250
def upload_data_bag_item
  ui.confirm("Data bag item #{@data_bag_item_name} exists, overwrite it") if data_bag_item_exist?

  knife_data_bag_from_file = Chef::Knife::DataBagFromFile.new
  knife_data_bag_from_file.ui = ui
  knife_data_bag_from_file.name_args = [@data_bag_name, data_bag_item_file]
  knife_data_bag_from_file.run
end
user_hash(user_name) click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 290
def user_hash(user_name)
  user_hash = users.select { |user| user.profile.displayName =~ /^#{user_name}$/i }.shift
  if user_hash.nil?
    ui.info("Cannot find a user with the name \"#{user_name}\" in the specified Okta tenant")
  end
  user_hash
end
users() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 286
def users
  @users ||= okta_client.list_users.shift
end
validate_arguments() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 97
def validate_arguments
  return unless @name_args.size < 3
  ui.msg(opt_parser)
  exit(1)
end
validate_okta_config() click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 103
def validate_okta_config
  config[:okta_attribute] ||= ENV["OKTA_ATTRIBUTE"] if ENV["OKTA_ATTRIBUTE"]
  config[:okta_endpoint] ||= ENV["OKTA_ENDPOINT"] if ENV["OKTA_ENDPOINT"]
  config[:okta_token] ||= ENV["OKTA_TOKEN"] if ENV["OKTA_TOKEN"]

  unless config[:okta_attribute]
    ui.fatal("You must use specify an Okta identity profile attribute, either using --okta-endpoint or knife[:okta_endpoint] in your config.")
    exit(1)
  end

  # This should probably be more dynamic, e.g. later on once we have a user hash to look if
  # the provided attribute matches a profile key and if not, fail, but for now we're OK with this.
  unless %w{displayName email login}.include?(config[:okta_attribute])
    ui.fatal('Unsupported value for --okta-attribute, please specify either "displayName", "email" or "login".')
    exit(1)
  end

  unless config[:okta_endpoint]
    ui.fatal("You must use specify an Okta API endpoint, either using --okta-endpoint or knife[:okta_endpoint] in your config.")
    exit(1)
  end

  unless config[:okta_token]
    ui.fatal("You must use specify an Okta API token, either using --okta-token or knife[:okta_token] in your config.")
    exit(1)
  end
end
values_for_key(key) click to toggle source
# File lib/chef/knife/data_bag_from_okta_group.rb, line 155
def values_for_key(key)
  values = []

  @okta_groups.each do |okta_group|
    group_members = active_group_members(group_id(okta_group))
    group_members.each do |group_member|
      values << group_member[:profile][key.to_sym]
    end
  end

  values.compact.sort.uniq
end