google_apps 0.3.0

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.
@@ -0,0 +1,8 @@
1
+ require 'google_apps/transport'
2
+ require 'google_apps/atom/atom'
3
+ require 'google_apps/atom/user'
4
+ require 'google_apps/atom/group'
5
+ require 'google_apps/atom/public_key'
6
+ require 'google_apps/atom/export'
7
+ require 'google_apps/atom/message'
8
+ require 'google_apps/atom/message_attributes'
@@ -0,0 +1,10 @@
1
+ require 'libxml'
2
+ require 'digest/sha1'
3
+ require 'base64'
4
+
5
+ module GoogleApps
6
+ module Atom
7
+ include LibXML
8
+ HASH_FUNCTION = "SHA-1"
9
+ end
10
+ end
@@ -0,0 +1,52 @@
1
+ module GoogleApps
2
+ module Atom
3
+ class Export
4
+ def initialize
5
+ @document = Atom::XML::Document.new
6
+ set_header
7
+ end
8
+
9
+ def to_s
10
+ @document.to_s
11
+ end
12
+
13
+ def start_date(date)
14
+ add_prop('beginDate', date)
15
+ end
16
+
17
+ def end_date(date)
18
+ add_prop('endDate', date)
19
+ end
20
+
21
+ def include_deleted
22
+ add_prop('includeDeleted', 'true')
23
+ end
24
+
25
+ def query(query_string)
26
+ add_prop('searchQuery', query_string)
27
+ end
28
+
29
+ def content(type)
30
+ add_prop('packageContent', type)
31
+ end
32
+
33
+
34
+ private
35
+
36
+ def set_header
37
+ @document.root = Atom::XML::Node.new 'atom:entry'
38
+
39
+ Atom::XML::Namespace.new(@document.root, 'atom', 'http://www.w3.org/2005/Atom')
40
+ Atom::XML::Namespace.new(@document.root, 'apps', 'http://schemas.google.com/apps/2006')
41
+ end
42
+
43
+ def add_prop(name, value)
44
+ prop = Atom::XML::Node.new('apps:property')
45
+ prop['name'] = name
46
+ prop['value'] = value
47
+
48
+ @document.root << prop
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ module GoogleApps
2
+ module Atom
3
+ class Group
4
+ def initialize
5
+ @document = Atom::XML::Document.new
6
+ add_header
7
+ end
8
+
9
+ def new_group(group_data)
10
+ group_data.keys.each do |key|
11
+ prop = Atom::XML::Node.new('apps:property')
12
+ prop_name(prop, key)
13
+ prop.attributes['value'] = group_data[key]
14
+ @document.root << prop
15
+ end
16
+ end
17
+
18
+ def to_s
19
+ @document.to_s
20
+ end
21
+
22
+ private
23
+
24
+ def add_header
25
+ @document.root = Atom::XML::Node.new('atom:entry')
26
+
27
+ Atom::XML::Namespace.new(@document.root, 'atom', 'http://www.w3.org/2005/Atom')
28
+ Atom::XML::Namespace.new(@document.root, 'apps', 'http://schemas.google.com/apps/2006')
29
+ Atom::XML::Namespace.new(@document.root, 'gd', 'http://schemas.google.com/g/2005')
30
+ end
31
+
32
+ def prop_name(property, key)
33
+ case key
34
+ when :id
35
+ property.attributes['name'] = 'groupId'
36
+ when :name
37
+ property.attributes['name'] = 'groupName'
38
+ when :description
39
+ property.attributes['name'] = 'description'
40
+ when :perms
41
+ property.attributes['name'] = 'emailPermission'
42
+ end
43
+
44
+ property
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,21 @@
1
+ module GoogleApps
2
+ module Atom
3
+ class Message
4
+ def initialize
5
+ @document = Atom::XML::Document.new
6
+ end
7
+
8
+ def from(filename)
9
+ message = File.read(filename)
10
+ @document.root = Atom::XML::Node.new('apps:rfc822Msg', message)
11
+ Atom::XML::Namespace.new(@document.root, 'apps', 'http://schemas.google.com/apps/2006')
12
+
13
+ @document
14
+ end
15
+
16
+ def to_s
17
+ @document.to_s
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ module GoogleApps
2
+ module Atom
3
+ class MessageAttributes
4
+ def initialize
5
+ @document = Atom::XML::Document.new
6
+ set_header
7
+ end
8
+
9
+ def add_property(prop)
10
+ property = Atom::XML::Node.new 'apps:mailItemProperty'
11
+ property['value'] = prop
12
+
13
+ @document.root << property
14
+ end
15
+
16
+ def add_label(name)
17
+ label = Atom::XML::Node.new 'apps:label'
18
+ label['labelName'] = name
19
+
20
+ @document.root << label
21
+ end
22
+
23
+ def to_s
24
+ @document.to_s
25
+ end
26
+
27
+ private
28
+
29
+ def set_header
30
+ @document.root = Atom::XML::Node.new 'atom:entry' # API Docs show just entry here
31
+
32
+ Atom::XML::Namespace.new(@document.root, 'atom', 'http://www.w3.org/2005/Atom') # API Docs show this as just xmlns
33
+ Atom::XML::Namespace.new(@document.root, 'apps', 'http://schemas.google.com/apps/2006')
34
+
35
+ @document.root << category
36
+ @document.root << content
37
+ end
38
+
39
+ def category
40
+ header = Atom::XML::Node.new 'category'
41
+ header['scheme'] = 'http://schemas.google.com/g/2005#kind'
42
+ header['term'] = 'http://schemas.google.com/apps/2006#mailItem'
43
+
44
+ header
45
+ end
46
+
47
+ def content
48
+ header = Atom::XML::Node.new 'atom:content'
49
+ Atom::XML::Namespace.new(header, 'atom', 'http://www.w3.org/2005/Atom')
50
+ header['type'] = 'message/rfc822'
51
+
52
+ header
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,31 @@
1
+ module GoogleApps
2
+ module Atom
3
+ class PublicKey
4
+ def initialize
5
+ @document = Atom::XML::Document.new
6
+ add_header
7
+ end
8
+
9
+ def new_key(key)
10
+ property = Atom::XML::Node.new('apps:property')
11
+ property['name'] = 'publicKey'
12
+ property['value'] = Base64.encode64 key
13
+
14
+ @document.root << property
15
+ end
16
+
17
+ def to_s
18
+ @document.to_s
19
+ end
20
+
21
+ private
22
+
23
+ def add_header
24
+ @document.root = Atom::XML::Node.new('atom:entry')
25
+
26
+ Atom::XML::Namespace.new(@document.root, 'atom', 'http://www.w3.org/2005/Atom')
27
+ Atom::XML::Namespace.new(@document.root, 'apps', 'http://schemas.google.com/apps/2006')
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,70 @@
1
+ module GoogleApps
2
+ module Atom
3
+ class User
4
+ attr_reader :document
5
+
6
+ def initialize
7
+ @document = Atom::XML::Document.new
8
+ add_header
9
+ end
10
+
11
+ def new_user(user_name, first, last, password, quota)
12
+ new_doc
13
+ add_header
14
+ @document.root << login_node(user_name, password)
15
+ @document.root << quota_node(quota)
16
+ @document.root << name_node(first, last)
17
+
18
+ @document
19
+ end
20
+
21
+ def new_doc
22
+ @document = Atom::XML::Document.new
23
+ end
24
+
25
+ def login_node(user_name, password)
26
+ login = Atom::XML::Node.new('apps:login')
27
+ login['userName'] = user_name
28
+ login['password'] = Digest::SHA1.hexdigest password
29
+ login['hashFunctionName'] = Atom::HASH_FUNCTION
30
+ login['suspended'] = "false"
31
+
32
+ login
33
+ end
34
+
35
+ def quota_node(limit)
36
+ quota = Atom::XML::Node.new('apps:quota')
37
+ quota['limit'] = limit.to_s
38
+
39
+ quota
40
+ end
41
+
42
+ def name_node(first, last)
43
+ name = Atom::XML::Node.new('apps:name')
44
+ name['familyName'] = last
45
+ name['givenName'] = first
46
+
47
+ name
48
+ end
49
+
50
+ def to_s
51
+ @document.to_s
52
+ end
53
+
54
+ private
55
+
56
+ def add_header
57
+ @document.root = Atom::XML::Node.new('atom:entry')
58
+
59
+ Atom::XML::Namespace.new(@document.root, 'atom', 'http://www.w3.org/2005/Atom')
60
+ Atom::XML::Namespace.new(@document.root, 'apps', 'http://schemas.google.com/apps/2006')
61
+
62
+ category = Atom::XML::Node.new('atom:category')
63
+ category.attributes['scheme'] = 'http://schemas.google.com/g/2005#kind'
64
+ category.attributes['term'] = 'http://schemas.google.com/apps/2006#user'
65
+
66
+ @document.root << category
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,235 @@
1
+ require 'net/http'
2
+ require 'cgi'
3
+ require 'openssl'
4
+ require 'rexml/document'
5
+
6
+ module GoogleApps
7
+ class Transport
8
+ attr_reader :request, :response
9
+ attr_accessor :auth, :user, :group, :nickname
10
+
11
+ BOUNDARY = "=AaB03xDFHT8xgg"
12
+
13
+ def initialize(domain, targets = {})
14
+ @auth = targets[:auth] || "https://www.google.com/accounts/ClientLogin"
15
+ @user = targets[:user] || "https://apps-apis.google.com/a/feeds/#{domain}/user/2.0"
16
+ @pubkey = targets[:pubkey] || "https://apps-apis.google.com/a/feeds/compliance/audit/publickey/#{domain}"
17
+ @migration = targets[:migration] || "https://apps-apis.google.com/a/feeds/migration/2.0/#{domain}"
18
+ @group = targets[:group]
19
+ @nickname = targets[:nickname]
20
+ @export = targets[:export] || "https://apps-apis.google.com/a/feeds/compliance/audit/mail/export/#{domain}"
21
+ @token = nil
22
+ @response = nil
23
+ @request = nil
24
+ end
25
+
26
+
27
+ # authenticate will take the provided account and
28
+ # password and attempt to authenticate them with
29
+ # Google
30
+ #
31
+ # authenticate 'username@domain', 'password'
32
+ #
33
+ # authenticate returns the HTTP response received
34
+ # from Google
35
+ def authenticate(account, pass)
36
+ uri = URI(@auth)
37
+ @request = Net::HTTP::Post.new(uri.path)
38
+ @request.body = auth_body(account, pass)
39
+ set_headers :auth
40
+
41
+ @response = request(uri)
42
+ @response.body.split("\n").grep(/auth=(.*)/i)
43
+
44
+ @token = $1
45
+
46
+ @response
47
+ end
48
+
49
+ # request_export performs the GoogleApps API call to
50
+ # generate a mailbox export. It takes the username
51
+ # and an GoogleApps::Atom::Export instance as
52
+ # arguments
53
+ #
54
+ # request_export 'username', document
55
+ #
56
+ # request_export returns the HTTP response received
57
+ # from Google.
58
+ def request_export(username, document)
59
+ uri = URI(@export + "/#{username}")
60
+ @request = Net::HTTP::Post.new uri.path
61
+ @request.body = document
62
+ set_headers :user
63
+
64
+ @response = request(uri)
65
+ end
66
+
67
+ # export_status checks the status of a mailbox export
68
+ # request. It takes the username and the request_id
69
+ # as arguments
70
+ #
71
+ # export_status 'username', 847576
72
+ #
73
+ # export_status will return the body of the HTTP
74
+ # response from Google
75
+ def export_status(username, req_id)
76
+ uri = URI(@export + "/#{username}/#{req_id}")
77
+ @request = Net::HTTP::Get.new uri.path
78
+ set_headers :user
79
+
80
+ # TODO: Return actual status not whole body.
81
+ (@response = request(uri)).body
82
+ end
83
+
84
+ def fetch_export(flename) # :nodoc:
85
+ # TODO: Shouldn't rely on export_status being run first. Self, this is lazy and stupid.
86
+ doc = REXML::Document.new(@response.body)
87
+ urls = []
88
+ doc.elements.each('entry/apps:property') do |property|
89
+ urls << property.attributes['value'] if property.attributes['name'].match 'fileUrl'
90
+ end
91
+
92
+ urls.each do |url|
93
+ download(url, filename)
94
+ end
95
+ end
96
+
97
+ # download makes a get request of the provided url
98
+ # and writes the body to the provided filename.
99
+ #
100
+ # download 'url', 'save_file'
101
+ def download(url, filename)
102
+ uri = URI(url)
103
+ @request = Net::HTTP::Get.new uri.path
104
+ set_headers :user
105
+
106
+ File.new(filename, "w") do |file|
107
+ file.puts request(uri).body
108
+ end
109
+ end
110
+
111
+ # add is a generic target for method_missing. It is
112
+ # intended to handle the general case of adding
113
+ # to the GoogleApps Domain. It takes an API endpoint
114
+ # and a GoogleApps::Atom document as arguments.
115
+ #
116
+ # add 'endpoint', document
117
+ #
118
+ # add returns the HTTP response received from Google.
119
+ def add(endpoint, document)
120
+ uri = URI(instance_variable_get("@#{endpoint.to_s}"))
121
+ @request = Net::HTTP::Post.new(uri.path)
122
+ @request.body = document
123
+ set_headers :user
124
+
125
+ @response = request(uri)
126
+ end
127
+
128
+ # update is a generic target for method_missing. It is
129
+ # intended to handle the general case of updating an
130
+ # item that already exists in your GoogleApps Domain.
131
+ # It takes an API endpoint and a GoogleApps::Atom document
132
+ # as arguments.
133
+ #
134
+ # update 'endpoint', document
135
+ #
136
+ # update returns the HTTP response received from Google
137
+ def update(endpoint, document)
138
+ # TODO: Username needs to come from somewhere for uri
139
+ uri = URI(instance_variable_get("@#{endpoint.to_s}") + "/")
140
+ end
141
+
142
+ # delete is a generic target for method_missing. It is
143
+ # intended to handle the general case of deleting an
144
+ # item from your GoogleApps Domain. delete takes an
145
+ # API endpoint and an item identifier as argumets.
146
+ #
147
+ # delete 'endpoint', 'id'
148
+ #
149
+ # delete returns the HTTP response received from Google.
150
+ def delete(endpoint, id)
151
+ uri = URI(instance_variable_get("@#{endpoint.to_s}") + "/#{id}")
152
+ @request = Net::HTTP::Delete.new(uri.path)
153
+ set_headers :user
154
+
155
+ @response = request(uri)
156
+ end
157
+
158
+ # migration performs mail migration from a local
159
+ # mail environment to GoogleApps. migrate takes a
160
+ # username a GoogleApps::Atom::Properties dcoument
161
+ # and the message as plain text (String) as arguments.
162
+ #
163
+ # migrate 'user', properties, message
164
+ #
165
+ # migrate returns the HTTP response received from Google.
166
+ def migrate(username, properties, message)
167
+ uri = URI(@migration + "/#{username}/mail")
168
+ @request = Net::HTTP::Post.new(uri.path)
169
+ @request.body = multi_part(properties, message)
170
+ set_headers :migrate
171
+
172
+ @response = request(uri)
173
+ end
174
+
175
+ def method_missing(name, *args)
176
+ super unless name.match /([a-z]*)_([a-z]*)/
177
+
178
+ case $1
179
+ when "new"
180
+ self.send(:add, $2, *args)
181
+ when "delete"
182
+ self.send(:delete, $2, *args)
183
+ else
184
+ super
185
+ end
186
+ end
187
+
188
+
189
+ private
190
+
191
+ # auth_body generates the body for the authentication
192
+ # request made by authenticate.
193
+ #
194
+ # auth_body 'username@domain', 'password'
195
+ #
196
+ # auth_body returns a string in the form of HTTP
197
+ # query parameters.
198
+ def auth_body(account, pass)
199
+ "&Email=#{CGI::escape(account)}&Passwd=#{CGI::escape(pass)}&accountType=HOSTED&service=apps"
200
+ end
201
+
202
+ def request(uri)
203
+ # TODO: Clashes with @request reader
204
+ Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') do |http|
205
+ http.request(@request)
206
+ end
207
+ end
208
+
209
+ def set_headers(request_type)
210
+ case request_type
211
+ when :auth
212
+ @request['content-type'] = "application/x-www-form-urlencoded"
213
+ when :migrate
214
+ @request['content-type'] = "multipart/related; boundary=\"#{BOUNDARY}\""
215
+ @request['authorization'] = "GoogleLogin auth=#{@token}"
216
+ else
217
+ @request['content-type'] = "application/atom+xml"
218
+ @request['authorization'] = "GoogleLogin auth=#{@token}"
219
+ end
220
+ end
221
+
222
+ def multi_part(properties, message)
223
+ post_body = []
224
+ post_body << "--#{BOUNDARY}\n"
225
+ post_body << "Content-Type: application/atom+xml\n\n"
226
+ post_body << properties.to_s
227
+ post_body << "\n--#{BOUNDARY}\n"
228
+ post_body << "Content-Type: message/rfc822\n\n"
229
+ post_body << message.to_s
230
+ post_body << "--#{BOUNDARY}--}"
231
+
232
+ post_body.join
233
+ end
234
+ end
235
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: google_apps
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Glen Holcomb
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-05 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Library for interfacing with Google Apps' Domain and Application APIs
15
+ email:
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - !binary |-
21
+ bGliL2dvb2dsZV9hcHBzL2F0b20vYXRvbS5yYg==
22
+ - !binary |-
23
+ bGliL2dvb2dsZV9hcHBzL2F0b20vZXhwb3J0LnJi
24
+ - !binary |-
25
+ bGliL2dvb2dsZV9hcHBzL2F0b20vZ3JvdXAucmI=
26
+ - !binary |-
27
+ bGliL2dvb2dsZV9hcHBzL2F0b20vbWVzc2FnZS5yYg==
28
+ - !binary |-
29
+ bGliL2dvb2dsZV9hcHBzL2F0b20vbWVzc2FnZV9hdHRyaWJ1dGVzLnJi
30
+ - !binary |-
31
+ bGliL2dvb2dsZV9hcHBzL2F0b20vcHVibGljX2tleS5yYg==
32
+ - !binary |-
33
+ bGliL2dvb2dsZV9hcHBzL2F0b20vdXNlci5yYg==
34
+ - !binary |-
35
+ bGliL2dvb2dsZV9hcHBzL3RyYW5zcG9ydC5yYg==
36
+ - !binary |-
37
+ bGliL2dvb2dsZV9hcHBzLnJi
38
+ homepage: https://github.com/LeakyBucket/google_apps
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 1.8.10
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Google Apps APIs
62
+ test_files: []