google_apps 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []