socialcast 1.2.3 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.travis.yml +1 -2
- data/bin/socialcast +2 -2
- data/lib/socialcast.rb +7 -15
- data/lib/socialcast/command_line/cli.rb +161 -0
- data/lib/socialcast/command_line/message.rb +18 -0
- data/lib/socialcast/command_line/provision.rb +330 -0
- data/lib/socialcast/command_line/version.rb +5 -0
- data/socialcast.gemspec +13 -10
- data/spec/fixtures/fake_attribute_map.rb +8 -6
- data/spec/fixtures/ldap_with_account_type_without_roles.yml +44 -0
- data/spec/fixtures/ldap_with_class_ldap_attribute.yml +47 -0
- data/spec/fixtures/ldap_with_connection_mapping.yml +52 -0
- data/spec/fixtures/ldap_with_connection_permission_mapping.yml +59 -0
- data/spec/fixtures/ldap_with_custom_attributes.yml +50 -0
- data/spec/fixtures/ldap_with_manager_attribute.yml +6 -3
- data/spec/fixtures/ldap_with_multiple_connection_mappings.yml +51 -0
- data/spec/fixtures/ldap_with_multiple_connection_permission_mappings.yml +75 -0
- data/spec/fixtures/ldap_with_plugin_mapping.yml +1 -1
- data/spec/fixtures/ldap_with_roles_without_account_type.yml +48 -0
- data/spec/fixtures/ldap_with_unique_identifier.yml +50 -0
- data/spec/fixtures/ldap_without_account_type_or_roles.yml +42 -0
- data/spec/{cli_spec.rb → socialcast/command_line/cli_spec.rb} +94 -82
- data/spec/socialcast/command_line/provision_spec.rb +497 -0
- data/spec/spec_helper.rb +2 -1
- metadata +103 -26
- data/lib/ext/array_ext.rb +0 -11
- data/lib/ext/string_ext.rb +0 -13
- data/lib/socialcast/cli.rb +0 -339
- data/lib/socialcast/message.rb +0 -17
- data/lib/socialcast/net_ldap_ext.rb +0 -87
- data/lib/socialcast/version.rb +0 -3
- data/spec/net_ldap_ext_spec.rb +0 -107
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NGRhZWZlNmVkZGY1ODEwZTcyNTJkMmYyZmE4ZjQ3Yzc4ZWIwNDIyNw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MDMzZGZiZjdmN2NkOTk5NzZjYTU2MzY3YzIxZDQxZWNkMmQxNWMwNw==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MmEzZWUwYjZlZDk1ZGM3NWRkYjNlYWRiMWU3NGIxZTg3ZWEzOTg2NGRiZGFk
|
10
|
+
NDk4MGQ0NWI1OGNmZWMzYTEwZWJmZDg0NjI3OGRlZmViNmI0YjAxNjQ2NGU4
|
11
|
+
M2I1NmZmYTllYWRlMjkwZmY2YTY2ZjgyZDdmZTYwYjY3ZTFhNzI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MTJmNDA3MTQ1ZDdkMDhhNDMyOWNiNjcyZTRhN2UzOWU2YjEzMDJmY2RiNDk3
|
14
|
+
YzZjMzMzNDdmYWYxMzllMmM0MGUxZTFiNTRiNzViZDVlZGRmZTQ1YjA4ZmRm
|
15
|
+
YjJiMzVjMzlmOGVlNDEzYzEzYTNhMTlkYzg5MGVlMmEwZTkyMTk=
|
data/.travis.yml
CHANGED
data/bin/socialcast
CHANGED
data/lib/socialcast.rb
CHANGED
@@ -1,40 +1,32 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'fileutils'
|
3
|
-
require File.join(File.dirname(__FILE__), 'ext', 'array_ext') unless Array.respond_to?(:wrap)
|
4
|
-
require File.join(File.dirname(__FILE__), 'ext', 'string_ext')
|
5
3
|
|
6
4
|
module Socialcast
|
7
|
-
|
8
|
-
def config_dir
|
5
|
+
module CommandLine
|
6
|
+
def self.config_dir
|
9
7
|
config_dir = File.expand_path '~/.socialcast'
|
10
8
|
FileUtils.mkdir config_dir, :mode => 0700 unless File.exist?(config_dir)
|
11
9
|
config_dir
|
12
10
|
end
|
13
|
-
def credentials_file
|
11
|
+
def self.credentials_file
|
14
12
|
File.join config_dir, 'credentials.yml'
|
15
13
|
end
|
16
|
-
def credentials
|
14
|
+
def self.credentials
|
17
15
|
fail 'Unknown Socialcast credentials. Run `socialcast authenticate` to initialize' unless File.exist?(credentials_file)
|
18
16
|
@@credentials ||= YAML.load_file(credentials_file)
|
19
17
|
end
|
20
|
-
def credentials=(options)
|
18
|
+
def self.credentials=(options)
|
21
19
|
File.open(credentials_file, "w") do |f|
|
22
20
|
f.write(options.to_yaml)
|
23
21
|
end
|
24
22
|
File.chmod 0600, credentials_file
|
25
23
|
end
|
26
|
-
def basic_auth_options
|
27
|
-
{:user => credentials[:user], :password => credentials[:password]}
|
28
|
-
end
|
29
|
-
def default_options
|
30
|
-
basic_auth_options
|
31
|
-
end
|
32
24
|
# configure restclient for api call
|
33
|
-
def resource_for_path(path, options = {}, debug = true)
|
25
|
+
def self.resource_for_path(path, options = {}, debug = true)
|
34
26
|
RestClient.log = Logger.new(STDOUT) if debug
|
35
27
|
RestClient.proxy = credentials[:proxy] if credentials[:proxy]
|
36
28
|
url = ['https://', credentials[:domain], path].join
|
37
|
-
RestClient::Resource.new url, options.merge(
|
29
|
+
RestClient::Resource.new url, options.merge({ :user => credentials[:user], :password => credentials[:password] })
|
38
30
|
end
|
39
31
|
end
|
40
32
|
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
require "thor"
|
6
|
+
require 'json'
|
7
|
+
require 'rest_client'
|
8
|
+
require 'highline'
|
9
|
+
require 'socialcast'
|
10
|
+
require 'socialcast/command_line/message'
|
11
|
+
require 'socialcast/command_line/provision'
|
12
|
+
|
13
|
+
require 'logger'
|
14
|
+
require 'fileutils'
|
15
|
+
|
16
|
+
# uncomment to debug HTTP traffic
|
17
|
+
# class ActiveResource::Connection
|
18
|
+
# def configure_http(http)
|
19
|
+
# http = apply_ssl_options(http)
|
20
|
+
# # Net::HTTP timeouts default to 60 seconds.
|
21
|
+
# if @timeout
|
22
|
+
# http.open_timeout = @timeout
|
23
|
+
# http.read_timeout = @timeout
|
24
|
+
# end
|
25
|
+
# http.set_debug_output STDOUT
|
26
|
+
# http
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
|
30
|
+
module Socialcast
|
31
|
+
module CommandLine
|
32
|
+
class CLI < Thor
|
33
|
+
include Thor::Actions
|
34
|
+
|
35
|
+
method_option :trace, :type => :boolean, :aliases => '-v'
|
36
|
+
def initialize(*args); super(*args) end
|
37
|
+
|
38
|
+
desc "authenticate", "Authenticate using your Socialcast credentials"
|
39
|
+
method_option :user, :type => :string, :aliases => '-u', :desc => 'email address for the authenticated user'
|
40
|
+
method_option :password, :type => :string, :aliases => '-p', :desc => 'password for the authenticated user'
|
41
|
+
method_option :domain, :type => :string, :default => 'api.socialcast.com', :desc => 'Socialcast community domain'
|
42
|
+
method_option :proxy, :type => :string, :desc => 'HTTP proxy options for connecting to Socialcast server'
|
43
|
+
def authenticate
|
44
|
+
user = options[:user] || ask('Socialcast username: ')
|
45
|
+
password = options[:password] || HighLine.new.ask("Socialcast password: ") { |q| q.echo = false }.to_s
|
46
|
+
domain = options[:domain]
|
47
|
+
|
48
|
+
url = ['https://', domain, '/api/authentication'].join
|
49
|
+
say "Authenticating #{user} to #{url}"
|
50
|
+
params = {:email => user, :password => password }
|
51
|
+
RestClient.log = Logger.new(STDOUT) if options[:trace]
|
52
|
+
RestClient.proxy = options[:proxy] if options[:proxy]
|
53
|
+
resource = RestClient::Resource.new url
|
54
|
+
response = resource.post params, :accept => :json
|
55
|
+
say "API response: #{response.body.to_s}" if options[:trace]
|
56
|
+
communities = JSON.parse(response.body.to_s)['communities']
|
57
|
+
domain = communities.detect {|c| c['domain'] == domain} ? domain : communities.first['domain']
|
58
|
+
|
59
|
+
Socialcast::CommandLine.credentials = {:user => user, :password => password, :domain => domain, :proxy => options[:proxy]}
|
60
|
+
say "Authentication successful for #{domain}"
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "share MESSAGE", "Posts a new message into socialcast"
|
64
|
+
method_option :url, :type => :string, :desc => '(optional) url to associate to the message'
|
65
|
+
method_option :message_type, :type => :string, :desc => '(optional) force an alternate message_type'
|
66
|
+
method_option :attachments, :type => :array, :default => []
|
67
|
+
method_option :group_id, :type => :numeric, :desc => "(optional) ID of group to post into"
|
68
|
+
def share(message = nil)
|
69
|
+
message ||= $stdin.read_nonblock(100_000) rescue nil
|
70
|
+
|
71
|
+
attachment_ids = []
|
72
|
+
options[:attachments].each do |path|
|
73
|
+
Dir[File.expand_path(path)].each do |attachment|
|
74
|
+
say "Uploading attachment #{attachment}..."
|
75
|
+
uploader = Socialcast::CommandLine.resource_for_path '/api/attachments', {}, options[:trace]
|
76
|
+
uploader.post({:attachment => File.new(attachment)}, {:accept => :json}) do |response, request, result|
|
77
|
+
if response.code == 201
|
78
|
+
attachment_ids << JSON.parse(response.body)['attachment']['id']
|
79
|
+
else
|
80
|
+
say "Error uploading attachment: #{response.body}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
ActiveResource::Base.logger = Logger.new(STDOUT) if options[:trace]
|
87
|
+
Socialcast::CommandLine::Message.configure_from_credentials
|
88
|
+
Socialcast::CommandLine::Message.create :body => message, :url => options[:url], :message_type => options[:message_type], :attachment_ids => attachment_ids, :group_id => options[:group_id]
|
89
|
+
|
90
|
+
say "Message has been shared"
|
91
|
+
end
|
92
|
+
|
93
|
+
desc 'provision', 'provision users from ldap compatible user repository'
|
94
|
+
method_option :config, :default => 'ldap.yml', :aliases => '-c', :desc => 'Path to ldap config file'
|
95
|
+
method_option :output, :default => Socialcast::CommandLine::Provision::DEFAULT_OUTPUT_FILE, :aliases => '-o', :desc => 'Name of the output file'
|
96
|
+
method_option :setup, :type => :boolean, :desc => 'Create an example ldap config file and exit'
|
97
|
+
method_option :delete_users_file, :type => :boolean, :desc => 'Delete the output file'
|
98
|
+
method_option :test, :type => :boolean, :desc => 'Do not persist changes'
|
99
|
+
method_option :skip_emails, :type => :boolean, :desc => 'Do not send signup emails to users'
|
100
|
+
method_option :force, :type => :boolean, :aliases => '-f', :default => false, :desc => 'Proceed with provisioning even if no users are found, which would deactivate all users in the community'
|
101
|
+
method_option :sanity_check, :type => :boolean, :default => false, :desc => 'Double check that users marked for termination really no longer exist'
|
102
|
+
method_option :plugins, :type => :array, :desc => "Pass in an array of plugins. Can be either the gem require or the absolute path to a ruby file"
|
103
|
+
def provision
|
104
|
+
config = ldap_config options
|
105
|
+
load_plugins options
|
106
|
+
|
107
|
+
Socialcast::CommandLine::Provision.new(config, options).provision
|
108
|
+
|
109
|
+
rescue Socialcast::CommandLine::Provision::ProvisionError => e
|
110
|
+
Kernel.abort e.message
|
111
|
+
end
|
112
|
+
|
113
|
+
desc 'sync_photos', 'Upload default avatar photos from LDAP repository'
|
114
|
+
method_option :config, :default => 'ldap.yml', :aliases => '-c'
|
115
|
+
def sync_photos
|
116
|
+
config = ldap_config options
|
117
|
+
|
118
|
+
Socialcast::CommandLine::Provision.new(config).sync_photos
|
119
|
+
end
|
120
|
+
|
121
|
+
no_tasks do
|
122
|
+
def load_plugins(options)
|
123
|
+
Array.wrap(options[:plugins]).each do |plugin|
|
124
|
+
begin
|
125
|
+
require plugin
|
126
|
+
rescue LoadError => e
|
127
|
+
fail "Unable to load #{plugin}: #{e}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def ldap_config(options)
|
133
|
+
config_file = File.expand_path options[:config]
|
134
|
+
|
135
|
+
if options[:setup]
|
136
|
+
create_file config_file do
|
137
|
+
File.read File.join(File.dirname(__FILE__), '..', '..', '..', 'config', 'ldap.yml')
|
138
|
+
end
|
139
|
+
say "Created config file: #{config_file}"
|
140
|
+
Kernel.exit 0
|
141
|
+
end
|
142
|
+
|
143
|
+
fail "Unable to load configuration file: #{config_file}" unless File.exists?(config_file)
|
144
|
+
say "Using configuration file: #{config_file}"
|
145
|
+
config = YAML.load_file config_file
|
146
|
+
|
147
|
+
mappings = config.fetch 'mappings', {}
|
148
|
+
required_mappings = %w{email first_name last_name}
|
149
|
+
required_mappings.each do |field|
|
150
|
+
unless mappings.has_key? field
|
151
|
+
fail "Missing required mapping: #{field}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
config
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_resource'
|
2
|
+
|
3
|
+
ActiveResource::Base.include_root_in_json = true
|
4
|
+
|
5
|
+
module Socialcast
|
6
|
+
module CommandLine
|
7
|
+
class Message < ActiveResource::Base
|
8
|
+
headers['Accept'] = 'application/json'
|
9
|
+
|
10
|
+
def self.configure_from_credentials
|
11
|
+
Socialcast::CommandLine::Message.site = ['https://', Socialcast::CommandLine.credentials[:domain], '/api'].join
|
12
|
+
Socialcast::CommandLine::Message.proxy = Socialcast::CommandLine.credentials[:proxy] if Socialcast::CommandLine.credentials[:proxy]
|
13
|
+
Socialcast::CommandLine::Message.user = Socialcast::CommandLine.credentials[:user]
|
14
|
+
Socialcast::CommandLine::Message.password = Socialcast::CommandLine.credentials[:password]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,330 @@
|
|
1
|
+
require 'net/ldap'
|
2
|
+
|
3
|
+
require 'zlib'
|
4
|
+
require 'builder'
|
5
|
+
require 'set'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
module Socialcast
|
9
|
+
module CommandLine
|
10
|
+
class Provision
|
11
|
+
DEFAULT_OUTPUT_FILE = 'users.xml.gz'
|
12
|
+
|
13
|
+
class ProvisionError < StandardError; end
|
14
|
+
|
15
|
+
def initialize(ldap_config, options = {})
|
16
|
+
@ldap_config = ldap_config.dup
|
17
|
+
@options = options.dup
|
18
|
+
|
19
|
+
@options[:output] ||= DEFAULT_OUTPUT_FILE
|
20
|
+
end
|
21
|
+
|
22
|
+
def each_user_hash
|
23
|
+
each_ldap_entry do |ldap, entry, attr_mappings, perm_mappings|
|
24
|
+
yield build_user_hash_from_mappings(ldap, entry, attr_mappings, perm_mappings)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def provision
|
29
|
+
http_config = @ldap_config.fetch 'http', {}
|
30
|
+
|
31
|
+
user_whitelist = Set.new
|
32
|
+
output_file = File.join Dir.pwd, @options[:output]
|
33
|
+
|
34
|
+
Zlib::GzipWriter.open(output_file) do |gz|
|
35
|
+
xml = Builder::XmlMarkup.new(:target => gz, :indent => 1)
|
36
|
+
xml.instruct!
|
37
|
+
xml.export do |export|
|
38
|
+
export.users(:type => "array") do |users|
|
39
|
+
each_user_hash do |user_hash|
|
40
|
+
users << user_hash.to_xml(:skip_instruct => true, :root => 'user')
|
41
|
+
user_whitelist << [user_hash['contact_info']['email'], user_hash['unique_identifier'], user_hash['employee_number']]
|
42
|
+
end
|
43
|
+
end # users
|
44
|
+
end # export
|
45
|
+
end # gzip
|
46
|
+
|
47
|
+
if @options[:sanity_check]
|
48
|
+
puts "Sanity checking users currently marked as needing to be terminated"
|
49
|
+
each_ldap_connection do |ldap_connection_name, connection, ldap|
|
50
|
+
attr_mappings = attribute_mappings(ldap_connection_name)
|
51
|
+
(current_socialcast_users(http_config) - user_whitelist).each do |user_identifiers|
|
52
|
+
combined_filters = []
|
53
|
+
['email', 'unique_identifier', 'employee_number'].each_with_index do |identifier, index|
|
54
|
+
combined_filters << ((attr_mappings[identifier].blank? || user_identifiers[index].nil?) ? nil : Net::LDAP::Filter.eq(attr_mappings[identifier], user_identifiers[index]))
|
55
|
+
end
|
56
|
+
combined_filters.compact!
|
57
|
+
filter = ((combined_filters.size > 1) ? '(|%s)' : '%s') % combined_filters.join(' ')
|
58
|
+
filter = Net::LDAP::Filter.construct(filter) & Net::LDAP::Filter.construct(connection["filter"])
|
59
|
+
ldap_result = ldap.search(:return_result => true, :base => connection["basedn"], :filter => filter, :attributes => ldap_search_attributes(ldap_connection_name))
|
60
|
+
raise ProvisionError.new "Found user marked for termination that should not be terminated: #{user_identifiers}" unless ldap_result.blank?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
if user_whitelist.empty? && !@options[:force]
|
66
|
+
raise ProvisionError.new "Skipping upload to Socialcast since no users were found"
|
67
|
+
else
|
68
|
+
puts "Uploading dataset to Socialcast..."
|
69
|
+
resource = Socialcast::CommandLine.resource_for_path '/api/users/provision', http_config
|
70
|
+
begin
|
71
|
+
File.open(output_file, 'r') do |file|
|
72
|
+
request_params = {:file => file}
|
73
|
+
request_params[:skip_emails] = 'true' if (@ldap_config['options']["skip_emails"] || @options[:skip_emails])
|
74
|
+
request_params[:test] = 'true' if (@ldap_config['options']["test"] || @options[:test])
|
75
|
+
resource.post request_params, :accept => :json
|
76
|
+
end
|
77
|
+
rescue RestClient::Unauthorized => e
|
78
|
+
raise ProvisionError.new "Authenticated user either does not have administration privileges or the community is not configured to allow provisioning. Please contact Socialcast support to if you need help." if e.http_code == 401
|
79
|
+
end
|
80
|
+
puts "Finished"
|
81
|
+
end
|
82
|
+
File.delete(output_file) if (@ldap_config['options']['delete_users_file'] || @options[:delete_users_file])
|
83
|
+
end
|
84
|
+
|
85
|
+
def sync_photos
|
86
|
+
http_config = @ldap_config.fetch 'http', {}
|
87
|
+
|
88
|
+
@ldap_config["connections"].keys.each do |ldap_connection_name|
|
89
|
+
attribute_mappings(ldap_connection_name).fetch('profile_photo')
|
90
|
+
end
|
91
|
+
|
92
|
+
search_users_resource = Socialcast::CommandLine.resource_for_path '/api/users/search', http_config
|
93
|
+
|
94
|
+
each_ldap_entry do |ldap, entry, attr_mappings, _|
|
95
|
+
email = grab(entry, attr_mappings['email'])
|
96
|
+
if profile_photo_data = grab(entry, attr_mappings['profile_photo'])
|
97
|
+
profile_photo_data = profile_photo_data.force_encoding('binary')
|
98
|
+
|
99
|
+
user_search_response = search_users_resource.get(:params => { :q => email, :per_page => 1 }, :accept => :json)
|
100
|
+
user_info = JSON.parse(user_search_response)['users'].first
|
101
|
+
if user_info && user_info['avatars'] && user_info['avatars']['is_system_default']
|
102
|
+
puts "Uploading photo for #{email}"
|
103
|
+
|
104
|
+
user_resource = Socialcast::CommandLine.resource_for_path "/api/users/#{user_info['id']}", http_config
|
105
|
+
content_type = case profile_photo_data
|
106
|
+
when Regexp.new("\AGIF8", nil, 'n')
|
107
|
+
'gif'
|
108
|
+
when Regexp.new('\A\x89PNG', nil, 'n')
|
109
|
+
'png'
|
110
|
+
when Regexp.new("\A\xff\xd8\xff\xe0\x00\x10JFIF", nil, 'n'), Regexp.new("\A\xff\xd8\xff\xe1(.*){2}Exif", nil, 'n')
|
111
|
+
'jpg'
|
112
|
+
else
|
113
|
+
puts "Skipping photo for #{email}: unknown image format (supports .gif, .png, .jpg)"
|
114
|
+
next
|
115
|
+
end
|
116
|
+
|
117
|
+
tempfile = Tempfile.new(["photo_upload", ".#{content_type}"])
|
118
|
+
tempfile.write(profile_photo_data)
|
119
|
+
tempfile.rewind
|
120
|
+
begin
|
121
|
+
user_resource.put({ :user => { :profile_photo => { :data => tempfile } } })
|
122
|
+
ensure
|
123
|
+
tempfile.unlink
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def dereference_mail(entry, ldap_connection, dn_field, mail_attribute)
|
133
|
+
dn = grab(entry, dn_field)
|
134
|
+
ldap_connection.search(:base => dn, :scope => Net::LDAP::SearchScope_BaseObject) do |manager_entry|
|
135
|
+
return grab(manager_entry, mail_attribute)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def build_user_hash_from_mappings(ldap_connection, entry, attr_mappings, perm_mappings)
|
140
|
+
user_hash = HashWithIndifferentAccess.new
|
141
|
+
primary_attributes = %w{unique_identifier first_name last_name employee_number}
|
142
|
+
primary_attributes.each do |attribute|
|
143
|
+
next unless attr_mappings.has_key?(attribute)
|
144
|
+
user_hash[attribute] = grab(entry, attr_mappings[attribute])
|
145
|
+
end
|
146
|
+
|
147
|
+
contact_attributes = %w{email location cell_phone office_phone}
|
148
|
+
user_hash['contact_info'] = {}
|
149
|
+
contact_attributes.each do |attribute|
|
150
|
+
next unless attr_mappings.has_key?(attribute)
|
151
|
+
user_hash['contact_info'][attribute] = grab(entry, attr_mappings[attribute])
|
152
|
+
end
|
153
|
+
|
154
|
+
custom_attributes = attr_mappings.keys - (primary_attributes + contact_attributes)
|
155
|
+
|
156
|
+
user_hash['custom_fields'] = []
|
157
|
+
custom_attributes.each do |attribute|
|
158
|
+
if attribute == 'manager'
|
159
|
+
user_hash['custom_fields'] << { 'id' => 'manager_email', 'label' => 'manager_email', 'value' => dereference_mail(entry, ldap_connection, attr_mappings[attribute], attr_mappings['email']) }
|
160
|
+
else
|
161
|
+
user_hash['custom_fields'] << { 'id' => attribute, 'label' => attribute, 'value' => grab(entry, attr_mappings[attribute]) }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
membership_attribute = perm_mappings.fetch 'attribute_name', 'memberof'
|
166
|
+
memberships = entry[membership_attribute]
|
167
|
+
external_ldap_groups = Array.wrap(perm_mappings.fetch('account_types', {})['external'])
|
168
|
+
if external_ldap_groups.any? { |external_ldap_group| memberships.include?(external_ldap_group) }
|
169
|
+
user_hash['account_type'] = 'external'
|
170
|
+
else
|
171
|
+
user_hash['account_type'] = 'member'
|
172
|
+
if permission_roles_mappings = perm_mappings['roles']
|
173
|
+
user_hash['roles'] = []
|
174
|
+
permission_roles_mappings.each_pair do |socialcast_role, ldap_groups|
|
175
|
+
Array.wrap(ldap_groups).each do |ldap_group|
|
176
|
+
if memberships.include?(ldap_group)
|
177
|
+
user_hash['roles'] << socialcast_role
|
178
|
+
break
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
user_hash
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
def each_ldap_entry(&block)
|
190
|
+
count = 0
|
191
|
+
|
192
|
+
each_ldap_connection do |ldap_connection_name, connection, ldap|
|
193
|
+
attr_mappings = attribute_mappings(ldap_connection_name)
|
194
|
+
perm_mappings = permission_mappings(ldap_connection_name)
|
195
|
+
ldap.search(:return_result => false, :filter => connection["filter"], :base => connection["basedn"], :attributes => ldap_search_attributes(ldap_connection_name)) do |entry|
|
196
|
+
if grab(entry, attr_mappings["email"]).present? || (attr_mappings.has_key?("unique_identifier") && grab(entry, attr_mappings["unique_identifier"]).present?)
|
197
|
+
yield ldap, entry, attr_mappings, perm_mappings
|
198
|
+
end
|
199
|
+
|
200
|
+
count += 1
|
201
|
+
puts "Scanned #{count} users" if ((count % 100) == 0)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
puts "Finished scanning #{count} users"
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
def each_ldap_connection
|
209
|
+
@ldap_config["connections"].each_pair do |ldap_connection_name, connection|
|
210
|
+
puts "Connecting to #{ldap_connection_name} at #{[connection["host"], connection["port"]].join(':')}"
|
211
|
+
ldap = create_ldap_instance(connection)
|
212
|
+
puts "Searching base DN: #{connection["basedn"]} with filter: #{connection["filter"]}"
|
213
|
+
yield ldap_connection_name, connection, ldap
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def create_ldap_instance(connection)
|
218
|
+
ldap = Net::LDAP.new :host => connection["host"], :port => connection["port"], :base => connection["basedn"]
|
219
|
+
ldap.encryption connection['encryption'].to_sym if connection['encryption']
|
220
|
+
ldap.auth connection["username"], connection["password"]
|
221
|
+
ldap
|
222
|
+
end
|
223
|
+
|
224
|
+
def ldap_search_attributes(ldap_connection_name)
|
225
|
+
attr_mappings = attribute_mappings(ldap_connection_name)
|
226
|
+
perm_mappings = permission_mappings(ldap_connection_name)
|
227
|
+
|
228
|
+
membership_attribute = perm_mappings.fetch 'attribute_name', 'memberof'
|
229
|
+
attributes = attr_mappings.values.map do |mapping_value|
|
230
|
+
value = begin
|
231
|
+
mapping_value.camelize.constantize
|
232
|
+
rescue NameError
|
233
|
+
mapping_value
|
234
|
+
end
|
235
|
+
|
236
|
+
case value
|
237
|
+
when Hash
|
238
|
+
dup_mapping_value = value.dup
|
239
|
+
dup_mapping_value.delete("value")
|
240
|
+
dup_mapping_value.values
|
241
|
+
when String
|
242
|
+
value
|
243
|
+
when Class, Module
|
244
|
+
if value.respond_to?(:attributes)
|
245
|
+
value.attributes
|
246
|
+
else
|
247
|
+
mapping_value
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end.flatten
|
251
|
+
attributes << membership_attribute
|
252
|
+
end
|
253
|
+
|
254
|
+
def current_socialcast_users(http_config)
|
255
|
+
current_socialcast_list = Set.new
|
256
|
+
request_params = {:per_page => 500}
|
257
|
+
request_params[:page] = 1
|
258
|
+
resource = create_socialcast_user_index_request(http_config, request_params)
|
259
|
+
loop do
|
260
|
+
response = resource.get :accept => :json
|
261
|
+
result = JSON.parse(response)
|
262
|
+
users = result["users"]
|
263
|
+
break if users.blank?
|
264
|
+
request_params[:page] += 1
|
265
|
+
resource = create_socialcast_user_index_request(http_config, request_params)
|
266
|
+
users.each do |user|
|
267
|
+
current_socialcast_list << [user['contact_info']['email'], user['company_login'], user['employee_number']]
|
268
|
+
end
|
269
|
+
end
|
270
|
+
current_socialcast_list
|
271
|
+
end
|
272
|
+
|
273
|
+
def create_socialcast_user_index_request(http_config, request_params)
|
274
|
+
path_template = "/api/users?per_page=%{per_page}&page=%{page}"
|
275
|
+
Socialcast::CommandLine.resource_for_path((path_template % request_params), http_config)
|
276
|
+
end
|
277
|
+
|
278
|
+
def attribute_mappings(connection_name)
|
279
|
+
@attribute_mappings ||= {}
|
280
|
+
|
281
|
+
unless @attribute_mappings[connection_name]
|
282
|
+
@attribute_mappings[connection_name] = @ldap_config['connections'][connection_name].fetch 'mappings', nil
|
283
|
+
@attribute_mappings[connection_name] ||= @ldap_config.fetch 'mappings', {}
|
284
|
+
end
|
285
|
+
|
286
|
+
@attribute_mappings[connection_name]
|
287
|
+
end
|
288
|
+
|
289
|
+
def permission_mappings(connection_name)
|
290
|
+
@permission_mappings ||= {}
|
291
|
+
|
292
|
+
unless @permission_mappings[connection_name]
|
293
|
+
@permission_mappings[connection_name] = @ldap_config['connections'][connection_name].fetch 'permission_mappings', nil
|
294
|
+
@permission_mappings[connection_name] ||= @ldap_config.fetch 'permission_mappings', {}
|
295
|
+
end
|
296
|
+
|
297
|
+
@permission_mappings[connection_name]
|
298
|
+
end
|
299
|
+
|
300
|
+
# grab a *single* value of an attribute
|
301
|
+
# abstracts away ldap multivalue attributes
|
302
|
+
def grab(entry, attribute)
|
303
|
+
const_attribute = begin
|
304
|
+
attribute.camelize.constantize
|
305
|
+
rescue NameError
|
306
|
+
attribute
|
307
|
+
end
|
308
|
+
|
309
|
+
case const_attribute
|
310
|
+
when Hash
|
311
|
+
dup_attribute = const_attribute.dup
|
312
|
+
value = dup_attribute.delete("value")
|
313
|
+
sprintf value, Hash[dup_attribute.map { |k, v| [k, grab(entry, v)] }].symbolize_keys
|
314
|
+
when String
|
315
|
+
normalize_ldap_value(entry, attribute)
|
316
|
+
when Class, Module
|
317
|
+
if const_attribute.respond_to?(:run)
|
318
|
+
const_attribute.run(entry)
|
319
|
+
else
|
320
|
+
normalize_ldap_value(entry, attribute)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def normalize_ldap_value(entry, attribute)
|
326
|
+
Array.wrap(entry[attribute]).compact.first
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|