Last active
October 14, 2020 12:32
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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