zimbra-soap-api 0.0.7.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.irbrc +6 -0
  4. data/Gemfile +6 -0
  5. data/README +21 -0
  6. data/Rakefile +1 -0
  7. data/lib/zimbra.rb +94 -0
  8. data/lib/zimbra/account.rb +94 -0
  9. data/lib/zimbra/acl.rb +63 -0
  10. data/lib/zimbra/alias.rb +4 -0
  11. data/lib/zimbra/appointment.rb +274 -0
  12. data/lib/zimbra/appointment/alarm.rb +101 -0
  13. data/lib/zimbra/appointment/attendee.rb +97 -0
  14. data/lib/zimbra/appointment/invite.rb +360 -0
  15. data/lib/zimbra/appointment/recur_exception.rb +83 -0
  16. data/lib/zimbra/appointment/recur_rule.rb +184 -0
  17. data/lib/zimbra/appointment/reply.rb +91 -0
  18. data/lib/zimbra/auth.rb +46 -0
  19. data/lib/zimbra/base.rb +217 -0
  20. data/lib/zimbra/calendar.rb +27 -0
  21. data/lib/zimbra/common_elements.rb +51 -0
  22. data/lib/zimbra/cos.rb +124 -0
  23. data/lib/zimbra/delegate_auth_token.rb +49 -0
  24. data/lib/zimbra/directory.rb +175 -0
  25. data/lib/zimbra/distribution_list.rb +147 -0
  26. data/lib/zimbra/domain.rb +63 -0
  27. data/lib/zimbra/ext/handsoap_curb_driver.rb +21 -0
  28. data/lib/zimbra/ext/hash.rb +72 -0
  29. data/lib/zimbra/ext/string.rb +10 -0
  30. data/lib/zimbra/extra/date_helpers.rb +111 -0
  31. data/lib/zimbra/folder.rb +100 -0
  32. data/lib/zimbra/handsoap_account_service.rb +44 -0
  33. data/lib/zimbra/handsoap_service.rb +75 -0
  34. data/lib/zimbra/version.rb +3 -0
  35. data/spec/fixtures/xml_api_requests/appointments/create.xml +84 -0
  36. data/spec/fixtures/xml_api_responses/alarms/15_minutes_before.xml +26 -0
  37. data/spec/fixtures/xml_api_responses/alarms/using_all_intervals.xml +26 -0
  38. data/spec/fixtures/xml_api_responses/appointments/appointment_response_1.xml +40 -0
  39. data/spec/fixtures/xml_api_responses/attendees/one_attendee_and_one_reply.xml +27 -0
  40. data/spec/fixtures/xml_api_responses/attendees/three_attendees_response_1.xml +30 -0
  41. data/spec/fixtures/xml_api_responses/multiple_invites/recurring_with_exceptions.xml +109 -0
  42. data/spec/fixtures/xml_api_responses/recur_rules/day_27_of_every_2_months.xml +27 -0
  43. data/spec/fixtures/xml_api_responses/recur_rules/every_2_days.xml +26 -0
  44. data/spec/fixtures/xml_api_responses/recur_rules/every_3_weeks_on_tuesday_and_friday.xml +30 -0
  45. data/spec/fixtures/xml_api_responses/recur_rules/every_day_50_instances.xml +27 -0
  46. data/spec/fixtures/xml_api_responses/recur_rules/every_monday_wednesday_friday.xml +31 -0
  47. data/spec/fixtures/xml_api_responses/recur_rules/every_tuesday.xml +29 -0
  48. data/spec/fixtures/xml_api_responses/recur_rules/every_weekday_with_end_date.xml +34 -0
  49. data/spec/fixtures/xml_api_responses/recur_rules/every_year_on_february_2.xml +28 -0
  50. data/spec/fixtures/xml_api_responses/recur_rules/first_day_of_every_month.xml +36 -0
  51. data/spec/fixtures/xml_api_responses/recur_rules/first_monday_of_every_february.xml +31 -0
  52. data/spec/fixtures/xml_api_responses/recur_rules/first_weekend_day_of_every_month.xml +31 -0
  53. data/spec/fixtures/xml_api_responses/recur_rules/last_day_of_every_month.xml +36 -0
  54. data/spec/fixtures/xml_api_responses/recur_rules/second_day_of_every_2_months.xml +36 -0
  55. data/spec/fixtures/xml_api_responses/recur_rules/second_wednesday_of_every_month.xml +29 -0
  56. data/spec/fixtures/xml_api_responses/recur_rules/weekly_with_an_exception.xml +44 -0
  57. data/spec/spec_helper.rb +32 -0
  58. data/spec/zimbra/acl_spec.rb +11 -0
  59. data/spec/zimbra/appointment/alarm_spec.rb +33 -0
  60. data/spec/zimbra/appointment/invite_spec.rb +62 -0
  61. data/spec/zimbra/appointment/recur_rule_spec.rb +307 -0
  62. data/spec/zimbra/appointment_spec.rb +175 -0
  63. data/spec/zimbra/common_elements_spec.rb +33 -0
  64. data/spec/zimbra/distribution_list_spec.rb +54 -0
  65. data/zimbra.gemspec +28 -0
  66. metadata +197 -0
@@ -0,0 +1,63 @@
1
+ module Zimbra
2
+ class Domain < Zimbra::Base
3
+ class << self
4
+ def acl_name
5
+ 'domain'
6
+ end
7
+ end
8
+
9
+ def count_accounts
10
+ DomainService.count_accounts(id)
11
+ end
12
+
13
+ def save
14
+ DomainService.modify(self)
15
+ end
16
+
17
+ end
18
+
19
+ class DomainService < HandsoapService
20
+
21
+ def count_accounts(id)
22
+ xml = invoke("n2:CountAccountRequest") do |message|
23
+ Builder.count_accounts(message, id)
24
+ end
25
+ Parser.count_accounts_response(xml)
26
+ end
27
+
28
+ def delete
29
+ xml = invoke("n2:DeleteDomainRequest") do |message|
30
+ Builder.delete(message, id)
31
+ end
32
+ end
33
+
34
+ class Builder
35
+ class << self
36
+
37
+
38
+ def count_accounts(message, id)
39
+ message.add 'domain', id do |c|
40
+ c.set_attr 'by', 'id'
41
+ end
42
+ end
43
+
44
+ def delete(message, id)
45
+ message.add 'id', id
46
+ end
47
+ end
48
+ end
49
+ class Parser
50
+ class << self
51
+ def count_accounts_response(response)
52
+ hash = {}
53
+ (response/"//n2:cos").map do |node|
54
+ cos_id = (node/'@id').to_s
55
+ hash[cos_id] = node.to_s.to_i
56
+ end
57
+ hash
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ class Handsoap::Http::Drivers::CurbDriver
2
+ def get_curl(url)
3
+ if @curl
4
+ @curl.url = url
5
+ else
6
+ @curl = ::Curl::Easy.new(url)
7
+ @curl.timeout = Handsoap.timeout
8
+ @curl.enable_cookies = @enable_cookies
9
+
10
+ # enables both deflate and gzip compression of responses
11
+ @curl.encoding = ''
12
+
13
+ if Handsoap.follow_redirects?
14
+ @curl.follow_location = true
15
+ @curl.max_redirects = Handsoap.max_redirects
16
+ end
17
+ end
18
+ @curl.ssl_verify_peer = false
19
+ @curl
20
+ end
21
+ end
@@ -0,0 +1,72 @@
1
+ module Zimbra
2
+ class Hash < ::Hash
3
+ # Thanks to https://gist.github.com/dimus/335286 for this code
4
+
5
+ class << self
6
+ def symbolize_keys(hash, recursive = false)
7
+ hash.keys.each do |key|
8
+ value = hash.delete(key)
9
+ key = key.respond_to?(:to_sym) ? key.to_sym : key
10
+ hash[key] = (recursive && value.is_a?(::Hash)) ? symbolize_keys(value.dup, recursive) : value
11
+ end
12
+ hash
13
+ end
14
+
15
+ def from_xml(xml_io)
16
+ begin
17
+ result = Nokogiri::XML(xml_io)
18
+ return { result.root.name.to_sym => xml_node_to_hash(result.root)}
19
+ rescue Exception => e
20
+ # raise your custom exception here
21
+ end
22
+ end
23
+
24
+ def xml_node_to_hash(node)
25
+ # If we are at the root of the document, start the hash
26
+ if node.element?
27
+ result_hash = {}
28
+ if node.attributes != {}
29
+ result_hash[:attributes] = {}
30
+ node.attributes.keys.each do |key|
31
+ result_hash[:attributes][node.attributes[key].name.to_sym] = prepare(node.attributes[key].value)
32
+ end
33
+ end
34
+ if node.children.size > 0
35
+ node.children.each do |child|
36
+ result = xml_node_to_hash(child)
37
+
38
+ if child.name == "text"
39
+ unless child.next_sibling || child.previous_sibling
40
+ if result_hash[:attributes]
41
+ result_hash['value'] = prepare(result)
42
+ return result_hash
43
+ else
44
+ return prepare(result)
45
+ end
46
+ end
47
+ elsif result_hash[child.name.to_sym]
48
+ if result_hash[child.name.to_sym].is_a?(Object::Array)
49
+ result_hash[child.name.to_sym] << prepare(result)
50
+ else
51
+ result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << prepare(result)
52
+ end
53
+ else
54
+ result_hash[child.name.to_sym] = prepare(result)
55
+ end
56
+ end
57
+
58
+ return result_hash
59
+ else
60
+ return result_hash
61
+ end
62
+ else
63
+ return prepare(node.content.to_s)
64
+ end
65
+ end
66
+
67
+ def prepare(data)
68
+ (data.class == String && data.to_i.to_s == data) ? data.to_i : data
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,10 @@
1
+ module Zimbra
2
+ class String < ::String
3
+
4
+ class << self
5
+ def camel_case_lower(string)
6
+ string.split('_').inject([]){ |buffer,e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,111 @@
1
+ module Zimbra
2
+ class DateHelpers
3
+ class Frequency
4
+ FREQUENCIES = [
5
+ { name: :secondly, zimbra_name: 'SEC', abbreviations: [] },
6
+ { name: :minutely, zimbra_name: 'MIN', abbreviations: [] },
7
+ { name: :hourly, zimbra_name: 'HOU', abbreviations: [] },
8
+ { name: :daily, zimbra_name: 'DAI', abbreviations: [] },
9
+ { name: :weekly, zimbra_name: 'WEE', abbreviations: [] },
10
+ { name: :monthly, zimbra_name: 'MON', abbreviations: [] },
11
+ { name: :yearly, zimbra_name: 'YEA', abbreviations: [] }
12
+ ]
13
+
14
+ class << self
15
+ def all
16
+ @all ||= FREQUENCIES.inject([]) do |frequencies, data|
17
+ frequencies << new(data)
18
+ end
19
+ end
20
+
21
+ def find(name_or_abbreviation)
22
+ all.find { |frequency| frequency.match?(name_or_abbreviation) }
23
+ end
24
+ end
25
+
26
+ attr_accessor :name, :zimbra_name, :abbreviations
27
+
28
+ def initialize(args = {})
29
+ @name = args[:name]
30
+ @zimbra_name = args[:zimbra_name]
31
+ @abbreviations = args[:abbreviations]
32
+ end
33
+
34
+ def match?(name_or_abbreviation)
35
+ downcased_matcher = name_or_abbreviation.to_s.downcase
36
+ ([name.to_s, zimbra_name.to_s] + abbreviations).map(&:downcase).include?(downcased_matcher)
37
+ end
38
+
39
+ def to_sym
40
+ name.downcase.to_sym
41
+ end
42
+ end
43
+
44
+ class WeekDay
45
+ WEEK_DAYS = [
46
+ {
47
+ id: 1, name: :Sunday, zimbra_name: 'SU',
48
+ abbreviations: ['su', 'sun']
49
+ },
50
+ {
51
+ id: 2, name: :Monday, zimbra_name: 'MO',
52
+ abbreviations: ['mo', 'mon']
53
+ },
54
+ {
55
+ id: 3, name: :Tuesday, zimbra_name: 'TU',
56
+ abbreviations: ['tu', 'tue']
57
+ },
58
+ {
59
+ id: 4, name: :Wednesday, zimbra_name: 'WE',
60
+ abbreviations: ['we', 'wed']
61
+ },
62
+ {
63
+ id: 5, name: :Thursday, zimbra_name: 'TH',
64
+ abbreviations: ['th', 'thu', 'thur', 'thurs']
65
+ },
66
+ {
67
+ id: 6, name: :Friday, zimbra_name: 'FR',
68
+ abbreviations: ['fr', 'fri']
69
+ },
70
+ {
71
+ id: 7, name: :Saturday, zimbra_name: 'SA',
72
+ abbreviations: ['sa', 'sat']
73
+ }
74
+ ] unless const_defined?(:WEEK_DAYS)
75
+
76
+ class << self
77
+ def all
78
+ @all ||= WEEK_DAYS.inject([]) do |week_days, data|
79
+ week_days << new(data)
80
+ end
81
+ end
82
+
83
+ def find(id_name_or_abbreviation)
84
+ all.find { |week_day| week_day.match?(id_name_or_abbreviation) }
85
+ end
86
+ end
87
+
88
+ attr_accessor :id, :name, :abbreviations, :zimbra_name
89
+
90
+ def initialize(args = {})
91
+ @id = args[:id]
92
+ @name = args[:name]
93
+ @zimbra_name = args[:zimbra_name]
94
+ @abbreviations = args[:abbreviations]
95
+ end
96
+
97
+ def match?(id_name_or_abbreviation)
98
+ if id_name_or_abbreviation.is_a?(Fixnum)
99
+ id_name_or_abbreviation == id
100
+ else
101
+ downcased_matcher = id_name_or_abbreviation.to_s.downcase
102
+ ([name.to_s, zimbra_name.to_s] + abbreviations).map(&:downcase).include?(downcased_matcher)
103
+ end
104
+ end
105
+
106
+ def to_sym
107
+ name.downcase.to_sym
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,100 @@
1
+ # http://files.zimbra.com/docs/soap_api/8.0.4/soap-docs-804/api-reference/zimbraMail/GetFolder.html
2
+ module Zimbra
3
+ class Folder
4
+ class << self
5
+ def all
6
+ FolderService.all
7
+ end
8
+
9
+ def find_all_by_view(view)
10
+ FolderService.find_all_by_view(view)
11
+ end
12
+ end
13
+
14
+ ATTRS = [
15
+ :id, :uuid, :name, :view, :absolute_folder_path,
16
+ :parent_folder_id, :parent_folder_uuid,
17
+ :non_folder_item_count, :non_folder_item_size,
18
+ :revision, :imap_next_uid, :imap_modified_sequence, :modified_sequence, :activesync_disabled,
19
+ :modified_date
20
+ ] unless const_defined?(:ATTRS)
21
+
22
+ attr_accessor *ATTRS
23
+
24
+ def initialize(args = {})
25
+ self.attributes = args
26
+ end
27
+
28
+ def attributes=(args = {})
29
+ ATTRS.each do |attr_name|
30
+ self.send(:"#{attr_name}=", (args[attr_name] || args[attr_name.to_s])) if args.has_key?(attr_name) || args.has_key?(attr_name.to_s)
31
+ end
32
+ end
33
+ end
34
+
35
+ class FolderService < HandsoapAccountService
36
+ def all
37
+ xml = invoke("n2:GetFolderRequest")
38
+ parse_xml_responses(xml)
39
+ end
40
+
41
+ def find_all_by_view(view)
42
+ xml = invoke("n2:GetFolderRequest") do |message|
43
+ Builder.find_all_by_view(message, view)
44
+ end
45
+ parse_xml_responses(xml)
46
+ end
47
+
48
+ def parse_xml_responses(xml)
49
+ Parser.get_all_response(xml)
50
+ end
51
+
52
+ class Builder
53
+ class << self
54
+ def find_all_by_view(message, view)
55
+ message.set_attr 'view', view
56
+ end
57
+ end
58
+ end
59
+
60
+ class Parser
61
+ ATTRIBUTE_MAPPING = {
62
+ :id => :id,
63
+ :uuid => :uuid,
64
+ :name => :name,
65
+ :view => :view,
66
+ :absFolderPath => :absolute_folder_path,
67
+ :l => :parent_folder_id,
68
+ :luuid => :parent_folder_uuid,
69
+ :n => :non_folder_item_count,
70
+ :s => :non_folder_item_size,
71
+ :rev => :revision,
72
+ :i4next => :imap_next_uid,
73
+ :i4ms => :imap_modified_sequence,
74
+ :ms => :modified_sequence,
75
+ :activesyncdisabled => :activesync_disabled,
76
+ :md => :modified_date
77
+ }
78
+
79
+ class << self
80
+ def get_all_response(response)
81
+ (response/"//n2:folder").map do |node|
82
+ folder_response(node)
83
+ end
84
+ end
85
+
86
+ def folder_response(node)
87
+ folder_attributes = ATTRIBUTE_MAPPING.inject({}) do |attrs, (xml_name, attr_name)|
88
+ attrs[attr_name] = (node/"@#{xml_name}").to_s
89
+ attrs
90
+ end
91
+ initialize_from_attributes(folder_attributes)
92
+ end
93
+
94
+ def initialize_from_attributes(folder_attributes)
95
+ Zimbra::Folder.new(folder_attributes)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,44 @@
1
+ require 'handsoap'
2
+
3
+ module Zimbra
4
+ module HandsoapAccountNamespaces
5
+ def request_namespaces(doc)
6
+ doc.alias 'n1', "urn:zimbra"
7
+ doc.alias 'n2', "urn:zimbraMail"
8
+ doc.alias 'env', 'http://schemas.xmlsoap.org/soap/envelope/'
9
+ end
10
+ def response_namespaces(doc)
11
+ doc.add_namespace 'n2', "urn:zimbraMail"
12
+ end
13
+ end
14
+
15
+ module HandsoapAccountUriOverrides
16
+ def uri
17
+ Zimbra.account_api_url
18
+ end
19
+ def envelope_namespace
20
+ 'http://www.w3.org/2003/05/soap-envelope'
21
+ end
22
+ def request_content_type
23
+ "application/soap+xml"
24
+ end
25
+ end
26
+
27
+ class HandsoapAccountService < Handsoap::Service
28
+ include HandsoapErrors
29
+ include HandsoapAccountNamespaces
30
+ extend HandsoapAccountUriOverrides
31
+
32
+ def on_create_document(doc)
33
+ request_namespaces(doc)
34
+ header = doc.find("Header")
35
+ header.add "n1:context" do |s|
36
+ s.set_attr "env:mustUnderstand", "0"
37
+ s.add "n1:authToken", Zimbra.account_auth_token
38
+ end
39
+ end
40
+ def on_response_document(doc)
41
+ response_namespaces(doc)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,75 @@
1
+ require 'handsoap'
2
+
3
+ module Zimbra
4
+ module HandsoapErrors
5
+ class SOAPFault < StandardError; end
6
+ class NotFound < StandardError; end
7
+ class NotImplemented < StandardError; end
8
+
9
+ @@response = nil
10
+
11
+ def on_http_error(response)
12
+ @@response = response
13
+ return nil if soap_fault_not_found?
14
+ report_error(response) if http_error?
15
+ end
16
+ def report_error(response)
17
+ message = response.body.scan(/<faultstring>(.*)<\/faultstring>/).first.first
18
+ raise SOAPFault, message
19
+ end
20
+ def on_after_create_http_request(request)
21
+ @@response = nil
22
+ end
23
+
24
+ def soap_fault_not_found?
25
+ @@response && @@response.body =~ /no such/
26
+ end
27
+ def http_error?
28
+ @@response && (500..599).include?(@@response.status)
29
+ end
30
+ def http_not_found?
31
+ @@response && (400..499).include?(@@response.status)
32
+ end
33
+ end
34
+
35
+ module HandsoapNamespaces
36
+ def request_namespaces(doc)
37
+ doc.alias 'n1', "urn:zimbra"
38
+ doc.alias 'n2', "urn:zimbraAdmin"
39
+ doc.alias 'env', 'http://schemas.xmlsoap.org/soap/envelope/'
40
+ end
41
+ def response_namespaces(doc)
42
+ doc.add_namespace 'n2', "urn:zimbraAdmin"
43
+ end
44
+ end
45
+
46
+ module HandsoapUriOverrides
47
+ def uri
48
+ Zimbra.admin_api_url
49
+ end
50
+ def envelope_namespace
51
+ 'http://www.w3.org/2003/05/soap-envelope'
52
+ end
53
+ def request_content_type
54
+ "application/soap+xml"
55
+ end
56
+ end
57
+
58
+ class HandsoapService < Handsoap::Service
59
+ include HandsoapErrors
60
+ include HandsoapNamespaces
61
+ extend HandsoapUriOverrides
62
+
63
+ def on_create_document(doc)
64
+ request_namespaces(doc)
65
+ header = doc.find("Header")
66
+ header.add "n1:context" do |s|
67
+ s.set_attr "env:mustUnderstand", "0"
68
+ s.add "n1:authToken", Zimbra.auth_token
69
+ end
70
+ end
71
+ def on_response_document(doc)
72
+ response_namespaces(doc)
73
+ end
74
+ end
75
+ end