socialcast 1.1.5 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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