reset macauthorization tree. Initial checkin of new type/provider macauthorization type
finished rights flush, working on rules new better way of doing stdin Finished work on rules creation and deletion spec tests for type and provider and some code cleanup to adhere to DRY Signed-off-by: Nigel Kersten <[EMAIL PROTECTED]> --- .../provider/macauthorization/macauthorization.rb | 310 ++++++++++++++++++++ lib/puppet/type/macauthorization.rb | 142 +++++++++ lib/rake | 1 + spec/unit/provider/macauthorization.rb | 153 ++++++++++ spec/unit/type/macauthorization.rb | 80 +++++ 5 files changed, 686 insertions(+), 0 deletions(-) create mode 100644 lib/puppet/provider/macauthorization/macauthorization.rb create mode 100644 lib/puppet/type/macauthorization.rb create mode 120000 lib/rake create mode 100644 spec/unit/provider/macauthorization.rb create mode 100644 spec/unit/type/macauthorization.rb diff --git a/lib/puppet/provider/macauthorization/macauthorization.rb b/lib/puppet/provider/macauthorization/macauthorization.rb new file mode 100644 index 0000000..d29c1d2 --- /dev/null +++ b/lib/puppet/provider/macauthorization/macauthorization.rb @@ -0,0 +1,310 @@ +require 'facter' +require 'facter/util/plist' +require 'puppet' +require 'tempfile' + +Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppet::Provider do + + 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 + + AuthDB = "/etc/authorization" + + @rights = {} + @rules = {} + @parsed_auth_db = {} + @comment = "" # Not implemented yet. Is there any real need to? + + # This map exists due to the use of hyphens and reserved words in + # the authorization schema. + PuppetToNativeAttributeMap = { :allow_root => "allow-root", + :authenticate_user => "authenticate-user", + :auth_class => "class", + :k_of_n => "k-of-n", + :session_owner => "session-owner", } + + mk_resource_methods + + class << self + attr_accessor :parsed_auth_db + attr_accessor :rights + attr_accessor :rules + attr_accessor :comments # Not implemented yet. + + 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 + + # standard required provider instance methods + + def initialize(resource) + 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 rather than repeating code. + new_values = {} + 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 + end + + def destroy + # We explicitly delete here rather than in the flush method. + case resource[:auth_type] + when :right + destroy_right + when :rule + destroy_rule + else + 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 true + else + return false + end + end + + + def flush + # 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("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 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? + current_values = {} + 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. + new_values = current_values.merge(specified_values) + set_right(resource[:name], new_values) + end + + def flush_rule + authdb = Plist::parse_xml(AuthDB) + authdb_rules = authdb["rules"].dup + current_values = {} + if authdb_rules[resource[:name]] + current_values = authdb_rules[resource[:name]] + end + specified_values = convert_plist_to_native_attributes(@property_hash) + new_values = current_values.merge(specified_values) + set_rule(resource[:name], new_values) + end + + def set_right(name, values) + # Both creates and modifies rights as it simply overwrites them. + # The security binary only allows for writes using stdin, so we + # dump the values to a tempfile. + values = convert_plist_to_native_attributes(values) + tmp = Tempfile.new('puppet_macauthorization') + begin + Plist::Emit.save_plist(values, tmp.path) + 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 + end + 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 + values = convert_plist_to_native_attributes(values) + authdb = Plist::parse_xml(AuthDB) + authdb["rules"][name] = values + + begin + Plist::Emit.save_plist(authdb, AuthDB) + rescue + 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. + newplist = {} + propertylist.each_pair do |key, value| + 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 + elsif not key.is_a?(String) + new_key = key.to_s + end + newplist[new_key] = value + end + newplist + end + + def retrieve_value(resource_name, attribute) + + if not self.class.parsed_auth_db.has_key?(resource_name) + raise Puppet::Error.new("Cannot find #{resource_name} in auth db") + end + + if PuppetToNativeAttributeMap.has_key?(attribute) + native_attribute = PuppetToNativeAttributeMap[attribute] + else + native_attribute = attribute.to_s + end + + if self.class.parsed_auth_db[resource_name].has_key?(native_attribute) + value = self.class.parsed_auth_db[resource_name][native_attribute] + case value + when true, "true", :true + value = :true + when false, "false", :false + value = :false + end + + @property_hash[attribute] = value + return value + else + @property_hash.delete(attribute) + return "" # so ralsh doesn't display it. + end + 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. + + properties = [ :allow_root, :authenticate_user, :auth_class, :comment, + :group, :k_of_n, :mechanisms, :rule, :session_owner, + :shared, :timeout, :tries ] + + properties.each do |field| + define_method(field.to_s) do + retrieve_value(resource[:name], field) + end + + define_method(field.to_s + "=") do |value| + @property_hash[field] = value + end + end + + def auth_type + if resource.should(:auth_type) != nil + return resource.should(:auth_type) + elsif self.exists? + # this is here just for ralsh, so it can work out what type it is. + if self.class.rights.has_key?(resource[:name]) + return :right + elsif self.class.rules.has_key?(resource[:name]) + return :rule + else + raise Puppet::Error.new("#{resource[:name]} is unknown type.") + end + else + raise Puppet::Error.new("auth_type required for new resources.") + end + end + + def auth_type=(value) + @property_hash[:auth_type] = value + end + +end \ No newline at end of file diff --git a/lib/puppet/type/macauthorization.rb b/lib/puppet/type/macauthorization.rb new file mode 100644 index 0000000..46e02dd --- /dev/null +++ b/lib/puppet/type/macauthorization.rb @@ -0,0 +1,142 @@ +Puppet::Type.newtype(:macauthorization) do + + @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 + + autorequire(:file) do + ["/etc/authorization"] + end + + def munge_boolean(value) + case value + when true, "true", :true: + :true + when false, "false", :false + :false + else + raise Puppet::Error("munge_boolean only takes booleans") + end + end + + newparam(:name) do + 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 rule’s 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' or a 'rule'. 'comment' has not yet been + implemented." + + newvalue(:right) + newvalue(:rule) + # newvalue(:comment) # not yet implemented. + end + + newproperty(:allow_root, :boolean => true) do + 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) + + munge do |value| + @resource.munge_boolean(value) + end + end + + newproperty(:authenticate_user, :boolean => true) do + desc "Corresponds to 'authenticate-user' in the authorization store, + renamed due to hyphens being problematic." + + newvalue(:true) + newvalue(:false) + + munge do |value| + @resource.munge_boolean(value) + end + end + + newproperty(:auth_class) do + 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 "The 'comment' attribute for authorization resources." + end + + newproperty(:group) do + 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. 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 "an array of suitable mechanisms." + end + + newproperty(:rule, :array_match => :all) do + 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) + + munge do |value| + @resource.munge_boolean(value) + 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 diff --git a/lib/rake b/lib/rake new file mode 120000 index 0000000..0ac8c76 --- /dev/null +++ b/lib/rake @@ -0,0 +1 @@ +/Qualia/Repos/github/reductive-build/lib/rake \ No newline at end of file diff --git a/spec/unit/provider/macauthorization.rb b/spec/unit/provider/macauthorization.rb new file mode 100644 index 0000000..4754b11 --- /dev/null +++ b/spec/unit/provider/macauthorization.rb @@ -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 = [EMAIL PROTECTED] => @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 "[EMAIL PROTECTED]" + + # 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 \ No newline at end of file diff --git a/spec/unit/type/macauthorization.rb b/spec/unit/type/macauthorization.rb new file mode 100644 index 0000000..a27841c --- /dev/null +++ b/spec/unit/type/macauthorization.rb @@ -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 \ No newline at end of file -- 1.5.6.4 --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Puppet Developers" group. To post to this group, send email to puppet-dev@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/puppet-dev?hl=en -~----------~----~----~----~------~----~------~--~---