zimbra-soap-api 0.0.7.9

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 (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