google_apps_api 0.1.0 → 0.2.1
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.
- data/.gitignore +1 -1
- data/VERSION +1 -1
- data/google_apps_api.gemspec +9 -4
- data/lib/config/calendar.yml +36 -9
- data/lib/config/contacts.yml +35 -0
- data/lib/config/profiles.yml +1 -0
- data/lib/config/provisioning.yml +10 -3
- data/lib/google_apps_api/base_api.rb +120 -36
- data/lib/google_apps_api/calendar.rb +305 -30
- data/lib/google_apps_api/contacts.rb +83 -53
- data/lib/google_apps_api/provisioning.rb +66 -242
- data/test/example_connection_config.yml +4 -0
- data/test/google_apps_api_base_api_test.rb +76 -0
- data/test/google_apps_api_calendar_test.rb +225 -54
- data/test/google_apps_api_contacts_test.rb +103 -27
- data/test/google_apps_api_off_domain_calendar_test.rb +27 -0
- data/test/google_apps_api_provisioning_test.rb +3 -4
- data/test/test_helper.rb +5 -0
- metadata +9 -4
- data/private/gapps-config.yml +0 -5
- data/private/userscalendars.xml +0 -113
@@ -1,7 +1,4 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
|
-
include REXML
|
3
|
-
|
4
|
-
|
5
2
|
|
6
3
|
module GoogleAppsApi #:nodoc:
|
7
4
|
|
@@ -12,54 +9,332 @@ module GoogleAppsApi #:nodoc:
|
|
12
9
|
def initialize(*args)
|
13
10
|
super(:calendar, *args)
|
14
11
|
end
|
12
|
+
|
13
|
+
def set_calendar_for_user(calendar, user, *args)
|
14
|
+
options = args.extract_options!
|
15
|
+
|
16
|
+
existing_acl = "none"
|
17
|
+
need_to_create = false
|
18
|
+
|
19
|
+
cal = retrieve_calendar_for_user(calendar, user)
|
20
|
+
if cal
|
21
|
+
existing_acl = cal.details[:accesslevel]
|
22
|
+
else
|
23
|
+
need_to_create = true
|
24
|
+
end
|
25
|
+
|
26
|
+
acl = options.delete(:accesslevel) || existing_acl
|
27
|
+
|
28
|
+
if need_to_create
|
29
|
+
raise "Must set accesslevel for a newly added calendar" unless acl
|
30
|
+
owner_acl = CalendarAcl.new(:calendar => calendar, :scope => user, :role => "owner")
|
31
|
+
set_calendar_acl(owner_acl)
|
32
|
+
add_calendar_to_user(calendar, user)
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
if acl == "none"
|
37
|
+
remove_calendar_from_user(calendar, user) unless existing_acl == "none"
|
38
|
+
else
|
15
39
|
|
16
|
-
|
17
|
-
|
40
|
+
if acl != existing_acl
|
41
|
+
set_calendar_acl(CalendarAcl.new(:calendar => calendar, :scope => user, :role => acl))
|
42
|
+
end
|
43
|
+
update_calendar_for_user(calendar, user, options) unless options.empty?
|
44
|
+
|
45
|
+
end
|
18
46
|
end
|
19
47
|
|
20
|
-
def add_calendar_to_user(calendar, user)
|
21
|
-
|
48
|
+
def add_calendar_to_user(calendar, user, *args)
|
49
|
+
req = <<-DESCXML
|
50
|
+
<entry xmlns='http://www.w3.org/2005/Atom'>
|
51
|
+
<id>#{calendar.full_id_escaped}</id>
|
52
|
+
</entry>
|
53
|
+
DESCXML
|
54
|
+
|
55
|
+
options = args.extract_options!.merge(:username => user.full_id_escaped, :body => req.strip)
|
56
|
+
request(:add_calendar_to_user, options)
|
22
57
|
end
|
23
58
|
|
24
|
-
|
25
|
-
|
59
|
+
|
60
|
+
def remove_calendar_from_user(calendar, user, *args)
|
61
|
+
options = args.extract_options!.merge(:username => user.full_id_escaped, :calendar => calendar.full_id_escaped)
|
62
|
+
request(:remove_calendar_from_user, options)
|
26
63
|
end
|
27
|
-
end
|
28
64
|
|
29
65
|
|
66
|
+
def update_calendar_for_user(calendar, user, *args)
|
30
67
|
|
68
|
+
username = user.full_id_escaped
|
31
69
|
|
70
|
+
cal = nil
|
71
|
+
cal = retrieve_calendar_for_user(calendar, user)
|
72
|
+
|
73
|
+
cal_id = cal.full_id_escaped
|
32
74
|
|
33
|
-
|
75
|
+
options = args.extract_options!.merge(:username => username, :calendar => cal_id)
|
76
|
+
|
77
|
+
details = cal.details.merge(options)
|
78
|
+
|
79
|
+
|
80
|
+
req = <<-DESCXML
|
81
|
+
<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gCal='http://schemas.google.com/gCal/2005' xmlns:gd='http://schemas.google.com/g/2005'>
|
82
|
+
<id>http://www.google.com/calendar/feeds/#{username}/allcalendars/full/#{cal_id}</id>
|
83
|
+
|
84
|
+
<title type='text'>#{details[:title]}</title>
|
85
|
+
<summary type='text'>#{details[:summary]}</summary>
|
86
|
+
<gCal:timezone value='#{details[:timezone]}'/>
|
87
|
+
<gCal:hidden value='#{details[:hidden].to_s}'/>
|
88
|
+
<gCal:color value='#{details[:color]}'/>
|
89
|
+
<gCal:selected value='#{details[:selected].to_s}'/>
|
90
|
+
<gd:where valueString='#{details[:where]}'/>
|
91
|
+
</entry>
|
92
|
+
|
93
|
+
DESCXML
|
94
|
+
|
95
|
+
options[:body] = req.strip
|
96
|
+
request(:update_calendar_for_user, options)
|
97
|
+
end
|
98
|
+
|
99
|
+
# lists all calendards for a user
|
100
|
+
def retrieve_calendars_for_user(user, *args)
|
101
|
+
options = args.extract_options!.merge(:username => user.full_id_escaped)
|
102
|
+
request(:retrieve_calendars_for_user, options)
|
103
|
+
end
|
34
104
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
105
|
+
# lists all calendards for a user
|
106
|
+
def retrieve_calendar_for_user(calendar, user, *args)
|
107
|
+
options = args.extract_options!.merge(:calendar => calendar.full_id_escaped, :username => user.full_id_escaped)
|
108
|
+
retries = options[:retries] || 5
|
109
|
+
|
110
|
+
while (retries > 0)
|
111
|
+
begin
|
112
|
+
return request(:retrieve_calendar_for_user, options)
|
113
|
+
rescue GDataError => g
|
114
|
+
retries -= 1
|
115
|
+
sleep 0.5
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
return nil
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
# returns array of acls for a given calendar
|
124
|
+
def retrieve_acls_for_calendar(calendar, *args)
|
125
|
+
options = args.extract_options!.merge(:calendar => calendar.full_id_escaped)
|
126
|
+
request(:retrieve_acls_for_calendar, options)
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
# generally, use set_calendar_acl, it works for both creates and updates
|
131
|
+
# this will throw an exception "Version Conflict" if it already exists
|
132
|
+
def create_calendar_acl(acl, *args)
|
133
|
+
req = <<-DESCXML
|
134
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
135
|
+
<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gAcl='http://schemas.google.com/acl/2007'>
|
136
|
+
<category scheme='http://schemas.google.com/g/2005#kind'
|
137
|
+
term='http://schemas.google.com/acl/2007#accessRule'/>
|
138
|
+
<gAcl:scope type='#{acl.scope.kind}' value='#{acl.scope.full_id}'></gAcl:scope>
|
139
|
+
<gAcl:role
|
140
|
+
value='http://schemas.google.com/gCal/2005##{acl.role}'>
|
141
|
+
</gAcl:role>
|
142
|
+
</entry>
|
143
|
+
DESCXML
|
144
|
+
options = args.extract_options!.merge(:calendar => acl.calendar.full_id_escaped, :body => req.strip)
|
145
|
+
request(:create_calendar_acl, options)
|
42
146
|
end
|
147
|
+
|
148
|
+
# you can substitute set_calendar_acl with role set to none
|
149
|
+
def remove_calendar_acl(acl, *args)
|
150
|
+
options = args.extract_options!.merge(:calendar => acl.calendar.full_id_escaped, :scope => acl.scope.qualified_id_escaped)
|
151
|
+
request(:remove_calendar_acl, options)
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
# updates a given acl for a given scope
|
156
|
+
def set_calendar_acl(acl, *args)
|
157
|
+
req = <<-DESCXML
|
158
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
159
|
+
<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gAcl='http://schemas.google.com/acl/2007'
|
160
|
+
xmlns:gd='http://schemas.google.com/g/2005'
|
161
|
+
gd:etag='W/"DU4ERH47eCp7ImA9WxRVEkQ."'>
|
162
|
+
<category scheme='http://schemas.google.com/g/2005#kind'
|
163
|
+
term='http://schemas.google.com/acl/2007#accessRule'/>
|
164
|
+
<gAcl:scope type='#{acl.scope.kind}' value='#{acl.scope.full_id}'></gAcl:scope>
|
165
|
+
<gAcl:role
|
166
|
+
value='#{acl.role_schema}'>
|
167
|
+
</gAcl:role>
|
168
|
+
</entry>
|
169
|
+
DESCXML
|
170
|
+
|
171
|
+
options = args.extract_options!.merge(:calendar => acl.calendar.full_id_escaped, :scope => acl.scope.qualified_id_escaped, :body => req.strip)
|
172
|
+
request(:set_calendar_acl, options)
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
|
43
177
|
end
|
44
178
|
|
45
|
-
|
46
|
-
|
179
|
+
end
|
180
|
+
|
181
|
+
class CalendarAcl
|
182
|
+
attr_accessor :calendar, :scope, :role
|
183
|
+
|
184
|
+
def initialize(*args)
|
185
|
+
options = args.extract_options!
|
186
|
+
if (_xml = options[:xml])
|
187
|
+
_xml = _xml.kind_of?(Nokogiri::XML::Document) ? _xml.children.first : _xml
|
188
|
+
scope_kind = _xml.at_css('gAcl|scope').attribute("type").content
|
189
|
+
scope_id = _xml.at_css('gAcl|scope').attribute("value").content
|
190
|
+
|
191
|
+
@scope = Entity.new(scope_kind => scope_id)
|
192
|
+
@role = _xml.at_css('title').content
|
193
|
+
@calendar = CalendarEntity.new(_xml.at_css('id').content.gsub(/^.*feeds\//,"").gsub(/\/acl.*$/,""))
|
194
|
+
|
195
|
+
else
|
196
|
+
|
197
|
+
@scope = options[:scope]
|
198
|
+
@role = options[:role]
|
199
|
+
@calendar = options[:calendar]
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def role_schema
|
204
|
+
if @role == "none"
|
205
|
+
"none"
|
206
|
+
else
|
207
|
+
"http://schemas.google.com/gCal/2005##{@role}"
|
208
|
+
end
|
47
209
|
end
|
210
|
+
end
|
211
|
+
|
48
212
|
|
49
|
-
|
50
|
-
|
213
|
+
# def role_url
|
214
|
+
# "http://schemas.google.com/gCal/2005#{@role}"
|
215
|
+
# end
|
216
|
+
# end
|
217
|
+
|
218
|
+
|
219
|
+
class CalendarEntity < Entity
|
220
|
+
attr_reader :details
|
221
|
+
|
222
|
+
def initialize(*args)
|
223
|
+
@details = {}
|
224
|
+
|
225
|
+
options = args.extract_options!
|
226
|
+
if options.has_key?(:xml)
|
227
|
+
parse_calendar_xml(options[:xml])
|
228
|
+
super(:calendar => @calendar_id)
|
229
|
+
else
|
230
|
+
@title = options.delete(:title)
|
231
|
+
|
232
|
+
if args.first.kind_of?(String)
|
233
|
+
super(:calendar => args.first)
|
234
|
+
else
|
235
|
+
super(options.merge(:kind => "calendar"))
|
236
|
+
end
|
237
|
+
end
|
51
238
|
end
|
239
|
+
|
240
|
+
def ==(other)
|
241
|
+
super(other)
|
242
|
+
end
|
243
|
+
|
244
|
+
#
|
245
|
+
# # updates with details.
|
246
|
+
# def refresh_details!(c_api, *args)
|
247
|
+
# c_api.retrieve_calendar_details(self, *args)
|
248
|
+
# end
|
249
|
+
#
|
250
|
+
def get_acls(c_api, *args)
|
251
|
+
c_api.retrieve_acls_for_calendar(self, *args)
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
def parse_calendar_xml(xml)
|
256
|
+
entry = xml.kind_of?(Nokogiri::XML::Document) ? xml.children.first : xml
|
257
|
+
|
258
|
+
@kind = "calendar"
|
259
|
+
|
260
|
+
case entry.attribute("kind").content
|
261
|
+
when "calendar#calendar"
|
262
|
+
@calendar_id = entry.at_css('id').content.gsub(/^.*calendars\//,"")
|
263
|
+
@details[:title] = entry.at_css('title').content
|
264
|
+
@details[:summary] = entry.at_css('summary')
|
265
|
+
@details[:summary] = @details[:summary] ? @details[:summary].content : ""
|
266
|
+
@details[:timezone] = entry.at_css('gCal|timezone').attribute("value").content
|
267
|
+
@details[:accesslevel] = entry.at_css('gCal|accesslevel').attribute("value").content
|
268
|
+
@details[:where] = entry.at_css('gd|where')
|
269
|
+
@details[:where] = @details[:where] ? @details[:where].attribute("valueString").content : ""
|
270
|
+
@details[:color] = entry.at_css('gCal|color').attribute("value").content
|
271
|
+
|
272
|
+
@details[:hidden] = entry.at_css('gCal|hidden').attribute("value").content == "true"
|
273
|
+
@details[:selected] = entry.at_css('gCal|selected').attribute("value").content == "true"
|
274
|
+
when "calendar#acl"
|
275
|
+
@calendar_id = entry.at_css('id').content.gsub(/^.*feeds\//,"").gsub(/\/acl.*$/,"")
|
276
|
+
end
|
52
277
|
|
53
|
-
def add_message
|
54
|
-
Nokogiri::XML::Builder.new { |xml|
|
55
|
-
xml.entry(:xmlns => "http://www.w3.org/2005/Atom") {
|
56
|
-
xml.id_ {
|
57
|
-
xml.text id.to_s
|
58
|
-
}
|
59
|
-
}
|
60
|
-
}.to_xml
|
61
278
|
end
|
279
|
+
|
62
280
|
end
|
63
281
|
|
64
282
|
|
283
|
+
# acl names: none, read, freebusy, editor, owner, root
|
284
|
+
# class CalendarEntry
|
285
|
+
# attr_accessor :calendar_id, :title, :entity, :role
|
286
|
+
# def initialize(*args)
|
287
|
+
# options = args.extract_options!
|
288
|
+
# if (_xml = options[:xml])
|
289
|
+
# kind = _xml.attribute("kind")
|
290
|
+
# raise "invalid xml passed" unless kind
|
291
|
+
#
|
292
|
+
# case kind.content
|
293
|
+
# when "calendar#calendar"
|
294
|
+
# user_id = _xml.at_css('id').content.gsub(/^.*feeds\//,"").gsub(/\/.*$/,"")
|
295
|
+
#
|
296
|
+
# @entity = Entity.new("user", user_id)
|
297
|
+
# @role = _xml.at_css('gCal|accesslevel').attribute("value").content
|
298
|
+
#
|
299
|
+
# @calendar_id = _xml.at_css('id').content.gsub(/^.*calendars\//,"")
|
300
|
+
# @title = _xml.at_css('title').content
|
301
|
+
# when "calendar#acl"
|
302
|
+
# scope_type = _xml.at_css('gAcl|scope').attribute("type").content
|
303
|
+
# scope_id = _xml.at_css('gAcl|scope').attribute("value").content
|
304
|
+
# @entity = Entity.new(scope_type, scope_id)
|
305
|
+
# @role = _xml.at_css('title').content
|
306
|
+
# @calendar_id = _xml.at_css('id').content.gsub(/^.*feeds\//,"").gsub(/\/acl.*$/,"")
|
307
|
+
# end
|
308
|
+
# else
|
309
|
+
# @calendar_id = options[:calendar_id]
|
310
|
+
# @title = options[:title]
|
311
|
+
# @entity = options[:entity]
|
312
|
+
# @role = options[:role]
|
313
|
+
#
|
314
|
+
# end
|
315
|
+
#
|
316
|
+
# raise "Calendar ID required" unless @calendar_id
|
317
|
+
#
|
318
|
+
# end
|
319
|
+
#
|
320
|
+
# def to_s
|
321
|
+
# @calendar_id
|
322
|
+
# end
|
323
|
+
#
|
324
|
+
# def to_url
|
325
|
+
# CGI::escape(CGI::unescape(@calendar_id))
|
326
|
+
# end
|
327
|
+
#
|
328
|
+
# def add_message
|
329
|
+
# req = <<-DESCXML
|
330
|
+
# <entry xmlns='http://www.w3.org/2005/Atom'>
|
331
|
+
# <id>#{CGI::escape(calendar_id.to_s)}</id>
|
332
|
+
# </entry>
|
333
|
+
# DESCXML
|
334
|
+
#
|
335
|
+
# req.strip
|
336
|
+
# end
|
337
|
+
# end
|
338
|
+
|
339
|
+
|
65
340
|
end
|
@@ -1,53 +1,83 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
#
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module GoogleAppsApi #:nodoc:
|
4
|
+
module Contacts
|
5
|
+
class Api < GoogleAppsApi::BaseApi
|
6
|
+
attr_reader :token
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
super(:contacts, *args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def retrieve_all_contacts(*args)
|
13
|
+
options = args.extract_options!
|
14
|
+
request(:retrieve_all_contacts, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove_contact(contact, *args)
|
18
|
+
options = args.extract_options!.merge(:contact => contact.id_escaped, :merge_headers => {"If-Match" => "*"})
|
19
|
+
|
20
|
+
request(:remove_contact, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_contact(contact, *args)
|
24
|
+
|
25
|
+
req = <<-DESCXML
|
26
|
+
<atom:entry xmlns:atom='http://www.w3.org/2005/Atom'
|
27
|
+
xmlns:gd='http://schemas.google.com/g/2005'>
|
28
|
+
<atom:category scheme='http://schemas.google.com/g/2005#kind'
|
29
|
+
term='http://schemas.google.com/contact/2008#contact' />
|
30
|
+
<atom:title type='text'>#{contact.name}</atom:title>
|
31
|
+
DESCXML
|
32
|
+
|
33
|
+
contact.emails.each_pair do |loc, email|
|
34
|
+
req += <<-DESCXML
|
35
|
+
<gd:email rel='http://schemas.google.com/g/2005##{loc}'
|
36
|
+
primary='#{contact.primary_email == loc ? 'true' : 'false'}'
|
37
|
+
address='#{email}' displayName='#{contact.name}' />
|
38
|
+
DESCXML
|
39
|
+
end
|
40
|
+
|
41
|
+
req += "</atom:entry>"
|
42
|
+
|
43
|
+
options = args.extract_options!.merge(:body => req.strip)
|
44
|
+
|
45
|
+
request(:create_contact, options)
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
class ContactEntity < Entity
|
53
|
+
attr_reader :name, :emails, :primary_email
|
54
|
+
|
55
|
+
def initialize(*args)
|
56
|
+
@emails = {}
|
57
|
+
|
58
|
+
options = args.extract_options!
|
59
|
+
if (_xml = options[:xml])
|
60
|
+
xml = _xml.at_css("entry") || _xml
|
61
|
+
@kind = "contact"
|
62
|
+
@id = xml.at_css("id").content.gsub(/^.+\/base\//,"")
|
63
|
+
@domain = xml.at_css("id").content.gsub(/^.+\/contacts\/([^\/]+)\/.+$/,"\\1")
|
64
|
+
@name = xml.at_css("title").content
|
65
|
+
else
|
66
|
+
if args.first.kind_of?(String)
|
67
|
+
super(:contact => args.first)
|
68
|
+
else
|
69
|
+
@name = options.delete(:name)
|
70
|
+
@emails = options.delete(:emails) || {}
|
71
|
+
|
72
|
+
super(options.merge(:kind => "contact"))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def ==(other)
|
78
|
+
super(other)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|