Add role support to user type and an implemention

modify user type:
    add ensure = role logic
    add roles property
    add manages_solaris_rbac feature
    refactored 'list' property to reuse logic for groups in roles
This commit is contained in:
Andrew Shafer 2008-10-01 18:58:09 -06:00
Родитель 2fba85af73
Коммит d1abb86565
5 изменённых файлов: 489 добавлений и 54 удалений

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

@ -0,0 +1,78 @@
require 'puppet/property'
module Puppet
class Property
class List < Property
def should_to_s(should_value)
#just return the should value
should_value
end
def is_to_s(currentvalue)
currentvalue.join(delimiter)
end
def membership
:membership
end
def add_should_with_current(should, current)
if current.is_a?(Array)
should += current
end
should.uniq
end
def inclusive?
@resource[membership] == :inclusive
end
def should
unless defined? @should and @should
return nil
end
members = @should
#inclusive means we are managing everything so if it isn't in should, its gone
if ! inclusive?
members = add_should_with_current(members, retrieve)
end
members.sort.join(delimiter)
end
def delimiter
","
end
def retrieve
#ok, some 'convention' if the list property is named groups, provider should implement a groups method
if tmp = provider.send(name) and tmp != :absent
return tmp.split(delimiter)
else
return :absent
end
end
def prepare_is_for_comparison(is)
if is.is_a? Array
is = is.sort.join(delimiter)
end
is
end
def insync?(is)
unless defined? @should and @should
return true
end
unless is
return true
end
return (prepare_is_for_comparison(is) == self.should)
end
end
end
end

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

@ -0,0 +1,89 @@
require 'puppet/util/user_attr'
Puppet::Type.type(:user).provide :user_role_add, :parent => Puppet::Type::User::ProviderUseradd do
desc "User management inherits ``useradd`` and adds logic to manage roles on Solaris using roleadd."
defaultfor :operatingsystem => :solaris
commands :add => "useradd", :delete => "userdel", :modify => "usermod", :role_add => "roleadd", :role_delete => "roledel", :role_modify => "rolemod"
options :home, :flag => "-d", :method => :dir
options :comment, :method => :gecos
options :groups, :flag => "-G"
options :roles, :flag => "-R"
verify :gid, "GID must be an integer" do |value|
value.is_a? Integer
end
verify :groups, "Groups must be comma-separated" do |value|
value !~ /\s/
end
has_features :manages_homedir, :allows_duplicates, :manages_solaris_rbac
if Puppet.features.libshadow?
has_feature :manages_passwords
end
def user_attributes
@user_attributes ||= UserAttr.get_attributes_by_name(@resource[:name])
end
def flush
@user_attributes = nil
end
def command(cmd)
if is_role? or (!exists? and @resource[:ensure] == :role)
cmd = ("role_" + cmd.to_s).intern
end
super(cmd)
end
def is_role?
user_attributes and user_attributes[:type] == "role"
end
def run(cmd, msg)
begin
execute(cmd)
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Could not %s %s %s: %s" %
[msg, @resource.class.name, @resource.name, detail]
end
end
def transition(type)
cmd = [command(:modify)]
cmd << "-K" << "type=#{type}"
cmd << @resource[:name]
end
def create
if is_role?
run(transition("normal"), "transition role to")
else
run(addcmd, "create")
end
end
def destroy
run(deletecmd, "delete "+ (is_role? ? "role" : "user"))
end
def create_role
if exists? and !is_role?
run(transition("role"), "transition user to")
else
run(addcmd, "create role")
end
end
def roles
if user_attributes
user_attributes[:roles]
end
end
end

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

@ -1,5 +1,6 @@
require 'etc'
require 'facter'
require 'puppet/property/list'
module Puppet
newtype(:user) do
@ -21,6 +22,9 @@ module Puppet
"The provider can modify user passwords, by accepting a password
hash."
feature :manages_solaris_rbac,
"The provider can manage roles and normal users"
newproperty(:ensure, :parent => Puppet::Property::Ensure) do
newvalue(:present, :event => :user_created) do
provider.create
@ -30,6 +34,10 @@ module Puppet
provider.delete
end
newvalue(:role, :event => :role_created, :required_features => :manages_solaris_rbac) do
provider.create_role
end
desc "The basic state that the object should be in."
# If they're talking about the thing at all, they generally want to
@ -44,7 +52,11 @@ module Puppet
def retrieve
if provider.exists?
return :present
if provider.respond_to?(:is_role?) and provider.is_role?
return :role
else
return :present
end
else
return :absent
end
@ -125,72 +137,40 @@ module Puppet
end
end
newproperty(:groups) do
newproperty(:groups, :parent => Puppet::Property::List) do
desc "The groups of which the user is a member. The primary
group should not be listed. Multiple groups should be
specified as an array."
def should_to_s(newvalue)
self.should
end
def is_to_s(currentvalue)
currentvalue.join(",")
end
# We need to override this because the groups need to
# be joined with commas
def should
current_value = retrieve
unless defined? @should and @should
return nil
end
if @resource[:membership] == :inclusive
return @should.sort.join(",")
else
members = @should
if current_value.is_a?(Array)
members += current_value
end
return members.uniq.sort.join(",")
end
end
def retrieve
if tmp = provider.groups and tmp != :absent
return tmp.split(",")
else
return :absent
end
end
def insync?(is)
unless defined? @should and @should
return true
end
unless defined? is and is
return true
end
tmp = is
if is.is_a? Array
tmp = is.sort.join(",")
end
return tmp == self.should
end
validate do |value|
if value =~ /^\d+$/
raise ArgumentError, "Group names must be provided, not numbers"
end
if value.include?(",")
puts value
raise ArgumentError, "Group names must be provided as an array, not a comma-separated list"
end
end
end
newproperty(:roles, :parent => Puppet::Property::List, :required_features => :manages_solaris_rbac) do
desc "The roles of which the user the user has. The roles should be
specified as an array."
def membership
:role_membership
end
validate do |value|
if value =~ /^\d+$/
raise ArgumentError, "Role names must be provided, not numbers"
end
if value.include?(",")
raise ArgumentError, "Role names must be provided as an array, not a comma-separated list"
end
end
end
newparam(:name) do
desc "User name. While limitations are determined for
each operating system, it is generally a good idea to keep to
@ -202,7 +182,17 @@ module Puppet
desc "Whether specified groups should be treated as the only groups
of which the user is a member or whether they should merely
be treated as the minimum membership list."
newvalues(:inclusive, :minimum)
defaultto :minimum
end
newparam(:role_membership) do
desc "Whether specified roles should be treated as the only roles
of which the user is a member or whether they should merely
be treated as the minimum membership list."
newvalues(:inclusive, :minimum)
defaultto :minimum

147
spec/unit/property/list.rb Normal file
Просмотреть файл

@ -0,0 +1,147 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/property/list'
list_class = Puppet::Property::List
describe list_class do
it "should be a subclass of Property" do
list_class.superclass.must == Puppet::Property
end
describe "as an instance" do
before do
# Wow that's a messy interface to the resource.
list_class.initvars
@resource = stub 'resource', :[]= => nil, :property => nil
@property = list_class.new(:resource => @resource)
end
it "should have a , as default delimiter" do
@property.delimiter.should == ","
end
it "should have a :membership as default membership" do
@property.membership.should == :membership
end
it "should return the same value passed into should_to_s" do
@property.should_to_s("foo") == "foo"
end
it "should return the passed in array values joined with the delimiter from is_to_s" do
@property.is_to_s(["foo","bar"]).should == "foo,bar"
end
describe "when adding should to current" do
it "should add the arrays when current is an array" do
@property.add_should_with_current(["foo"], ["bar"]).should == ["foo", "bar"]
end
it "should return should if current is not a array" do
@property.add_should_with_current(["foo"], :absent).should == ["foo"]
end
it "should return only the uniq elements" do
@property.add_should_with_current(["foo", "bar"], ["foo", "baz"]).should == ["foo", "bar", "baz"]
end
end
describe "when calling inclusive?" do
it "should use the membership method to look up on the @resource" do
@property.expects(:membership).returns(:membership)
@resource.expects(:[]).with(:membership)
@property.inclusive?
end
it "should return true when @resource[membership] == inclusive" do
@property.stubs(:membership).returns(:membership)
@resource.stubs(:[]).with(:membership).returns(:inclusive)
@property.inclusive?.must == true
end
it "should return false when @resource[membership] != inclusive" do
@property.stubs(:membership).returns(:membership)
@resource.stubs(:[]).with(:membership).returns(:minimum)
@property.inclusive?.must == false
end
end
describe "when calling should" do
it "should return nil if @should is nil" do
@property.should.must == nil
end
it "should return the sorted values of @should as a string if inclusive" do
@property.should = ["foo", "bar"]
@property.expects(:inclusive?).returns(true)
@property.should.must == "bar,foo"
end
it "should return the uniq sorted values of @should + retrieve as a string if !inclusive" do
@property.should = ["foo", "bar"]
@property.expects(:inclusive?).returns(false)
@property.expects(:retrieve).returns(["foo","baz"])
@property.should.must == "bar,baz,foo"
end
end
describe "when calling retrieve" do
before do
@provider = mock("provider")
@property.stubs(:provider).returns(@provider)
end
it "should send 'name' to the provider" do
@provider.expects(:send).with(:group)
@property.expects(:name).returns(:group)
@property.retrieve
end
it "should return an array with the provider returned info" do
@provider.stubs(:send).with(:group).returns("foo,bar,baz")
@property.stubs(:name).returns(:group)
@property.retrieve == ["foo", "bar", "baz"]
end
it "should return :absent when the provider returns :absent" do
@provider.stubs(:send).with(:group).returns(:absent)
@property.stubs(:name).returns(:group)
@property.retrieve == :absent
end
end
describe "when calling insync?" do
it "should return true unless @should is defined and not nil" do
@property.insync?("foo") == true
end
it "should return true unless the passed in values is not nil" do
@property.should = "foo"
@property.insync?(nil) == true
end
it "should call prepare_is_for_comparison with value passed in and should" do
@property.should = "foo"
@property.expects(:prepare_is_for_comparison).with("bar")
@property.expects(:should)
@property.insync?("bar")
end
it "should return true if prepared value == should value" do
@property.should = "bar,foo"
@property.expects(:inclusive?).returns(true)
@property.insync?(["bar","foo"]).must == true
end
it "should return false if prepared value != should value" do
@property.should = "bar,baz,foo"
@property.expects(:inclusive?).returns(true)
@property.insync?(["bar","foo"]).must == false
end
end
end
end

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

@ -0,0 +1,131 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
provider_class = Puppet::Type.type(:user).provider(:user_role_add)
describe provider_class do
before do
@resource = stub("resource", :name => "myuser", :managehome? => nil)
@resource.stubs(:should).returns "fakeval"
@resource.stubs(:[]).returns "fakeval"
@resource.stubs(:allowdupe?).returns false
@provider = provider_class.new(@resource)
end
describe "when calling command" do
before do
klass = stub("provider")
klass.stubs(:command).with(:foo).returns("userfoo")
klass.stubs(:command).with(:role_foo).returns("rolefoo")
@provider.stubs(:class).returns(klass)
end
it "should use the command if not a role and ensure!=role" do
@provider.stubs(:is_role?).returns(false)
@provider.stubs(:exists?).returns(false)
@resource.stubs(:[]).with(:ensure).returns(:present)
@provider.command(:foo).should == "userfoo"
end
it "should use the role command when a role" do
@provider.stubs(:is_role?).returns(true)
@provider.command(:foo).should == "rolefoo"
end
it "should use the role command when !exists and ensure=role" do
@provider.stubs(:is_role?).returns(false)
@provider.stubs(:exists?).returns(false)
@resource.stubs(:[]).with(:ensure).returns(:role)
@provider.command(:foo).should == "rolefoo"
end
end
describe "when calling transition" do
it "should return foomod setting the type to bar" do
@provider.expects(:command).with(:modify).returns("foomod")
@provider.transition("bar").should == ["foomod", "-K", "type=bar", "fakeval"]
end
end
describe "when calling create" do
it "should use the add command when the user doesn't exist" do
@provider.stubs(:exists?).returns(false)
@provider.expects(:addcmd).returns("useradd")
@provider.expects(:run)
@provider.create
end
it "should use transition(normal) when the user is a role" do
@provider.stubs(:exists?).returns(true)
@provider.stubs(:is_role?).returns(true)
@provider.expects(:transition).with("normal")
@provider.expects(:run)
@provider.create
end
end
describe "when calling destroy" do
it "should use the delete command if the user exists and is not a role" do
@provider.stubs(:exists?).returns(true)
@provider.stubs(:is_role?).returns(false)
@provider.expects(:deletecmd)
@provider.expects(:run)
@provider.destroy
end
it "should use the delete command if the user is a role" do
@provider.stubs(:exists?).returns(true)
@provider.stubs(:is_role?).returns(true)
@provider.expects(:deletecmd)
@provider.expects(:run)
@provider.destroy
end
end
describe "when calling create_role" do
it "should use the transition(role) if the user exists" do
@provider.stubs(:exists?).returns(true)
@provider.stubs(:is_role?).returns(false)
@provider.expects(:transition).with("role")
@provider.expects(:run)
@provider.create_role
end
it "should use the add command when role doesn't exists" do
@provider.stubs(:exists?).returns(false)
@provider.expects(:addcmd)
@provider.expects(:run)
@provider.create_role
end
end
describe "when allow duplicate is enabled" do
before do
@resource.expects(:allowdupe?).returns true
@provider.expects(:execute).with { |args| args.include?("-o") }
end
it "should add -o when the user is being created" do
@provider.create
end
it "should add -o when the uid is being modified" do
@provider.uid = 150
end
end
describe "when getting roles" do
it "should get the user_attributes" do
@provider.expects(:user_attributes)
@provider.roles
end
it "should get the :roles attribute" do
attributes = mock("attributes")
attributes.expects(:[]).with(:roles)
@provider.stubs(:user_attributes).returns(attributes)
@provider.roles
end
end
end