-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Facl fixes #26
Facl fixes #26
Changes from 7 commits
2cf59b0
ae76752
efa0d37
e860c2a
cd79af3
6e379a4
5f12031
e9f7485
a4fcc03
c49be1b
16d425e
1cd1708
58b4f26
cf1fb62
10c6236
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,24 +1,32 @@ | ||||
require 'etc' | ||||
require 'fileutils' | ||||
require 'ood_support' | ||||
require 'ostruct' | ||||
require 'pathname' | ||||
require 'yaml/store' | ||||
|
||||
|
||||
class Mapping < ActiveRecord::Base | ||||
attr_accessor :save_message | ||||
validates :user, :app, :dataset, presence: true | ||||
validates :user, :app, :dataset, presence: { message: "A %{attribute} must be selected." } | ||||
validate :dataset_path_must_exist | ||||
validate :app_path_may_not_be_blank | ||||
validates_uniqueness_of :user, scope: [:user, :app], message: "Unable to create a second mapping between user and app." | ||||
|
||||
def initialize(params) | ||||
super(params) | ||||
|
||||
@save_message = '' | ||||
end | ||||
|
||||
# Type dataset as a Pathname | ||||
def dataset | ||||
Pathname.new(super) | ||||
super ? Pathname.new(super): Pathname.new('') | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
irb(main):012:0> Pathname.new('').to_s
=> ""
irb(main):013:0> Pathname.new('').exist?
=> false
irb(main):014:0> Pathname.new('').file?
=> false
irb(main):015:0> Pathname.new('').directory?
=> false
irb(main):016:0> Pathname.new('').expand_path
=> #<Pathname:/Users/efranz>
irb(main):016:0> Pathname.new('').expand_path.exist?
=> true
irb(main):025:0> Pathname.new('').expand_path.directory?
=> true And I might get clever and suggest Pathname.new('/dev/null') but then this has its own problems:
Of course, nil is normally not desired, because then you have to add guard clauses everywhere and nil is generally a bad idea. But in this case I wonder if we should use it for the "null object", unless there is a sensible null object for Pathname that can be produced (maybe we could make our own? - like Pathname.new('') and then override expand_path and other methods that return values we don't like). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was looking for a default constructor or invalid test for |
||||
end | ||||
|
||||
# Type app as a Pathname | ||||
def app | ||||
Pathname.new(super) | ||||
super ? Pathname.new(super): Pathname.new('') | ||||
end | ||||
|
||||
# @return [Array<String>] | ||||
|
@@ -55,28 +63,29 @@ def to_hash | |||
end | ||||
|
||||
# Custom destructor | ||||
def self.destroy_and_remove_facls(id) | ||||
def destroy_and_remove_facls | ||||
begin | ||||
mapping = find(id) | ||||
mapping.remove_rx_facl(mapping.dataset) | ||||
mapping.remove_rx_facl(mapping.app) | ||||
mapping.destroy | ||||
dump_to_yaml | ||||
remove_rx_facl(dataset) | ||||
remove_rx_facl(app) | ||||
destroy | ||||
Mapping.dump_to_yaml | ||||
@save_message = 'Mapping successfully destroyed.' | ||||
|
||||
return true | ||||
rescue OodSupport::InvalidPath, OodSupport::BadExitCode => e | ||||
@save_message = 'Unable to destroy mapping because ' + e.to_s | ||||
|
||||
return false | ||||
rescue ActiveRecord::RecordNotFound # User is probably mashing the delete | ||||
return false | ||||
end | ||||
end | ||||
|
||||
# Create a user readable string from the error.messages hash | ||||
def format_error_messages | ||||
@save_message = errors.messages.map {|_, message| message}.join(' ') | ||||
@save_message = errors.messages.map do |key, message| | ||||
return message.join(' ') unless key == :base | ||||
|
||||
message | ||||
end.join(' ') | ||||
end | ||||
|
||||
# Custom save method | ||||
|
@@ -95,10 +104,6 @@ def save_and_set_facls | |||
@save_message = 'Mapping successfully created.' | ||||
|
||||
return true | ||||
rescue ActiveRecord::RecordNotUnique => e | ||||
@save_message = "Unable to create duplicate mapping between #{user}, #{app}, and #{dataset}" | ||||
|
||||
return false | ||||
rescue OodSupport::InvalidPath, OodSupport::BadExitCode => e | ||||
@save_message = "Unable to set FACLS because " + e.to_s | ||||
|
||||
|
@@ -126,13 +131,13 @@ def self.permission_sensitive_dirs | |||
|
||||
# @return [String] | ||||
def self.directory_permissions_command | ||||
permission_sensitive_dirs.map{|directory| "chmod 0775 #{directory.to_s}"}.join(' && ') | ||||
permission_sensitive_dirs.map{|directory| "chmod 2775 #{directory.to_s}"}.join(' && ') | ||||
end | ||||
|
||||
# @return [Boolean] | ||||
def should_add_facl?(pathname) | ||||
# Calling owned first protects rx_facl_exists? from throwing InvalidPath | ||||
pathname.owned? && ! rx_facl_exists?(pathname) | ||||
Mapping.can_modify_facl?(pathname) && ! rx_facl_exists?(pathname) | ||||
end | ||||
|
||||
# Idempotently add a RX entry to the ACL for a file | ||||
|
@@ -141,6 +146,8 @@ def add_rx_facl(pathname) | |||
|
||||
entry = build_facl_entry_for_user(user, Configuration.facl_user_domain) | ||||
OodSupport::ACLs::Nfs4ACL.add_facl(path: pathname, entry: entry) | ||||
|
||||
logger.debug "add_rx_facl(#{pathname}) succeeded for #{user} and #{pathname.to_s}" | ||||
end | ||||
|
||||
# Check if pathname / user combination occurs once or less in the database | ||||
|
@@ -154,7 +161,7 @@ def pathname_uniq_for_user?(pathname) | |||
|
||||
# @return [Boolean] | ||||
def should_remove_facl?(pathname) | ||||
pathname.owned? && rx_facl_exists?(pathname) && pathname_uniq_for_user?(pathname) | ||||
Mapping.can_modify_facl?(pathname) && rx_facl_exists?(pathname) && pathname_uniq_for_user?(pathname) | ||||
end | ||||
|
||||
# Conditionally remove RX FACLs | ||||
|
@@ -166,6 +173,8 @@ def remove_rx_facl(pathname) | |||
|
||||
entry = build_facl_entry_for_user(user, Configuration.facl_user_domain) | ||||
OodSupport::ACLs::Nfs4ACL.rem_facl(path: pathname, entry: entry) | ||||
|
||||
logger.debug "remove_rx_facl(#{pathname}) succeeded for #{user} and #{pathname.to_s}" | ||||
end | ||||
|
||||
# Build FACL for user and domain combination | ||||
|
@@ -221,9 +230,60 @@ def dataset_path_must_exist | |||
errors.add(:base, "Dataset must exist.") unless dataset.exist? | ||||
end | ||||
|
||||
# Validator for app | ||||
def app_path_may_not_be_blank | ||||
errors.add(:base, "You must select an app.") unless app.exist? | ||||
end | ||||
|
||||
# Directories between $HOME and the configured database directory must be set to 775 | ||||
# @return [Boolean] | ||||
def self.directory_perms_are_775?(directory) | ||||
directory.stat.mode.to_s(8).end_with?('775') | ||||
end | ||||
|
||||
# Checks to see if FACLs are modifiable by the user | ||||
# | ||||
# Note that this check assumes that no negative permissions have been set. | ||||
# | ||||
# @return [Boolean] | ||||
def self.can_modify_facl?(pathname) | ||||
pathname.exist? && ( pathname.owned? || group_facl_entry_has_C_set?(pathname) ) | ||||
end | ||||
|
||||
# Get the group owner's name | ||||
# @return [String] | ||||
def self.get_groupname_for_pathname(pathname) | ||||
Etc.getgrgid(pathname.stat.gid).name | ||||
end | ||||
|
||||
# Check if pathname is owned by the admin_group | ||||
# @return [Boolean] | ||||
def self.app_has_correct_group_ownership?(pathname) | ||||
get_groupname_for_pathname(pathname) == Configuration.admin_group | ||||
end | ||||
|
||||
# Does the app have permission modification enabled for the GROUP principle | ||||
# @return [Boolean] | ||||
def self.group_facl_entry_has_C_set?(pathname) | ||||
result = OodSupport::ACLs::Nfs4ACL.get_facl( | ||||
path: pathname | ||||
).entries.select{ | ||||
|entry| entry.principle == 'GROUP' | ||||
}.first.permissions.include?(:C) | ||||
|
||||
logger.debug "group_facl_entry_has_C_set?(#{pathname}) == #{result}" | ||||
|
||||
result | ||||
end | ||||
|
||||
def self.get_mapping_for_id(id) | ||||
begin | ||||
return Mapping.find(id) | ||||
rescue ActiveRecord::RecordNotFound | ||||
OpenStruct.new( | ||||
:destroy_and_remove_facls => false, | ||||
:save_message => "No mapping exists with id #{id}" | ||||
) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of having this, why can't we just do in the controller:
and in the controller handle the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One situation where they would see it is if there is a delay between submitting the first delete and immediately sending another. I know quite a few people who click buttons more than once. I agree that the OpenStruct usage isn't ideal, it's just something that I was playing with on Friday. I liked that it let me avoid having to add an error handler for RecordNotFound. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as the multiple button clicks, the presence of the alert should prevent this from occurring:
A single click presents the confirmation alert, and only if that is "okay" does the delete request get sent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And of course as we discussed offline, I think its appropriate to not add rescue for RecordNotFound since Rails should handle this to be 404. |
||||
end | ||||
end | ||||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
<div class="container"> | ||
<div class="jumbotron"> | ||
<h1 class="display-4">No Apps Installed</h1> | ||
<p>Please install one or more apps to <%= ENV['SHARED_APPS_ROOT'] =%>.</p> | ||
<p>Datasets should be installed to <%= ENV['APP_DATASET_ROOT'] =%>.</p> | ||
<p>Please install one or more apps to <%= Configuration.shared_apps_root =%>.</p> | ||
<p>Datasets should be installed to <%= Configuration.app_dataset_root =%>.</p> | ||
</div> | ||
<%= link_to 'Back', mappings_path, class: 'btn btn-default' %> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need this anymore if you don't use it anywhere.