Skip to content
nickjer edited this page Jun 30, 2016 · 50 revisions

Ideas...

# acl.rb

require 'openstruct'

class ACL
  attr_reader :entries, :context

  def initialize(entries:, context: {})
    @entires = entries
    @context = context
  end

  # @example Check if user 'bob' has 'r' access
  #   my_acl.allow?(principle: User.new('bob'), permission: :r)
  #   #=> true
  def allow?(principle:, permission:)
    entries.each do |entry|
      if entry.match(principle: principle, permission: permission, context: context)
        # 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 false # default deny
  end
end
# acl_entry.rb

class ACLEntry
  attr_reader :principle, :permissions

  def initialize(principle:, permissions:)
    @principle = principle.to_s
    @permissions = permissions.map(&:to_sym)
  end

  def is_allow?
    true
  end

  def is_deny?
    !allow?
  end

  def match(principle:, permission:, context: {})
    self.principle == principle && self.permissions.include?(permission.to_sym)
  end

  def ==(other)
    [self.class, principle, permissions.sort] == [other.class, other.principle, other.permissions.sort]
  end

  alias_method :eql, :==

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

module ACLs
  class NFS4 < ACL
    def self.get_facl(file)
      # Handle errors here (e.g., file doesn't exist, ...)
      stat = Pathname.new(file).stat
      entries = parse `nfs4_getfacl "#{file}"`
      new(entries: entries, context: {owner: User.new(stat.uid).name, group: Group.new(stat.gid).name})
    end

    def self.parse(acl)
      entries = []
      acl.to_s.split(/\n|,/).collect(&:strip).grep(/^[^#]/) do |entry|
        entries << NFS4ACLEntry.parse entry
      end
      entries
    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 to_s
      entries.join(", ")
    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 ]
    DEFAULT_DOMAIN = "osc.edu"
    FORMAT = %r(^[#{VALID_TYPE.join}]:[#{VALID_FLAG.join}]*:\w+@\w*:[#{VALID_PERMISSION.join}]+)

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

    def self.parse(entry)
      # Create new acl entry from string
      entry = entry.to_s.strip
      raise InvalidFormat if FORMAT =~ entry
      type, flags, principle, permissions = entry.split(":")
      flags = flags.chars
      permissions = permissions.chars
      principle, domain = principle.split("@")
      new(type: type, flags: flags, principle: principle, domain: domain, permissions: permissions)
    end

    def initialize(type:, flags:, domain: nil, **kwargs)
      super(kwargs)
      @type = type.to_sym
      @flags = flags.map(&:to_sym)
      @domain = domain.to_s
      # 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:, context: {})
      # FIXME: USER@, GROUP@, EVERYONE@
      if principle.is_a?(User) && group_acl?
        principle.groups.include?(self.principle) && self.permissions.include?(permission.to_sym)
      elsif principle.is_a?(User) || (principle.is_a?(Group) && group_acl?)
        self.principle == principle && self.permissions.include?(permission.to_sym)
      else
        false
      end
    end

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

    def ==(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