gprov 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +14 -0
- data/README.markdown +7 -0
- data/Rakefile +5 -0
- data/lib/gprov.rb +4 -0
- data/lib/gprov/auth.rb +1 -0
- data/lib/gprov/auth/clientlogin.rb +65 -0
- data/lib/gprov/connection.rb +88 -0
- data/lib/gprov/error.rb +27 -0
- data/lib/gprov/provision.rb +9 -0
- data/lib/gprov/provision/customerid.rb +44 -0
- data/lib/gprov/provision/entrybase.rb +82 -0
- data/lib/gprov/provision/entrybase/classmethods.rb +52 -0
- data/lib/gprov/provision/entrybase/xmlattr.rb +92 -0
- data/lib/gprov/provision/feed.rb +44 -0
- data/lib/gprov/provision/group.rb +147 -0
- data/lib/gprov/provision/member.rb +48 -0
- data/lib/gprov/provision/orgmember.rb +84 -0
- data/lib/gprov/provision/orgunit.rb +89 -0
- data/lib/gprov/provision/owner.rb +46 -0
- data/lib/gprov/provision/user.rb +132 -0
- data/lib/gprov/version.rb +3 -0
- data/spec/gdata/auth/clientlogin_spec.rb +47 -0
- data/spec/gdata/connection_spec.rb +62 -0
- data/spec/gdata/provision/entrybase_spec.rb +39 -0
- data/spec/spec_helper.rb +10 -0
- metadata +118 -0
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright 2011 Puppet Labs
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
14
|
+
|
data/README.markdown
ADDED
data/Rakefile
ADDED
data/lib/gprov.rb
ADDED
data/lib/gprov/auth.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'gprov/auth/clientlogin'
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# = gprov/auth/clientlogin.rb Implements the google clientLogin authentication method
|
2
|
+
#
|
3
|
+
# == Overview
|
4
|
+
#
|
5
|
+
# Implements the Google clientLogin method as documented in
|
6
|
+
# http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html
|
7
|
+
#
|
8
|
+
# == Authors
|
9
|
+
#
|
10
|
+
# Adrien Thebo
|
11
|
+
#
|
12
|
+
# == Copyright
|
13
|
+
#
|
14
|
+
# 2011 Puppet Labs
|
15
|
+
#
|
16
|
+
require 'httparty'
|
17
|
+
module GProv
|
18
|
+
module Auth
|
19
|
+
class ClientLogin
|
20
|
+
include HTTParty
|
21
|
+
base_uri 'https://www.google.com/accounts/ClientLogin'
|
22
|
+
|
23
|
+
# Instantiates a new ClientLogin object.
|
24
|
+
#
|
25
|
+
# Arguments:
|
26
|
+
# * email: the email account to use for authentication
|
27
|
+
# * password
|
28
|
+
# * service: the Google service to generate authentication for
|
29
|
+
# * options: An additional hash of parameters
|
30
|
+
# * debug: turns on debug information for the request/response
|
31
|
+
#
|
32
|
+
# TODO make service an optional field
|
33
|
+
def initialize(email, password, service, options={})
|
34
|
+
@email = email
|
35
|
+
@password = password
|
36
|
+
@service = service
|
37
|
+
@options = options
|
38
|
+
|
39
|
+
# additional options parsing
|
40
|
+
if @options[:debug]
|
41
|
+
self.class.debug_output $stderr
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Given an instantiated ClientLogin object, performs the actual
|
46
|
+
# request/response handling of the authentication information.
|
47
|
+
#
|
48
|
+
# TODO More comprehensive error checking for this.
|
49
|
+
# TODO CAPTCHA handling
|
50
|
+
def token
|
51
|
+
form_data = {
|
52
|
+
"accountType" => "HOSTED",
|
53
|
+
"Email" => @email,
|
54
|
+
"Passwd" => @password,
|
55
|
+
"service" => @service,
|
56
|
+
}
|
57
|
+
|
58
|
+
response = self.class.post('', {:body => form_data})
|
59
|
+
if response.code == 200 and response.body =~ /Auth=(.*)\n/
|
60
|
+
$1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# = gprov/connection.rb: common interface for the google apps API
|
2
|
+
#
|
3
|
+
# == Overview
|
4
|
+
#
|
5
|
+
# Provides a single point of access for making http requests against the google
|
6
|
+
# apps API.
|
7
|
+
#
|
8
|
+
# This adds the correct authorization header and content-type for make
|
9
|
+
# requests against the google API work correctly, so that calling classes
|
10
|
+
# don't need to handle authentication, formatting, or basic error handling.
|
11
|
+
#
|
12
|
+
# == Authors
|
13
|
+
#
|
14
|
+
# Adrien Thebo
|
15
|
+
#
|
16
|
+
# == Copyright
|
17
|
+
#
|
18
|
+
# 2011 Puppet Labs
|
19
|
+
#
|
20
|
+
require 'httparty'
|
21
|
+
require 'gprov/error'
|
22
|
+
|
23
|
+
module GProv
|
24
|
+
class Connection
|
25
|
+
include HTTParty
|
26
|
+
base_uri "https://apps-apis.google.com/a/feeds/"
|
27
|
+
|
28
|
+
attr_reader :domain
|
29
|
+
def initialize(domain, token, options = {})
|
30
|
+
@domain = domain
|
31
|
+
@token = token
|
32
|
+
@options = options
|
33
|
+
|
34
|
+
if @options[:debug]
|
35
|
+
self.class.debug_output $stderr
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_headers
|
40
|
+
{:headers => {
|
41
|
+
'Authorization' => "GoogleLogin auth=#{@token}",
|
42
|
+
'Content-Type' => 'application/atom+xml',
|
43
|
+
}}
|
44
|
+
end
|
45
|
+
|
46
|
+
# Forward instance level http methods to the class, after adding
|
47
|
+
# authorization and content-type information.
|
48
|
+
[:put, :get, :post, :delete].each do |verb|
|
49
|
+
define_method verb do |path, *args|
|
50
|
+
|
51
|
+
# Assign default arguments and validate passed arguments
|
52
|
+
if path.nil?
|
53
|
+
raise "#{self.class}##{verb} requires a non-nil path"
|
54
|
+
end
|
55
|
+
options = *args
|
56
|
+
options ||= {}
|
57
|
+
options.merge! default_headers
|
58
|
+
|
59
|
+
# Interpolate the :domain substring into a url to allow for the domain
|
60
|
+
# to be in an arbitary position of a request
|
61
|
+
path.gsub!(":domain", @domain)
|
62
|
+
|
63
|
+
if options[:noop] or @options[:noop]
|
64
|
+
$stderr.puts "Would have attempted the following call"
|
65
|
+
$stderr.puts "#{verb} #{path} #{options.inspect}"
|
66
|
+
else
|
67
|
+
# Return the request to the calling class so that the caller can
|
68
|
+
# determine the outcome of the request.
|
69
|
+
output = self.class.send(verb, path, options)
|
70
|
+
case output.code
|
71
|
+
when 401
|
72
|
+
raise GProv::Error::TokenInvalid.new(output)
|
73
|
+
when 403
|
74
|
+
raise GProv::Error::InputInvalid.new(output)
|
75
|
+
when 503
|
76
|
+
raise GProv::Error::QuotaExceeded.new(output)
|
77
|
+
else
|
78
|
+
if ! output.success?
|
79
|
+
raise GProv::Error.new(output)
|
80
|
+
else
|
81
|
+
output
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/gprov/error.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# = gprov/error.rb
|
2
|
+
#
|
3
|
+
# == Overview
|
4
|
+
#
|
5
|
+
# Common definition of possible gprov errors.
|
6
|
+
#
|
7
|
+
# == Authors
|
8
|
+
#
|
9
|
+
# Adrien Thebo
|
10
|
+
#
|
11
|
+
# == Copyright
|
12
|
+
#
|
13
|
+
# 2011 Puppet Labs
|
14
|
+
#
|
15
|
+
require 'gprov'
|
16
|
+
module GProv
|
17
|
+
class Error < Exception
|
18
|
+
attr_reader :request
|
19
|
+
|
20
|
+
def initialize(request = nil)
|
21
|
+
@request = request
|
22
|
+
end
|
23
|
+
class TokenInvalid < GProv::Error; end
|
24
|
+
class InputInvalid < GProv::Error; end
|
25
|
+
class QuotaExceeded < GProv::Error; end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# = gprov/customerid.rb
|
2
|
+
#
|
3
|
+
# == Overview
|
4
|
+
#
|
5
|
+
# Retrieves a customerid string for an accompanying domain
|
6
|
+
#
|
7
|
+
# == Authors
|
8
|
+
#
|
9
|
+
# Adrien Thebo
|
10
|
+
#
|
11
|
+
# == Copyright
|
12
|
+
#
|
13
|
+
# 2011 Puppet Labs
|
14
|
+
#
|
15
|
+
require 'gprov'
|
16
|
+
require 'gprov/provision/entrybase'
|
17
|
+
module GProv
|
18
|
+
module Provision
|
19
|
+
class CustomerID < GProv::Provision::EntryBase
|
20
|
+
|
21
|
+
xmlattr :customer_id, :xpath => %Q{apps:property[@name = "customerId"]/@value}
|
22
|
+
xmlattr :name, :xpath => %Q{apps:property[@name = "name"]/@value}
|
23
|
+
xmlattr :description, :xpath => %Q{apps:property[@name = "description"]/@value}
|
24
|
+
|
25
|
+
xmlattr :customer_org_unit_name do
|
26
|
+
xpath %Q{apps:property[@name = "customerOrgUnitName"]/@value}
|
27
|
+
end
|
28
|
+
|
29
|
+
xmlattr :customer_org_unit_description do
|
30
|
+
xpath %Q{apps:property[@name = "customerOrgUnitDescription"]/@value}
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.get(connection, options={})
|
34
|
+
response = connection.get("/customer/2.0/customerId")
|
35
|
+
|
36
|
+
document = Nokogiri::XML(response.body)
|
37
|
+
xml = document.root
|
38
|
+
|
39
|
+
new(:status => :clean, :connection => connection, :source => xml)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# = gprov/provision/entrybase.rb: base class for provisioning api objects
|
2
|
+
#
|
3
|
+
# == Overview
|
4
|
+
#
|
5
|
+
# Provides the top level constructs for mapping XML feeds to objects and back
|
6
|
+
#
|
7
|
+
# == Authors
|
8
|
+
#
|
9
|
+
# Adrien Thebo
|
10
|
+
#
|
11
|
+
# == Copyright
|
12
|
+
#
|
13
|
+
# 2011 Puppet Labs
|
14
|
+
#
|
15
|
+
require 'nokogiri'
|
16
|
+
require 'gprov/provision/entrybase/classmethods'
|
17
|
+
module GProv
|
18
|
+
module Provision
|
19
|
+
class EntryBase
|
20
|
+
|
21
|
+
extend GProv::Provision::EntryBase::ClassMethods
|
22
|
+
|
23
|
+
# Status with respect to google.
|
24
|
+
# TODO protected?
|
25
|
+
# values: :new, :clean, :dirty, :deleted
|
26
|
+
attr_reader :status
|
27
|
+
attr_reader :connection
|
28
|
+
|
29
|
+
# Instantiates a new entry object.
|
30
|
+
#
|
31
|
+
# Possible data sources:
|
32
|
+
# * Hash of attribute names and values
|
33
|
+
# * A nokogiri node containing the root of the object
|
34
|
+
def initialize(opts={})
|
35
|
+
|
36
|
+
@status = (opts[:status] || :new)
|
37
|
+
|
38
|
+
if opts[:connection]
|
39
|
+
@connection = opts[:connection]
|
40
|
+
else
|
41
|
+
raise ArgumentError, "#{self.class}.new requires a connection parameter"
|
42
|
+
end
|
43
|
+
|
44
|
+
case source = opts[:source]
|
45
|
+
when Hash
|
46
|
+
attributes_from_hash source
|
47
|
+
when Nokogiri::XML::Node
|
48
|
+
hash = xml_to_hash(source)
|
49
|
+
attributes_from_hash hash
|
50
|
+
when NilClass
|
51
|
+
# New object!
|
52
|
+
else
|
53
|
+
raise
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Takes all xml_attr_accessors defined and an xml document and
|
58
|
+
# extracts the values from the xml into a hash.
|
59
|
+
def xml_to_hash(xml)
|
60
|
+
h = {}
|
61
|
+
if attrs = self.class.attributes
|
62
|
+
attrs.inject(h) do |hash, attr|
|
63
|
+
hash[attr.name] = attr.parse(xml)
|
64
|
+
hash
|
65
|
+
end
|
66
|
+
end
|
67
|
+
h
|
68
|
+
end
|
69
|
+
|
70
|
+
# Maps hash key/value pairs to object attributes
|
71
|
+
def attributes_from_hash(hash)
|
72
|
+
hash.each_pair do |k, v|
|
73
|
+
if respond_to? "#{k}=".intern
|
74
|
+
send("#{k}=".intern, v)
|
75
|
+
else
|
76
|
+
raise ArgumentError, %Q{Received invalid attribute "#{k}"}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# = gprov/provision/entrybase/classmethods.rb
|
2
|
+
#
|
3
|
+
# == Overview
|
4
|
+
#
|
5
|
+
# Generates the DSL style behavior for entrybase and adds some convenience
|
6
|
+
# methods
|
7
|
+
#
|
8
|
+
# == Authors
|
9
|
+
#
|
10
|
+
# Adrien Thebo
|
11
|
+
#
|
12
|
+
# == Copyright
|
13
|
+
#
|
14
|
+
# 2011 Puppet Labs
|
15
|
+
#
|
16
|
+
require 'nokogiri'
|
17
|
+
require 'gprov/provision/entrybase/xmlattr'
|
18
|
+
module GProv
|
19
|
+
module Provision
|
20
|
+
class EntryBase
|
21
|
+
module ClassMethods
|
22
|
+
# Generates xmlattrs and encapsulates parsing logic
|
23
|
+
def xmlattr(name, options={}, &block)
|
24
|
+
attr = GProv::Provision::EntryBase::XMLAttr.new(name, options)
|
25
|
+
attr.instance_eval &block if block_given?
|
26
|
+
@attrs ||= []
|
27
|
+
@attrs << attr
|
28
|
+
attr_accessor name
|
29
|
+
end
|
30
|
+
|
31
|
+
# This is a class method because xmlattr objects are not directly
|
32
|
+
# exposed, so parsing needs to happen in the class.
|
33
|
+
|
34
|
+
# Provides an ordered list of xml attributes. Mainly used to give
|
35
|
+
# a list of attributes in a specific order.
|
36
|
+
def attributes
|
37
|
+
@attrs
|
38
|
+
end
|
39
|
+
|
40
|
+
def attribute_names
|
41
|
+
@attrs.map {|a| a.name }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Transforms standard ruby attribute names to something slightly more
|
45
|
+
# human readable.
|
46
|
+
def attribute_titles
|
47
|
+
attribute_names.map {|f| f.to_s.capitalize.sub(/$/, ":").gsub(/_/, " ") }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# = gprov/provision/entrybase/xmlattr.rb: attribute accessors with xml annotations
|
2
|
+
#
|
3
|
+
# == Overview
|
4
|
+
#
|
5
|
+
# Defines a data type and provides the logic for extracting and formatting
|
6
|
+
# object information from xml data
|
7
|
+
#
|
8
|
+
# Attribute accessors are not directly defined, because this class was designed
|
9
|
+
# to be used DSL style
|
10
|
+
#
|
11
|
+
# == Examples
|
12
|
+
#
|
13
|
+
# xmlattr :demo, :type => :numeric, :xpath => "example/xpath/text()"
|
14
|
+
#
|
15
|
+
# xmlattr :demo do
|
16
|
+
# type :numeric
|
17
|
+
# xpath "example/xpath/text()"
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# == Authors
|
21
|
+
#
|
22
|
+
# Adrien Thebo
|
23
|
+
#
|
24
|
+
# == Copyright
|
25
|
+
#
|
26
|
+
# 2011 Puppet Labs
|
27
|
+
#
|
28
|
+
require 'nokogiri'
|
29
|
+
|
30
|
+
module GProv
|
31
|
+
module Provision
|
32
|
+
class EntryBase
|
33
|
+
class XMLAttr
|
34
|
+
attr_reader :name
|
35
|
+
def initialize(name, options={})
|
36
|
+
@name = name
|
37
|
+
@type = :string
|
38
|
+
methodhash(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def xpath(val=nil)
|
42
|
+
@xpath = val if val
|
43
|
+
@xpath
|
44
|
+
end
|
45
|
+
|
46
|
+
def type(val=nil)
|
47
|
+
|
48
|
+
if [:numeric, :string, :bool].include? val
|
49
|
+
@type = val
|
50
|
+
else
|
51
|
+
raise ArgumentException
|
52
|
+
end
|
53
|
+
|
54
|
+
@type
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse(xml)
|
58
|
+
@value = xml.at_xpath(@xpath).to_s
|
59
|
+
format
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def format
|
65
|
+
case @type
|
66
|
+
when :numeric
|
67
|
+
@value = @value.to_i
|
68
|
+
when :string
|
69
|
+
# no op
|
70
|
+
when :bool
|
71
|
+
if @value == "true"
|
72
|
+
@value = true
|
73
|
+
else # XXX sketchy
|
74
|
+
@value = false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
@value
|
78
|
+
end
|
79
|
+
|
80
|
+
def methodhash(hash)
|
81
|
+
hash.each_pair do |method, value|
|
82
|
+
if respond_to? method
|
83
|
+
send method, value
|
84
|
+
else
|
85
|
+
raise ArgumentError, %Q{Received invalid attribute "#{method}"}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|