me_sd 0.0.3beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/me_sd.rb +384 -0
  3. metadata +59 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 941dc0362e5bc77e988b58c63f85819035a7b4a1
4
+ data.tar.gz: d67a49a6fc43bd400f51a08770a3c613e69f049c
5
+ SHA512:
6
+ metadata.gz: 5fe673eefd73e36425110010575f8e4fccc89a6de38c045843e24d2439b69ba54ffcf48fb97a9e26eb932a222e42df98d83131ed81fa81123f66f41de735bb7b
7
+ data.tar.gz: 85f5debddf474d137ee6e29574a23a0c943ddca418f4ce70b99ad0d96e12a58fb1fcded640e40610953012debe34b2995e4c61f57ad77322565e515582a55661
data/lib/me_sd.rb ADDED
@@ -0,0 +1,384 @@
1
+ # sd = MESD.new({ host: "192.168.0.150", port: "8080", username: "user", password: "P@ssw0rd" })
2
+ # # default port is "80"
3
+ # => true
4
+ # unless sd.errors
5
+ # requests = sd.get_all_requests
6
+ # requests[0].data
7
+ # end
8
+ # => #<MESD::Request:0x0000000265d360 @id="29", ..., @description="request decription", @resolution="request resolution", ...>
9
+ # request = Request.new({ session: sd.session, id: 29 })
10
+ # request.data(:name, :resolution)
11
+ # => #<MESD::Request:0x000000023b6800 @id="29", ..., @name="request name", @resolution="request resolution">
12
+ # request.get_resolution
13
+ # => "request resolution"
14
+
15
+ class MESD
16
+ attr_accessor :session, :last_error, :curobj, :current_body
17
+
18
+ require "net/http"
19
+ EXCEPTIONS = [Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::EHOSTUNREACH, EOFError,
20
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError]
21
+
22
+ def initialize(args)
23
+ host = args[:host]
24
+ port = args[:port] || "80"
25
+ username = args[:username]
26
+ password = args[:password]
27
+ uri = URI("http://#{host}:#{port}")
28
+ begin
29
+ Net::HTTP.start(uri.host, uri.port) do |http|
30
+ request = http.get(uri)
31
+ cookie = request.response["set-cookie"]
32
+ uri = "#{uri}/j_security_check"
33
+ auth_data = ""\
34
+ "j_username=#{username}&"\
35
+ "j_password=#{password}&"\
36
+ "AdEnable=false&"\
37
+ "DomainCount=0&"\
38
+ "LDAPEnable=false&"\
39
+ "LocalAuth=No&"\
40
+ "LocalAuthWithDomain=No&"\
41
+ "dynamicUserAddition_status=true&"\
42
+ "hidden=Select+a+Domain&"\
43
+ "hidden=For+Domain&"\
44
+ "localAuthEnable=true&"\
45
+ "loginButton=Login&"\
46
+ "logonDomainName=-1&"\
47
+ ""
48
+ auth_headers = {
49
+ "Referer" => "http://#{host}:#{port}",
50
+ "Host" => "#{host}:#{port}",
51
+ "Cookie" => "#{cookie};",
52
+ }
53
+ request = http.post(uri, auth_data, auth_headers)
54
+ @session = {
55
+ host: host,
56
+ port: port,
57
+ cookie: cookie,
58
+ }
59
+ @last_error = "wrong credentials" unless self.session_healthy?(self.session)
60
+ end
61
+ rescue *EXCEPTIONS => @last_error
62
+ end
63
+ end
64
+
65
+ # logs in and tries to find out is session healthy
66
+ # criteria: logout button is present
67
+ def session_healthy?(session)
68
+ return false unless session
69
+ session_healthy = false
70
+ uri = URI("http://#{session[:host]}:#{session[:port]}/MySchedule.do")
71
+ begin
72
+ Net::HTTP.start(uri.host, uri.port) do |http|
73
+ request = Net::HTTP::Get.new(uri)
74
+ request.add_field("Cookie", "#{session[:cookie]}")
75
+ request = http.request(request)
76
+ # ...
77
+ # <a style="display:inline" href="\&quot;javascript:" prelogout('null')\"="">Log out</a>
78
+ # ...
79
+ session_healthy = true if /preLogout/.match(request.body)
80
+ end
81
+ rescue *EXCEPTIONS => @last_error
82
+ end
83
+ session_healthy
84
+ end
85
+
86
+ def get_all_requests
87
+ requests = Array.new
88
+ select_all_requests
89
+ puts "Getting total #{@curobj['_TL']} requests:"
90
+ get_requests_urls(@current_body).each { |url| requests.push(Request.new({ session: @session, url: url })) }
91
+ begin
92
+ not_last_page = next_page
93
+ get_requests_urls(@current_body).each { |url| requests.push(Request.new({ session: @session, url: url })) }
94
+ end while not_last_page
95
+ requests
96
+ end
97
+
98
+ def select_all_requests
99
+ session = self.session
100
+ return false unless session
101
+ uri = URI("http://#{session[:host]}:#{session[:port]}/WOListView.do")
102
+ begin
103
+ Net::HTTP.start(uri.host, uri.port) do |http|
104
+ data = "globalViewName=All_Requests&viewName=All_Requests"
105
+ headers = {
106
+ "Referer" => "http://#{session[:host]}:#{session[:port]}/WOListView.do",
107
+ "Host" => "#{session[:host]}:#{session[:port]}",
108
+ "Cookie" => "#{session[:cookie]}",
109
+ }
110
+ request = http.post(uri, data, headers)
111
+ @current_body = request.response.body
112
+ @curobj = get_curobj
113
+ end
114
+ rescue *EXCEPTIONS => @last_error
115
+ end
116
+ end
117
+
118
+ def next_page
119
+ require "date"
120
+ session = self.session
121
+ return false unless session
122
+ # 13 digits time
123
+ timestamp = DateTime.now.strftime("%Q")
124
+ uri = URI("http://#{session[:host]}:#{session[:port]}/STATE_ID/#{timestamp}/"\
125
+ "RequestsView.cc?UNIQUE_ID=RequestsView&SUBREQUEST=true")
126
+ begin
127
+ Net::HTTP.start(uri.host, uri.port) do |http|
128
+ request = Net::HTTP::Get.new(uri)
129
+ request.add_field("Referer", "http://#{session[:host]}:#{session[:port]}/WOListView.do")
130
+ @curobj = get_curobj
131
+ return false unless @curobj
132
+ print "#{(@curobj["_FI"].to_f / @curobj["_TL"].to_f * 100).round}%.."
133
+ # increment page number
134
+ @curobj["_PN"] = (@curobj["_PN"].to_i + 1).to_s
135
+ # update first item
136
+ @curobj["_FI"] = (@curobj["_FI"].to_i + @curobj["_PL"].to_i).to_s
137
+ # @curobj.flatten.join("/") =>
138
+ # "_PN/2/_PL/25/_TL/28/globalViewName/All_Requests/_TI/25/_FI/1/_SO/D/viewName/All_Requests"
139
+ request.add_field("Cookie",
140
+ "#{session[:cookie]}; "\
141
+ "STATE_COOKIE=%26RequestsView/ID/#{@curobj['ID']}/VGT/#{timestamp}/#{@curobj.flatten.join('/')}"\
142
+ "/_VMD/1/ORIGROOT/#{@curobj['ID']}%26_REQS/_RVID/RequestsView/_TIME/#{timestamp}; "\
143
+ "301RequestsshowThreadedReq=showThreadedReqshow; "\
144
+ "301RequestshideThreadedReq=hideThreadedReqhide"\
145
+ ""
146
+ )
147
+ request = http.request(request)
148
+ @current_body = request.response.body
149
+ end
150
+ rescue *EXCEPTIONS => @last_error
151
+ end
152
+ # if (first item + per page) > total items then it is the last page
153
+ if (@curobj["_FI"].to_i + @curobj["_PL"].to_i) > @curobj["_TL"].to_i
154
+ puts "100%"
155
+ return false
156
+ end
157
+ true
158
+ end
159
+
160
+ def get_curobj
161
+ body = @current_body
162
+ # somewhere in body
163
+ # "<Script>curObj=V33;curObj[\"_PN\"]=\"1\";curObj[\"_PL\"]=\"25\";curObj[\"_TL\"]=\"28\";"\
164
+ # "curObj[\"globalViewName\"]=\"All_Requests\";curObj[\"_TI\"]=\"25\";curObj[\"_FI\"]=\"1\";"\
165
+ # "curObj[\"_SO\"]=\"D\";curObj[\"viewName\"]=\"All_Requests\";</Script>"
166
+ search_start_str = "<Script>curObj=V"
167
+ curobj_start_pos = body.index(search_start_str)
168
+ return false unless curobj_start_pos
169
+ v = /<Script>curObj=V(?<V>\d+);/.match(body)["V"]
170
+ curobj_end_pos = body.index("</Script>", curobj_start_pos)
171
+ curobj_raw = body[curobj_start_pos + search_start_str.size + v.size...curobj_end_pos]
172
+ # curobj_raw =>
173
+ # "curObj[\"_PN\"]=\"1\";curObj[\"_PL\"]=\"25\";curObj[\"_TL\"]=\"28\";"\
174
+ # "curObj[\"globalViewName\"]=\"All_Requests\";curObj[\"_TI\"]=\"25\";curObj[\"_FI\"]=\"1\";"\
175
+ # "curObj[\"_SO\"]=\"D\";curObj[\"viewName\"]=\"All_Requests\";"
176
+ curObj = Hash.new
177
+ curobj_raw.split(";").each { |c| eval(c) }
178
+ curObj["ID"] = v
179
+ # curObj =>
180
+ # {"_PN"=>"1", "_PL"=>"25", "_TL"=>"28", "globalViewName"=>"All_Requests",
181
+ # "_TI"=>"25", "_FI"=>"1", "_SO"=>"D", "viewName"=>"All_Requests"}
182
+ curObj
183
+ end
184
+
185
+ def get_requests_urls(body)
186
+ urls = body.scan(/href=\"WorkOrder\.do\?woMode=viewWO&woID=\d+&&fromListView=true\"/)
187
+ # drop href=" and ending quot
188
+ urls.each_with_index { |url, i| urls[i] = url["href=\"".size..-2] }
189
+ end
190
+
191
+ private :select_all_requests, :next_page, :get_curobj, :get_requests_urls
192
+ end
193
+
194
+ class Request < MESD
195
+ props = [:name, :author_name, :status, :priority, :create_date, :description, :resolution]
196
+ attr_accessor :id, *props
197
+
198
+ # shortens "request.data(:resolution).resolution" to "request.get_resolution"
199
+ props.each do |prop|
200
+ define_method("get_#{prop.to_s}") do
201
+ request = self.data(prop)
202
+ request.send("#{prop.to_s}")
203
+ end
204
+ end
205
+
206
+ def initialize(args)
207
+ if args[:id]
208
+ @id = args[:id]
209
+ elsif args[:url]
210
+ if args[:url] =~ /WorkOrder\.do\?woMode=viewWO&woID=(?<ID>\d+)&&fromListView=true/
211
+ @id = Regexp.last_match("ID").to_i
212
+ else
213
+ return false
214
+ end
215
+ end
216
+ @session = args[:session]
217
+ true
218
+ end
219
+
220
+ def data(*args)
221
+ return false unless self.id
222
+ if args.size == 0
223
+ only = []
224
+ else
225
+ only = args
226
+ end
227
+ unless session_healthy?(@session)
228
+ @last_error = "session error"
229
+ return false
230
+ end
231
+ props = [
232
+ {
233
+ name: :description,
234
+ url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
235
+ search_function: {
236
+ name: "value_between",
237
+ args: ["<td style=\"padding-left:10px;\" colspan=\"3\" valign=\"top\" class=\"fontBlack textareadesc\">", "</td>"],
238
+ },
239
+ post_processing_functions: [:strip],
240
+ },
241
+ {
242
+ name: :resolution,
243
+ url: "AddResolution.do?mode=viewWOResolution&woID=#{self.id}",
244
+ search_function: {
245
+ name: "value_between",
246
+ args: ["<td colspan=\"3\" valign=\"top\" class=\"fontBlack textareadesc\">", "</td>"],
247
+ },
248
+ post_processing_functions: [:strip],
249
+ },
250
+ {
251
+ name: :status,
252
+ url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
253
+ search_function: {
254
+ name: "html_parse",
255
+ args: [["css", "#WOHeaderSummary_DIV"], ["css", "#status_PH"], "text"],
256
+ },
257
+ post_processing_functions: [:semicolon_space_value, :symbolize],
258
+ },
259
+ {
260
+ name: :priority,
261
+ url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
262
+ search_function: {
263
+ name: "html_parse",
264
+ args: [["css", "#WOHeaderSummary_DIV"], ["css", "#priority_PH"], "text"],
265
+ },
266
+ post_processing_functions: [:semicolon_space_value, :symbolize],
267
+ },
268
+ {
269
+ name: :author_name,
270
+ url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
271
+ search_function: {
272
+ name: "html_parse",
273
+ args: [["css", "#requesterName_PH"], "text"],
274
+ },
275
+ },
276
+ {
277
+ name: :create_date,
278
+ url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
279
+ search_function: {
280
+ name: "html_parse",
281
+ args: [["css", "#CREATEDTIME_CUR"], "text"],
282
+ },
283
+ post_processing_functions: [:parse_date],
284
+ },
285
+ {
286
+ name: :name,
287
+ url: "WorkOrder.do?woMode=viewWO&woID=#{self.id}",
288
+ search_function: {
289
+ name: "html_parse",
290
+ args: [["css", "#requestSubject_ID"], "text"],
291
+ },
292
+ post_processing_functions: [:strip],
293
+ },
294
+ ]
295
+ props.each do |property|
296
+ next if !only.empty? && !only.include?(property[:name])
297
+ uri = URI("http://#{@session[:host]}:#{@session[:port]}/#{property[:url]}")
298
+ begin
299
+ Net::HTTP.start(uri.host, uri.port) do |http|
300
+ http_request = Net::HTTP::Get.new(uri)
301
+ http_request.add_field("Cookie", "#{@session[:cookie]}")
302
+ http_request = http.request(http_request)
303
+ @current_body = http_request.response.body
304
+ auth_error_pos = @current_body.index("AuthError")
305
+ if auth_error_pos
306
+ @last_error = "auth error"
307
+ return false
308
+ end
309
+ permitions_error_pos = @current_body.index("Request does not fall under your permitted scope")
310
+ if permitions_error_pos
311
+ @last_error = "no permitions error"
312
+ return false
313
+ end
314
+ operational_error_pos = @current_body.index("failurebox")
315
+ if operational_error_pos
316
+ @last_error = "operational error"
317
+ return false
318
+ end
319
+ value = self.method(property[:search_function][:name]).call(property[:search_function][:args])
320
+ if property[:post_processing_functions]
321
+ functions = property[:post_processing_functions]
322
+ functions.each do |function|
323
+ if value.methods.include?(function)
324
+ value = value.method(function).call
325
+ elsif self.private_methods.include?(function)
326
+ value = self.method(function).call(value)
327
+ end
328
+ end
329
+ end
330
+ self.send("#{property[:name]}=", value)
331
+ end
332
+ rescue *EXCEPTIONS => @last_error
333
+ end
334
+ end
335
+ self
336
+ end
337
+
338
+ def html_parse(steps)
339
+ require "nokogiri"
340
+ value = Nokogiri::HTML(@current_body)
341
+ Array(steps).each { |step| value = value.send(*step) }
342
+ value
343
+ end
344
+
345
+ def value_between(bounds)
346
+ search_start_pos = @current_body.index(bounds[0])
347
+ return "" unless search_start_pos
348
+ search_end_pos = @current_body.index(bounds[1], search_start_pos)
349
+ @current_body[search_start_pos + bounds[0].size..search_end_pos-1].force_encoding("UTF-8")
350
+ end
351
+
352
+ def semicolon_space_value(value)
353
+ value.strip[/:(.*)/m, 1].strip
354
+ end
355
+
356
+ def parse_date(date)
357
+ require "date"
358
+ DateTime.parse(date)
359
+ end
360
+
361
+ def symbolize(value)
362
+ matching = {
363
+ # status
364
+ :open => ["Открыта", "Open"],
365
+ :on_hold => ["Ожидание", "On Hold"],
366
+ :resolved => ["Решена", "Resolved"],
367
+ :closed => ["Закрыта", "Closed"],
368
+ :rejected => ["Отклонена", ""],
369
+ # priority
370
+ :minimal => ["Минимальный", ""],
371
+ :low => ["Низкий", "Low"],
372
+ :normal => ["", "Normal"],
373
+ :medium => ["Средний", "Medium"],
374
+ :high => ["Высокий", "High"],
375
+ :highest => ["Наивысший", ""],
376
+ }
377
+ matching.each do |result, candidates|
378
+ return result if candidates.include?(value)
379
+ end
380
+ value.to_sym
381
+ end
382
+
383
+ private :html_parse, :value_between, :semicolon_space_value, :parse_date, :symbolize
384
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: me_sd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3beta
5
+ platform: ruby
6
+ authors:
7
+ - Alexander Morozov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ description: Introduces 'MESD' class that works with ManageEngine ServiceDesk Plus
28
+ without API access.
29
+ email: ntcomp12@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/me_sd.rb
35
+ homepage: https://github.com/kengho/me_sd
36
+ licenses:
37
+ - MIT
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">"
51
+ - !ruby/object:Gem::Version
52
+ version: 1.3.1
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.5.1
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: ManageEngine ServiceDesk Plus gem
59
+ test_files: []