gprov 0.0.3
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/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
|