spec tests for type and provider and some code cleanup to adhere to DRY

This commit is contained in:
Nigel Kersten 2008-12-05 13:28:55 -08:00 коммит произвёл James Turnbull
Родитель 0f2fc88a39
Коммит 0caa9c57c6
4 изменённых файлов: 420 добавлений и 164 удалений

Просмотреть файл

@ -1,17 +1,26 @@
require 'facter'
require 'facter/util/plist'
require 'puppet'
require 'tempfile'
Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppet::Provider do
# Puppet::Type.type(:macauthorization).provide :macauth do
desc "Manage Mac OS X authorization database."
desc "Manage Mac OS X authorization database rules and rights."
commands :security => "/usr/bin/security"
commands :sw_vers => "/usr/bin/sw_vers"
confine :operatingsystem => :darwin
product_version = sw_vers "-productVersion"
confine :true => if /^10.5/.match(product_version) or /^10.6/.match(product_version)
true
end
defaultfor :operatingsystem => :darwin
AuthorizationDB = "/etc/authorization"
AuthDB = "/etc/authorization"
@rights = {}
@rules = {}
@ -24,7 +33,7 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe
:authenticate_user => "authenticate-user",
:auth_class => "class",
:k_of_n => "k-of-n",
}
:session_owner => "session-owner", }
mk_resource_methods
@ -32,46 +41,53 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe
attr_accessor :parsed_auth_db
attr_accessor :rights
attr_accessor :rules
attr_accessor :comments # Not implemented yet. Is there any real need to?
end
attr_accessor :comments # Not implemented yet.
def self.prefetch(resources)
self.populate_rules_rights
end
def self.instances
self.populate_rules_rights
self.parsed_auth_db.collect do |k,v|
new(:name => k)
def prefetch(resources)
self.populate_rules_rights
end
def instances
if self.parsed_auth_db == {}
self.prefetch(nil)
end
self.parsed_auth_db.collect do |k,v|
new(:name => k)
end
end
def populate_rules_rights
auth_plist = Plist::parse_xml(AuthDB)
if not auth_plist
raise Puppet::Error.new("Cannot parse: #{AuthDB}")
end
self.rights = auth_plist["rights"].dup
self.rules = auth_plist["rules"].dup
self.parsed_auth_db = self.rights.dup
self.parsed_auth_db.merge!(self.rules.dup)
end
end
def self.populate_rules_rights
auth_plist = Plist::parse_xml(AuthorizationDB)
if not auth_plist
raise Puppet::Error.new("Unable to parse authorization db at #{AuthorizationDB}")
end
self.rights = auth_plist["rights"].dup
self.rules = auth_plist["rules"].dup
self.parsed_auth_db = self.rights.dup
self.parsed_auth_db.merge!(self.rules.dup)
end
# standard required provider instance methods
def initialize(resource)
if self.class.parsed_auth_db.nil?
self.class.prefetch
if self.class.parsed_auth_db == {}
self.class.prefetch(resource)
end
super
end
def create
# we just fill the @property_hash in here and let the flush method deal with it
# we just fill the @property_hash in here and let the flush method
# deal with it rather than repeating code.
new_values = {}
Puppet::Type.type(resource.class.name).validproperties.each do |property|
next if property == :ensure
if value = resource.should(property) and value != ""
new_values[property] = value
validprops = Puppet::Type.type(resource.class.name).validproperties
validprops.each do |prop|
next if prop == :ensure
if value = resource.should(prop) and value != ""
new_values[prop] = value
end
end
@property_hash = new_values.dup
@ -85,26 +101,12 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe
when :rule
destroy_rule
else
raise Puppet::Error("You must specify the auth_type when removing macauthorization resources.")
end
end
def destroy_right
security :authorizationdb, :remove, resource[:name]
end
def destroy_rule
authdb = Plist::parse_xml(AuthorizationDB)
authdb_rules = authdb["rules"].dup
if authdb_rules[resource[:name]]
authdb["rules"].delete(resource[:name])
Plist::Emit.save_plist(authdb, AuthorizationDB)
raise Puppet::Error.new("Must specify auth_type when destroying.")
end
end
def exists?
if self.class.parsed_auth_db.has_key?(resource[:name])
# return :present
return true
else
return false
@ -113,24 +115,47 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe
def flush
if resource[:ensure] != :absent # deletion happens in the destroy methods
# deletion happens in the destroy methods
if resource[:ensure] != :absent
case resource[:auth_type]
when :right
flush_right
when :rule
flush_rule
else
raise Puppet::Error.new("flushing something that isn't a right or a rule.")
raise Puppet::Error.new("flush requested for unknown type.")
end
@property_hash.clear
end
end
# utility methods below
def destroy_right
security "authorizationdb", :remove, resource[:name]
end
def destroy_rule
authdb = Plist::parse_xml(AuthDB)
authdb_rules = authdb["rules"].dup
if authdb_rules[resource[:name]]
begin
authdb["rules"].delete(resource[:name])
Plist::Emit.save_plist(authdb, AuthDB)
rescue Errno::EACCES => e
raise Puppet::Error.new("Error saving #{AuthDB}: #{e}")
end
end
end
def flush_right
# first we re-read the right just to make sure we're in sync for values
# that weren't specified in the manifest. As we're supplying the whole
# plist when specifying the right it seems safest to be paranoid.
cmds = [] << :security << "authorizationdb" << "read" << resource[:name]
# first we re-read the right just to make sure we're in sync for
# values that weren't specified in the manifest. As we're supplying
# the whole plist when specifying the right it seems safest to be
# paranoid given the low cost of quering the db once more.
cmds = []
cmds << :security << "authorizationdb" << "read" << resource[:name]
output = execute(cmds, :combine => false)
current_values = Plist::parse_xml(output)
if current_values.nil?
@ -138,14 +163,14 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe
end
specified_values = convert_plist_to_native_attributes(@property_hash)
# take the current values, merge the specified values to obtain a complete
# description of the new values.
# take the current values, merge the specified values to obtain a
# complete description of the new values.
new_values = current_values.merge(specified_values)
set_right(resource[:name], new_values)
end
def flush_rule
authdb = Plist::parse_xml(AuthorizationDB)
authdb = Plist::parse_xml(AuthDB)
authdb_rules = authdb["rules"].dup
current_values = {}
if authdb_rules[resource[:name]]
@ -163,11 +188,13 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe
values = convert_plist_to_native_attributes(values)
tmp = Tempfile.new('puppet_macauthorization')
begin
# tmp.flush
Plist::Emit.save_plist(values, tmp.path)
# tmp.flush
cmds = [] << :security << "authorizationdb" << "write" << name
output = execute(cmds, :combine => false, :stdinfile => tmp.path.to_s)
cmds = []
cmds << :security << "authorizationdb" << "write" << name
output = execute(cmds, :combine => false,
:stdinfile => tmp.path.to_s)
rescue Errno::EACCES => e
raise Puppet::Error.new("Cannot save right to #{tmp.path}: #{e}")
ensure
tmp.close
tmp.unlink
@ -175,29 +202,30 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe
end
def set_rule(name, values)
# Both creates and modifies rules as it overwrites the entry in the rules
# dictionary.
# Unfortunately the security binary doesn't support modifying rules at all
# so we have to twiddle the whole plist... :( See Apple Bug #6386000
# Both creates and modifies rules as it overwrites the entry in the
# rules dictionary. Unfortunately the security binary doesn't
# support modifying rules at all so we have to twiddle the whole
# plist... :( See Apple Bug #6386000
values = convert_plist_to_native_attributes(values)
authdb = Plist::parse_xml(AuthorizationDB)
authdb = Plist::parse_xml(AuthDB)
authdb["rules"][name] = values
begin
Plist::Emit.save_plist(authdb, AuthorizationDB)
Plist::Emit.save_plist(authdb, AuthDB)
rescue
raise Puppet::Error.new("Couldn't write to authorization db at #{AuthorizationDB}")
raise Puppet::Error.new("Error writing to: #{AuthDB}")
end
end
def convert_plist_to_native_attributes(propertylist)
# This mainly converts the keys from the puppet attributes to the 'native'
# ones, but also enforces that the keys are all Strings rather than Symbols
# so that any merges of the resultant Hash are sane.
# This mainly converts the keys from the puppet attributes to the
# 'native' ones, but also enforces that the keys are all Strings
# rather than Symbols so that any merges of the resultant Hash are
# sane.
newplist = {}
propertylist.each_pair do |key, value|
next if key == :ensure
next if key == :auth_type
next if key == :ensure # not part of the auth db schema.
next if key == :auth_type # not part of the auth db schema.
new_key = key
if PuppetToNativeAttributeMap.has_key?(key)
new_key = PuppetToNativeAttributeMap[key].to_s
@ -212,7 +240,7 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe
def retrieve_value(resource_name, attribute)
if not self.class.parsed_auth_db.has_key?(resource_name)
raise Puppet::Error.new("Unable to find resource #{resource_name} in authorization db.")
raise Puppet::Error.new("Cannot find #{resource_name} in auth db")
end
if PuppetToNativeAttributeMap.has_key?(attribute)
@ -234,80 +262,28 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe
return value
else
@property_hash.delete(attribute)
return ""
return "" # so ralsh doesn't display it.
end
end
def allow_root
retrieve_value(resource[:name], :allow_root)
end
def allow_root=(value)
@property_hash[:allow_root] = value
end
# property methods below
#
# We define them all dynamically apart from auth_type which is a special
# case due to not being in the actual authorization db schema.
def authenticate_user
retrieve_value(resource[:name], :authenticate_user)
end
def authenticate_user= (dosync)
@property_hash[:authenticate_user] = value
end
def auth_class
retrieve_value(resource[:name], :auth_class)
end
def auth_class=(value)
@property_hash[:auth_class] = value
end
def comment
retrieve_value(resource[:name], :comment)
end
def comment=(value)
@property_hash[:comment] = value
end
def group
retrieve_value(resource[:name], :group)
end
def group=(value)
@property_hash[:group] = value
end
def k_of_n
retrieve_value(resource[:name], :k_of_n)
end
def k_of_n=(value)
@property_hash[:k_of_n] = value
end
properties = [ :allow_root, :authenticate_user, :auth_class, :comment,
:group, :k_of_n, :mechanisms, :rule, :session_owner,
:shared, :timeout, :tries ]
def mechanisms
retrieve_value(resource[:name], :mechanisms)
end
properties.each do |field|
define_method(field.to_s) do
retrieve_value(resource[:name], field)
end
def mechanisms=(value)
@property_hash[:mechanisms] = value
end
def rule
retrieve_value(resource[:name], :rule)
end
def rule=(value)
@property_hash[:rule] = value
end
def shared
retrieve_value(resource[:name], :shared)
end
def shared=(value)
@property_hash[:shared] = value
define_method(field.to_s + "=") do |value|
@property_hash[field] = value
end
end
def auth_type
@ -320,10 +296,10 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe
elsif self.class.rules.has_key?(resource[:name])
return :rule
else
raise Puppet::Error.new("Unable to determine if macauthorization type: #{resource[:name]} is a right or a rule.")
raise Puppet::Error.new("#{resource[:name]} is unknown type.")
end
else
raise Puppet::Error.new("You must specify the auth_type for new macauthorization resources.")
raise Puppet::Error.new("auth_type required for new resources.")
end
end

Просмотреть файл

@ -1,8 +1,9 @@
require 'ruby-debug'
Puppet::Type.newtype(:macauthorization) do
@doc = "Manage authorization databases"
@doc = "Manage the Mac OS X authorization database.
See: http://developer.apple.com/documentation/Security/Conceptual/Security_Overview/Security_Services/chapter_4_section_5.html
for more information."
ensurable
@ -10,8 +11,6 @@ Puppet::Type.newtype(:macauthorization) do
["/etc/authorization"]
end
# This probably shouldn't be necessary for properties that have declared
# themselves to be booleans already.
def munge_boolean(value)
case value
when true, "true", :true:
@ -24,19 +23,32 @@ Puppet::Type.newtype(:macauthorization) do
end
newparam(:name) do
desc "The name of the right or rule to be managed."
desc "The name of the right or rule to be managed.
Corresponds to 'key' in Authorization Services. The key is the name
of a rule. A key uses the same naming conventions as a right. The
Security Server uses a rules key to match the rule with a right.
Wildcard keys end with a .. The generic rule has an empty key value.
Any rights that do not match a specific rule use the generic rule."
isnamevar
end
newproperty(:auth_type) do
desc "type - can be a right a rule or a comment"
desc "type - can be a 'right' or a 'rule'. 'comment' has not yet been
implemented."
newvalue(:right)
newvalue(:rule)
newvalue(:comment)
# newvalue(:comment) # not yet implemented.
end
newproperty(:allow_root, :boolean => true) do
desc "Corresponds to 'allow-root' in the authorization store. hyphens not allowed..."
desc "Corresponds to 'allow-root' in the authorization store, renamed
due to hyphens being problematic. Specifies whether a right should be
allowed automatically if the requesting process is running with
uid == 0. AuthorizationServices defaults this attribute to false if
not specified"
newvalue(:true)
newvalue(:false)
@ -46,7 +58,9 @@ Puppet::Type.newtype(:macauthorization) do
end
newproperty(:authenticate_user, :boolean => true) do
desc "authenticate-user"
desc "Corresponds to 'authenticate-user' in the authorization store,
renamed due to hyphens being problematic."
newvalue(:true)
newvalue(:false)
@ -56,34 +70,40 @@ Puppet::Type.newtype(:macauthorization) do
end
newproperty(:auth_class) do
desc "Corresponds to 'class' in the authorization store. class is
a reserved word in Puppet syntax, so we use 'authclass'."
# newvalue(:user)
# newvalue(:'evaluate-mechanisms')
desc "Corresponds to 'class' in the authorization store, renamed due
to 'class' being a reserved word."
newvalue(:user)
newvalue(:'evaluate-mechanisms')
end
newproperty(:comment) do
desc "Comment. simple enough eh?"
desc "The 'comment' attribute for authorization resources."
end
newproperty(:group) do
desc "group"
desc "The user must authenticate as a member of this group. This
attribute can be set to any one group."
end
newproperty(:k_of_n) do
desc "k-of-n. odd."
desc "k-of-n. Built-in rights only show a value of '1' or absent,
other values may be acceptable. Undocumented."
end
newproperty(:mechanisms, :array_matching => :all) do
desc "mechanisms"
desc "an array of suitable mechanisms."
end
newproperty(:rule, :array_match => :all) do
desc "rule"
end
newproperty(:shared, :boolean => true) do
desc "shared"
desc "The rule(s) that this right refers to."
end
newproperty(:session_owner, :boolean => true) do
desc "Corresponds to 'session-owner' in the authorization store,
renamed due to hyphens being problematic. Whether the session owner
automatically matches this rule or right."
newvalue(:true)
newvalue(:false)
@ -92,4 +112,31 @@ Puppet::Type.newtype(:macauthorization) do
end
end
newproperty(:shared, :boolean => true) do
desc "If this is set to true, then the Security Server marks the
credentials used to gain this right as shared. The Security Server
may use any shared credentials to authorize this right. For maximum
security, set sharing to false so credentials stored by the Security
Server for one application may not be used by another application."
newvalue(:true)
newvalue(:false)
munge do |value|
@resource.munge_boolean(value)
end
end
newproperty(:timeout) do
desc "The credential used by this rule expires in the specified
number of seconds. For maximum security where the user must
authenticate every time, set the timeout to 0. For minimum security,
remove the timeout attribute so the user authenticates only once per
session."
end
newproperty(:tries) do
desc "The number of tries allowed."
end
end

Просмотреть файл

@ -0,0 +1,153 @@
#!/usr/bin/env ruby
#
# Unit testing for the macauthorization provider
#
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet'
require 'facter/util/plist'
provider_class = Puppet::Type.type(:macauthorization).provider(:macauthorization)
describe provider_class do
before :each do
# Create a mock resource
@resource = stub 'resource'
@provider = provider_class.new(@resource)
@authname = "foo.spam.eggs.puppettest"
@authplist = {}
@rules = {@authname => @authplist}
@authdb = {}
@authdb["rules"] = @rules
# A catch all; no parameters set
@resource.stubs(:[]).returns(nil)
# But set name, ensure
@resource.stubs(:[]).with(:name).returns @authname
@resource.stubs(:[]).with(:ensure).returns :present
@resource.stubs(:ref).returns "MacAuthorization[#{@authname}]"
# stub out the provider methods that actually touch the filesystem
# or execute commands
@provider.stubs(:populate_rules_rights).returns("")
# Stub out Plist::parse_xml
Plist.stubs("parse_xml").returns(@authdb)
end
it "should have a create method" do
@provider.should respond_to(:create)
end
it "should have a destroy method" do
@provider.should respond_to(:destroy)
end
it "should have an exists? method" do
@provider.should respond_to(:exists?)
end
it "should have a flush method" do
@provider.should respond_to(:flush)
end
properties = [ :allow_root, :authenticate_user, :auth_class, :comment,
:group, :k_of_n, :mechanisms, :rule, :session_owner,
:shared, :timeout, :tries, :auth_type ]
properties.each do |prop|
it "should have a #{prop.to_s} method" do
@provider.should respond_to(prop.to_s)
end
it "should have a #{prop.to_s}= method" do
@provider.should respond_to(prop.to_s + "=")
end
end
describe "when destroying a right" do
before :each do
@resource.stubs(:[]).with(:auth_type).returns(:right)
end
it "should call the internal method destroy_right" do
@provider.expects("destroy_right")
@provider.destroy
end
it "should call the external command 'security authorizationdb remove @authname" do
@provider.expects(:security).with("authorizationdb", :remove, @authname)
@provider.destroy
end
end
describe "when destroying a rule" do
before :each do
@resource.stubs(:[]).with(:auth_type).returns(:rule)
end
it "should call the internal method destroy_rule" do
@provider.expects("destroy_rule")
@provider.destroy
end
end
describe "when flushing a right" do
before :each do
@resource.stubs(:[]).with(:auth_type).returns(:right)
end
it "should call the internal method flush_right" do
@provider.expects("flush_right")
@provider.flush
end
it "should call the internal method set_right" do
@provider.expects("set_right")
@provider.flush
end
it "should read and write to the auth database with the right arguments" do
@provider.expects(:execute).with() { |cmds, args|
cmds.include?("read") and
cmds.include?(@authname) and
args[:combine] == false
}.once
@provider.expects(:execute).with() { |cmds, args|
cmds.include?("write") and
cmds.include?(@authname) and
args[:combine] == false and
args[:stdinfile] != nil
}.once
@provider.flush
end
end
describe "when flushing a rule" do
before :each do
@resource.stubs(:[]).with(:auth_type).returns(:rule)
end
it "should call the internal method flush_rule" do
@provider.expects("flush_rule")
@provider.flush
end
it "should call the internal method set_rule" do
@provider.expects("set_rule")
@provider.flush
end
end
end

Просмотреть файл

@ -0,0 +1,80 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
macauth_type = Puppet::Type.type(:macauthorization)
describe macauth_type, "when validating attributes" do
parameters = [:name,]
properties = [:auth_type, :allow_root, :authenticate_user, :auth_class,
:comment, :group, :k_of_n, :mechanisms, :rule,
:session_owner, :shared, :timeout, :tries]
parameters.each do |parameter|
it "should have a %s parameter" % parameter do
macauth_type.attrclass(parameter).ancestors.should be_include(Puppet::Parameter)
end
it "should have documentation for its %s parameter" % parameter do
macauth_type.attrclass(parameter).doc.should be_instance_of(String)
end
end
properties.each do |property|
it "should have a %s property" % property do
macauth_type.attrclass(property).ancestors.should be_include(Puppet::Property)
end
it "should have documentation for its %s property" % property do
macauth_type.attrclass(property).doc.should be_instance_of(String)
end
end
end
describe macauth_type, "when validating properties" do
before do
@provider = stub 'provider'
@resource = stub 'resource', :resource => nil, :provider => @provider, :line => nil, :file => nil
end
after do
macauth_type.clear
end
it "should have a default provider inheriting from Puppet::Provider" do
macauth_type.defaultprovider.ancestors.should be_include(Puppet::Provider)
end
it "should be able to create a instance" do
macauth_type.create(:name => "foo").should_not be_nil
end
it "should be able to create an instance" do
lambda {
macauth_type.create(:name => 'foo')
}.should_not raise_error
end
it "should support :present as a value to :ensure" do
lambda {
macauth_type.create(:name => "foo", :ensure => :present)
}.should_not raise_error
end
it "should support :absent as a value to :ensure" do
lambda {
macauth_type.create(:name => "foo", :ensure => :absent)
}.should_not raise_error
end
end
describe "instances" do
it "should have a valid provider" do
macauth_type.create(:name => "foo").provider.class.ancestors.should be_include(Puppet::Provider)
end
end