Skip to content

Instantly share code, notes, and snippets.

@icelander
Last active October 14, 2020 12:32
Show Gist options
  • Save icelander/b1b976cb8cbba1b5b3049ee33e325324 to your computer and use it in GitHub Desktop.
Save icelander/b1b976cb8cbba1b5b3049ee33e325324 to your computer and use it in GitHub Desktop.
This script generates an Okta import file from a Mattermost team or channels
#!/usr/bin/env ruby
require 'csv'
require 'yaml'
require 'httparty'
require 'uri'
# ## members_to_okta.rb
#
# ### About
#
# members_to_okta.rb is a Ruby script that generates a CSV file containing the
# users who are members of a team or channel suitable for importing into Okta.
# This is a combination of these gists:
# - https://gist.github.com/icelander/2b7e36e7fbfe3d9efa91c08c9a8a2a9e
# - https://gist.github.com/icelander/13540e6916dbd58c80fbf883a773d57c
#
# ### File Format
#
# The file created will be named `team-name-YYYY-MM-DD.csv` or
# `team-name-channel-name-YYYY-MM-DD.csv` and will contain the following fields,
# repeated for each different team:
#
# - Team Name
# - Channel Name
# - First Name
# - Last Name
# - Username
# - Email Address
# - Date Channel Last Viewed
#
# ### Usage:
#
# **Dependencies:** This script requires that you install Ruby -
# https://www.ruby-lang.org/en/documentation/installation/ - and install the
# required gems:
#
# - csv
# - yaml
# - uri
# - httparty
#
# Once you have Ruby installed, install them with this command:
#
# gem install csv yaml uri httparty
#
#
# 1. Change these values to match your environment. Be sure to quote the strings properly, e.g.
#
# mattermost_url = 'this is correct'
# admin_username = this is not correct
# auth_token = 'Neither is this"
#
mattermost_url = 'https://mattermost.example.com/'
auth_token = 'gqqceh7sjifiirinrg9d8fkwrh'
# 2. Run it through Ruby: `ruby members_to_okta.rb team-name[:channel-name]`
#
# If you want to get all the users for a team, provide just the team name, e.g.
#
# ruby members_to_okta.rb team-name
#
# If you want a specific channel, include the channel name:
#
# ruby members_to_okta.rb team-name:channel-name
#
# CSV files are generated in the current working directory.
class MattermostApi
include HTTParty
format :json
# UNCOMMENT NEXT LINE TO DEBUG REQUESTS
# debug_output $stdout
def initialize(mattermost_url, auth_token)
# Default Options
@options = {
headers: {
'Content-Type' => 'application/json',
'User-Agent' => 'Mattermost-HTTParty'
},
# TODO Make this more secure
verify: false
}
# check the config for mattermost_url
if mattermost_url.nil? or !url_valid?(mattermost_url)
raise 'url is required in configuration'
else
if mattermost_url[-1] != '/'
mattermost_url = mattermost_url + '/'
end
end
@base_uri = mattermost_url + 'api/v4/'
if auth_token.nil?
raise 'token not set'
end
@options[:headers]['Authorization'] = "Bearer #{auth_token}"
@options[:body] = nil
test_response = self.class.get("#{@base_uri}users/me", @options)
if test_response.code != 200
puts "Could not connect to Mattermost server, aborting."
exit 1
end
end
def url_valid?(url)
url = URI.parse(url) rescue false
end
def get_team_members(team_name)
puts "Getting channels for team: #{team_name}"
team = get_url("/teams/name/#{team_name}")
# Return Values
# - "id": "string",
# - "create_at": 0,
# - "update_at": 0,
# - "delete_at": 0,
# - "display_name": "string",
# - "name": "string",
# - "description": "string",
# - "email": "string",
# - "type": "string",
# - "allowed_domains": "string",
# - "invite_id": "string",
# - "allow_open_invite": true
return false if team.nil?
channels = get_url("/channels")
# Return value:
# - "id": "string",
# - "create_at": 0,
# - "update_at": 0,
# - "delete_at": 0,
# - "team_id": "string",
# - "type": "string",
# - "display_name": "string",
# - "name": "string",
# - "header": "string",
# - "purpose": "string",
# - "last_post_at": 0,
# - "total_msg_count": 0,
# - "extra_update_at": 0,
# - "creator_id": "string"
output = []
channels.each_with_index do |channel, index|
if channel['team_id'] == team['id']
output = output + get_channel_members(team_name, channel['name'])
end
end
return output
end
def get_channel_members(team_name, channel_name)
puts " - Getting members of channel: #{channel_name}"
channel = get_url("/teams/name/#{team_name}/channels/name/#{channel_name}")
members = get_url("/channels/#{channel['id']}/members")
# Return Values:
# - "channel_id": "string",
# - "user_id": "string",
# - "roles": "string",
# - "last_viewed_at": 0,
# - "msg_count": 0,
# - "mention_count": 0,
# - "notify_props":
# {},
# "last_update_at": 0
# }
output = []
members.each_with_index do |member, index|
user = get_url("/users/#{member['user_id']}")
output << user
end
return output
end
def get_all_users
get_url('users')
end
def get_url(url)
# TODO: Make this handle pagination
JSON.parse(self.class.get("#{@base_uri}#{url}", @options).to_s)
end
end
$mm = MattermostApi.new(mattermost_url, auth_token)
# If it contains a colon, get just that channel
if ARGV[0].include? ":"
team_name, channel_name = ARGV[0].split(':')
filename = "./#{team_name}-#{channel_name}-#{Time.now.strftime("%Y-%m-%d")}.csv"
puts "Outputting channel members of #{team_name}:#{channel_name} to #{filename}"
users = $mm.get_channel_members(team_name, channel_name)
else # get all channels for that team
team_name = ARGV[0]
filename = "./#{team_name}-#{Time.now.strftime("%Y-%m-%d")}.csv"
puts "Outputting team members of #{team_name} to #{filename}"
users = $mm.get_team_members(ARGV[0])
end
# Maps Okta CSV fields to Mattermost user properties
csv_fields = {'login' => 'email',
'firstName' => 'first_name',
'lastName' => 'last_name',
'middleName' => nil,
'honorificPrefix' => nil,
'honorificSuffix' => nil,
'email' => 'email',
'title' => 'position',
'displayName' => 'username',
'nickName' => 'nickname',
'profileUrl' => nil,
'secondEmail' => nil,
'mobilePhone' => nil,
'primaryPhone' => nil,
'streetAddress' => nil,
'city' => nil,
'state' => nil,
'zipCode' => nil,
'countryCode' => nil,
'postalAddress' => nil,
'preferredLanguage' => nil,
'locale' => 'locale',
'timezone' => nil,
'userType' => 'roles',
'employeeNumber' => nil,
'costCenter' => nil,
'organization' => nil,
'division' => nil,
'department' => nil,
'managerId' => nil,
'manager' => nil}
# TODO: Provision user via Okta API
CSV.open("./okta_import.csv", "wb") do |csv|
csv << csv_fields.keys
users.each do |user|
# Skip SAML users
next if user['auth_service'] == 'saml'
user_data = []
puts "Processing user #{user['id']} - #{user['email']}"
csv_fields.each do |okta_index, mattermost_index|
# TODO: Filter by auth_method
# TODO: Fix Formatting of locale Valid values are concatenation of the ISO 639-1 two letter language code
if mattermost_index.nil?
user_data << ""
else
puts "\tSetting #{okta_index} to #{user[mattermost_index]}"
user_data << user[mattermost_index]
end
end
csv << user_data
end
end
puts "File output complete"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment