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,27 @@
1
+ module Zimbra
2
+ class Calendar < Folder
3
+ class << self
4
+ def all
5
+ CalendarService.find_all_by_view('appointment').reject { |c| c.view.nil? || c.view != 'appointment' }
6
+ end
7
+ end
8
+
9
+ def appointments
10
+ Zimbra::Appointment.find_all_by_calendar_id(id)
11
+ end
12
+ end
13
+
14
+ class CalendarService < FolderService
15
+ def parse_xml_responses(xml)
16
+ Parser.get_all_response(xml)
17
+ end
18
+
19
+ class Parser < Zimbra::FolderService::Parser
20
+ class << self
21
+ def initialize_from_attributes(folder_attributes)
22
+ Zimbra::Calendar.new(folder_attributes)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,51 @@
1
+ module Zimbra
2
+ class A
3
+ class << self
4
+ def inject(xmldoc, name, value, extra_attributes = {})
5
+ new(name, value, extra_attributes).inject(xmldoc)
6
+ end
7
+
8
+ def read(xmldoc, name)
9
+ nodes = (xmldoc/"//n2:a[@n='#{name}']")
10
+ return nil if nodes.nil?
11
+ if nodes.size > 1
12
+ nodes.map { |n| from_node(n, name).value }
13
+ else
14
+ from_node(nodes, name).value
15
+ end
16
+ end
17
+
18
+ def from_node(node, name)
19
+ new(name, node.to_s)
20
+ end
21
+ end
22
+
23
+ attr_accessor :name, :value, :extra_attributes
24
+
25
+ def initialize(name, value, extra_attributes = {})
26
+ self.name = name
27
+ self.value = value
28
+ self.extra_attributes = extra_attributes || {}
29
+ end
30
+
31
+ def inject(xmldoc)
32
+ xmldoc.add 'a', value do |a|
33
+ a.set_attr 'n', name
34
+ extra_attributes.each do |eaname, eavalue|
35
+ a.set_attr eaname, eavalue
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ class Boolean
42
+ def self.read(value)
43
+ case value
44
+ when 'TRUE' then true
45
+ when 'FALSE' then false
46
+ when true then true
47
+ else false
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,124 @@
1
+ module Zimbra
2
+ class Cos
3
+ class << self
4
+ def find_by_id(id)
5
+ CosService.get_by_id(id)
6
+ end
7
+
8
+ def find_by_name(name)
9
+ CosService.get_by_name(name)
10
+ end
11
+
12
+ def create(name)
13
+ CosService.create(name)
14
+ end
15
+
16
+ def acl_name
17
+ 'cos'
18
+ end
19
+ end
20
+
21
+ attr_accessor :id, :name, :acls
22
+
23
+ def initialize(id, name, acls = [])
24
+ self.id = id
25
+ self.name = name
26
+ self.acls = acls || []
27
+ end
28
+
29
+ def save
30
+ CosService.modify(self)
31
+ end
32
+
33
+ def delete
34
+ CosService.delete(self)
35
+ end
36
+ end
37
+
38
+ class CosService < HandsoapService
39
+ def get_by_id(id)
40
+ response = invoke("n2:GetCosRequest") do |message|
41
+ Builder.get_by_id(message, id)
42
+ end
43
+ return nil if soap_fault_not_found?
44
+ Parser.cos_response(response/"//n2:cos")
45
+ end
46
+
47
+ def get_by_name(name)
48
+ response = invoke("n2:GetCosRequest") do |message|
49
+ Builder.get_by_name(message, name)
50
+ end
51
+ return nil if soap_fault_not_found?
52
+ Parser.cos_response(response/"//n2:cos")
53
+ end
54
+
55
+ def create(name)
56
+ response = invoke("n2:CreateCosRequest") do |message|
57
+ Builder.create(message, name)
58
+ end
59
+ Parser.cos_response(response/"//n2:cos")
60
+ end
61
+
62
+ def modify(cos)
63
+ xml = invoke("n2:ModifyCosRequest") do |message|
64
+ Builder.modify(message, cos)
65
+ end
66
+ Parser.cos_response(xml/'//n2:cos')
67
+ end
68
+
69
+ def delete(cos)
70
+ xml = invoke("n2:DeleteCosRequest") do |message|
71
+ Builder.delete(message, cos)
72
+ end
73
+ end
74
+
75
+ class Builder
76
+ class << self
77
+ def get_by_id(message, id)
78
+ message.add 'cos', id do |c|
79
+ c.set_attr 'by', 'id'
80
+ end
81
+ end
82
+
83
+ def get_by_name(message, name)
84
+ message.add 'cos', name do |c|
85
+ c.set_attr "by", 'name'
86
+ end
87
+ end
88
+
89
+ def create(message, name)
90
+ message.add 'name', name
91
+ end
92
+
93
+ def modify(message, cos)
94
+ message.add 'id', cos.id
95
+ modify_attributes(message, cos)
96
+ end
97
+ def modify_attributes(message, cos)
98
+ if cos.acls.empty?
99
+ ACL.delete_all(message)
100
+ else
101
+ cos.acls.each do |acl|
102
+ acl.apply(message)
103
+ end
104
+ end
105
+ end
106
+
107
+ def delete(message, cos)
108
+ message.add 'id', cos.id
109
+ end
110
+ end
111
+ end
112
+
113
+ class Parser
114
+ class << self
115
+ def cos_response(node)
116
+ id = (node/'@id').to_s
117
+ name = (node/'@name').to_s
118
+ acls = Zimbra::ACL.read(node)
119
+ Zimbra::Cos.new(id, name, acls)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,49 @@
1
+ # http://files.zimbra.com/docs/soap_api/8.0.4/soap-docs-804/api-reference/zimbraAdmin/DelegateAuth.html
2
+
3
+ module Zimbra
4
+ class DelegateAuthToken
5
+ class << self
6
+ def for_account_name(account_name)
7
+ DelegateAuthTokenService.get_by_account_name(account_name)
8
+ end
9
+ end
10
+
11
+ attr_accessor :account_name, :token, :lifetime
12
+
13
+ def initialize(args = {})
14
+ self.account_name = args[:account_name]
15
+ self.token = args[:token]
16
+ self.lifetime = args[:lifetime]
17
+ end
18
+ end
19
+
20
+ class DelegateAuthTokenService < HandsoapService
21
+ def get_by_account_name(account_name)
22
+ xml = invoke("n2:DelegateAuthRequest") do |message|
23
+ Builder.get_by_account_name(message, account_name)
24
+ end
25
+ return nil unless xml
26
+ Parser.delegate_auth_token_response(account_name, xml)
27
+ end
28
+
29
+ class Builder
30
+ class << self
31
+ def get_by_account_name(message, account_name)
32
+ message.add 'account', account_name do |c|
33
+ c.set_attr 'by', 'name'
34
+ end
35
+ end
36
+ end
37
+ end
38
+ class Parser
39
+ class << self
40
+ def delegate_auth_token_response(account_name, response)
41
+ auth_token = (response/'//n2:authToken').to_s
42
+ lifetime = (response/'//n2:lifetime').to_i
43
+
44
+ Zimbra::DelegateAuthToken.new(account_name: account_name, token: auth_token, lifetime: lifetime)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,175 @@
1
+ module Zimbra
2
+ module Directory
3
+
4
+ TARGET_TYPES_MAPPING = {
5
+ Zimbra::Account => 'account',
6
+ Zimbra::DistributionList => 'dl',
7
+ Zimbra::Domain => 'domain'
8
+ }
9
+
10
+ class << self
11
+ # Run a search over the Ldap server of Zimbra
12
+ # query: is a valid LDAP search query
13
+ # types: a comma separated string of types of objects to look for
14
+ # domain: the email domain you want to limit the search to
15
+ # options[:limit]: max results of the search
16
+ # options[:offset]
17
+ # options[:sort_by]
18
+ # options[:sort_ascending]: 1=true , 0=false
19
+ def search(query = '', type: 'account', domain: nil, **options)
20
+ options[:limit] ||= 25
21
+ DirectoryService.search(query, type.to_sym, domain, options)
22
+ end
23
+
24
+ def add_grant(target, acl)
25
+ DirectoryService.add_grant(target.id, target.zimbra_type, acl)
26
+ end
27
+
28
+ def get_grants(target)
29
+ DirectoryService.get_grants(target.id, target.zimbra_type)
30
+ end
31
+
32
+ def revoke_grant(target, acl)
33
+ DirectoryService.revoke_grant(target.id, target.zimbra_type, acl)
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ class DirectoryService < HandsoapService
40
+ # This are the type off types (objects) that Zimbra has
41
+ # "distributionlists,aliases,accounts,dynamicgroups,resources,domains"
42
+ ZIMBRA_TYPES_HASH = {
43
+ distribution_list: { zimbra_type: 'distributionlists', node_name: 'dl', class: Zimbra::DistributionList },
44
+ distributionlist: { zimbra_type: 'distributionlists', node_name: 'dl', class: Zimbra::DistributionList },
45
+ #alias: { zimbra_type: 'aliases', node_name: 'alias', class: Zimbra::Alias },
46
+ account: { zimbra_type: 'accounts', node_name: 'account', class: Zimbra::Account },
47
+ domain: { zimbra_type: 'domains', node_name: 'domain', class: Zimbra::Domain }
48
+ }
49
+
50
+ def add_grant(id, type, acl)
51
+ xml = invoke("n2:GrantRightRequest") do |message|
52
+ Builder.add_grant(message, id, type, acl)
53
+ end
54
+ return nil if soap_fault_not_found?
55
+ true
56
+ end
57
+
58
+ def search(query, type, domain, options = {})
59
+ xml = invoke("n2:SearchDirectoryRequest") do |message|
60
+ Builder.search_directory(message, query, type, domain, options)
61
+ end
62
+ return nil if soap_fault_not_found?
63
+ Parser.search_directory_response(xml, type)
64
+ end
65
+
66
+ # method to get the grants on an object
67
+ # check https://files.zimbra.com/docs/soap_api/8.5.0/api-reference/zimbraAdmin/GetGrants.html
68
+ def get_grants(id, type)
69
+ xml = invoke('n2:GetGrantsRequest') do |message|
70
+ Builder.get_grants(message, id, type)
71
+ end
72
+ return nil if soap_fault_not_found?
73
+ Parser.get_grants_response(xml, type)
74
+ end
75
+
76
+ def revoke_grant(id, type, acl)
77
+ xml = invoke("n2:RevokeRightRequest") do |message|
78
+ Builder.revoke_grant(message, id, type, acl)
79
+ end
80
+ return nil if soap_fault_not_found?
81
+ true
82
+ end
83
+
84
+ module Builder
85
+ class << self
86
+ def search_directory(message, query, type, domain, options)
87
+ message.set_attr 'types', ZIMBRA_TYPES_HASH[type][:zimbra_type]
88
+ message.set_attr 'query', query
89
+ message.set_attr('domain', domain) if domain
90
+ message.set_attr('limit', options[:limit]) if options[:limit]
91
+ message.set_attr('offset', options[:offset]) if options[:offset]
92
+ message.set_attr('sort_by', options[:sort_by]) if options[:sort_by]
93
+ message.set_attr('sort_ascending', options[:sort_ascending]) if options[:sort_ascending]
94
+ end
95
+
96
+ def get_grants(message, id, type)
97
+ message.add 'target', id do |c|
98
+ c.set_attr 'by', 'id'
99
+ c.set_attr 'type', type
100
+ end
101
+ end
102
+
103
+ def add_grant(message, id, type, acl)
104
+ message.add 'target', id do |c|
105
+ c.set_attr 'by', 'id'
106
+ c.set_attr 'type', type
107
+ end
108
+ message.add 'grantee', acl.grantee_name do |grantee|
109
+ grantee.set_attr 'by', 'name'
110
+ grantee.set_attr 'type', acl.grantee_class.acl_name
111
+ end
112
+ message.add 'right', acl.name
113
+ end
114
+
115
+ def revoke_grant(message, id, type, acl)
116
+ message.add 'target', id do |c|
117
+ c.set_attr 'by', 'id'
118
+ c.set_attr 'type', type
119
+ end
120
+ message.add 'grantee', acl.grantee_name do |grantee|
121
+ grantee.set_attr 'by', 'name'
122
+ grantee.set_attr 'type', acl.grantee_class.acl_name
123
+ end
124
+ message.add 'right', acl.name
125
+ end
126
+
127
+ end
128
+ end
129
+
130
+ module Parser
131
+ class << self
132
+ def search_directory_response(response, type)
133
+ # look for the node given by the type
134
+ items = (response/"//n2:#{ZIMBRA_TYPES_HASH[type][:node_name]}")
135
+ items.map { |i| object_list_response(i, type) }
136
+ end
137
+
138
+ def get_grants_response(response, type)
139
+ result = []
140
+ grants = (response/"//n2:grant")
141
+ grants.each do |n|
142
+ hash = {}
143
+ hash[:grantee_id] = (n/'n2:grantee'/'@id').to_s
144
+ hash[:grantee_class] = Zimbra::ACL::TARGET_MAPPINGS[(n/'n2:grantee'/'@type').to_s]
145
+ hash[:grantee_name] = (n/'n2:grantee'/'@name').to_s
146
+ hash[:name] = (n/'n2:right').to_s
147
+ result << Zimbra::ACL.new(hash)
148
+ end
149
+ result
150
+ end
151
+
152
+ # This just call the parser_response method of the object
153
+ # in the case of the account type, it will call
154
+ # Zimbra::AccountService::Parser.account_response(node)
155
+ def object_list_response(node, type)
156
+ node = clean_node node
157
+ class_name = ZIMBRA_TYPES_HASH[type][:class].name.gsub(/Zimbra::/, '')
158
+ Zimbra::BaseService::Parser.response(class_name, node, true)
159
+ end
160
+
161
+ # This method is to erase all others nodes from document
162
+ # so the xpath search like (//xxxx) works, beacuse (//) always start
163
+ # at the beginning of the document, not the current element
164
+ def clean_node(node)
165
+ element = node.instance_variable_get("@element")
166
+ directory_response = element.document.css("n2|SearchDirectoryResponse", 'n2' => 'urn:zimbraAdmin').first
167
+ directory_response.children.each {|c| c.remove}
168
+ directory_response.add_child element
169
+ node
170
+ end
171
+
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,147 @@
1
+ module Zimbra
2
+ class DistributionList < Zimbra::Base
3
+ class << self
4
+ def acl_name
5
+ 'grp'
6
+ end
7
+ end
8
+
9
+ attr_accessor :id, :name, :admin_console_ui_components, :admin_group
10
+ attr_accessor :members, :restricted, :display_name, :cn, :mail
11
+
12
+ def initialize(id, name, zimbra_attrs = {}, node = nil)
13
+ super
14
+ @cn = zimbra_attrs['cn']
15
+ @display_name = zimbra_attrs['displayName']
16
+ self.admin_group = zimbra_attrs['zimbraIsAdminGroup']
17
+ @members = Zimbra::DistributionListService::Parser.get_members node
18
+ @restricted = !acls.nil?
19
+ @original_members = self.members.dup
20
+ end
21
+
22
+ def admin_console_ui_components
23
+ @admin_console_ui_components ||= []
24
+ end
25
+
26
+ def modify_members(members_group = [])
27
+ return unless members_group.any?
28
+ self.members = members_group
29
+ DistributionListService.modify_members(self)
30
+ end
31
+
32
+ def members
33
+ @members ||= []
34
+ end
35
+
36
+ def new_members
37
+ self.members - @original_members
38
+ end
39
+
40
+ def removed_members
41
+ @original_members - self.members
42
+ end
43
+
44
+ def admin_group=(val)
45
+ @admin_group = Zimbra::Boolean.read(val)
46
+ end
47
+ def admin_group?
48
+ @admin_group
49
+ end
50
+
51
+ def restricted?
52
+ @restricted
53
+ end
54
+
55
+ def add_alias(alias_name)
56
+ DistributionListService.add_alias(self,alias_name)
57
+ end
58
+
59
+ def save
60
+ DistributionListService.modify(self)
61
+ end
62
+ end
63
+
64
+ class DistributionListService < HandsoapService
65
+ def create(name)
66
+ xml = invoke("n2:CreateDistributionListRequest") do |message|
67
+ Builder.create(message, name)
68
+ end
69
+ Parser.distribution_list_response(xml/'//n2:dl')
70
+ end
71
+
72
+ def modify_members(distribution_list)
73
+ distribution_list.new_members.each do |member|
74
+ add_member(distribution_list, member)
75
+ end
76
+ distribution_list.removed_members.each do |member|
77
+ remove_member(distribution_list, member)
78
+ end
79
+ return true
80
+ end
81
+
82
+ def add_member(distribution_list, member)
83
+ xml = invoke("n2:AddDistributionListMemberRequest") do |message|
84
+ Builder.add_member(message, distribution_list.id, member)
85
+ end
86
+ end
87
+
88
+ def remove_member(distribution_list, member)
89
+ xml = invoke("n2:RemoveDistributionListMemberRequest") do |message|
90
+ Builder.remove_member(message, distribution_list.id, member)
91
+ end
92
+ end
93
+
94
+ def add_alias(distribution_list,alias_name)
95
+ xml = invoke('n2:AddDistributionListAliasRequest') do |message|
96
+ Builder.add_alias(message,distribution_list.id,alias_name)
97
+ end
98
+ end
99
+
100
+ module Builder
101
+ class << self
102
+ def modify_admin_console_ui_components(message, distribution_list)
103
+ if distribution_list.admin_console_ui_components.empty?
104
+ A.inject(message, 'zimbraAdminConsoleUIComponents', '')
105
+ else
106
+ distribution_list.admin_console_ui_components.each do |component|
107
+ A.inject(message, 'zimbraAdminConsoleUIComponents', component)
108
+ end
109
+ end
110
+ end
111
+
112
+ def modify_is_admin_group(message, distribution_list)
113
+ A.inject(message, 'zimbraIsAdminGroup', (distribution_list.admin_group? ? 'TRUE' : 'FALSE'))
114
+ end
115
+
116
+ def add_member(message, distribution_list_id, member)
117
+ message.add 'id', distribution_list_id
118
+ message.add 'dlm', member
119
+ end
120
+
121
+ def remove_member(message, distribution_list_id, member)
122
+ message.add 'id', distribution_list_id
123
+ message.add 'dlm', member
124
+ end
125
+
126
+ def add_alias(message,id,alias_name)
127
+ message.add 'id', id
128
+ message.add 'alias', alias_name
129
+ end
130
+ end
131
+ end
132
+
133
+ # Doc Placeholder
134
+ module Parser
135
+ class << self
136
+ def get_members(node)
137
+ # Return this if we are getting here by find_by_*
138
+ return (node/"//n2:dlm").map(&:to_s).compact if (node/"//n2:dlm").any?
139
+
140
+ # Return this if we get here by DirectorySearch
141
+ fwds = A.read(node, 'zimbraMailForwardingAddress')
142
+ fwds.is_a?(Array) ? fwds.map(&:to_s).compact : [fwds].compact
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end