Skip to content
nickjer edited this page Jul 2, 2016 · 50 revisions

Ideas...

# acl.rb

require 'openstruct'

class ACL
  attr_reader :entries

  def initialize(entries:)
    @entries = entries
  end

  def allow?(principle:)
    ordered_check_w_default_deny(principle: principle)
  end

  private
    def ordered_check_w_default_deny(**kwargs)
      ordered_check(default: false, **kwargs)
    end

    def ordered_check_w_default_allow(**kwargs)
      ordered_check(default: true, **kwargs)
    end

    def ordered_check(default:, **kwargs)
      entries.each do |entry|
        if entry.match(**kwargs)
          # Check if its an allow or deny acl entry (may not be both)
          return true  if entry.is_allow?
          return false if entry.is_deny?
        end
      end
      return default
    end
end
# acl_entry.rb

class ACLEntry
  attr_reader :principle

  def self.parse(entry)
    new(principle: entry.to_s.strip)
  end

  def initialize(principle:)
    @principle = principle.to_s
  end

  def is_allow?
    true
  end

  def is_deny?
    !is_allow?
  end

  def match(principle:)
    self.principle == principle
  end

  def to_s
    principle
  end

  def ==(other)
    self.to_s == other
  end

  def eql?(other)
    [self.class, principle] == [other.class, other.principle]
  end

  def hash
    [self.class, principle].hash
  end
end
# acls/nfs4.rb

module ACLs
  class NFS4 < ACL
    attr_reader :owner, :group

    def self.get_facl(file:)
      # Handle errors here (e.g., file doesn't exist, ...)
      stat = Pathname.new(file).stat
      parse(acl: `nfs4_getfacl "#{file}"`, owner: User.new(stat.uid), group: Group.new(stat.gid))
    end

    def self.parse(acl:, **kwargs)
      entries = []
      acl.to_s.strip.split(/\n|,/).collect(&:strip).grep(/^[^#]/) do |entry|
        entries << NFS4ACLEntry.parse entry
      end
      new(entries: entries, **kwargs)
    end

    def self.add_facl(file:, entry:)
      `nfs4_setfacl -a "#{entry}" "#{file}"`
    end

    def self.rem_facl(file:, entry:)
      `nfs4_setfacl -x "#{entry}" "#{file}"`
    end

    def self.set_facl(file:, acl:)
      `nfs4_setfacl -s "#{acl}" "#{file}"`
    end

    def initialize(owner:, group:, **kwargs)
      super(kwargs)
      @owner = owner.to_s
      @group = group.to_s
    end

    def allow?(principle:, permission:)
      ordered_check_w_default_deny(
        principle: principle,
        permission: permission,
        owner: owner,
        group: group
      )
    end

    def to_s
      entries.join("\n")
    end
  end

  class NFS4Entry < ACLEntry
    VALID_TYPE = %i[ A U D L ]
    VALID_FLAG = %i[ f d p i S F g ]
    VALID_PERMISSION = %i[ r w a x d D t T n N c C o y ]
    REGEX_PATTERN = %r[^(?<type>[#{VALID_TYPE.join}]):(?<flags>[#{VALID_FLAG.join}]*):(?<principle>\w+)@(?<domain>\w*):(?<permissions>[#{VALID_PERMISSION.join}]+)$]

    attr_reader :type, :flags, :domain, :permissions

    def self.parse(entry)
      # Create new acl entry from string
      entry = REGEX_PATTERN.match(entry.to_s.strip) do |m|
        new(
          type:        m[:type],
          flags:       m[:flags].chars,
          principle:   m[:principle],
          domain:      m[:domain],
          permissions: m[:permissions].chars
        )
      end
      entry ? entry : raise InvalidFormat
    end

    def initialize(type:, flags:, domain:, permissions:, owner:, group:, **kwargs)
      super(kwargs)
      @type = type.to_sym
      @flags = flags.map(&:to_sym)
      @domain = domain.to_s
      @permissions = permissions.map(&:to_sym)
      # Check for invalid values!
    end

    def is_allow?
      type == :A
    end

    def is_deny?
      type == :D
    end

    def group_acl?
      flags.include? :g
    end

    def match(principle:, permission:, owner:, group:)
      # FIXME: EVERYONE@ -- This is a special catch-all principal which applies to any entity that is not matched by any of the above.
      entry_principle = self.principle
      entry_principle = owner     if self.principle == "OWNER"
      entry_principle = group     if self.principle == "GROUP"
      entry_principle = principle if self.principle == "EVERYONE"
      if principle.is_a?(User) && group_acl?
        principle.groups.include?(entry_principle) && permissions.include?(permission.to_sym)
      elsif principle.is_a?(User) || (principle.is_a?(Group) && group_acl?)
        entry_principle == principle && permissions.include?(permission.to_sym)
      else
        false
      end
    end

    def to_s
      "#{type}:#{flags.join}:#{principle}@#{domain}:#{permissions.join}"
    end

    def ==(other)
      self.to_s == other
    end

    def eql?(other)
      [self.class, type, flags.sort, principle, domain, permissions.sort] == [other.class, other.type, other.flags.sort, other.principle, other.domain, other.permissions.sort]
    end

    def hash
      [self.class, type, flags.sort, principle, domain, permissions.sort].hash
    end
  end
end
acl = OodSupport::ACLs::NFS4.get_facl("/path/to/file")

# Check if user "jnicklas" has read permissions to file
user = OodSupport::User.new "jnicklas"
acl.allow?(principle: user, permission: :r)
#=> false

# Add "rx" permissions for user "jnicklas" to file
entry = OodSupport::ACLs::NFS4Entry.new(type: :A, flags: [], principle: "jnicklas", domain: "osc.edu", permissions: [:r, :x])
OodSupport::ACLs::NFS4.add_facl("/path/to/file", entry)
acl = OodSupport::ACLs::NFS4.get_facl("/path/to/file")
acl.allow?(principle: user, permission: :r)
#=> true
Clone this wiki locally