clio-gcal4ruby 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,55 @@
1
+ #=CHANGELOG
2
+ #==version 0.3.1_OAuth
3
+ #* Git repo by tvongaza to implement OAuth authentication into the GCal4Ruby gem
4
+ #==version 0.3.1_AuthSub
5
+ #* Git repo by mgornick to implement Authsub authentication into the GCal4Ruby gem
6
+ #==version 0.3.1
7
+ #* Fixed Event.find method to work with secondary calendars and google apps accounts. Fix provided by groesser3.
8
+ #* Added max results to Calendar.find.
9
+ #==version 0.3.0
10
+ #* Rewrote Event.find to improve performance significantly.
11
+ #* Added improvements to event recurrence handling, including loading existing recurrences, changing recurring events to non recurring and vice versa.
12
+ #* Added support for initialization attributes to Event, Calendar, Service and Recurrence.
13
+ #* Fixed query string typo in Event.find. Fix provided by nat.lownes.
14
+ #==version 0.2.11
15
+ #* Added support for GML elements in calendar events. Fix provided by nat.lownes.
16
+ #* Fixed event status bug where cancelled events were marked confirmed. Fix provided by rifraf.
17
+ #==version 0.2.10
18
+ #* Fixed library support for Google Hosted Apps accounts running in forced SSL mode.
19
+ #==version 0.2.9
20
+ #* Fixed small SSL redirect bug due to variable misnaming in base.rb. Fix provided by JohnMetta
21
+ #==version 0.2.8
22
+ #* Merged changes from edruder and h13ronim from the unofficial github repo - http://github.com/h13ronim/gcal4ruby/commits/master
23
+ #==version 0.2.7
24
+ #* Added fix for finding events in calendars that have more than 25 entries
25
+ #==version 0.2.6
26
+ #* Added fix for updated google calendar XML per http://cookingandcoding.wordpress.com/2009/06/08/new-ruby-google-calendar-api-gem-gcal4ruby/#comment-183
27
+ #==version 0.2.5
28
+ #* Added calendar color support to to_iframe methods in Calendar and Service.
29
+ #==version 0.2.4
30
+ #* Fixed bug with ACL check in Calendar#load
31
+ #==version 0.2.3
32
+ #* Implemented to_iframe method for calendars and services to output embeddable iframe text.
33
+ #* Added switch to turn off ACL check for public calendars. Can increase effeciency if turned off.
34
+ #==version 0.2.2
35
+ #* Fixed URL encoding problem in Event.find method.
36
+ #* cleaned up Event.find method to allow for finding events by id
37
+ #* updated Calendar.find method to add params hash
38
+ #* added 'published', 'updated', and 'edited' attributes
39
+ #==version 0.2.1
40
+ #* fixed Event.find calendar specification
41
+ #==version 0.2.0
42
+ #* Fixed redirect URI query params problem
43
+ #* Updated syntax for finding events to include most google api parameters, Non-backwards compatible.
44
+ #==version 0.1.4
45
+ #* Added additional search criteria for Event.find
46
+ #==version 0.1.3
47
+ #* Added support for authenticating with Google Hosted Apps accounts
48
+ #* Added flag to indicate whether a calendar is editable
49
+ #* Added handling to gracefully throw error when trying to create event on a non-editable (shared) calendar
50
+ #==version 0.1.2
51
+ #* Fixed to_xml dump problem with hidden and selected attributes
52
+ #==version 0.1.1
53
+ #* Added all_day indicator to event to indicate an all day event
54
+ #==version 0.1.0
55
+ #* Initial Version
data/README.markdown ADDED
@@ -0,0 +1,136 @@
1
+ NOTES
2
+ ----
3
+
4
+ In all of the example code here and in the API you need to namespace the object like `GCal4Ruby::Calendar` instead of just `Calendar`. This documentation needs a lot of reformatting.
5
+
6
+ GCal4Ruby
7
+ ===
8
+
9
+ Introduction
10
+ ------------
11
+
12
+ GCal4Ruby is a full featured wrapper for the google calendar API. GCal4Ruby implements
13
+ all of the functionality available through the Google Calnedar API, including permissions,
14
+ attendees, reminders and event recurrence.
15
+
16
+ Author and Contact Information
17
+ ------------------------------
18
+
19
+ This branch was made by Matt Gornick (http://github.com/mgornick) to implement Google AuthSub into the original
20
+ GCal4Ruby that was created and is maintained by {Mike Reich}[mailto:mike@seabourneconsulting.com]
21
+ and is licenses under the GPL v2. Feel free to use and update, but be sure to contribute your
22
+ code back to the project and attribute as required by the license.
23
+
24
+ Website
25
+ -------
26
+
27
+ http://rubyforge.org/projects/gcal4ruby/
28
+
29
+ Example Application
30
+ -------------------
31
+
32
+ http://github.com/mgornick/studentplanr
33
+
34
+ Description
35
+ -----------
36
+
37
+ GCal4Ruby has three major components: the service, calendar and event objects. Each service
38
+ has many calendars, which in turn have many events. Each service is the representation of a
39
+ google account, and thus must be successfully authenticated using valid Google Calendar
40
+ account credentials.
41
+
42
+ Examples
43
+ --------
44
+
45
+ Below are some common usage examples. For more examples, check the documentation.
46
+
47
+ Service
48
+ -------
49
+
50
+ 1. Authenticate
51
+ service = Service.new
52
+ service.authenticate("user@gmail.com", "password")
53
+
54
+ 2. Authenticate with Google AuthSub
55
+ # your authentication controller (example is index action in the home controller):
56
+ scope = 'http://www.google.com/calendar/feeds/'
57
+ next_url = 'http://127.0.0.1:3000/home/authenticated'
58
+ secure = false # set secure = true for signed AuthSub requests
59
+ session = true
60
+ @authsub_link = GData::Auth::AuthSub.get_url(next_url, scope, secure, session)
61
+
62
+ # in your next_url controller as defined by your AuthSub link (in example would be the authenticated action)
63
+ @token = params[:token]
64
+ client = GData::Client::Calendar.new
65
+ client.authsub_token = params[:token] # extract the single-use token from the URL query params
66
+ session[:token] = client.auth_handler.upgrade()
67
+ client.authsub_token = session[:token] if session[:token]
68
+
69
+ service= GCal4Ruby::Service.new
70
+ service.authsub_authenticate(session[:token], "user@gmail.com")
71
+
72
+ 3. Get Calendar List
73
+ calendars = service.calendars
74
+
75
+ Calendar
76
+ --------
77
+
78
+ All usages assume a successfully authenticated Service.
79
+
80
+ 1. Create a new Calendar
81
+ cal = Calendar.new(service)
82
+
83
+ 2. Find an existing Calendar
84
+ cal = Calendar.find(service, "New Calendar", :first)
85
+
86
+ 3. Find all calendars containing the search term
87
+ cal = Calendar.find(service, "Soccer Team")
88
+
89
+ 4. Find a calendar by ID
90
+ cal = Calendar.find(service, id, :first)
91
+
92
+ Event
93
+ -----
94
+
95
+ All usages assume a successfully authenticated Service and valid Calendar.
96
+
97
+ 1. Create a new Event
98
+ event = Event.new(calendar)
99
+ event.title = "Soccer Game"
100
+ event.start = Time.parse("12-06-2009 at 12:30 PM")
101
+ event.end = Time.parse("12-06-2009 at 1:30 PM")
102
+ event.where = "Merry Playfields"
103
+ event.save
104
+
105
+ 2. Find an existing Event
106
+ event = Event.find(cal, "Soccer Game", {:scope => :first})
107
+
108
+ 3. Find all events containing the search term
109
+ event = Event.find(cal, "Soccer Game")
110
+
111
+ 4. Create a recurring event for every saturday
112
+ event = Event.new(calendar)
113
+ event.title = "Baseball Game"
114
+ event.where = "Municipal Stadium"
115
+ event.recurrence = Recurrence.new
116
+ event.recurrence.start = Time.parse("13-06-2009 at 4:30 PM")
117
+ event.recurrence.end = Time.parse("13-06-2009 at 6:30 PM")
118
+ event.recurrence.frequency = {"weekly" => ["SA"]}
119
+ event.save
120
+
121
+ 5. Create an event with a 15 minute email reminder
122
+ event = Event.new(calendar)
123
+ event.title = "Dinner with Kate"
124
+ event.start = Time.parse("20-06-2009 at 5 pm")
125
+ event.end = Time.parse("20-06-209 at 8 pm")
126
+ event.where = "Luigi's"
127
+ event.reminder = {:minutes => 15, :method => 'email'}
128
+ event.save
129
+
130
+ 6. Create an event with attendees
131
+ event = Event.new(calendar)
132
+ event.title = "Dinner with Kate"
133
+ event.start = Time.parse("20-06-2009 at 5 pm")
134
+ event.end = Time.parse("20-06-209 at 8 pm")
135
+ event.attendees => {:name => "Kate", :email => "kate@gmail.com"}
136
+ event.save
data/lib/gcal4ruby.rb ADDED
@@ -0,0 +1 @@
1
+ require "gcal4ruby/service"
@@ -0,0 +1,339 @@
1
+ require "rexml/document"
2
+ require "cgi"
3
+ require "uri"
4
+ require "net/http"
5
+ require "net/https"
6
+ require "open-uri"
7
+ require "nkf"
8
+ require "time"
9
+
10
+ Net::HTTP.version_1_2
11
+
12
+ # GCal4Ruby is a full featured wrapper for the google calendar API
13
+
14
+ # =Usage:
15
+
16
+ module GCal4Ruby
17
+
18
+ CALENDAR_XML = "<entry xmlns='http://www.w3.org/2005/Atom'
19
+ xmlns:gd='http://schemas.google.com/g/2005'
20
+ xmlns:gCal='http://schemas.google.com/gCal/2005'>
21
+ <title type='text'></title>
22
+ <summary type='text'></summary>
23
+ <gCal:timezone value=''></gCal:timezone>
24
+ <gCal:hidden value=''></gCal:hidden>
25
+ <gCal:color value=''></gCal:color>
26
+ <gd:where rel='' label='' valueString=''></gd:where>
27
+ </entry>"
28
+
29
+ ACL_XML = "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gAcl='http://schemas.google.com/acl/2007'>
30
+ <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/acl/2007#accessRule'/>
31
+ <gAcl:scope type='default'></gAcl:scope>
32
+ <gAcl:role value=''></gAcl:role>
33
+ </entry>"
34
+
35
+ EVENT_XML = "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005'>
36
+ <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'></category>
37
+ <title type='text'></title>
38
+ <content type='text'></content>
39
+ <gd:transparency value=''></gd:transparency>
40
+ <gd:eventStatus value=''></gd:eventStatus>
41
+ <gd:where valueString=''></gd:where>
42
+ <gd:when startTime='' endTime=''></gd:when>
43
+ </entry>
44
+ "
45
+
46
+ class AuthenticationFailed < StandardError; end #:nodoc: all
47
+
48
+ class NotAuthenticated < StandardError; end
49
+
50
+ class InvalidService < StandardError; end
51
+
52
+ class HTTPPostFailed < StandardError; end
53
+
54
+ class HTTPPutFailed < StandardError; end
55
+
56
+ class HTTPGetFailed < StandardError; end
57
+
58
+ class HTTPDeleteFailed < StandardError; end
59
+
60
+ class CalendarSaveFailed < StandardError; end
61
+
62
+ class EventSaveFailed < StandardError; end
63
+
64
+ class RecurrenceValueError < StandardError; end
65
+
66
+ class CalendarNotEditable < StandardError; end
67
+
68
+ class QueryParameterError < StandardError; end
69
+
70
+ #The ProxyInfo class contains information for configuring a proxy connection
71
+
72
+ class ProxyInfo
73
+ attr_accessor :address, :port, :username, :password
74
+ @address = nil
75
+ @port = nil
76
+ @username = nil
77
+ @password = nil
78
+
79
+ #The initialize function accepts four variables for configuring the ProxyInfo object.
80
+ #The proxy connection is initiated using the builtin Net::HTTP proxy support.
81
+
82
+ def initialize(address, port, username=nil, password=nil)
83
+ @address = address
84
+ @port = port
85
+ @username = username
86
+ @password = password
87
+ end
88
+ end
89
+
90
+ #The Base class includes the basic HTTP methods for sending and receiving
91
+ #messages from the Google Calendar API. You shouldn't have to use this class
92
+ #directly, rather access the functionality through the Service subclass.
93
+
94
+ class Base
95
+ AUTH_URL = "https://www.google.com/accounts/ClientLogin"
96
+ CALENDAR_LIST_FEED = "http://www.google.com/calendar/feeds/default/allcalendars/full"
97
+ @proxy_info = nil
98
+ @auth_token = nil
99
+ @debug = false
100
+
101
+ #Contains the ProxyInfo object for using a proxy server
102
+ attr_accessor :proxy_info
103
+
104
+ #If set to true, debug will dump all raw HTTP requests and responses
105
+ attr_accessor :debug
106
+
107
+ # Sends an HTTP POST request. The header should be a hash of name/value pairs.
108
+ # Returns the Net::HTTPResponse object on succces, or raises the appropriate
109
+ # error if a non 20x response code is received.
110
+ def send_post(url, content, header=nil)
111
+ header = auth_header(header)
112
+ ret = nil
113
+ location = URI.parse(url)
114
+ puts "url = "+url if @debug
115
+ ret = do_post(location, header, content)
116
+ while ret.is_a?(Net::HTTPRedirection)
117
+ puts "Redirect received, resending post" if @debug
118
+ if oauth?
119
+ header ||= {}
120
+ header['Cookie'] = ret['set-cookie'] if ret['set-cookie']
121
+ end
122
+ ret = do_post(ret['location'], header, content)
123
+ end
124
+ if ret.is_a?(Net::HTTPSuccess)
125
+ puts "20x response received\nResponse: \n"+ret.read_body if @debug
126
+ return ret
127
+ else
128
+ puts "invalid response received: "+ret.code if @debug
129
+ raise HTTPPostFailed, ret.body
130
+ end
131
+ end
132
+
133
+ def do_post(url, header, content)
134
+ ret = nil
135
+ if url.is_a?(String)
136
+ location = URI.parse(url)
137
+ else
138
+ location = url
139
+ end
140
+
141
+ puts "Starting post\nHeader: #{header}\n" if @debug
142
+ if oauth?
143
+ header ||= {}
144
+ header.merge!({ 'Accept'=>'application/atom+xml', 'Content-Type' => 'application/atom+xml' })
145
+ ret = @auth_token.post(url.to_s, content, header)
146
+ else
147
+ http = get_http_object(location)
148
+ http.start do |ht|
149
+ ret = ht.post(location.to_s, content, header)
150
+ end
151
+ end
152
+ return ret
153
+ end
154
+
155
+ # Sends an HTTP PUT request. The header should be a hash of name/value pairs.
156
+ # Returns the Net::HTTPResponse object on succces, or raises the appropriate
157
+ # error if a non 20x response code is received.
158
+ def send_put(url, content, header=nil)
159
+ header = auth_header(header)
160
+ ret = nil
161
+ location = URI.parse(url)
162
+ puts "url = "+url if @debug
163
+ ret = do_put(location, header, content)
164
+ while ret.is_a?(Net::HTTPRedirection)
165
+ puts "Redirect received, resending post" if @debug
166
+ ret = do_put(ret['location'], header, content)
167
+ end
168
+ if ret.is_a?(Net::HTTPSuccess)
169
+ puts "20x response received\nResponse: \n"+ret.read_body if @debug
170
+ return ret
171
+ else
172
+ puts "invalid response received: "+ret.code if @debug
173
+ raise HTTPPutFailed, ret.body
174
+ end
175
+ end
176
+
177
+ def do_put(url, header, content)
178
+ ret = nil
179
+ if url.is_a?(String)
180
+ location = URI.parse(url)
181
+ else
182
+ location = url
183
+ end
184
+
185
+ puts "Starting put\nHeader: #{header}\n" if @debug
186
+ if oauth?
187
+ header ||= {}
188
+ header.merge!({ 'Accept'=>'application/atom+xml', 'Content-Type' => 'application/atom+xml' })
189
+ ret = @auth_token.put(url.to_s, content, header)
190
+ else
191
+ http = get_http_object(location)
192
+ http.start do |ht|
193
+ ret = ht.put(location.to_s, content, header)
194
+ end
195
+ end
196
+ return ret
197
+ end
198
+
199
+ # Sends an HTTP GET request. The header should be a hash of name/value pairs.
200
+ # Returns the Net::HTTPResponse object on succces, or raises the appropriate
201
+ # error if a non 20x response code is received.
202
+ def send_get(url, header = nil)
203
+ header = auth_header(header)
204
+ ret = nil
205
+ location = URI.parse(url)
206
+ puts "url = "+url if @debug
207
+ ret = do_get(location, header)
208
+
209
+ while ret.is_a?(Net::HTTPRedirection)
210
+ puts "Redirect received from #{location.to_s}, resending get to #{ret['location']}" if @debug
211
+ if oauth?
212
+ header ||= {}
213
+ header['Cookie'] = ret['set-cookie'] if ret['set-cookie']
214
+ end
215
+ ret = do_get(ret['location'], header)
216
+ end
217
+ if ret.is_a?(Net::HTTPSuccess)
218
+ puts "20x response received\nResponse: \n"+ret.read_body if @debug
219
+ return ret
220
+ else
221
+ puts "Error received, resending get" if @debug
222
+ raise HTTPGetFailed, ret.body
223
+ end
224
+ end
225
+
226
+ def do_get(url, header)
227
+ ret = nil
228
+ if url.is_a?(String)
229
+ location = URI.parse(url)
230
+ else
231
+ location = url
232
+ end
233
+
234
+ puts "Starting get\nHeader: #{header}\n" if @debug
235
+ if oauth?
236
+ ret = @auth_token.get(url.to_s, header)
237
+ else
238
+ http = get_http_object(location)
239
+ http.start do |ht|
240
+ ret = ht.get(location.to_s, header)
241
+ end
242
+ end
243
+ return ret
244
+ end
245
+
246
+ # Sends an HTTP DELETE request. The header should be a hash of name/value pairs.
247
+ # Returns the Net::HTTPResponse object on succces, or raises the appropriate
248
+ # error if a non 20x response code is received.
249
+ def send_delete(url, header = nil)
250
+ header = auth_header(header)
251
+ ret = nil
252
+ location = URI.parse(url)
253
+ puts "url = "+url if @debug
254
+ ret = do_delete(location, header)
255
+ while ret.is_a?(Net::HTTPRedirection)
256
+ puts "Redirect received, resending post" if @debug
257
+ ret = do_delete(ret['location'], header)
258
+ end
259
+ if ret.is_a?(Net::HTTPSuccess)
260
+ puts "20x response received\nResponse: \n"+ret.read_body if @debug
261
+ return true
262
+ else
263
+ puts "invalid response received: "+ret.code if @debug
264
+ raise HTTPDeleteFailed, ret.body
265
+ end
266
+ end
267
+
268
+ def do_delete(url, header)
269
+ ret = nil
270
+ if url.is_a?(String)
271
+ location = URI.parse(url)
272
+ else
273
+ location = url
274
+ end
275
+
276
+ puts "Starting get\nHeader: #{header}\n" if @debug
277
+ if oauth?
278
+ ret = @auth_token.delete(url.to_s, header)
279
+ else
280
+ http = get_http_object(location)
281
+ http.start do |ht|
282
+ ret = ht.delete(location.to_s, header)
283
+ end
284
+ end
285
+ return ret
286
+ end
287
+
288
+ private
289
+
290
+ def get_http_object(location)
291
+ if @proxy_info and @proxy_info.address
292
+ http = Net::HTTP.new(location.host, location.port, @proxy_info.address, @proxy_info.port, @proxy_info.username, @proxy_info.password)
293
+ else
294
+ http = Net::HTTP.new(location.host, location.port)
295
+ end
296
+ if location.scheme == 'https'
297
+ #fixed http/http misnaming via JohnMetta
298
+ puts "SSL True" if @debug
299
+ http.use_ssl = true
300
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
301
+ end
302
+ return http
303
+ end
304
+
305
+ def auth_header_client_login(header)
306
+ if @auth_token
307
+ if header
308
+ header.merge!({'Authorization' => "GoogleLogin auth=#{@auth_token}", "GData-Version" => "2.1"})
309
+ else
310
+ header = {'Authorization' => "GoogleLogin auth=#{@auth_token}", "GData-Version" => "2.1"}
311
+ end
312
+ end
313
+
314
+ return header
315
+ end
316
+
317
+ #auth header that uses google authsub
318
+ def auth_header_authsub(header)
319
+ if @auth_token
320
+ if header
321
+ header.merge!({'Authorization' => "AuthSub token=#{@auth_token}", "GData-Version" => "2.1"})
322
+ else
323
+ header = {'Authorization' => "AuthSub token=#{@auth_token}", "GData-Version" => "2.1"}
324
+ end
325
+ end
326
+ return header
327
+ end
328
+
329
+ # routes the authentication to the proper header based upon auth type
330
+ def auth_header(header)
331
+ if @auth_type == 'AuthSub'
332
+ return auth_header_authsub(header)
333
+ elsif @auth_type == 'ClientLogin'
334
+ return auth_header_client_login(header)
335
+ end
336
+ end
337
+
338
+ end
339
+ end