google_apps 0.5 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +38 -0
- data/LICENSE +7 -0
- data/README.md +461 -0
- data/google_apps.gemspec +20 -0
- data/lib/google_apps/atom/atom.rb +6 -9
- data/lib/google_apps/atom/export.rb +5 -6
- data/lib/google_apps/atom/feed.rb +17 -6
- data/lib/google_apps/document_handler.rb +6 -38
- data/lib/google_apps/transport.rb +110 -194
- data/spec/credentials.example.yaml +2 -0
- data/spec/fixture_xml/mes_attr.xml +8 -0
- data/spec/fixture_xml/test_doc.xml +5 -0
- data/spec/fixture_xml/user.xml +5 -0
- data/spec/fixture_xml/users_feed.xml +49 -0
- data/spec/google_apps/apps_request_spec.rb +81 -0
- data/spec/google_apps/atom/document_spec.rb +74 -0
- data/spec/google_apps/atom/export_spec.rb +70 -0
- data/spec/google_apps/atom/feed_spec.rb +93 -0
- data/spec/google_apps/atom/group_member_spec.rb +49 -0
- data/spec/google_apps/atom/group_owner_spec.rb +43 -0
- data/spec/google_apps/atom/group_spec.rb +155 -0
- data/spec/google_apps/atom/message_attributes_spec.rb +73 -0
- data/spec/google_apps/atom/nickname_spec.rb +66 -0
- data/spec/google_apps/atom/node_spec.rb +57 -0
- data/spec/google_apps/atom/public_key_spec.rb +32 -0
- data/spec/google_apps/atom/user_spec.rb +304 -0
- data/spec/google_apps/document_handler_spec.rb +48 -0
- data/spec/google_apps/transport_spec.rb +235 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/helpers.rb +66 -0
- metadata +104 -6
data/google_apps.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = 'google_apps'
|
3
|
+
spec.version = '0.9'
|
4
|
+
spec.date = '2013-03-11'
|
5
|
+
spec.license = "MIT"
|
6
|
+
spec.summary = 'Google Apps APIs'
|
7
|
+
spec.description = 'Library for interfacing with Google Apps\' Domain and Application APIs'
|
8
|
+
spec.authors = ['Glen Holcomb', 'Will Read']
|
9
|
+
spec.files = Dir.glob(File.join('**', 'lib', '**', '*.rb'))
|
10
|
+
spec.homepage = 'https://github.com/LeakyBucket/google_apps'
|
11
|
+
|
12
|
+
spec.add_dependency('libxml-ruby', '>= 2.2.2')
|
13
|
+
spec.add_dependency('httparty')
|
14
|
+
|
15
|
+
spec.add_development_dependency 'rspec'
|
16
|
+
spec.add_development_dependency 'webmock'
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split("\n")
|
19
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
end
|
@@ -29,16 +29,13 @@ module GoogleApps
|
|
29
29
|
|
30
30
|
ENTRY_TAG = ["<atom:entry xmlns:atom=\"#{NAMESPACES[:atom]}\" xmlns:apps=\"#{NAMESPACES[:apps]}\" xmlns:gd=\"#{NAMESPACES[:gd]}\">", '</atom:entry>']
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
#
|
35
|
-
# Adds a Module Function that creates a corresponding document.
|
32
|
+
# Adds a Module Function that creates a corresponding document.
|
36
33
|
# This allows for a centralized location for document creation.
|
37
|
-
#
|
38
|
-
# @param [String] type
|
39
|
-
#
|
40
|
-
# @visibility public
|
41
|
-
# @return
|
34
|
+
#
|
35
|
+
# @param [String] type should correspond to the class name
|
36
|
+
#
|
37
|
+
# @visibility public
|
38
|
+
# @return
|
42
39
|
def add_doc_dispatcher(type)
|
43
40
|
eval "def #{type}(*args)\n #{type.camel_up}.new *args\nend" # Needs __file__ and __line__
|
44
41
|
module_function type.to_sym
|
@@ -13,8 +13,8 @@ module GoogleApps
|
|
13
13
|
def to_s
|
14
14
|
@doc.to_s
|
15
15
|
end
|
16
|
-
|
17
|
-
# start_date specifies a start date for the extract.
|
16
|
+
|
17
|
+
# start_date specifies a start date for the extract.
|
18
18
|
# Matching results that occurred before this date will
|
19
19
|
# not be included in the result set. start_date takes
|
20
20
|
# a string as an argument, the format is as follows:
|
@@ -31,7 +31,7 @@ module GoogleApps
|
|
31
31
|
add_prop('beginDate', date)
|
32
32
|
end
|
33
33
|
|
34
|
-
# end_date specifies an end date for the extract.
|
34
|
+
# end_date specifies an end date for the extract.
|
35
35
|
# Matching results that occurred past this date will
|
36
36
|
# not be included in the result set. end_date takes
|
37
37
|
# a string as an argument, the format is as follows:
|
@@ -49,7 +49,7 @@ module GoogleApps
|
|
49
49
|
end
|
50
50
|
|
51
51
|
# include_deleted will specify that matches which
|
52
|
-
# have been deleted should be returned as well.
|
52
|
+
# have been deleted should be returned as well.
|
53
53
|
# The default is to omit deleted matches.
|
54
54
|
#
|
55
55
|
# include_deleted
|
@@ -72,7 +72,7 @@ module GoogleApps
|
|
72
72
|
end
|
73
73
|
|
74
74
|
# content specifies the data to be returned in the
|
75
|
-
# mailbox export. There are two valid arguments:
|
75
|
+
# mailbox export. There are two valid arguments:
|
76
76
|
# 'FULL_MESSAGE' or 'HEADER_ONLY'
|
77
77
|
#
|
78
78
|
# content 'HEADER_ONLY'
|
@@ -82,7 +82,6 @@ module GoogleApps
|
|
82
82
|
add_prop('packageContent', type)
|
83
83
|
end
|
84
84
|
|
85
|
-
|
86
85
|
private
|
87
86
|
|
88
87
|
# set_header adds the appropriate XML boilerplate for
|
@@ -4,16 +4,12 @@ module GoogleApps
|
|
4
4
|
# TODO: Google's feed responses are inconsistent. Will need special fun time, assholes.
|
5
5
|
attr_reader :doc, :items, :next_page
|
6
6
|
|
7
|
-
#
|
8
|
-
# doesn't work in that case as group members also have group in
|
9
|
-
# the id url.
|
7
|
+
# FIXED: If we grab the first id element in the feed we can simply scan it.
|
10
8
|
TYPE_MATCH = /\/(user|nickname|group|member)/
|
11
9
|
|
12
10
|
|
13
11
|
def initialize(xml)
|
14
|
-
|
15
|
-
matches = id_element.scan(TYPE_MATCH).flatten
|
16
|
-
type = matches.join '_'
|
12
|
+
type = determine_type xml # This only works when xml is a string.
|
17
13
|
|
18
14
|
super(xml)
|
19
15
|
|
@@ -109,6 +105,21 @@ module GoogleApps
|
|
109
105
|
def entry_wrap(content_array)
|
110
106
|
content_array.unshift(Atom::ENTRY_TAG[0]).push(Atom::ENTRY_TAG[1])
|
111
107
|
end
|
108
|
+
|
109
|
+
|
110
|
+
#
|
111
|
+
# Determine the feed type from the feed id element.
|
112
|
+
#
|
113
|
+
# @param [String] xml
|
114
|
+
#
|
115
|
+
# @visibility public
|
116
|
+
# @return snake cased doc type
|
117
|
+
def determine_type(xml)
|
118
|
+
id_element = xml.scan(/<id.*?\/id/).first
|
119
|
+
matches = id_element.scan(TYPE_MATCH).flatten
|
120
|
+
|
121
|
+
matches.join '_'
|
122
|
+
end
|
112
123
|
end
|
113
124
|
end
|
114
125
|
end
|
@@ -1,71 +1,39 @@
|
|
1
1
|
module GoogleApps
|
2
2
|
class DocumentHandler
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(args)
|
7
|
-
set_format args[:format]
|
3
|
+
def initialize
|
4
|
+
@documents = look_up_doc_types
|
8
5
|
end
|
9
6
|
|
10
|
-
|
11
7
|
# create_doc creates a document of the specified format
|
12
8
|
# from the given string.
|
13
9
|
def create_doc(text, type = nil)
|
14
10
|
@documents.include?(type) ? doc_of_type(text, type) : unknown_type(text)
|
15
11
|
end
|
16
12
|
|
17
|
-
|
18
13
|
# unknown_type takes a string and returns a document of
|
19
14
|
# of the corresponding @format.
|
20
15
|
def unknown_type(text)
|
21
|
-
|
22
|
-
when :atom, :xml
|
23
|
-
Atom::XML::Document.string(text)
|
24
|
-
end
|
16
|
+
Atom::XML::Document.string(text)
|
25
17
|
end
|
26
18
|
|
27
|
-
|
28
|
-
# format= sets the format for the DocumentHandler
|
29
|
-
def format=(format)
|
30
|
-
set_format format
|
31
|
-
end
|
32
|
-
|
33
|
-
|
34
19
|
# doc_of_type takes a document type and a string and
|
35
20
|
# returns a document of that type in the current format.
|
36
21
|
def doc_of_type(text, type)
|
37
|
-
raise "No
|
22
|
+
raise "No Atom document of type: #{type}" unless @documents.include?(type.to_s)
|
38
23
|
|
39
|
-
|
40
|
-
when :atom, :xml
|
41
|
-
GoogleApps::Atom.send(type, text)
|
42
|
-
end
|
24
|
+
GoogleApps::Atom.send(type, text)
|
43
25
|
end
|
44
26
|
|
45
|
-
|
46
|
-
|
47
27
|
private
|
48
28
|
|
49
|
-
|
50
29
|
# look_up_doc_types returns a list of document types the
|
51
30
|
# library supports in the current format.
|
52
31
|
def look_up_doc_types
|
53
|
-
|
54
|
-
when :atom, :xml
|
55
|
-
Atom::Document.types.inject([]) { |types, subclass| types | [sub_to_meth(subclass)] }
|
56
|
-
end
|
32
|
+
Atom::Document.types.map { |subclass| sub_to_meth(subclass) }
|
57
33
|
end
|
58
34
|
|
59
|
-
|
60
35
|
def sub_to_meth(subclass) # TODO: This shouldn't be both here and in GoogleApps::Atom::Document
|
61
36
|
subclass.to_s.split('::').last.scan(/[A-Z][a-z0-9]+/).map(&:downcase).join('_')
|
62
37
|
end
|
63
|
-
|
64
|
-
|
65
|
-
# set_format Sets @format and @documents
|
66
|
-
def set_format(format)
|
67
|
-
@format = format
|
68
|
-
@documents = look_up_doc_types
|
69
|
-
end
|
70
38
|
end
|
71
39
|
end
|
@@ -4,53 +4,35 @@ require 'rexml/document'
|
|
4
4
|
|
5
5
|
module GoogleApps
|
6
6
|
class Transport
|
7
|
-
attr_reader :
|
8
|
-
|
7
|
+
attr_reader :domain, :token
|
8
|
+
attr_reader :user, :group, :nickname, :export, :pubkey, :requester, :migration
|
9
9
|
|
10
|
-
SUCCESS_CODES = [200, 201, 202]
|
11
10
|
BOUNDARY = "=AaB03xDFHT8xgg"
|
12
11
|
PAGE_SIZE = {
|
13
12
|
user: 100,
|
14
13
|
group: 200
|
15
14
|
}
|
15
|
+
FEEDS_ROOT = 'https://apps-apis.google.com/a/feeds'
|
16
16
|
|
17
|
-
def initialize(
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@migration = targets[:migration] || "#{@feeds_root}/migration/2.0/#{domain}"
|
23
|
-
@group = targets[:group] || "#{@feeds_root}/group/2.0/#{domain}"
|
24
|
-
@nickname = targets[:nickname] || "#{@feeds_root}/#{domain}/nickname/2.0"
|
25
|
-
@audit = "#{@feeds_root}/compliance/audit/mail"
|
26
|
-
@export = targets[:export] || "#{@audit}/export/#{domain}"
|
27
|
-
@monitor = targets[:monitor] || "#{@audit}/monitor/#{domain}"
|
28
|
-
@domain = domain
|
29
|
-
@requester = AppsRequest || targets[:requester]
|
30
|
-
@doc_handler = DocumentHandler.new format: (targets[:format] || :atom)
|
31
|
-
@token = nil
|
32
|
-
@response = nil
|
33
|
-
@request = nil
|
34
|
-
@feeds = []
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
# authenticate will take the provided account and
|
39
|
-
# password and attempt to authenticate them with
|
40
|
-
# Google
|
41
|
-
#
|
42
|
-
# authenticate 'username@domain', 'password'
|
43
|
-
#
|
44
|
-
# authenticate returns the HTTP response received
|
45
|
-
# from Google
|
46
|
-
def authenticate(account, pass)
|
47
|
-
add(@auth, nil, auth_body(account, pass), :auth)
|
17
|
+
def initialize(options)
|
18
|
+
@domain = options[:domain]
|
19
|
+
@token = options[:token]
|
20
|
+
@refresh_token = options[:refresh_token]
|
21
|
+
@token_changed_callback = options[:token_changed_callback]
|
48
22
|
|
49
|
-
|
23
|
+
@user = "#{FEEDS_ROOT}/#{@domain}/user/2.0"
|
24
|
+
@pubkey = "#{FEEDS_ROOT}/compliance/audit/publickey/#{@domain}"
|
25
|
+
@migration = "#{FEEDS_ROOT}/migration/2.0/#{@domain}"
|
26
|
+
@group = "#{FEEDS_ROOT}/group/2.0/#{@domain}"
|
27
|
+
@nickname = "#{FEEDS_ROOT}/#{@domain}/nickname/2.0"
|
50
28
|
|
51
|
-
|
52
|
-
|
29
|
+
audit_root = "#{FEEDS_ROOT}/compliance/audit/mail"
|
30
|
+
@export = "#{audit_root}/export/#{@domain}"
|
31
|
+
@monitor = "#{audit_root}/monitor/#{@domain}"
|
53
32
|
|
33
|
+
@requester = AppsRequest
|
34
|
+
@doc_handler = DocumentHandler.new
|
35
|
+
end
|
54
36
|
|
55
37
|
# request_export performs the GoogleApps API call to
|
56
38
|
# generate a mailbox export. It takes the username
|
@@ -62,9 +44,11 @@ module GoogleApps
|
|
62
44
|
# request_export returns the request ID on success or
|
63
45
|
# the HTTP response object on failure.
|
64
46
|
def request_export(username, document)
|
65
|
-
|
47
|
+
response = add(export + "/#{username}", document)
|
48
|
+
process_response(response)
|
49
|
+
export = create_doc(response.body, :export_response)
|
66
50
|
|
67
|
-
|
51
|
+
export.find('//apps:property').inject(nil) do |request_id, node|
|
68
52
|
node.attributes['name'] == 'requestId' ? node.attributes['value'].to_i : request_id
|
69
53
|
end
|
70
54
|
end
|
@@ -79,26 +63,28 @@ module GoogleApps
|
|
79
63
|
# export_status will return the body of the HTTP response
|
80
64
|
# from Google
|
81
65
|
def export_status(username, req_id)
|
82
|
-
get(
|
66
|
+
response = get(export + "/#{username}", req_id)
|
67
|
+
process_response(response)
|
68
|
+
create_doc(response.body, :export_status)
|
83
69
|
end
|
84
70
|
|
71
|
+
def create_doc(response_body, type = nil)
|
72
|
+
@doc_handler.create_doc(response_body, type)
|
73
|
+
end
|
85
74
|
|
86
75
|
# export_ready? checks the export_status response for the
|
87
76
|
# presence of an apps:property element with a fileUrl name
|
88
77
|
# attribute.
|
89
78
|
#
|
90
|
-
# export_ready?
|
79
|
+
# export_ready?(export_status('username', 847576))
|
91
80
|
#
|
92
81
|
# export_ready? returns true if there is a fileUrl present
|
93
82
|
# in the response and false if there is no fileUrl present
|
94
83
|
# in the response.
|
95
|
-
def export_ready?(
|
96
|
-
|
97
|
-
|
98
|
-
!(export_file_urls.empty?)
|
84
|
+
def export_ready?(export_status_doc)
|
85
|
+
export_file_urls(export_status_doc).any?
|
99
86
|
end
|
100
87
|
|
101
|
-
|
102
88
|
# fetch_export downloads the mailbox export from Google.
|
103
89
|
# It takes a username, request id and a filename as
|
104
90
|
# arguments. If the export consists of more than one file
|
@@ -110,8 +96,9 @@ module GoogleApps
|
|
110
96
|
# fetch_export reutrns nil in the event that the export is
|
111
97
|
# not yet ready.
|
112
98
|
def fetch_export(username, req_id, filename)
|
113
|
-
|
114
|
-
|
99
|
+
export_status_doc = export_status(username, req_id)
|
100
|
+
if export_ready?(export_status_doc)
|
101
|
+
download_export(export_status_doc, filename).each_with_index { |url, index| url.gsub!(/.*/, "#{filename}#{index}")}
|
115
102
|
else
|
116
103
|
nil
|
117
104
|
end
|
@@ -123,10 +110,10 @@ module GoogleApps
|
|
123
110
|
#
|
124
111
|
# download 'url', 'save_file'
|
125
112
|
def download(url, filename)
|
126
|
-
|
113
|
+
request = requester.new :get, URI(url), headers(:other)
|
127
114
|
|
128
115
|
File.open(filename, "w") do |file|
|
129
|
-
file.puts
|
116
|
+
file.puts request.send_request.body
|
130
117
|
end
|
131
118
|
end
|
132
119
|
|
@@ -139,13 +126,11 @@ module GoogleApps
|
|
139
126
|
# get 'endpoint', 'username'
|
140
127
|
#
|
141
128
|
# get returns the HTTP response received from Google.
|
142
|
-
def get(endpoint,
|
129
|
+
def get(endpoint, id = nil)
|
143
130
|
id ? uri = URI(endpoint + build_id(id)) : uri = URI(endpoint)
|
144
|
-
|
145
|
-
|
146
|
-
@response = @request.send_request
|
131
|
+
request = requester.new :get, uri, headers(:other)
|
147
132
|
|
148
|
-
|
133
|
+
request.send_request
|
149
134
|
end
|
150
135
|
|
151
136
|
|
@@ -158,9 +143,14 @@ module GoogleApps
|
|
158
143
|
#
|
159
144
|
# get_users returns the final response from google.
|
160
145
|
def get_users(options = {})
|
161
|
-
|
162
|
-
|
146
|
+
limit = options[:limit] || 1000000
|
147
|
+
response = get(user + "?startUsername=#{options[:start]}")
|
148
|
+
process_response(response)
|
149
|
+
|
150
|
+
pages = fetch_pages(response, limit, :feed)
|
163
151
|
|
152
|
+
return_all(pages)
|
153
|
+
end
|
164
154
|
|
165
155
|
# get_groups retrieves all the groups from the domain
|
166
156
|
#
|
@@ -168,31 +158,14 @@ module GoogleApps
|
|
168
158
|
#
|
169
159
|
# get_groups returns the final response from Google.
|
170
160
|
def get_groups(options = {})
|
171
|
-
|
172
|
-
|
161
|
+
limit = options[:limit] || 1000000
|
162
|
+
response = get(group + "#{options[:extra]}" + "?startGroup=#{options[:start]}")
|
163
|
+
process_response(response, :feed)
|
164
|
+
pages = fetch_pages(response, limit, :feed)
|
173
165
|
|
174
|
-
|
175
|
-
# get_all retrieves a batch of records of the specified type
|
176
|
-
# from google. You must specify the type of object you want
|
177
|
-
# to retreive. You can also specify a start point and a limit.
|
178
|
-
#
|
179
|
-
# get_all 'users', start: 'lholcomb2', limit: 300
|
180
|
-
#
|
181
|
-
# get_all returns the HTTP response received from Google.
|
182
|
-
def get_all(type, options = {})
|
183
|
-
@feeds, page = [], 0
|
184
|
-
type = normalize_type type
|
185
|
-
|
186
|
-
options[:limit] ? limit = options[:limit] : limit = 1000000
|
187
|
-
options[:start] ? get(instance_variable_get("@#{type}") + "#{options[:extra]}" + "?#{start_query(type)}=#{options[:start]}", :feed) : get(instance_variable_get("@#{type}") + "#{options[:extra]}", :feed)
|
188
|
-
|
189
|
-
fetch_feed(page, limit, :feed)
|
190
|
-
|
191
|
-
#@response
|
192
|
-
return_all
|
166
|
+
return_all(pages)
|
193
167
|
end
|
194
168
|
|
195
|
-
|
196
169
|
# Retrieves the members of the requested group.
|
197
170
|
#
|
198
171
|
# @param [String] group_id the Group ID in the Google Apps Environment
|
@@ -201,7 +174,7 @@ module GoogleApps
|
|
201
174
|
# @return
|
202
175
|
def get_members_of(group_id, options = {})
|
203
176
|
options[:extra] = "/#{group_id}/member"
|
204
|
-
|
177
|
+
get_groups options
|
205
178
|
end
|
206
179
|
|
207
180
|
|
@@ -216,7 +189,9 @@ module GoogleApps
|
|
216
189
|
#
|
217
190
|
# add_member_to returns the response received from Google.
|
218
191
|
def add_member_to(group_id, document)
|
219
|
-
add(
|
192
|
+
response = add(group + "/#{group_id}/member", document)
|
193
|
+
process_response(response)
|
194
|
+
create_doc(response.body)
|
220
195
|
end
|
221
196
|
|
222
197
|
|
@@ -227,7 +202,7 @@ module GoogleApps
|
|
227
202
|
# @visibility public
|
228
203
|
# @return
|
229
204
|
def add_owner_to(group_id, document)
|
230
|
-
add(
|
205
|
+
add(group + "/#{group_id}/owner", nil, document)
|
231
206
|
end
|
232
207
|
|
233
208
|
# TODO: Refactor delete froms.
|
@@ -239,18 +214,16 @@ module GoogleApps
|
|
239
214
|
#
|
240
215
|
# delete_member_from returns the respnse received from Google.
|
241
216
|
def delete_member_from(group_id, member_id)
|
242
|
-
delete(
|
217
|
+
delete(group + "/#{group_id}/member", member_id)
|
243
218
|
end
|
244
219
|
|
245
|
-
|
246
|
-
#
|
247
220
|
# @param [String] group_id Email address of group
|
248
221
|
# @param [String] owner_id Email address of owner to remove
|
249
222
|
#
|
250
223
|
# @visibility public
|
251
224
|
# @return
|
252
225
|
def delete_owner_from(group_id, owner_id)
|
253
|
-
delete(
|
226
|
+
delete(group + "/#{group_id}/owner", owner_id)
|
254
227
|
end
|
255
228
|
|
256
229
|
|
@@ -273,15 +246,13 @@ module GoogleApps
|
|
273
246
|
# add 'endpoint', document
|
274
247
|
#
|
275
248
|
# add returns the HTTP response received from Google.
|
276
|
-
def add(endpoint,
|
249
|
+
def add(endpoint, document, header_type = nil)
|
277
250
|
header_type = :others unless header_type
|
278
251
|
uri = URI(endpoint)
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
@response = @request.send_request
|
252
|
+
request = requester.new :post, uri, headers(header_type)
|
253
|
+
request.add_body document.to_s
|
283
254
|
|
284
|
-
|
255
|
+
request.send_request
|
285
256
|
end
|
286
257
|
|
287
258
|
# update is a generic target for method_missing. It is
|
@@ -290,17 +261,15 @@ module GoogleApps
|
|
290
261
|
# It takes an API endpoint and a GoogleApps::Atom document
|
291
262
|
# as arguments.
|
292
263
|
#
|
293
|
-
# update 'endpoint', document
|
264
|
+
# update 'endpoint', target, document
|
294
265
|
#
|
295
266
|
# update returns the HTTP response received from Google
|
296
|
-
def update(endpoint,
|
267
|
+
def update(endpoint, target, document)
|
297
268
|
uri = URI(endpoint + "/#{target}")
|
298
|
-
|
299
|
-
|
269
|
+
request = requester.new :put, uri, headers(:other)
|
270
|
+
request.add_body document.to_s
|
300
271
|
|
301
|
-
|
302
|
-
|
303
|
-
process_response type
|
272
|
+
request.send_request
|
304
273
|
end
|
305
274
|
|
306
275
|
# delete is a generic target for method_missing. It is
|
@@ -313,9 +282,9 @@ module GoogleApps
|
|
313
282
|
# delete returns the HTTP response received from Google.
|
314
283
|
def delete(endpoint, id)
|
315
284
|
uri = URI(endpoint + "/#{id}")
|
316
|
-
|
285
|
+
request = requester.new :delete, uri, headers(:other)
|
317
286
|
|
318
|
-
|
287
|
+
request.send_request
|
319
288
|
end
|
320
289
|
|
321
290
|
# migration performs mail migration from a local
|
@@ -327,47 +296,39 @@ module GoogleApps
|
|
327
296
|
#
|
328
297
|
# migrate returns the HTTP response received from Google.
|
329
298
|
def migrate(username, properties, message)
|
330
|
-
|
331
|
-
|
299
|
+
request = requester.new(:post, URI(migration + "/#{username}/mail"), headers(:migration))
|
300
|
+
request.add_body multi_part(properties.to_s, message)
|
332
301
|
|
333
|
-
|
302
|
+
request.send_request
|
334
303
|
end
|
335
304
|
|
336
|
-
|
337
|
-
|
338
305
|
def method_missing(name, *args)
|
339
306
|
super unless name.match /([a-z]*)_([a-z]*)/
|
340
307
|
|
341
308
|
case $1
|
342
309
|
when "new", "add"
|
343
|
-
self.send(:add,
|
310
|
+
response = self.send(:add, send($2), *args)
|
311
|
+
process_response(response)
|
312
|
+
create_doc(response.body, $2)
|
344
313
|
when "delete"
|
345
|
-
self.send(:delete,
|
314
|
+
response = self.send(:delete, send($2), *args)
|
315
|
+
process_response(response)
|
316
|
+
create_doc(response.body, $2)
|
346
317
|
when "update"
|
347
|
-
self.send(:update,
|
318
|
+
response = self.send(:update, send($2), *args)
|
319
|
+
process_response(response)
|
320
|
+
create_doc(response.body, $2)
|
348
321
|
when "get"
|
349
|
-
self.send(:get,
|
322
|
+
response = self.send(:get, send($2), *args)
|
323
|
+
process_response(response)
|
324
|
+
create_doc(response.body, $2)
|
350
325
|
else
|
351
326
|
super
|
352
327
|
end
|
353
328
|
end
|
354
329
|
|
355
|
-
|
356
330
|
private
|
357
331
|
|
358
|
-
|
359
|
-
# auth_body generates the body for the authentication
|
360
|
-
# request made by authenticate.
|
361
|
-
#
|
362
|
-
# auth_body 'username@domain', 'password'
|
363
|
-
#
|
364
|
-
# auth_body returns a string in the form of HTTP
|
365
|
-
# query parameters.
|
366
|
-
def auth_body(account, pass)
|
367
|
-
"&Email=#{CGI::escape(account)}&Passwd=#{CGI::escape(pass)}&accountType=HOSTED&service=apps"
|
368
|
-
end
|
369
|
-
|
370
|
-
|
371
332
|
# build_id checks the id string. If it is formatted
|
372
333
|
# as a query string it is returned as is. If not
|
373
334
|
# a / is prepended to the id string.
|
@@ -375,19 +336,16 @@ module GoogleApps
|
|
375
336
|
id =~ /^\?/ ? id : "/#{id}"
|
376
337
|
end
|
377
338
|
|
378
|
-
|
379
|
-
# export_file_urls searches @response for any apps:property elements with a
|
339
|
+
# export_file_urls searches an export status doc for any apps:property elements with a
|
380
340
|
# fileUrl name attribute and returns an array of the values.
|
381
|
-
def export_file_urls
|
382
|
-
|
383
|
-
|
384
|
-
urls
|
341
|
+
def export_file_urls(export_status_doc)
|
342
|
+
export_status_doc.find("//apps:property[contains(@name, 'fileUrl')]").collect do |prop|
|
343
|
+
prop.attributes['value']
|
385
344
|
end
|
386
345
|
end
|
387
346
|
|
388
|
-
|
389
|
-
|
390
|
-
export_file_urls.each_with_index do |url, index|
|
347
|
+
def download_export(export, filename)
|
348
|
+
export_file_urls(export).each_with_index do |url, index|
|
391
349
|
download(url, filename + "#{index}")
|
392
350
|
end
|
393
351
|
end
|
@@ -396,100 +354,58 @@ module GoogleApps
|
|
396
354
|
# process_response takes the HTTPResponse and either returns a
|
397
355
|
# document of the specified type or in the event of an error it
|
398
356
|
# returns the HTTPResponse.
|
399
|
-
def process_response(
|
400
|
-
|
401
|
-
when nil
|
402
|
-
success_response? ? true : raise("Error: #{response.code}, #{response.message}")
|
403
|
-
else
|
404
|
-
success_response? ? @doc_handler.create_doc(@response.body, doc_type) : raise("Error: #{response.code}, #{response.message}")
|
405
|
-
end
|
357
|
+
def process_response(response)
|
358
|
+
raise("Error: #{response.code}, #{response.message}") unless success_response?(response)
|
406
359
|
end
|
407
360
|
|
408
|
-
|
409
|
-
|
410
|
-
# code.
|
411
|
-
def success_response?
|
412
|
-
SUCCESS_CODES.include?(@response.code.to_i)
|
361
|
+
def success_response?(response)
|
362
|
+
response.kind_of?(Net::HTTPSuccess)
|
413
363
|
end
|
414
364
|
|
415
|
-
|
416
|
-
#
|
417
|
-
|
418
365
|
# Takes all the items in each feed and puts them into one array.
|
419
366
|
#
|
420
367
|
# @visibility private
|
421
368
|
# @return Array of Documents
|
422
|
-
def return_all
|
423
|
-
|
369
|
+
def return_all(pages)
|
370
|
+
pages.inject([]) do |results, feed|
|
424
371
|
results | feed.items
|
425
372
|
end
|
426
373
|
end
|
427
374
|
|
428
|
-
|
429
|
-
# Grab the auth token from the response body
|
430
|
-
def set_auth_token
|
431
|
-
@response.body.split("\n").grep(/auth=(.*)/i)
|
432
|
-
|
433
|
-
@token = $1
|
434
|
-
end
|
435
|
-
|
436
|
-
|
437
375
|
# get_next_page retrieves the next page in the response.
|
438
|
-
def get_next_page(type)
|
439
|
-
|
440
|
-
|
376
|
+
def get_next_page(next_page_url, type)
|
377
|
+
response = get(next_page_url)
|
378
|
+
process_response(response)
|
379
|
+
GoogleApps::Atom.feed(response.body)
|
441
380
|
end
|
442
381
|
|
443
382
|
|
444
383
|
# fetch_feed retrieves the remaining pages in the request.
|
445
384
|
# It takes a page and a limit as arguments.
|
446
|
-
def
|
447
|
-
|
448
|
-
page += 1
|
385
|
+
def fetch_pages(response, limit, type)
|
386
|
+
pages = [GoogleApps::Atom.feed(response.body)]
|
449
387
|
|
450
|
-
while (
|
451
|
-
get_next_page type
|
452
|
-
page += 1
|
388
|
+
while (pages.last.next_page) and (pages.count * PAGE_SIZE[:user] < limit)
|
389
|
+
pages << get_next_page(pages.last.next_page, type)
|
453
390
|
end
|
391
|
+
pages
|
454
392
|
end
|
455
393
|
|
456
|
-
|
457
|
-
|
458
|
-
# query string used for retrieving batches of objects
|
459
|
-
# from Google.
|
460
|
-
def start_query(type)
|
461
|
-
case type
|
462
|
-
when 'user'
|
463
|
-
"startUsername"
|
464
|
-
when 'group'
|
465
|
-
"startGroup"
|
466
|
-
end
|
394
|
+
def singularize(type)
|
395
|
+
type.to_s.gsub(/s$/, '')
|
467
396
|
end
|
468
397
|
|
469
|
-
|
470
|
-
def normalize_type(type)
|
471
|
-
type.to_s.gsub!(/\w*s$/) { |match| match[0..-2] }
|
472
|
-
end
|
473
|
-
|
474
|
-
|
475
|
-
# add_feed adds a feed to the @feeds array.
|
476
|
-
def add_feed
|
477
|
-
@feeds << GoogleApps::Atom.feed(@response.body)
|
478
|
-
end
|
479
|
-
|
480
|
-
|
481
398
|
def headers(category)
|
482
399
|
case category
|
483
400
|
when :auth
|
484
401
|
[['content-type', 'application/x-www-form-urlencoded']]
|
485
402
|
when :migration
|
486
|
-
[['content-type', "multipart/related; boundary=\"#{BOUNDARY}\""], ['
|
403
|
+
[['content-type', "multipart/related; boundary=\"#{BOUNDARY}\""], ['Authorization', "OAuth #{@token}"]]
|
487
404
|
else
|
488
|
-
[['content-type', 'application/atom+xml'], ['
|
405
|
+
[['content-type', 'application/atom+xml'], ['Authorization', "OAuth #{@token}"]]
|
489
406
|
end
|
490
407
|
end
|
491
408
|
|
492
|
-
|
493
409
|
def multi_part(properties, message)
|
494
410
|
post_body = []
|
495
411
|
post_body << "--#{BOUNDARY}\n"
|