Class: SafePath
- Inherits:
-
Object
- Object
- SafePath
- Defined in:
- lib/ndr_support/safe_path.rb
Overview
SafePath
SafePath is a class which contains path to a file or directory. It also holds “path space” and permissions. The path space is a directory. Everything in this directory and all the subdirectories can be accessed with the permissions given to the constructor. The instance of the class checks whether the path constructed points to a directory, whcih is in the “path space”. The idea is to limit the access of the program to given directory.
Example of usage is :
sp = SafePath("dbs_inbox")
The root directory of the pathspace is in the file config/filesystem_paths.yml . In this case dbs_inbox has root /mounts/ron/dbs_inbox . Every path which starts with /mount/ron/dbs_inbox is considered as safe. If a path is constructed which is /mounts/ron/dbs_inbox/../../../etc/passwd for example then the class will evaluate the path and it will raise exception SecurityError.
The paths can be constructed by using +, join or join!. Example:
This:
sp = SafePath("dbs_inbox")
sp + "/my_dir"
Points to:
/mounts/ron/dbs_inbox/my_dir
The functions join and join! work in similar way. The difference between join and join! is that join creates new instance of the class SafePath and return it and join! doesn’t create new instance, but works in-place and after that it returns reference to the current instance. The both operators can be used like that:
sp.join("/my_dir") #this is the same as sp + "my_dir"
sp.join!("/my_dir") #this is NOT the same as sp "my_dir"
Warning the function sp.path = “some_path” will treat some_path as absolute path and if it doesn’t point to the root it will raise exception. The danger is that it returns the argument on the right hand side. So if it is a string the operator will return a string. This is the way ruby works. If it is used properly it shouldn’t be a problem. The best way to use it is:
sp.path = sp.root + "my_dir"
sp.root returns SafePath and after that + is called which also returns SafePath. So the right hand side of the expression is SafePath and the = will return SafePath.
Class Method Summary collapse
-
.configure!(filepath) ⇒ Object
Takes the path the filesystem_paths.yml file that should be used.
-
.fs_paths ⇒ Object
Returns the list of safe ‘root’ filesystem locations, or raises a SecurityError if no configuration has been provided.
Instance Method Summary collapse
-
#+(path) ⇒ Object
Another name for join.
- #==(other) ⇒ Object
-
#initialize(path_space, root_suffix = '') ⇒ SafePath
constructor
Takes: * path - This is a path to a directory.
-
#join(path) ⇒ Object
Used to construct path.
-
#join!(path) ⇒ Object
Used to construct path.
- #length ⇒ Object
-
#path=(path) ⇒ Object
Setter for path.
-
#path_space ⇒ Object
Getter for path space identifier.
-
#permissions ⇒ Object
Getter for permissions.
-
#permissions=(permissions) ⇒ Object
The permissions are specified in the yml file, but this function can select subset of these permissions.
-
#root ⇒ Object
Getter for the root of the path space.
-
#to_s ⇒ Object
WARNING: do not use sp.to_s + from_attacker .
-
#to_str ⇒ Object
WARNING: do not use s.to_s + “my_path” .
Constructor Details
#initialize(path_space, root_suffix = '') ⇒ SafePath
Takes:
* path - This is a path to a directory. Usually a string.
* path_space - This is identifier of the path space in whichi the system should work.
it is a string. To find list of path spaces with their roots, please
see config/filesystem_paths.yml
Raises:
* ArgumentError
* SecurityError
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/ndr_support/safe_path.rb', line 86 def initialize(path_space, root_suffix = '') # The class has to use different path definitions during test fs_paths = self.class.fs_paths platform = fs_paths.keys.select do |key| RUBY_PLATFORM.match key end[0] root_path_regexp = if platform == /mswin32|mingw32/ /\A([A-Z]:[\\\/]|\/\/)/ else /\A\// end fail ArgumentError, "The space #{path_space} doesn't exist. Please choose one of: #{fs_paths[platform].keys.inspect}" unless fs_paths[platform][path_space] fail ArgumentError, "The space #{path_space} is broken. The root path should be absolute path but it was #{fs_paths[platform][path_space]['root']}" unless fs_paths[platform][path_space]['root'].match(root_path_regexp) fail ArgumentError, "The space #{path_space} is broken. No permissions specified}" unless fs_paths[platform][path_space]['prms'] # The function verify uses @root. Therefore first assign the root path # specified in the yaml file. After that verify that the root path # specified from the contructor is subpath of the path specified in the # yaml file. If it is not raise exception before anything else is done. # # The reason to assign @root 2 times is that it is better to have the # logic verifing the path in only one function. This way it is going # to be easier to maintain it and keep it secure. # @root = File. fs_paths[platform][path_space]['root'] @root = verify(File.join(fs_paths[platform][path_space]['root'], root_suffix)) unless root_suffix.blank? @path_space = path_space @maximum_prms = fs_paths[platform][path_space]['prms'] @prm = nil self.path = @root end |
Class Method Details
.configure!(filepath) ⇒ Object
Takes the path the filesystem_paths.yml file that should be used. Attempting to reconfigure with new settings will raise a security error.
66 67 68 69 70 71 72 73 74 |
# File 'lib/ndr_support/safe_path.rb', line 66 def self.configure!(filepath) raise SecurityError, 'Attempt to re-assign SafePath config!' if defined?(@@fs_paths) File.open(filepath, 'r') do |file| @@fs_paths = YAML.safe_load(ERB.new(file.read).result, permitted_classes: [Regexp], aliases: true) @@fs_paths.freeze end end |
.fs_paths ⇒ Object
Returns the list of safe ‘root’ filesystem locations, or raises a SecurityError if no configuration has been provided.
55 56 57 58 59 60 61 |
# File 'lib/ndr_support/safe_path.rb', line 55 def self.fs_paths if defined?(@@fs_paths) @@fs_paths else fail SecurityError, 'SafePath not configured!' end end |
Instance Method Details
#+(path) ⇒ Object
Another name for join
223 224 225 |
# File 'lib/ndr_support/safe_path.rb', line 223 def +(path) self.join(path) end |
#==(other) ⇒ Object
125 126 127 |
# File 'lib/ndr_support/safe_path.rb', line 125 def ==(other) other.class == SafePath and other.root.to_s == @root and other. == self. and other.to_s == self.to_s end |
#join(path) ⇒ Object
Used to construct path. It joins the current path with the given one. Takes:
* path - path to be concatenated
Returns:
New instance of SafePath which contains the new path.
Raises:
* SecurityError
237 238 239 240 241 |
# File 'lib/ndr_support/safe_path.rb', line 237 def join(path) r = self.clone r.path = File.join(@path, path) r end |
#join!(path) ⇒ Object
Used to construct path. It joins the current path with the given one. Takes:
* path - path to be concatenated
Returns:
Reference to the current instance. It works in-place
Raises:
* SecurityError
253 254 255 256 |
# File 'lib/ndr_support/safe_path.rb', line 253 def join!(path) self.path = File.join(@path, path) self end |
#length ⇒ Object
258 259 260 |
# File 'lib/ndr_support/safe_path.rb', line 258 def length() @path.length end |
#path=(path) ⇒ Object
Setter for path.
Takes:
* path - The path.
Returns:
Array - this is the result of the assignment. Note it is the right hand side of the expression
Raises:
* SecurityError
Warning avoid using this in expressions like this (safe_path_new = (safe_path.path = safe_path.root.to_s + “/test”)) + path_from_attacker This is unsafe!
217 218 219 220 |
# File 'lib/ndr_support/safe_path.rb', line 217 def path=(path) @path = verify(path) self end |
#path_space ⇒ Object
164 165 166 |
# File 'lib/ndr_support/safe_path.rb', line 164 def path_space @path_space end |
#permissions ⇒ Object
151 152 153 154 155 156 157 |
# File 'lib/ndr_support/safe_path.rb', line 151 def if @prm @prm else @maximum_prms end end |
#permissions=(permissions) ⇒ Object
The permissions are specified in the yml file, but this function can select subset of these permissions. It cannot select permission, which is not specified in the yml file.
Takes:
* Array of permission or single permission - If it is array then
tha array could contain duplicates and it also can be nested.
All the duplicates will be removed and the array will be flattened
Returns:
Array - this is the reuslt of the assignment. Note it is the right hand side of the expression
Raises:
* ArgumentError
195 196 197 198 199 200 201 |
# File 'lib/ndr_support/safe_path.rb', line 195 def () err_mess = "permissions has to be one or more of the values: #{@maximum_prms.inspect}\n but it was #{.inspect}" @prm = [].flatten.each do |prm| fail ArgumentError, err_mess unless @maximum_prms.include?(prm) end.uniq end |
#root ⇒ Object
173 174 175 176 177 178 |
# File 'lib/ndr_support/safe_path.rb', line 173 def root r = self.clone r.path = @root r. = self. r end |
#to_s ⇒ Object
134 135 136 |
# File 'lib/ndr_support/safe_path.rb', line 134 def to_s @path end |
#to_str ⇒ Object
WARNING: do not use s.to_s + “my_path” . This is unsafe!
Returns:
String
143 144 145 |
# File 'lib/ndr_support/safe_path.rb', line 143 def to_str self.to_s end |