google_apps 0.5 → 0.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.
- 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"
|