socialcast 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +1 -2
  3. data/bin/socialcast +2 -2
  4. data/lib/socialcast.rb +7 -15
  5. data/lib/socialcast/command_line/cli.rb +161 -0
  6. data/lib/socialcast/command_line/message.rb +18 -0
  7. data/lib/socialcast/command_line/provision.rb +330 -0
  8. data/lib/socialcast/command_line/version.rb +5 -0
  9. data/socialcast.gemspec +13 -10
  10. data/spec/fixtures/fake_attribute_map.rb +8 -6
  11. data/spec/fixtures/ldap_with_account_type_without_roles.yml +44 -0
  12. data/spec/fixtures/ldap_with_class_ldap_attribute.yml +47 -0
  13. data/spec/fixtures/ldap_with_connection_mapping.yml +52 -0
  14. data/spec/fixtures/ldap_with_connection_permission_mapping.yml +59 -0
  15. data/spec/fixtures/ldap_with_custom_attributes.yml +50 -0
  16. data/spec/fixtures/ldap_with_manager_attribute.yml +6 -3
  17. data/spec/fixtures/ldap_with_multiple_connection_mappings.yml +51 -0
  18. data/spec/fixtures/ldap_with_multiple_connection_permission_mappings.yml +75 -0
  19. data/spec/fixtures/ldap_with_plugin_mapping.yml +1 -1
  20. data/spec/fixtures/ldap_with_roles_without_account_type.yml +48 -0
  21. data/spec/fixtures/ldap_with_unique_identifier.yml +50 -0
  22. data/spec/fixtures/ldap_without_account_type_or_roles.yml +42 -0
  23. data/spec/{cli_spec.rb → socialcast/command_line/cli_spec.rb} +94 -82
  24. data/spec/socialcast/command_line/provision_spec.rb +497 -0
  25. data/spec/spec_helper.rb +2 -1
  26. metadata +103 -26
  27. data/lib/ext/array_ext.rb +0 -11
  28. data/lib/ext/string_ext.rb +0 -13
  29. data/lib/socialcast/cli.rb +0 -339
  30. data/lib/socialcast/message.rb +0 -17
  31. data/lib/socialcast/net_ldap_ext.rb +0 -87
  32. data/lib/socialcast/version.rb +0 -3
  33. 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
- Y2Q0N2I4MGQyYzQ1MzBlYmExMmY5NjcyZDQ2YzNmMWZkYTNmZWFlZg==
4
+ NGRhZWZlNmVkZGY1ODEwZTcyNTJkMmYyZmE4ZjQ3Yzc4ZWIwNDIyNw==
5
5
  data.tar.gz: !binary |-
6
- N2Q1OWJjZmNhYTE4MmRlNzg5MmFmZGIxNTg4M2YyZTdjZjIyMzlhYw==
6
+ MDMzZGZiZjdmN2NkOTk5NzZjYTU2MzY3YzIxZDQxZWNkMmQxNWMwNw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- OGE0ZWRiY2IwMGNlNjQ2NzE2MTc4NzE4ZWQ4NmFiNzEzZWMxNjc2MWNmOWFj
10
- ZGU1YzZjZjM5ZmE1ZTgzMjI2OWI5ZWQzODk0YzQyY2FhOTdlMWFmMTIxYjRi
11
- Y2RjNWM2OWYyYmJhMzEyMWI5ODI4MzJiMjM4MmUyMDBhNTRhMmY=
9
+ MmEzZWUwYjZlZDk1ZGM3NWRkYjNlYWRiMWU3NGIxZTg3ZWEzOTg2NGRiZGFk
10
+ NDk4MGQ0NWI1OGNmZWMzYTEwZWJmZDg0NjI3OGRlZmViNmI0YjAxNjQ2NGU4
11
+ M2I1NmZmYTllYWRlMjkwZmY2YTY2ZjgyZDdmZTYwYjY3ZTFhNzI=
12
12
  data.tar.gz: !binary |-
13
- M2Q1ZjdjODVlY2I1MmRmOGVjMmE0ZjAzMzkxYWU0YTI3OWY1OGVjODNjZWI1
14
- ZWFhNTAyYzU5ODAyNTdjMjAwZTNiNGFkMDYxNjNhYmI4YjQ5MjVlZDBhNzcz
15
- NWQ1NzdjODQ2MjcwMzQ0MTVkNWI5Y2UwMTk3YjkxZjZjZTRmZmQ=
13
+ MTJmNDA3MTQ1ZDdkMDhhNDMyOWNiNjcyZTRhN2UzOWU2YjEzMDJmY2RiNDk3
14
+ YzZjMzMzNDdmYWYxMzllMmM0MGUxZTFiNTRiNzViZDVlZGRmZTQ1YjA4ZmRm
15
+ YjJiMzVjMzlmOGVlNDEzYzEzYTNhMTlkYzg5MGVlMmEwZTkyMTk=
data/.travis.yml CHANGED
@@ -1,6 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.8.7
4
3
  - 1.9.3
5
- - ree
6
4
  - 2.0.0
5
+ - 2.1.1
data/bin/socialcast CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  $:.unshift(File.expand_path('../../lib', __FILE__))
4
- require 'socialcast/cli'
4
+ require 'socialcast/command_line/cli'
5
5
 
6
- Socialcast::CLI.start
6
+ Socialcast::CommandLine::CLI.start
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
- class << self
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(default_options)
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