socialcast 1.1.5 → 1.1.6

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.
@@ -11,6 +11,7 @@ require File.join(File.dirname(__FILE__), 'net_ldap_ext')
11
11
  require 'zlib'
12
12
  require 'logger'
13
13
  require 'builder'
14
+ require 'set'
14
15
  require 'net/ldap'
15
16
 
16
17
  # uncomment to debug HTTP traffic
@@ -97,6 +98,7 @@ module Socialcast
97
98
  method_option :test, :type => :boolean
98
99
  method_option :skip_emails, :type => :boolean
99
100
  method_option :force, :type => :boolean, :aliases => '-f', :default => false
101
+ method_option :sanity_check, :type => :boolean, :default => false
100
102
  def provision
101
103
  config_file = File.expand_path options[:config]
102
104
 
@@ -106,11 +108,12 @@ module Socialcast
106
108
  end
107
109
  return
108
110
  end
109
-
111
+
110
112
  fail "Unable to load configuration file: #{config_file}" unless File.exists?(config_file)
111
113
  say "Using configuration file: #{config_file}"
112
-
114
+
113
115
  config = load_configuration config_file
116
+ http_config = config.fetch('http', {})
114
117
 
115
118
  required_mappings = %w{email first_name last_name}
116
119
  mappings = config.fetch 'mappings', {}
@@ -125,6 +128,8 @@ module Socialcast
125
128
  attributes = mappings.values
126
129
  attributes << membership_attribute
127
130
 
131
+ user_identifier_list = %w{email unique_identifier employee_number}
132
+ user_whitelist = Set.new
128
133
  count = 0
129
134
  output_file = File.join Dir.pwd, options[:output]
130
135
  Zlib::GzipWriter.open(output_file) do |gz|
@@ -132,20 +137,14 @@ module Socialcast
132
137
  xml.instruct!
133
138
  xml.export do |export|
134
139
  export.users(:type => "array") do |users|
135
- config["connections"].each_pair do |key, connection|
136
- say "Connecting to #{key} at #{[connection["host"], connection["port"]].join(':')}"
137
-
138
- ldap = Net::LDAP.new :host => connection["host"], :port => connection["port"], :base => connection["basedn"]
139
- ldap.encryption connection['encryption'].to_sym if connection['encryption']
140
- ldap.auth connection["username"], connection["password"]
141
- say "Searching base DN: #{connection["basedn"]} with filter: #{connection["filter"]}"
142
-
140
+ ldap_connections(config) do |key, connection, ldap|
143
141
  ldap.search(:return_result => false, :filter => connection["filter"], :base => connection["basedn"], :attributes => attributes) do |entry|
144
142
  next if entry.grab(mappings["email"]).blank? || (mappings.has_key?("unique_identifier") && entry.grab(mappings["unique_identifier"]).blank?)
145
143
 
146
144
  users.user do |user|
147
145
  entry.build_xml_from_mappings user, ldap, mappings, permission_mappings
148
146
  end
147
+ user_whitelist << user_identifier_list.map { |identifier| entry.grab(mappings[identifier]) }
149
148
  count += 1
150
149
  say "Scanned #{count} users" if ((count % 100) == 0)
151
150
  end # search
@@ -155,11 +154,27 @@ module Socialcast
155
154
  end # gzip
156
155
  say "Finished scanning #{count} users"
157
156
 
157
+ if options[:sanity_check]
158
+ say "Sanity checking users currently marked as needing to be terminated"
159
+ ldap_connections(config) do |key, connection, ldap|
160
+ (current_socialcast_users(http_config) - user_whitelist).each do |user_identifiers|
161
+ combined_filters = []
162
+ user_identifier_list.each_with_index do |identifier, index|
163
+ combined_filters << ((mappings[identifier].blank? || user_identifiers[index].nil?) ? nil : Net::LDAP::Filter.eq(mappings[identifier], user_identifiers[index]))
164
+ end
165
+ combined_filters.compact!
166
+ filter = ((combined_filters.size > 1) ? '(|%s)' : '%s') % combined_filters.join(' ')
167
+ filter = Net::LDAP::Filter.construct(filter) & Net::LDAP::Filter.construct(connection["filter"])
168
+ ldap_result = ldap.search(:return_result => true, :base => connection["basedn"], :filter => filter, :attributes => attributes)
169
+ abort("Found user marked for termination that should not be terminated: #{user_identifiers}") unless ldap_result.blank?
170
+ end
171
+ end
172
+ end
173
+
158
174
  if count == 0 && !options[:force]
159
175
  Kernel.abort("Skipping upload to Socialcast since no users were found")
160
176
  else
161
177
  say "Uploading dataset to Socialcast..."
162
- http_config = config.fetch('http', {})
163
178
  resource = Socialcast.resource_for_path '/api/users/provision', http_config
164
179
  File.open(output_file, 'r') do |file|
165
180
  request_params = {:file => file}
@@ -171,12 +186,47 @@ module Socialcast
171
186
  end
172
187
  File.delete(output_file) if (config['options']['delete_users_file'] || options[:delete_users_file])
173
188
  end
174
-
189
+
175
190
  no_tasks do
176
191
  def load_configuration(path)
177
192
  YAML.load_file path
178
193
  end
194
+ def ldap_connections(config)
195
+ config["connections"].each_pair do |key, connection|
196
+ say "Connecting to #{key} at #{[connection["host"], connection["port"]].join(':')}"
197
+ ldap = create_ldap_instance(connection)
198
+ say "Searching base DN: #{connection["basedn"]} with filter: #{connection["filter"]}"
199
+ yield key, connection, ldap
200
+ end
201
+ end
202
+ def create_ldap_instance(connection)
203
+ ldap = Net::LDAP.new :host => connection["host"], :port => connection["port"], :base => connection["basedn"]
204
+ ldap.encryption connection['encryption'].to_sym if connection['encryption']
205
+ ldap.auth connection["username"], connection["password"]
206
+ ldap
207
+ end
208
+ def current_socialcast_users(http_config)
209
+ current_socialcast_list = Set.new
210
+ request_params = {:per_page => 500, :format => 'json'}
211
+ request_params[:page] = 1
212
+ resource = create_socialcast_user_index_request(http_config, request_params)
213
+ while true
214
+ response = resource.get
215
+ result = JSON.parse(response)
216
+ users = result["users"]
217
+ break if users.blank?
218
+ request_params[:page] += 1
219
+ resource = create_socialcast_user_index_request(http_config, request_params)
220
+ users.each do |user|
221
+ current_socialcast_list << [user['contact_info']['email'], user['company_login'], user['employee_number']]
222
+ end
223
+ end
224
+ current_socialcast_list
225
+ end
226
+ def create_socialcast_user_index_request(http_config, request_params)
227
+ path_template = "/api/users.json?format=%{format}&per_page=%{per_page}&page=%{page}"
228
+ Socialcast.resource_for_path((path_template % request_params), http_config)
229
+ end
179
230
  end
180
-
181
231
  end
182
232
  end
@@ -9,7 +9,7 @@ class Net::LDAP::Entry
9
9
  when Hash
10
10
  value = attribute.delete("value")
11
11
  value % Hash[attribute.map {|k,v| [k, grab(v)]}].symbolize_keys
12
- else
12
+ when String
13
13
  Array.wrap(self[attribute]).compact.first
14
14
  end
15
15
  end
@@ -1,3 +1,3 @@
1
1
  module Socialcast
2
- VERSION = "1.1.5"
2
+ VERSION = "1.1.6"
3
3
  end
data/socialcast.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
20
20
  s.add_runtime_dependency 'highline', '>= 1.6.2'
21
21
  s.add_runtime_dependency 'socialcast-net-ldap', '>= 0.1.6'
22
22
  s.add_runtime_dependency 'activeresource', '>= 2.3.11'
23
- s.add_development_dependency "rspec", '>= 2.6.0'
23
+ s.add_development_dependency "rspec", '>= 2.11.0'
24
24
  s.add_development_dependency "webmock", '>= 1.7.7'
25
25
  s.add_development_dependency 'rake', '0.9.2.2'
26
26
 
data/spec/cli_spec.rb CHANGED
@@ -10,14 +10,14 @@ describe Socialcast::CLI do
10
10
  with(:body => /message\_type\"\:null/).
11
11
  with(:body => /testing/).
12
12
  to_return(:status => 200, :body => "", :headers => {})
13
-
13
+
14
14
  Socialcast::CLI.start ['share', 'testing']
15
15
  end
16
16
  it 'should send a POST with a message body of "testing" and nil message-type' do
17
17
  # See expectations
18
18
  end
19
19
  end
20
-
20
+
21
21
  context 'with a message_type message' do
22
22
  before do
23
23
  Socialcast.stub(:credentials).and_return(YAML.load_file(File.join(File.dirname(__FILE__), 'fixtures', 'credentials.yml')))
@@ -25,7 +25,7 @@ describe Socialcast::CLI do
25
25
  with(:body => /message\_type\"\:review\_request/).
26
26
  with(:body => /please\sreview/).
27
27
  to_return(:status => 200, :body => "", :headers => {})
28
-
28
+
29
29
  Socialcast::CLI.start ['share', 'please review', '--message_type=review_request']
30
30
  end
31
31
  it 'should send a POST with a message body of "please review" and message_type of "review_request"' do
@@ -38,7 +38,7 @@ describe Socialcast::CLI do
38
38
  stub_request(:post, "https://ryan%40socialcast.com:foo@test.staging.socialcast.com/api/messages.json").
39
39
  with(:body => /group\_id\"\:123/).
40
40
  to_return(:status => 200, :body => "", :headers => {})
41
-
41
+
42
42
  Socialcast::CLI.start ['share', 'hi', '--group_id=123']
43
43
  end
44
44
  it 'should send a POST with group_id param == 123' do
@@ -52,16 +52,16 @@ describe Socialcast::CLI do
52
52
  with(:body => /message\_type\"\:null/).
53
53
  with(:body => /testing/).
54
54
  to_return(:status => 200, :body => "", :headers => {})
55
-
55
+
56
56
  Socialcast::CLI.start ['share', 'testing']
57
57
  end
58
58
  it 'should send a POST with a message body of "testing" and nil message-type' do
59
59
  # See expectations
60
60
  end
61
61
  end
62
-
62
+
63
63
  end
64
-
64
+
65
65
  describe '#provision' do
66
66
  before do
67
67
  Socialcast::CLI.instance_eval do # to supress warning from stubbing load_configuration
@@ -144,6 +144,37 @@ describe Socialcast::CLI do
144
144
  @result.should_not =~ %r{roles}
145
145
  end
146
146
  end
147
+ context "with a user marked for termination that shouldn't be and sanity_check option passed" do
148
+ before do
149
+ @entry = Net::LDAP::Entry.new("cn=Ryan,dc=example,dc=com")
150
+ @entry[:mail] = 'ryan@example.com'
151
+ @valid_entry = Net::LDAP::Entry.new("cn=Sean,dc=example,dc=com")
152
+ @valid_entry[:mail] = 'sean@example.com'
153
+ ldap_search_block = double("ldapsearchblock")
154
+ ldap_search_block.should_receive(:search).and_yield(@entry)
155
+ ldap_return = double("ldapreturn")
156
+ ldap_return.should_receive(:search).with(include(:filter=>Net::LDAP::Filter.construct("(&(mail=sean@example.com)(mail=*))"))).and_return(@valid_entry)
157
+
158
+ Socialcast::CLI.any_instance.should_receive(:create_ldap_instance).and_return(ldap_search_block, ldap_return)
159
+
160
+ @result = ''
161
+ Zlib::GzipWriter.stub(:open).and_yield(@result)
162
+ Socialcast.stub(:credentials).and_return(YAML.load_file(File.join(File.dirname(__FILE__), 'fixtures', 'credentials.yml')))
163
+ Socialcast::CLI.any_instance.should_receive(:load_configuration).with(/ldap.yml/).and_return(YAML.load_file(File.join(File.dirname(__FILE__), 'fixtures', 'ldap.yml')))
164
+ File.stub(:open).with(/users.xml.gz/, anything).and_yield(@result)
165
+
166
+ Socialcast::CLI.any_instance.should_receive(:create_socialcast_user_index_request).and_return(
167
+ double("request1", :get => {"users" => [{"contact_info" => {"email" => @entry[:mail][0]}}]}.to_json),
168
+ double("request2", :get => {"users" => [{"contact_info" => {"email" => @valid_entry[:mail][0]}}]}.to_json),
169
+ double("empty_request", :get => {"users" => []}.to_json)
170
+ )
171
+
172
+ RestClient::Resource.any_instance.should_receive(:post).never
173
+ end
174
+ it 'does not post to Socialcast and throws Kernel.abort' do
175
+ lambda { Socialcast::CLI.start ['provision', '-c', 'spec/fixtures/ldap.yml', '--sanity_check', true] }.should raise_error SystemExit
176
+ end
177
+ end
147
178
  context 'with external group member' do
148
179
  before do
149
180
  @entry = Net::LDAP::Entry.new("dc=example,dc=com")
@@ -294,10 +325,10 @@ describe Socialcast::CLI do
294
325
  @entry[:mail] = 'ryan@example.com'
295
326
  @entry[:manager] = 'cn=bossman,dc=example,dc=com'
296
327
  @manager_email = 'bossman@example.com'
297
-
328
+
298
329
  @entry.stub(:dereference_mail).with(kind_of(Net::LDAP), "manager", "mail").and_return(@manager_email)
299
330
  Net::LDAP.any_instance.stub(:search).and_yield(@entry)
300
-
331
+
301
332
  @result = ''
302
333
  Zlib::GzipWriter.stub(:open).and_yield(@result)
303
334
  Socialcast.stub(:credentials).and_return(YAML.load_file(File.join(File.dirname(__FILE__), 'fixtures', 'credentials.yml')))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: socialcast
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.1.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-07-26 00:00:00.000000000 Z
13
+ date: 2012-08-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rest-client
17
- requirement: &2156001940 !ruby/object:Gem::Requirement
17
+ requirement: &2159242000 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 1.4.0
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *2156001940
25
+ version_requirements: *2159242000
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: json
28
- requirement: &2156001420 !ruby/object:Gem::Requirement
28
+ requirement: &2159241000 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 1.4.6
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *2156001420
36
+ version_requirements: *2159241000
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: thor
39
- requirement: &2156000780 !ruby/object:Gem::Requirement
39
+ requirement: &2159240520 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: 0.14.6
45
45
  type: :runtime
46
46
  prerelease: false
47
- version_requirements: *2156000780
47
+ version_requirements: *2159240520
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: highline
50
- requirement: &2156016340 !ruby/object:Gem::Requirement
50
+ requirement: &2159240060 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: 1.6.2
56
56
  type: :runtime
57
57
  prerelease: false
58
- version_requirements: *2156016340
58
+ version_requirements: *2159240060
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: socialcast-net-ldap
61
- requirement: &2156015780 !ruby/object:Gem::Requirement
61
+ requirement: &2159239420 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ! '>='
@@ -66,10 +66,10 @@ dependencies:
66
66
  version: 0.1.6
67
67
  type: :runtime
68
68
  prerelease: false
69
- version_requirements: *2156015780
69
+ version_requirements: *2159239420
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: activeresource
72
- requirement: &2156015320 !ruby/object:Gem::Requirement
72
+ requirement: &2159238740 !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
75
  - - ! '>='
@@ -77,21 +77,21 @@ dependencies:
77
77
  version: 2.3.11
78
78
  type: :runtime
79
79
  prerelease: false
80
- version_requirements: *2156015320
80
+ version_requirements: *2159238740
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: rspec
83
- requirement: &2156014820 !ruby/object:Gem::Requirement
83
+ requirement: &2159238080 !ruby/object:Gem::Requirement
84
84
  none: false
85
85
  requirements:
86
86
  - - ! '>='
87
87
  - !ruby/object:Gem::Version
88
- version: 2.6.0
88
+ version: 2.11.0
89
89
  type: :development
90
90
  prerelease: false
91
- version_requirements: *2156014820
91
+ version_requirements: *2159238080
92
92
  - !ruby/object:Gem::Dependency
93
93
  name: webmock
94
- requirement: &2156014320 !ruby/object:Gem::Requirement
94
+ requirement: &2159237620 !ruby/object:Gem::Requirement
95
95
  none: false
96
96
  requirements:
97
97
  - - ! '>='
@@ -99,10 +99,10 @@ dependencies:
99
99
  version: 1.7.7
100
100
  type: :development
101
101
  prerelease: false
102
- version_requirements: *2156014320
102
+ version_requirements: *2159237620
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: rake
105
- requirement: &2156013820 !ruby/object:Gem::Requirement
105
+ requirement: &2159237160 !ruby/object:Gem::Requirement
106
106
  none: false
107
107
  requirements:
108
108
  - - =
@@ -110,7 +110,7 @@ dependencies:
110
110
  version: 0.9.2.2
111
111
  type: :development
112
112
  prerelease: false
113
- version_requirements: *2156013820
113
+ version_requirements: *2159237160
114
114
  description: publish messages to your stream from a command line interface
115
115
  email:
116
116
  - ryan@socialcast.com